mlsec-raid 0.3.0__tar.gz → 0.4.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mlsec-raid
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Client for the RAID competition system
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -15,9 +15,13 @@ export RAID_KEY=<your-api-key> # or pass --api-key <y
15
15
 
16
16
  task-fetch <challenge> # downloads the challenge into ./<challenge>/
17
17
  task-check <challenge> # scores it locally with scoring/score.py on data/
18
- task-submit <challenge> # submits ./<challenge>/ without data/, scoring/ and .raidignore patterns, prints score and log
18
+ task-submit <challenge> # submits ./<challenge>/ without data/, scoring/ and .raidignore patterns
19
+ task-show # lists your jobs with their state (pending, running, success, failed) and score
20
+ task-log <job> # prints the log of a finished job, including the errors of your solution
19
21
  ```
20
22
 
23
+ Every command also works as `task <command>`, e.g. `task submit <challenge>`.
24
+
21
25
  The same is available in Python: `Raid(api_key).fetch(...)`, `.check(...)`, `.submit(...)`, `.push(...)`, `.wait(id)`, `.status()`, `.log(id)`, `.howto()`.
22
26
 
23
27
  Challenge authors: `task-check <challenge> --hidden --reference` scores `reference/` on `hidden/`. Admins upload a challenge directory with `task-push <directory>` and their own API key (created in the admin console): the directory's `challenge.yaml` sets the name and version (and the wave, baseline and badge of a new challenge), and the upload becomes the active version.
@@ -8,9 +8,13 @@ export RAID_KEY=<your-api-key> # or pass --api-key <y
8
8
 
9
9
  task-fetch <challenge> # downloads the challenge into ./<challenge>/
10
10
  task-check <challenge> # scores it locally with scoring/score.py on data/
11
- task-submit <challenge> # submits ./<challenge>/ without data/, scoring/ and .raidignore patterns, prints score and log
11
+ task-submit <challenge> # submits ./<challenge>/ without data/, scoring/ and .raidignore patterns
12
+ task-show # lists your jobs with their state (pending, running, success, failed) and score
13
+ task-log <job> # prints the log of a finished job, including the errors of your solution
12
14
  ```
13
15
 
16
+ Every command also works as `task <command>`, e.g. `task submit <challenge>`.
17
+
14
18
  The same is available in Python: `Raid(api_key).fetch(...)`, `.check(...)`, `.submit(...)`, `.push(...)`, `.wait(id)`, `.status()`, `.log(id)`, `.howto()`.
15
19
 
16
20
  Challenge authors: `task-check <challenge> --hidden --reference` scores `reference/` on `hidden/`. Admins upload a challenge directory with `task-push <directory>` and their own API key (created in the admin console): the directory's `challenge.yaml` sets the name and version (and the wave, baseline and badge of a new challenge), and the upload becomes the active version.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mlsec-raid"
3
- version = "0.3.0"
3
+ version = "0.4.0"
4
4
  description = "Client for the RAID competition system"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -10,7 +10,10 @@ dependencies = []
10
10
  task-fetch = "raid.cli:fetch"
11
11
  task-check = "raid.cli:check"
12
12
  task-submit = "raid.cli:submit"
13
+ task-show = "raid.cli:show"
14
+ task-log = "raid.cli:log"
13
15
  task-push = "raid.cli:push"
16
+ task = "raid.cli:main"
14
17
 
15
18
  [build-system]
16
19
  requires = ["uv_build>=0.8.17,<0.9.0"]
@@ -15,7 +15,7 @@ import urllib.request
15
15
  from pathlib import Path
16
16
 
17
17
  __all__ = ["Raid", "RaidError"]
18
- __version__ = "0.3.0"
18
+ __version__ = "0.4.0"
19
19
 
20
20
  DEFAULT_URL = "https://raid.mlsec.tu-berlin.de"
21
21
  PARTS = ("source", "data", "scoring") # source/ is unpacked into the challenge directory itself
@@ -110,7 +110,7 @@ class Raid:
110
110
  "POST", f"/api/submit/{challenge}", data=f, timeout=900,
111
111
  headers={"Content-Type": "application/gzip", "Content-Length": str(size)}) as r:
112
112
  result = json.load(r)
113
- print(f"Submitted {challenge} ({size / 1e6:.2f} MB) as submission {result['id']} (challenge version {result['version']}).")
113
+ print("Job submitted!\nRun task-show to see the status of your jobs.")
114
114
  return result["id"]
115
115
 
116
116
  def push(self, directory):
@@ -149,20 +149,27 @@ class Raid:
149
149
  return sub
150
150
 
151
151
  def status(self, challenge=None):
