Yichong Lu commited on
Commit
f2a492d
·
1 Parent(s): 9c3007d

add submission addition function

Browse files
Files changed (2) hide show
  1. competitions/app.py +7 -2
  2. competitions/utils.py +51 -36
competitions/app.py CHANGED
@@ -30,7 +30,6 @@ from competitions.text import SUBMISSION_SELECTION_TEXT, SUBMISSION_TEXT
30
  from competitions.utils import team_file_api, submission_api, leaderboard_api, error_log_api
31
  from competitions.enums import SubmissionStatus, ErrorMessage
32
 
33
-
34
  HF_TOKEN = os.environ.get("HF_TOKEN", None)
35
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
36
  COMPETITION_ID = os.environ.get("COMPETITION_ID")
@@ -333,15 +332,21 @@ def new_submission(
333
  if team_id not in team_file_api.get_team_white_list():
334
  return {"response": "You are not allowed to make submissions."}
335
 
 
 
336
  lock = FileLock(f"./submission_lock/{team_id}.lock", blocking=False)
337
  try:
338
  with lock:
339
  if submission_api.exists_submission_info(team_id) and submission_api.count_by_status(team_id, [SubmissionStatus.QUEUED, SubmissionStatus.PENDING, SubmissionStatus.PROCESSING]) > 0:
340
  return {"response": "Another submission is being processed. Please wait a moment."}
341
  competition_info = CompetitionInfo(competition_id=COMPETITION_ID, autotrain_token=HF_TOKEN)
 
 
 
 
342
  sub = Submissions(
343
  end_date=competition_info.end_date,
344
- submission_limit=competition_info.submission_limit,
345
  competition_id=COMPETITION_ID,
346
  token=HF_TOKEN,
347
  competition_type=competition_info.competition_type,
 
30
  from competitions.utils import team_file_api, submission_api, leaderboard_api, error_log_api
31
  from competitions.enums import SubmissionStatus, ErrorMessage
32
 
 
33
  HF_TOKEN = os.environ.get("HF_TOKEN", None)
34
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
35
  COMPETITION_ID = os.environ.get("COMPETITION_ID")
 
332
  if team_id not in team_file_api.get_team_white_list():
333
  return {"response": "You are not allowed to make submissions."}
334
 
335
+ team_submission_limit_dict = team_file_api.get_team_submission_limit(user_token)
336
+
337
  lock = FileLock(f"./submission_lock/{team_id}.lock", blocking=False)
338
  try:
339
  with lock:
340
  if submission_api.exists_submission_info(team_id) and submission_api.count_by_status(team_id, [SubmissionStatus.QUEUED, SubmissionStatus.PENDING, SubmissionStatus.PROCESSING]) > 0:
341
  return {"response": "Another submission is being processed. Please wait a moment."}
342
  competition_info = CompetitionInfo(competition_id=COMPETITION_ID, autotrain_token=HF_TOKEN)
343
+ if team_id in team_submission_limit_dict.keys():
344
+ submission_limit = team_submission_limit_dict[team_id]
345
+ else:
346
+ submission_limit = competition_info.submission_limit
347
  sub = Submissions(
348
  end_date=competition_info.end_date,
349
+ submission_limit=submission_limit,
350
  competition_id=COMPETITION_ID,
351
  token=HF_TOKEN,
352
  competition_type=competition_info.competition_type,
competitions/utils.py CHANGED
@@ -28,7 +28,6 @@ from competitions.params import EvalParams
28
 
29
  from . import HF_URL
30
 
31
-
32
  USER_TOKEN = os.environ.get("USER_TOKEN")
33
 
34
 
@@ -269,6 +268,7 @@ class TeamAlreadyExistsError(Exception):
269
  """Custom exception for when a team already exists."""
270
  pass
271
 
 
272
  class TeamFileApi:
273
  def __init__(self, hf_token: str, competition_id: str):
274
  self.hf_token = hf_token
@@ -358,7 +358,7 @@ class TeamFileApi:
358
  repo_type="dataset",
359
  )
360
  return team_id
361
-
362
  def create_team(self, user_token: str, team_name: str, other_data: Dict[str, Any]) -> str:
363
  user_info = token_information(token=user_token)
364
  return self._create_team(user_info["id"], team_name, other_data)
@@ -394,7 +394,7 @@ class TeamFileApi:
394
  repo_type="dataset",
395
  )
396
  return new_team_name
397
-
398
  @cached(cache=TTLCache(maxsize=1, ttl=600))
399
  def get_team_white_list(self) -> List[str]:
400
  file = hf_hub_download(
@@ -408,6 +408,19 @@ class TeamFileApi:
408
  team_white_list = json.load(f)
409
  return team_white_list
410
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
 
412
  team_file_api = TeamFileApi(
413
  os.environ.get("HF_TOKEN", None),
@@ -420,7 +433,7 @@ class UserTokenApi:
420
  self.hf_token = hf_token
421
  self.key = base64.b64decode(key_base64)
422
  self.competition_id = competition_id
423
-
424
  def _encrypt(self, text: str) -> str:
425
  aesgcm = AESGCM(self.key)
426
  nonce = os.urandom(12)
@@ -434,7 +447,7 @@ class UserTokenApi:
434
  ciphertext = data[12:]
435
  plaintext = aesgcm.decrypt(nonce, ciphertext, None)
436
  return plaintext.decode()
437
-
438
  def put(self, team_id: str, user_token: str):
439
  encrypted_token = self._encrypt(user_token)
440
  api = HfApi(token=self.hf_token)
@@ -475,13 +488,13 @@ class ServerManager:
475
  self.server_url_list = server_url_list
476
  self._cur_index = 0
477
  self._lock = threading.Lock()
478
-
479
  def get_next_server(self) -> str:
480
  with self._lock:
481
  server_url = self.server_url_list[self._cur_index]
482
  self._cur_index = (self._cur_index + 1) % len(self.server_url_list)
483
  return server_url
484
-
485
 
486
  server_manager = ServerManager(["https://xdimlab-hugsim-web-server-0.hf.space"])
487
 
@@ -491,7 +504,7 @@ class SubmissionApi:
491
  self.hf_token = hf_token
492
  self.competition_id = competition_id
493
  self.api = HfApi(token=hf_token)
494
-
495
  def exists_submission_info(self, team_id: str) -> bool:
496
  """
497
  Check if submission info exists for a given team ID.
@@ -521,7 +534,7 @@ class SubmissionApi:
521
  )
522
  with open(submission_info_path, 'r') as f:
523
  submission_info = json.load(f)
524
-
525
  return submission_info
526
 
527
  def upload_submission_info(self, team_id: str, user_submission_info: Dict[str, Any]):
@@ -545,10 +558,11 @@ class SubmissionApi:
545
 
546
  def update_submission_status(self, team_id: str, submission_id: str, status: int):
547
  self.update_submission_data(team_id, submission_id, {"status": status})
548
-
549
  def count_by_status(self, team_id: str, status_list: List[SubmissionStatus]) -> int:
550
  user_submission_info = self.download_submission_info(team_id)
551
- count = sum(1 for submission in user_submission_info["submissions"] if SubmissionStatus(submission["status"]) in status_list)
 
552
  return count
553
 
554
 
@@ -600,7 +614,7 @@ class ErrorLogApi:
600
  }
601
  token = jwt.encode(payload, self.encode_key, algorithm="HS256")
602
  return token
603
-
604
  def get_log_by_token(self, token: str) -> str:
605
  try:
606
  payload = jwt.decode(token, self.encode_key, algorithms=["HS256"])
@@ -617,7 +631,7 @@ class ErrorLogApi:
617
  )
618
  with open(log_file_path, 'r') as f:
619
  file_content = f.read()
620
-
621
  return file_content
622
 
623
 
@@ -669,12 +683,13 @@ class SpaceCleaner:
669
  submission_api.update_submission_data(
670
  team_id=team_id,
671
  submission_id=submission_id,
672
- data={"status": SubmissionStatus.FAILED.value, "error_message": ErrorMessage.BUILD_SPACE_FAILED.value},
 
673
  )
674
  else:
675
  self.api.restart_space(repo_id=space_id)
676
  return
677
-
678
  if space_info.runtime.stage == SpaceStage.RUNTIME_ERROR:
679
  log_content = error_log_api.get_log(space_id, kind="run")
680
  error_log_api.save_error_log(submission_id, log_content)
@@ -757,7 +772,6 @@ leaderboard_api = LeaderboardApi(
757
  )
758
 
759
 
760
-
761
  class DockerfileModifier:
762
  def __init__(self, allowed_hosts: str, source_so_path: str = "./network_filter.so"):
763
  self.allowed_hosts = allowed_hosts
@@ -765,46 +779,46 @@ class DockerfileModifier:
765
  self.tatget_so_dir = "/_app_extensions"
766
  self.tatget_so_path = os.path.join(self.tatget_so_dir, "network_filter.so")
767
  self.preload_prefix = f'LD_PRELOAD={self.tatget_so_path} ALLOWED_HOSTS="{allowed_hosts}"'
768
-
769
  def parse_dockerfile_line(self, line: str) -> Tuple[str, str, str]:
770
  """
771
  解析 Dockerfile 行,返回 (指令名, 原始命令, 格式类型)
772
  格式类型: 'exec' (JSON数组) 或 'shell' (shell命令)
773
  """
774
  line = line.strip()
775
-
776
  # 匹配 CMD 或 ENTRYPOINT
777
  cmd_match = re.match(r'^(CMD|ENTRYPOINT)\s+(.+)$', line, re.IGNORECASE)
778
  if not cmd_match:
779
  return "", "", ""
780
-
781
  instruction = cmd_match.group(1).upper()
782
  command_part = cmd_match.group(2).strip()
783
-
784
  # 判断是 exec 格式 (JSON数组) 还是 shell 格式
785
  if command_part.startswith('[') and command_part.endswith(']'):
786
  return instruction, command_part, "exec"
787
  else:
788
  return instruction, command_part, "shell"
789
-
790
  def modify_shell_format(self, command: str) -> str:
791
  """修改 shell 格式的命令"""
792
  # 在原命令前添加环境变量
793
  return f'{self.preload_prefix} {command}'
794
-
795
  def modify_exec_format(self, command: str) -> str:
796
  """修改 exec 格式 (JSON数组) 的命令"""
797
  try:
798
  # 解析 JSON 数组格式
799
  # 移除外层的方括号
800
  inner = command[1:-1].strip()
801
-
802
  # 简单的 JSON 数组解析
803
  parts = []
804
  current = ""
805
  in_quotes = False
806
  escape_next = False
807
-
808
  for char in inner:
809
  if escape_next:
810
  current += char
@@ -820,10 +834,10 @@ class DockerfileModifier:
820
  current = ""
821
  else:
822
  current += char
823
-
824
  if current.strip():
825
  parts.append(current.strip())
826
-
827
  # 移除引号并处理转义
828
  cleaned_parts = []
829
  for part in parts:
@@ -833,10 +847,10 @@ class DockerfileModifier:
833
  # 处理基本的转义字符
834
  part = part.replace('\\"', '"').replace('\\\\', '\\')
835
  cleaned_parts.append(part)
836
-
837
  if not cleaned_parts:
838
  return command
839
-
840
  # 构建新的命令
841
  # 第一个元素通常是 shell (/bin/sh, /bin/bash 等)
842
  # 如果第一个元素是 shell,修改执行的命令
@@ -855,22 +869,22 @@ class DockerfileModifier:
855
  # 直接执行的命令,需要通过 shell 包装
856
  original_cmd = ' '.join(cleaned_parts)
857
  new_parts = ['/bin/sh', '-c', f'{self.preload_prefix} {original_cmd}']
858
-
859
  # 重新构建 JSON 数组
860
  escaped_parts = []
861
  for part in new_parts:
862
  # 转义引号和反斜杠
863
  escaped = part.replace('\\', '\\\\').replace('"', '\\"')
864
  escaped_parts.append(f'"{escaped}"')
865
-
866
  return '[' + ', '.join(escaped_parts) + ']'
867
-
868
  except Exception as e:
869
  print(f"警告: 解析 exec 格式失败: {e}")
870
  print(f"原始命令: {command}")
871
  # 如果解析失败,转换为 shell 格式
872
  return f'{self.preload_prefix} {command}'
873
-
874
  def modify_dockerfile_content(self, content: str) -> Tuple[str, List[str]]:
875
  """
876
  修改 Dockerfile 内容
@@ -879,10 +893,10 @@ class DockerfileModifier:
879
  lines = content.splitlines()
880
  modified_lines = []
881
  changes = []
882
-
883
  for i, line in enumerate(lines, 1):
884
  instruction, command, format_type = self.parse_dockerfile_line(line)
885
-
886
  if instruction in ['CMD', 'ENTRYPOINT'] and command:
887
  if format_type == "shell":
888
  new_command = self.modify_shell_format(command)
@@ -892,7 +906,7 @@ class DockerfileModifier:
892
  new_line = f'{instruction} {new_command}'
893
  else:
894
  new_line = line
895
-
896
  changes.append(f"第 {i} 行: {instruction} 指令已修改")
897
  changes.append(f" 原始: {line}")
898
  changes.append(f" 修改: {new_line}")
@@ -908,7 +922,8 @@ class DockerfileModifier:
908
  if last_user is None:
909
  modified_lines.insert(-1, f"COPY {self.source_so_path}" + f" {self.tatget_so_path}")
910
  else:
911
- modified_lines.insert(-1, f"COPY --chown={last_user}:{last_user} {self.source_so_path} {self.tatget_so_path}")
 
912
  modified_lines.insert(-1, f"RUN chown -R {last_user}:{last_user} {self.tatget_so_dir}")
913
 
914
  return '\n'.join(modified_lines), changes
 
28
 
29
  from . import HF_URL
30
 
 
31
  USER_TOKEN = os.environ.get("USER_TOKEN")
32
 
33
 
 
268
  """Custom exception for when a team already exists."""
269
  pass
270
 
271
+
272
  class TeamFileApi:
273
  def __init__(self, hf_token: str, competition_id: str):
274
  self.hf_token = hf_token
 
358
  repo_type="dataset",
359
  )
360
  return team_id
361
+
362
  def create_team(self, user_token: str, team_name: str, other_data: Dict[str, Any]) -> str:
363
  user_info = token_information(token=user_token)
364
  return self._create_team(user_info["id"], team_name, other_data)
 
394
  repo_type="dataset",
395
  )
396
  return new_team_name
397
+
398
  @cached(cache=TTLCache(maxsize=1, ttl=600))
399
  def get_team_white_list(self) -> List[str]:
400
  file = hf_hub_download(
 
408
  team_white_list = json.load(f)
409
  return team_white_list
410
 
411
+ @cached(cache=TTLCache(maxsize=1, ttl=600))
412
+ def get_team_submission_limit(self) -> Dict[str, str]:
413
+ file = hf_hub_download(
414
+ repo_id=self.competition_id,
415
+ filename="team_submission_limit.json",
416
+ token=self.hf_token,
417
+ repo_type="dataset",
418
+ )
419
+
420
+ with open(file, "r", encoding="utf-8") as f:
421
+ team_submission_limit = json.load(f)
422
+ return team_submission_limit
423
+
424
 
425
  team_file_api = TeamFileApi(
426
  os.environ.get("HF_TOKEN", None),
 
433
  self.hf_token = hf_token
434
  self.key = base64.b64decode(key_base64)
435
  self.competition_id = competition_id
436
+
437
  def _encrypt(self, text: str) -> str:
438
  aesgcm = AESGCM(self.key)
439
  nonce = os.urandom(12)
 
447
  ciphertext = data[12:]
448
  plaintext = aesgcm.decrypt(nonce, ciphertext, None)
449
  return plaintext.decode()
450
+
451
  def put(self, team_id: str, user_token: str):
452
  encrypted_token = self._encrypt(user_token)
453
  api = HfApi(token=self.hf_token)
 
488
  self.server_url_list = server_url_list
489
  self._cur_index = 0
490
  self._lock = threading.Lock()
491
+
492
  def get_next_server(self) -> str:
493
  with self._lock:
494
  server_url = self.server_url_list[self._cur_index]
495
  self._cur_index = (self._cur_index + 1) % len(self.server_url_list)
496
  return server_url
497
+
498
 
499
  server_manager = ServerManager(["https://xdimlab-hugsim-web-server-0.hf.space"])
500
 
 
504
  self.hf_token = hf_token
505
  self.competition_id = competition_id
506
  self.api = HfApi(token=hf_token)
507
+
508
  def exists_submission_info(self, team_id: str) -> bool:
509
  """
510
  Check if submission info exists for a given team ID.
 
534
  )
535
  with open(submission_info_path, 'r') as f:
536
  submission_info = json.load(f)
537
+
538
  return submission_info
539
 
540
  def upload_submission_info(self, team_id: str, user_submission_info: Dict[str, Any]):
 
558
 
559
  def update_submission_status(self, team_id: str, submission_id: str, status: int):
560
  self.update_submission_data(team_id, submission_id, {"status": status})
561
+
562
  def count_by_status(self, team_id: str, status_list: List[SubmissionStatus]) -> int:
563
  user_submission_info = self.download_submission_info(team_id)
564
+ count = sum(1 for submission in user_submission_info["submissions"] if
565
+ SubmissionStatus(submission["status"]) in status_list)
566
  return count
567
 
568
 
 
614
  }
615
  token = jwt.encode(payload, self.encode_key, algorithm="HS256")
616
  return token
617
+
618
  def get_log_by_token(self, token: str) -> str:
619
  try:
620
  payload = jwt.decode(token, self.encode_key, algorithms=["HS256"])
 
631
  )
632
  with open(log_file_path, 'r') as f:
633
  file_content = f.read()
634
+
635
  return file_content
636
 
637
 
 
683
  submission_api.update_submission_data(
684
  team_id=team_id,
685
  submission_id=submission_id,
686
+ data={"status": SubmissionStatus.FAILED.value,
687
+ "error_message": ErrorMessage.BUILD_SPACE_FAILED.value},
688
  )
689
  else:
690
  self.api.restart_space(repo_id=space_id)
691
  return
692
+
693
  if space_info.runtime.stage == SpaceStage.RUNTIME_ERROR:
694
  log_content = error_log_api.get_log(space_id, kind="run")
695
  error_log_api.save_error_log(submission_id, log_content)
 
772
  )
773
 
774
 
 
775
  class DockerfileModifier:
776
  def __init__(self, allowed_hosts: str, source_so_path: str = "./network_filter.so"):
777
  self.allowed_hosts = allowed_hosts
 
779
  self.tatget_so_dir = "/_app_extensions"
780
  self.tatget_so_path = os.path.join(self.tatget_so_dir, "network_filter.so")
781
  self.preload_prefix = f'LD_PRELOAD={self.tatget_so_path} ALLOWED_HOSTS="{allowed_hosts}"'
782
+
783
  def parse_dockerfile_line(self, line: str) -> Tuple[str, str, str]:
784
  """
785
  解析 Dockerfile 行,返回 (指令名, 原始命令, 格式类型)
786
  格式类型: 'exec' (JSON数组) 或 'shell' (shell命令)
787
  """
788
  line = line.strip()
789
+
790
  # 匹配 CMD 或 ENTRYPOINT
791
  cmd_match = re.match(r'^(CMD|ENTRYPOINT)\s+(.+)$', line, re.IGNORECASE)
792
  if not cmd_match:
793
  return "", "", ""
794
+
795
  instruction = cmd_match.group(1).upper()
796
  command_part = cmd_match.group(2).strip()
797
+
798
  # 判断是 exec 格式 (JSON数组) 还是 shell 格式
799
  if command_part.startswith('[') and command_part.endswith(']'):
800
  return instruction, command_part, "exec"
801
  else:
802
  return instruction, command_part, "shell"
803
+
804
  def modify_shell_format(self, command: str) -> str:
805
  """修改 shell 格式的命令"""
806
  # 在原命令前添加环境变量
807
  return f'{self.preload_prefix} {command}'
808
+
809
  def modify_exec_format(self, command: str) -> str:
810
  """修改 exec 格式 (JSON数组) 的命令"""
811
  try:
812
  # 解析 JSON 数组格式
813
  # 移除外层的方括号
814
  inner = command[1:-1].strip()
815
+
816
  # 简单的 JSON 数组解析
817
  parts = []
818
  current = ""
819
  in_quotes = False
820
  escape_next = False
821
+
822
  for char in inner:
823
  if escape_next:
824
  current += char
 
834
  current = ""
835
  else:
836
  current += char
837
+
838
  if current.strip():
839
  parts.append(current.strip())
840
+
841
  # 移除引号并处理转义
842
  cleaned_parts = []
843
  for part in parts:
 
847
  # 处理基本的转义字符
848
  part = part.replace('\\"', '"').replace('\\\\', '\\')
849
  cleaned_parts.append(part)
850
+
851
  if not cleaned_parts:
852
  return command
853
+
854
  # 构建新的命令
855
  # 第一个元素通常是 shell (/bin/sh, /bin/bash 等)
856
  # 如果第一个元素是 shell,修改执行的命令
 
869
  # 直接执行的命令,需要通过 shell 包装
870
  original_cmd = ' '.join(cleaned_parts)
871
  new_parts = ['/bin/sh', '-c', f'{self.preload_prefix} {original_cmd}']
872
+
873
  # 重新构建 JSON 数组
874
  escaped_parts = []
875
  for part in new_parts:
876
  # 转义引号和反斜杠
877
  escaped = part.replace('\\', '\\\\').replace('"', '\\"')
878
  escaped_parts.append(f'"{escaped}"')
879
+
880
  return '[' + ', '.join(escaped_parts) + ']'
881
+
882
  except Exception as e:
883
  print(f"警告: 解析 exec 格式失败: {e}")
884
  print(f"原始命令: {command}")
885
  # 如果解析失败,转换为 shell 格式
886
  return f'{self.preload_prefix} {command}'
887
+
888
  def modify_dockerfile_content(self, content: str) -> Tuple[str, List[str]]:
889
  """
890
  修改 Dockerfile 内容
 
893
  lines = content.splitlines()
894
  modified_lines = []
895
  changes = []
896
+
897
  for i, line in enumerate(lines, 1):
898
  instruction, command, format_type = self.parse_dockerfile_line(line)
899
+
900
  if instruction in ['CMD', 'ENTRYPOINT'] and command:
901
  if format_type == "shell":
902
  new_command = self.modify_shell_format(command)
 
906
  new_line = f'{instruction} {new_command}'
907
  else:
908
  new_line = line
909
+
910
  changes.append(f"第 {i} 行: {instruction} 指令已修改")
911
  changes.append(f" 原始: {line}")
912
  changes.append(f" 修改: {new_line}")
 
922
  if last_user is None:
923
  modified_lines.insert(-1, f"COPY {self.source_so_path}" + f" {self.tatget_so_path}")
924
  else:
925
+ modified_lines.insert(-1,
926
+ f"COPY --chown={last_user}:{last_user} {self.source_so_path} {self.tatget_so_path}")
927
  modified_lines.insert(-1, f"RUN chown -R {last_user}:{last_user} {self.tatget_so_dir}")
928
 
929
  return '\n'.join(modified_lines), changes