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.
- evalmc-0.0.2/PKG-INFO +60 -0
- evalmc-0.0.2/README.md +37 -0
- {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/check_challenge.py +3 -0
- evalmc-0.0.2/evalmc/config_paths.py +9 -0
- {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/host.py +1 -7
- {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/login.py +1 -8
- {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/main.py +3 -4
- evalmc-0.0.2/evalmc/nominate.py +33 -0
- {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/push_helpers.py +63 -4
- {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/set_token.py +1 -7
- {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/submission.py +14 -28
- evalmc-0.0.2/evalmc/submit_onnx.py +49 -0
- evalmc-0.0.2/evalmc.egg-info/PKG-INFO +60 -0
- {evalmc-0.0.1 → evalmc-0.0.2}/evalmc.egg-info/SOURCES.txt +5 -2
- {evalmc-0.0.1 → evalmc-0.0.2}/pyproject.toml +1 -1
- evalmc-0.0.2/tests/test_cli_nominate.py +57 -0
- evalmc-0.0.2/tests/test_push_helpers.py +160 -0
- evalmc-0.0.2/tests/test_submission_api_base.py +92 -0
- evalmc-0.0.1/PKG-INFO +0 -89
- evalmc-0.0.1/README.md +0 -66
- evalmc-0.0.1/evalmc/submit.py +0 -88
- evalmc-0.0.1/evalmc/submit_onnx.py +0 -62
- evalmc-0.0.1/evalmc.egg-info/PKG-INFO +0 -89
- evalmc-0.0.1/tests/test_push_helpers.py +0 -32
- {evalmc-0.0.1 → evalmc-0.0.2}/LICENSE +0 -0
- {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/__init__.py +0 -0
- {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/__main__.py +0 -0
- {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/check_registered.py +0 -0
- {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/register.py +0 -0
- {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/status.py +0 -0
- {evalmc-0.0.1 → evalmc-0.0.2}/evalmc/utils.py +0 -0
- {evalmc-0.0.1 → evalmc-0.0.2}/evalmc.egg-info/dependency_links.txt +0 -0
- {evalmc-0.0.1 → evalmc-0.0.2}/evalmc.egg-info/entry_points.txt +0 -0
- {evalmc-0.0.1 → evalmc-0.0.2}/evalmc.egg-info/requires.txt +0 -0
- {evalmc-0.0.1 → evalmc-0.0.2}/evalmc.egg-info/top_level.txt +0 -0
- {evalmc-0.0.1 → evalmc-0.0.2}/setup.cfg +0 -0
- {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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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=
|
|
138
|
-
show_default=
|
|
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
|
|
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
|
|
@@ -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
|