152
- """Print your submissions (newest first) and return them as a list of dicts."""
152
+ """Print your jobs (submissions, newest first) and return them as a list of dicts."""
153
153
  query = f"?challenge={_check(challenge)}" if challenge else ""
154
154
  subs = self._json(f"/api/submissions{query}")["submissions"]
155
- print(f"{'ID':>6} {'CHALLENGE':<20} {'VERSION':<8} {'STATUS':<8} {'SCORE':>10} SUBMITTED")
155
+ print(f"{'JOB':>6} {'CHALLENGE':<20} {'VERSION':<8} {'STATE':<8} {'SCORE':>10} SUBMITTED")
156
156
  for s in subs:
157
157
  score = "" if s["score"] is None else f"{s['score']:.4f}"
158
158
  print(f"{s['id']:>6} {s['challenge']:<20} {s['version']:<8} {s['status']:<8} {score:>10} {s['submitted_at'][:19]}")
159
159
  if not subs:
160
- print("No submissions yet.")
160
+ print("No jobs yet.")
161
+ else:
162
+ print("Run task-log <job> to see the log of a finished job.")
161
163
  return subs
162
164
 
163
165
  def log(self, submission_id):
164
- """Return the scoring log of one of your submissions."""
165
- return self._json(f"/api/submissions/{int(submission_id)}")["log"]
166
+ """Return the scoring log of one of your finished submissions: the output and errors of the scoring run."""
167
+ if not str(submission_id).isdigit():
168
+ raise RaidError(f"Invalid job id {submission_id!r}. task-show lists your jobs.")
169
+ sub = self._json(f"/api/submissions/{submission_id}")
170
+ if sub["status"] in ("pending", "running"):
171
+ raise RaidError(f"Job {sub['id']} is {sub['status']}. Its log is available once it has finished.")
172
+ return sub["log"] or "No log output."
166
173
 
167
174
  def _request(self, method, endpoint, data=None, headers=None, timeout=60):
168
175
  if not self.api_key:
@@ -1,4 +1,4 @@
1
- """Command line tools: task-fetch, task-check, task-submit and task-push."""
1
+ """Command line tools: task-fetch, task-check, task-submit, task-show, task-log and task-push (also as `task <command>`)."""
2
2
  import argparse
3
3
  import sys
4
4
 
@@ -11,7 +11,8 @@ def _run(description, action, key=True, options=(), target=("challenge", "challe
11
11
  parser.add_argument("--api-key", help="your API key (default: $RAID_KEY)")
12
12
  for flag, text in options:
13
13
  parser.add_argument(flag, action="store_true", help=text)
14
- parser.add_argument(target[0], help=target[1])
14
+ if target:
15
+ parser.add_argument(target[0], help=target[1])
15
16
  args = parser.parse_args()
16
17
  try:
17
18
  action(Raid(getattr(args, "api_key", None)), args)
@@ -31,16 +32,30 @@ def check():
31
32
 
32
33
 
33
34
  def submit():
34
- def action(raid, args):
35
- sid = raid.submit(args.challenge)
36
- try:
37
- raid.wait(sid)
38
- except KeyboardInterrupt:
39
- sys.exit("\nStopped waiting. Your submission is still being scored.")
40
- _run("Submit ./<challenge>/ (without data/, scoring/ and .raidignore patterns), wait for the score and print the log.", action)
35
+ _run("Submit ./<challenge>/ for scoring (without data/, scoring/ and .raidignore patterns).",
36
+ lambda raid, a: raid.submit(a.challenge))
37
+
38
+
39
+ def show():
40
+ _run("List your jobs (submissions) with their state and score.", lambda raid, a: raid.status(), target=None)
41
+
42
+
43
+ def log():
44
+ _run("Print the log of a finished job: the output and errors of its scoring run.",
45
+ lambda raid, a: print(raid.log(a.job)), target=("job", "job id, see task-show"))
41
46
 
42
47
 
43
48
  def push():
44
49
  _run("Admins: upload a challenge directory (without reference/) as its new active version. "
45
50
  "challenge.yaml supplies the name, the version and the metadata of a new challenge.",
46
51
  lambda raid, a: raid.push(a.directory), target=("directory", "challenge directory, e.g. self-assessment"))
52
+
53
+
54
+ def main():
55
+ """`task submit <challenge>` is the same as `task-submit <challenge>`."""
56
+ commands = {"fetch": fetch, "check": check, "submit": submit, "show": show, "log": log, "push": push}
57
+ if len(sys.argv) < 2 or sys.argv[1] not in commands:
58
+ sys.exit("Usage: task {" + ",".join(commands) + "} ...")
59
+ command = sys.argv.pop(1)
60
+ sys.argv[0] = f"task {command}"
61
+ commands[command]()