evalmc 0.0.1__tar.gz → 0.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. evalmc-0.0.2/PKG-INFO +60 -0
  2. evalmc-0.0.2/README.md +37 -0
  3. {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/check_challenge.py +3 -0
  4. evalmc-0.0.2/evalmc/config_paths.py +9 -0
  5. {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/host.py +1 -7
  6. {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/login.py +1 -8
  7. {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/main.py +3 -4
  8. evalmc-0.0.2/evalmc/nominate.py +33 -0
  9. {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/push_helpers.py +63 -4
  10. {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/set_token.py +1 -7
  11. {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/submission.py +14 -28
  12. evalmc-0.0.2/evalmc/submit_onnx.py +49 -0
  13. evalmc-0.0.2/evalmc.egg-info/PKG-INFO +60 -0
  14. {evalmc-0.0.1 → evalmc-0.0.2}/evalmc.egg-info/SOURCES.txt +5 -2
  15. {evalmc-0.0.1 → evalmc-0.0.2}/pyproject.toml +1 -1
  16. evalmc-0.0.2/tests/test_cli_nominate.py +57 -0
  17. evalmc-0.0.2/tests/test_push_helpers.py +160 -0
  18. evalmc-0.0.2/tests/test_submission_api_base.py +92 -0
  19. evalmc-0.0.1/PKG-INFO +0 -89
  20. evalmc-0.0.1/README.md +0 -66
  21. evalmc-0.0.1/evalmc/submit.py +0 -88
  22. evalmc-0.0.1/evalmc/submit_onnx.py +0 -62
  23. evalmc-0.0.1/evalmc.egg-info/PKG-INFO +0 -89
  24. evalmc-0.0.1/tests/test_push_helpers.py +0 -32
  25. {evalmc-0.0.1 → evalmc-0.0.2}/LICENSE +0 -0
  26. {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/__init__.py +0 -0
  27. {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/__main__.py +0 -0
  28. {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/check_registered.py +0 -0
  29. {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/register.py +0 -0
  30. {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/status.py +0 -0
  31. {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/utils.py +0 -0
  32. {evalmc-0.0.1 → evalmc-0.0.2}/evalmc.egg-info/dependency_links.txt +0 -0
  33. {evalmc-0.0.1 → evalmc-0.0.2}/evalmc.egg-info/entry_points.txt +0 -0
  34. {evalmc-0.0.1 → evalmc-0.0.2}/evalmc.egg-info/requires.txt +0 -0
  35. {evalmc-0.0.1 → evalmc-0.0.2}/evalmc.egg-info/top_level.txt +0 -0
  36. {evalmc-0.0.1 → evalmc-0.0.2}/setup.cfg +0 -0
  37. {evalmc-0.0.1 → evalmc-0.0.2}/tests/test_cli_smoke.py +0 -0
evalmc-0.0.2/PKG-INFO ADDED
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: evalmc
3
+ Version: 0.0.2
4
+ Summary: MyoChallenge submission CLI
5
+ Author-email: MyoChallenge Team <caggiano@gmail.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/MyoHub/mcEvalProd-CLI
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: click>=8.0.0
19
+ Requires-Dist: docker>=6.0.0
20
+ Requires-Dist: requests>=2.25.0
21
+ Requires-Dist: boto3>=1.26.0
22
+ Dynamic: license-file
23
+
24
+ # evalmc — MyoChallenge submission CLI
25
+
26
+ ```bash
27
+ pip install -U evalmc
28
+ ```
29
+
30
+ Default API host is `https://api.mc.myosuite.org`.
31
+
32
+ ## Setup
33
+
34
+ ```bash
35
+ evalmc host --set-host https://api.mc.myosuite.org
36
+ evalmc login # email + auth token from mc26.myosuite.org
37
+ evalmc status
38
+ ```
39
+
40
+ ## Submit
41
+
42
+ **ONNX** (recommended for ChaseTag):
43
+
44
+ ```bash
45
+ evalmc submit-onnx model.onnx -t chasetag -p 1
46
+ ```
47
+
48
+ **Nominate** a scored in-window submission for title-cycle finals:
49
+
50
+ ```bash
51
+ evalmc nominate -t chasetag -p 1 --submission-id 123
52
+ ```
53
+
54
+ **Docker / ECR** (custom Python policies):
55
+
56
+ ```bash
57
+ evalmc push my-agent:tag -t chasetag -p 1
58
+ ```
59
+
60
+ Credentials live in `~/.myochallenge/` (`host_url`, `auth_token.json`).
evalmc-0.0.2/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # evalmc — MyoChallenge submission CLI
2
+
3
+ ```bash
4
+ pip install -U evalmc
5
+ ```
6
+
7
+ Default API host is `https://api.mc.myosuite.org`.
8
+
9
+ ## Setup
10
+
11
+ ```bash
12
+ evalmc host --set-host https://api.mc.myosuite.org
13
+ evalmc login # email + auth token from mc26.myosuite.org
14
+ evalmc status
15
+ ```
16
+
17
+ ## Submit
18
+
19
+ **ONNX** (recommended for ChaseTag):
20
+
21
+ ```bash
22
+ evalmc submit-onnx model.onnx -t chasetag -p 1
23
+ ```
24
+
25
+ **Nominate** a scored in-window submission for title-cycle finals:
26
+
27
+ ```bash
28
+ evalmc nominate -t chasetag -p 1 --submission-id 123
29
+ ```
30
+
31
+ **Docker / ECR** (custom Python policies):
32
+
33
+ ```bash
34
+ evalmc push my-agent:tag -t chasetag -p 1
35
+ ```
36
+
37
+ Credentials live in `~/.myochallenge/` (`host_url`, `auth_token.json`).
@@ -18,6 +18,8 @@ def check_task_phase(api_url, task, phase):
18
18
  "score_names": data.get("score_names"),
19
19
  "display_name": data.get("display_name"),
20
20
  "visible": data.get("visible"),
21
+ "evaluation_mode": data.get("evaluation_mode", "absolute"),
22
+ "access_mode": data.get("access_mode", "open"),
21
23
  "created_at": data.get("created_at"),
22
24
  }
23
25
  else:
@@ -112,6 +114,7 @@ def main():
112
114
  total_score_metrics += tp.get("num_scores", 0)
113
115
  label = tp.get("display_name") or f"{tp['task']} phase {tp['phase']}"
114
116
  print(f"✅ {label}: {tp['num_scores']} score metrics")
117
+ print(f" Format: {tp.get('evaluation_mode', 'absolute')} | Access: {tp.get('access_mode', 'open')}")
115
118
  print(f" Score Names: {', '.join(tp.get('score_names', []))}")
116
119
  print()
117
120
 
@@ -0,0 +1,9 @@
1
+ """Shared local config file locations for the evalmc CLI."""
2
+
3
+ from pathlib import Path
4
+
5
+ CONFIG_DIR = Path.home() / ".myochallenge"
6
+ HOST_URL_FILE = CONFIG_DIR / "host_url"
7
+ AUTH_TOKEN_FILE = CONFIG_DIR / "auth_token.json"
8
+
9
+ CONFIG_DIR.mkdir(exist_ok=True)
@@ -4,16 +4,10 @@ MyoChallenge Host - Configure API host URL
4
4
  """
5
5
 
6
6
  import sys
7
- from pathlib import Path
8
7
 
9
8
  import click
10
9
 
11
- # Configuration file paths
12
- CONFIG_DIR = Path.home() / ".myochallenge"
13
- HOST_URL_FILE = CONFIG_DIR / "host_url"
14
-
15
- # Ensure config directory exists
16
- CONFIG_DIR.mkdir(exist_ok=True)
10
+ from .config_paths import HOST_URL_FILE
17
11
 
18
12
 
19
13
  @click.command()
@@ -5,18 +5,11 @@ MyoChallenge Login - Interactive authentication and credential storage
5
5
 
6
6
  import json
7
7
  import sys
8
- from pathlib import Path
9
8
 
10
9
  import click
11
10
  import requests
12
11
 
13
- # Configuration file paths
14
- CONFIG_DIR = Path.home() / ".myochallenge"
15
- HOST_URL_FILE = CONFIG_DIR / "host_url"
16
- AUTH_TOKEN_FILE = CONFIG_DIR / "auth_token.json"
17
-
18
- # Ensure config directory exists
19
- CONFIG_DIR.mkdir(exist_ok=True)
12
+ from .config_paths import AUTH_TOKEN_FILE, HOST_URL_FILE
20
13
 
21
14
 
22
15
  def get_default_host():
@@ -10,8 +10,8 @@ from .login import login
10
10
  from .set_token import set_token
11
11
  from .status import status
12
12
  from .submission import push
13
- from .submit import submit
14
13
  from .submit_onnx import submit_onnx
14
+ from .nominate import nominate
15
15
 
16
16
 
17
17
  @click.group()
@@ -23,8 +23,7 @@ def main():
23
23
  Two submission paths are available:
24
24
 
25
25
  \b
26
- evalmc submit policy.zip -p 1 -t tabletennis # zip upload (recommended)
27
- evalmc submit-onnx model.onnx -p 1 -t elbowflexion # ONNX upload
26
+ evalmc submit-onnx model.onnx -p 1 -t elbowflexion # ONNX (elbow / ChaseTag)
28
27
  evalmc push my-image:tag -p 1 -t tabletennis # Docker/ECR image push
29
28
  """
30
29
  pass
@@ -34,6 +33,6 @@ main.add_command(host)
34
33
  main.add_command(login)
35
34
  main.add_command(set_token)
36
35
  main.add_command(push)
37
- main.add_command(submit)
38
36
  main.add_command(submit_onnx)
37
+ main.add_command(nominate)
39
38
  main.add_command(status)
@@ -0,0 +1,33 @@
1
+ """Select a scored title-cycle submission for the upcoming finals."""
2
+
3
+ import click
4
+ import requests
5
+
6
+ from .login import get_default_host
7
+ from .push_helpers import http_error_detail, resolve_credentials
8
+
9
+
10
+ @click.command()
11
+ @click.option("-t", "--task", required=True)
12
+ @click.option("-p", "--phase", required=True, type=int)
13
+ @click.option("--submission-id", required=True, type=int)
14
+ @click.option("-e", "--email", default=None)
15
+ @click.option("-a", "--auth-token", default=None)
16
+ def nominate(task, phase, submission_id, email, auth_token):
17
+ """Use SUBMISSION_ID for this title cycle's finals."""
18
+ email, auth_token = resolve_credentials(email, auth_token)
19
+ try:
20
+ response = requests.put(
21
+ f"{get_default_host()}/title-cycle/{task}/{phase}/nominate",
22
+ json={"submission_id": submission_id},
23
+ headers={"X-User-Email": email, "X-Auth-Token": auth_token},
24
+ timeout=20,
25
+ )
26
+ response.raise_for_status()
27
+ except requests.exceptions.HTTPError as exc:
28
+ raise click.ClickException(
29
+ f"Could not nominate submission: {http_error_detail(exc.response)}"
30
+ )
31
+ except requests.exceptions.RequestException as exc:
32
+ raise click.ClickException(f"Could not reach the API: {exc}")
33
+ click.echo(f"Submission {submission_id} will be used for finals.")
@@ -4,6 +4,9 @@ from __future__ import annotations
4
4
 
5
5
  from urllib.parse import urlparse
6
6
 
7
+ import click
8
+ import requests
9
+
7
10
  POLICY_PATHS = (
8
11
  "/policy/policy.py",
9
12
  "/policy/model.onnx",
@@ -51,7 +54,7 @@ def image_not_found_help(image: str, build_hint: str | None = None) -> str:
51
54
  lines.extend(
52
55
  [
53
56
  "",
54
- "For elbow flexion, zip/ONNX submission is usually simpler:",
57
+ "For elbow flexion, ONNX submission is usually simpler:",
55
58
  " evalmc submit-onnx model.onnx -t elbowflexion -p 1",
56
59
  ]
57
60
  )
@@ -95,9 +98,8 @@ def local_push_remote_warning(api_base: str, local_tag: str) -> str:
95
98
  f"\n"
96
99
  f"Expected outcome: submission status 'submitted' → 'failed' (no leaderboard entry).\n"
97
100
  f"\n"
98
- f"Recommended alternatives for elbow flexion:\n"
101
+ f"Recommended alternative for elbow flexion:\n"
99
102
  f" evalmc submit-onnx model.onnx -t elbowflexion -p 1\n"
100
- f" evalmc submit policy.zip -t elbowflexion -p 1\n"
101
103
  f"\n"
102
104
  f"For Docker on production, the server needs USE_LOCAL_STORAGE=false and ECR push.\n"
103
105
  f"Contact the organizers if ECR upload is required."
@@ -121,6 +123,63 @@ def policy_missing_help(image: str) -> str:
121
123
  )
122
124
 
123
125
 
126
+ def resolve_credentials(email: str | None, auth_token: str | None) -> tuple[str, str]:
127
+ """Fill in email/auth_token from stored credentials when omitted.
128
+
129
+ Raises click.ClickException if neither the args nor stored credentials
130
+ provide both values.
131
+ """
132
+ from .login import get_stored_auth
133
+
134
+ if not email or not auth_token:
135
+ stored = get_stored_auth()
136
+ if not stored:
137
+ raise click.ClickException(
138
+ "No credentials found. Run 'evalmc login' or pass --email and --auth-token."
139
+ )
140
+ email = email or stored["email"]
141
+ auth_token = auth_token or stored["auth_token"]
142
+ return email, auth_token
143
+
144
+
145
+ def post_submission_file(
146
+ api_base: str,
147
+ endpoint: str,
148
+ task: str,
149
+ phase: int,
150
+ email: str,
151
+ auth_token: str,
152
+ file_field: str,
153
+ file_name: str,
154
+ file_obj,
155
+ content_type: str,
156
+ ) -> dict:
157
+ """POST a submission file to the API; raise ClickException on failure."""
158
+ try:
159
+ resp = requests.post(
160
+ f"{api_base}{endpoint}",
161
+ data={"task": task, "phase": phase, "email": email, "auth_token": auth_token},
162
+ files={file_field: (file_name, file_obj, content_type)},
163
+ timeout=120,
164
+ )
165
+ resp.raise_for_status()
166
+ except requests.exceptions.HTTPError as e:
167
+ detail = http_error_detail(e.response)
168
+ if e.response.status_code == 403 and "invitation-only" in detail.lower():
169
+ detail = f"{detail} Ask an organizer to approve your team for this track."
170
+ raise click.ClickException(f"Server error {e.response.status_code}: {detail}")
171
+ except requests.exceptions.RequestException as e:
172
+ raise click.ClickException(f"Request failed: {e}")
173
+ return resp.json()
174
+
175
+
176
+ def echo_submission_result(data: dict) -> None:
177
+ click.echo("Submitted successfully!")
178
+ click.echo(f" Submission ID : {data['submission_id']}")
179
+ click.echo(f" Type : {data['submission_type']}")
180
+ click.echo(f" Status : {data['status']}")
181
+
182
+
124
183
  def post_submit_local_remote_note(submission_id, local_tag: str, api_base: str) -> str:
125
184
  return (
126
185
  f"\n⚠️ Submission {submission_id} recorded, but evaluation may fail on the server.\n"
@@ -132,5 +191,5 @@ def post_submit_local_remote_note(submission_id, local_tag: str, api_base: str)
132
191
  f" curl -s {api_base}/submissions/team/YOUR_TEAM | python3 -m json.tool\n"
133
192
  f"\n"
134
193
  f"If status becomes 'failed', the server could not run your local Docker tag.\n"
135
- f"Use evalmc submit-onnx or evalmc submit (zip) instead."
194
+ f"Use evalmc submit-onnx instead."
136
195
  )
@@ -5,16 +5,10 @@ MyoChallenge Set Token - Directly configure authentication token
5
5
 
6
6
  import json
7
7
  import sys
8
- from pathlib import Path
9
8
 
10
9
  import click
11
10
 
12
- # Configuration file paths
13
- CONFIG_DIR = Path.home() / ".myochallenge"
14
- AUTH_TOKEN_FILE = CONFIG_DIR / "auth_token.json"
15
-
16
- # Ensure config directory exists
17
- CONFIG_DIR.mkdir(exist_ok=True)
11
+ from .config_paths import AUTH_TOKEN_FILE
18
12
 
19
13
 
20
14
  @click.command()
@@ -3,10 +3,13 @@
3
3
  MyoChallenge Submission Script - ECR-based Docker image submission
4
4
  """
5
5
 
6
+ import json
7
+ import shutil
6
8
  import subprocess
7
9
  import sys
10
+ import tempfile
11
+ import time
8
12
  import uuid
9
- from pathlib import Path
10
13
 
11
14
  import click
12
15
  import docker
@@ -25,14 +28,6 @@ from .push_helpers import (
25
28
  upload_mode_summary,
26
29
  )
27
30
 
28
- # Configuration file paths
29
- CONFIG_DIR = Path.home() / ".myochallenge"
30
- HOST_URL_FILE = CONFIG_DIR / "host_url"
31
- AUTH_TOKEN_FILE = CONFIG_DIR / "auth_token.json"
32
-
33
- # Ensure config directory exists
34
- CONFIG_DIR.mkdir(exist_ok=True)
35
-
36
31
 
37
32
  # Configuration
38
33
  LOCAL_DOCKER_REGISTRY_URI = "localhost:5000"
@@ -134,8 +129,8 @@ def _run_subprocess(cmd: list[str], step: str) -> None:
134
129
  @click.option(
135
130
  "--api-base",
136
131
  help="API base URL",
137
- default="https://api.mc.myosuite.org",
138
- show_default=True,
132
+ default=None,
133
+ show_default="stored host, or https://api.mc.myosuite.org",
139
134
  )
140
135
  @click.option(
141
136
  "--skip-policy-check",
@@ -177,7 +172,7 @@ def push(image, phase, task, email, auth_token, url, public, private, api_base,
177
172
  if not public and not private:
178
173
  private = True # Default to private
179
174
 
180
- api_base = get_default_host()
175
+ api_base = api_base or get_default_host()
181
176
 
182
177
  click.echo(f"🚀 Starting submission for {image} to {task} phase {phase}")
183
178
  click.echo(f"🌐 API host: {api_base}")
@@ -250,15 +245,6 @@ def push(image, phase, task, email, auth_token, url, public, private, api_base,
250
245
  except requests.exceptions.RequestException as e:
251
246
  _fail(f"Authentication request failed: {e}")
252
247
 
253
- # Collect submission metadata (not used in ECR flow)
254
- # submission_metadata = {
255
- # "is_public": json.dumps(public),
256
- # "method_name": "",
257
- # "method_description": "",
258
- # "project_url": "",
259
- # "publication_url": "",
260
- # }
261
-
262
248
  # Generate unique tag for submission
263
249
  submission_tag = str(uuid.uuid4())
264
250
  click.echo(f"🏷️ Generated submission tag: {submission_tag}")
@@ -290,7 +276,7 @@ def push(image, phase, task, email, auth_token, url, public, private, api_base,
290
276
  if not click.confirm("Continue with local Docker tag anyway?", default=False):
291
277
  _fail(
292
278
  "Submission cancelled",
293
- "Use evalmc submit-onnx or evalmc submit (zip), or re-run with --yes.",
279
+ "Use evalmc submit-onnx, or re-run with --yes.",
294
280
  )
295
281
  elif not is_local_api(api_base):
296
282
  click.echo(local_push_remote_warning(api_base, local_tag))
@@ -327,6 +313,8 @@ def push(image, phase, task, email, auth_token, url, public, private, api_base,
327
313
  "task": task,
328
314
  "phase": phase,
329
315
  "image_uri": image_uri,
316
+ "email": email,
317
+ "auth_token": auth_token,
330
318
  },
331
319
  timeout=10,
332
320
  )
@@ -376,6 +364,8 @@ def push(image, phase, task, email, auth_token, url, public, private, api_base,
376
364
  "task": task,
377
365
  "phase": phase,
378
366
  "image_uri": image_uri,
367
+ "email": email,
368
+ "auth_token": auth_token,
379
369
  },
380
370
  timeout=10,
381
371
  )
@@ -412,8 +402,6 @@ def push(image, phase, task, email, auth_token, url, public, private, api_base,
412
402
  f"Server returned: {ecr_data}",
413
403
  )
414
404
 
415
- import time
416
-
417
405
  submission_id = str(uuid.uuid4())[
418
406
  :8
419
407
  ] # Use first 8 chars of UUID for uniqueness
@@ -425,10 +413,6 @@ def push(image, phase, task, email, auth_token, url, public, private, api_base,
425
413
  click.echo(f"⏰ Timestamp: {timestamp}")
426
414
 
427
415
  try:
428
- import json
429
- import shutil
430
- import tempfile
431
-
432
416
  temp_dir = tempfile.mkdtemp()
433
417
  dockerfile_path = f"{temp_dir}/Dockerfile"
434
418
 
@@ -485,6 +469,8 @@ def push(image, phase, task, email, auth_token, url, public, private, api_base,
485
469
  "task": task,
486
470
  "phase": phase,
487
471
  "image_uri": image_uri,
472
+ "email": email,
473
+ "auth_token": auth_token,
488
474
  },
489
475
  timeout=10,
490
476
  )
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env python3
2
+ """evalmc submit-onnx — upload a standalone ONNX model to MyoChallenge."""
3
+
4
+ from pathlib import Path
5
+
6
+ import click
7
+
8
+ from .login import get_default_host
9
+ from .push_helpers import echo_submission_result, post_submission_file, resolve_credentials
10
+
11
+
12
+ @click.command("submit-onnx")
13
+ @click.argument("onnx_path", type=click.Path(exists=True, path_type=Path))
14
+ @click.option("-p", "--phase", required=True, type=int, help="Challenge phase number")
15
+ @click.option("-t", "--task", required=True, help="Challenge task name (e.g. elbowflexion)")
16
+ @click.option("-e", "--email", default=None, help="Team email")
17
+ @click.option("-a", "--auth-token", default=None, help="Auth token")
18
+ def submit_onnx(onnx_path, phase, task, email, auth_token):
19
+ """Submit a standalone ONNX model (no policy.py required).
20
+
21
+ Works for elbow flexion and ChaseTag (obs→action graphs).
22
+
23
+ Example:
24
+ evalmc submit-onnx my_model.onnx -t elbowflexion -p 1
25
+ evalmc submit-onnx chase.onnx -t chasetag -p 1
26
+ """
27
+ if onnx_path.suffix.lower() != ".onnx":
28
+ raise click.ClickException("File must have .onnx extension")
29
+
30
+ api_base = get_default_host()
31
+ email, auth_token = resolve_credentials(email, auth_token)
32
+
33
+ click.echo(f"Submitting {onnx_path.name} → task={task} phase={phase}")
34
+
35
+ with open(onnx_path, "rb") as f:
36
+ data = post_submission_file(
37
+ api_base,
38
+ "/submit/onnx",
39
+ task,
40
+ phase,
41
+ email,
42
+ auth_token,
43
+ file_field="onnx_model",
44
+ file_name=onnx_path.name,
45
+ file_obj=f,
46
+ content_type="application/octet-stream",
47
+ )
48
+
49
+ echo_submission_result(data)
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: evalmc
3
+ Version: 0.0.2
4
+ Summary: MyoChallenge submission CLI
5
+ Author-email: MyoChallenge Team <caggiano@gmail.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/MyoHub/mcEvalProd-CLI
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: click>=8.0.0
19
+ Requires-Dist: docker>=6.0.0
20
+ Requires-Dist: requests>=2.25.0
21
+ Requires-Dist: boto3>=1.26.0
22
+ Dynamic: license-file
23
+
24
+ # evalmc — MyoChallenge submission CLI
25
+
26
+ ```bash
27
+ pip install -U evalmc
28
+ ```
29
+
30
+ Default API host is `https://api.mc.myosuite.org`.
31
+
32
+ ## Setup
33
+
34
+ ```bash
35
+ evalmc host --set-host https://api.mc.myosuite.org
36
+ evalmc login # email + auth token from mc26.myosuite.org
37
+ evalmc status
38
+ ```
39
+
40
+ ## Submit
41
+
42
+ **ONNX** (recommended for ChaseTag):
43
+
44
+ ```bash
45
+ evalmc submit-onnx model.onnx -t chasetag -p 1
46
+ ```
47
+
48
+ **Nominate** a scored in-window submission for title-cycle finals:
49
+
50
+ ```bash
51
+ evalmc nominate -t chasetag -p 1 --submission-id 123
52
+ ```
53
+
54
+ **Docker / ECR** (custom Python policies):
55
+
56
+ ```bash
57
+ evalmc push my-agent:tag -t chasetag -p 1
58
+ ```
59
+
60
+ Credentials live in `~/.myochallenge/` (`host_url`, `auth_token.json`).
@@ -5,15 +5,16 @@ evalmc/__init__.py
5
5
  evalmc/__main__.py
6
6
  evalmc/check_challenge.py
7
7
  evalmc/check_registered.py
8
+ evalmc/config_paths.py
8
9
  evalmc/host.py
9
10
  evalmc/login.py
10
11
  evalmc/main.py
12
+ evalmc/nominate.py
11
13
  evalmc/push_helpers.py
12
14
  evalmc/register.py
13
15
  evalmc/set_token.py
14
16
  evalmc/status.py
15
17
  evalmc/submission.py
16
- evalmc/submit.py
17
18
  evalmc/submit_onnx.py
18
19
  evalmc/utils.py
19
20
  evalmc.egg-info/PKG-INFO
@@ -22,5 +23,7 @@ evalmc.egg-info/dependency_links.txt
22
23
  evalmc.egg-info/entry_points.txt
23
24
  evalmc.egg-info/requires.txt
24
25
  evalmc.egg-info/top_level.txt
26
+ tests/test_cli_nominate.py
25
27
  tests/test_cli_smoke.py
26
- tests/test_push_helpers.py
28
+ tests/test_push_helpers.py
29
+ tests/test_submission_api_base.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "evalmc"
7
- version = "0.0.1"
7
+ version = "0.0.2"
8
8
  description = "MyoChallenge submission CLI"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -0,0 +1,57 @@
1
+ """CLI coverage for title-cycle nomination."""
2
+
3
+ from unittest.mock import MagicMock
4
+
5
+ from click.testing import CliRunner
6
+ import requests
7
+
8
+ from evalmc.main import main
9
+ import evalmc.nominate as nominate_module
10
+
11
+
12
+ def test_nominate_is_registered_and_calls_api(monkeypatch):
13
+ response = MagicMock()
14
+ response.raise_for_status.return_value = None
15
+ called = {}
16
+
17
+ def fake_put(url, **kwargs):
18
+ called["url"] = url
19
+ called["kwargs"] = kwargs
20
+ return response
21
+
22
+ monkeypatch.setattr(
23
+ nominate_module, "get_default_host", lambda: "https://api.example.com"
24
+ )
25
+ monkeypatch.setattr(nominate_module.requests, "put", fake_put)
26
+
27
+ result = CliRunner().invoke(main, [
28
+ "nominate", "-t", "chasetag", "-p", "1", "--submission-id", "42",
29
+ "-e", "team@example.com", "-a", "token123",
30
+ ])
31
+
32
+ assert result.exit_code == 0, result.output
33
+ assert "Submission 42 will be used for finals." in result.output
34
+ assert called["url"] == (
35
+ "https://api.example.com/title-cycle/chasetag/1/nominate"
36
+ )
37
+ assert called["kwargs"]["json"] == {"submission_id": 42}
38
+
39
+
40
+ def test_nominate_surfaces_invitational_access_error(monkeypatch):
41
+ response = MagicMock()
42
+ response.status_code = 403
43
+ response.json.return_value = {
44
+ "detail": "This invitation-only track requires organizer approval"
45
+ }
46
+ error = requests.exceptions.HTTPError(response=response)
47
+
48
+ monkeypatch.setattr(nominate_module.requests, "put", lambda *args, **kwargs: response)
49
+ response.raise_for_status.side_effect = error
50
+
51
+ result = CliRunner().invoke(main, [
52
+ "nominate", "-t", "chasetag", "-p", "1", "--submission-id", "42",
53
+ "-e", "team@example.com", "-a", "token123",
54
+ ])
55
+
56
+ assert result.exit_code != 0
57
+ assert "invitation-only track requires organizer approval" in result.output