fffiloni commited on
Commit
1351793
·
verified ·
1 Parent(s): 62add84

Upload 8 files

Browse files
Files changed (1) hide show
  1. src/bucket.py +112 -27
src/bucket.py CHANGED
@@ -133,11 +133,56 @@ def write_json(path: str, payload: dict[str, Any], token: str | None = None) ->
133
 
134
 
135
  def write_launch_metadata(run_id: str, *, bucket_source: str, payload: dict[str, Any], token: str | None = None) -> None:
 
 
 
 
 
 
 
 
136
  paths = RunPaths(run_id, bucket_source=bucket_source)
137
  launch = dict(payload)
138
  launch.setdefault("run_id", run_id)
139
  launch.setdefault("bucket_source", bucket_source)
 
140
  write_json(f"{paths.root}/launch.json", launch, token=token)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
 
143
  def read_events(run_id: str, *, bucket_source: str, token: str | None = None) -> list[dict[str, Any]]:
@@ -234,25 +279,26 @@ def _list_run_files(
234
 
235
 
236
  def summarize_run_bundle(run_id: str, bundle: dict[str, Any], *, bucket_source: str) -> dict[str, Any]:
 
237
  state = bundle.get("state") or {}
238
  launch = bundle.get("launch") or {}
239
  gate = bundle.get("inference_gate") or {}
240
  smoke = bundle.get("generation_smoke") or {}
241
  hardware = bundle.get("hardware_strategy") or {}
242
- target_space = state.get("target_space") or launch.get("target_space") or ""
243
- status = state.get("status") or launch.get("status") or gate.get("status") or smoke.get("status") or "unknown"
244
  return {
245
  "run_id": run_id,
246
- "kind": state.get("kind") or launch.get("kind") or "unknown",
247
  "status": status,
248
- "model_id": state.get("model_id") or launch.get("model_id") or state.get("model") or "",
249
  "target_space": target_space,
250
- "target_space_url": state.get("target_space_url") or launch.get("target_space_url") or (f"https://huggingface.co/spaces/{target_space}" if target_space else ""),
251
- "job_id": _job_id_from_launch_or_state(launch=launch, state=state),
252
- "job_url": _job_url_from_launch_or_state(launch=launch, state=state),
253
- "created_at": state.get("created_at") or launch.get("created_at") or "",
254
- "updated_at": state.get("updated_at") or state.get("created_at") or launch.get("updated_at") or launch.get("created_at") or "",
255
- "selected_hardware": hardware.get("selected_hardware") or state.get("selected_hardware") or launch.get("preferred_space_hardware") or state.get("hardware") or "",
256
  "manual_hardware_required": bool(gate.get("manual_hardware_required") or hardware.get("manual_action_required") or launch.get("manual_hardware_required")),
257
  "health_passed": bool((gate.get("implementation_signals") or {}).get("health_passed") or smoke.get("health_passed")),
258
  "smoke_test_passed": bool(smoke.get("ok") or smoke.get("status") == "success"),
@@ -271,6 +317,7 @@ def read_run_bundle(run_id: str, *, bucket_source: str, token: str | None = None
271
  "events": paths.events,
272
  "report": paths.report,
273
  },
 
274
  "launch": _safe_read_json(f"{paths.root}/launch.json", token=token),
275
  "state": read_json(paths.state, token=token) or {},
276
  "events": read_events(run_id, bucket_source=bucket_source, token=token),
@@ -292,6 +339,45 @@ def _run_id_from_path(path: str) -> str:
292
  return path.rstrip('/').split('/')[-1]
293
 
294
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  def list_recent_runs(
296
  *,
297
  bucket_source: str,
@@ -302,35 +388,34 @@ def list_recent_runs(
302
  ) -> list[dict[str, Any]]:
303
  """List recent run summaries from a user's bucket.
304
 
305
- Best effort: if a run folder is incomplete, return the information that is
306
- available instead of failing the entire explorer.
 
307
  """
308
- fs = _fs(token)
309
  root = f"{bucket_uri_from_source(bucket_source)}/runs"
310
- try:
311
- entries = fs.ls(root, detail=True)
312
- except FileNotFoundError:
313
- return []
314
- except Exception:
315
  return []
316
 
317
  runs: list[dict[str, Any]] = []
318
- for entry in entries:
319
- name = entry.get("name") if isinstance(entry, dict) else str(entry)
320
- if not name:
321
- continue
322
- run_id = _run_id_from_path(name)
323
- if run_id in {"runs", ""}:
324
- continue
325
  launch = read_json(f"{root}/{run_id}/launch.json", token=token) or {}
326
  state = read_json(f"{root}/{run_id}/state.json", token=token) or {}
327
  gate = read_json(f"{root}/{run_id}/inference_gate.json", token=token) or {}
328
  smoke = read_json(f"{root}/{run_id}/tests/generation_smoke.json", token=token) or read_json(f"{root}/{run_id}/generation_smoke.json", token=token) or {}
329
  hardware = read_json(f"{root}/{run_id}/hardware_strategy.json", token=token) or {}
330
- partial_bundle = {"launch": launch, "state": state, "inference_gate": gate, "generation_smoke": smoke, "hardware_strategy": hardware}
 
 
 
 
 
 
 
331
  item = summarize_run_bundle(run_id, partial_bundle, bucket_source=bucket_source)
332
  item["bucket_source"] = bucket_source
333
- haystack = " ".join(str(item.get(k, "")) for k in ["run_id", "model_id", "target_space", "status", "kind"]).lower()
334
  if query and query.lower() not in haystack:
335
  continue
336
  if status and status not in {"all", ""} and item["status"] != status:
 
133
 
134
 
135
  def write_launch_metadata(run_id: str, *, bucket_source: str, payload: dict[str, Any], token: str | None = None) -> None:
136
+ """Persist enough metadata immediately for the Run Explorer.
137
+
138
+ Workers can take time before writing state.json/events.jsonl. The custom UI
139
+ should still be able to show a just-launched run, open its Job, and resume
140
+ polling after a page refresh. Store launch.json plus a lightweight
141
+ summary.json and minimal state.json fallback. The worker may overwrite
142
+ state.json later with richer information.
143
+ """
144
  paths = RunPaths(run_id, bucket_source=bucket_source)
145
  launch = dict(payload)
146
  launch.setdefault("run_id", run_id)
147
  launch.setdefault("bucket_source", bucket_source)
148
+ launch.setdefault("status", "running")
149
  write_json(f"{paths.root}/launch.json", launch, token=token)
150
+ summary = {
151
+ "run_id": run_id,
152
+ "bucket_source": bucket_source,
153
+ "kind": launch.get("kind") or "unknown",
154
+ "status": launch.get("status") or "running",
155
+ "model_id": launch.get("model_id") or "",
156
+ "target_space": launch.get("target_space") or "",
157
+ "target_space_url": launch.get("target_space_url") or (f"https://huggingface.co/spaces/{launch.get('target_space')}" if launch.get("target_space") else ""),
158
+ "job_id": launch.get("job_id") or "",
159
+ "job_url": launch.get("job_url") or "",
160
+ "created_by": launch.get("created_by") or launch.get("username") or "",
161
+ "created_at": launch.get("created_at") or "",
162
+ "updated_at": launch.get("updated_at") or launch.get("created_at") or "",
163
+ "selected_hardware": launch.get("preferred_space_hardware") or launch.get("selected_hardware") or "",
164
+ "artifacts_url": f"https://huggingface.co/buckets/{bucket_source}/tree/main/runs/{run_id}",
165
+ }
166
+ write_json(f"{paths.root}/summary.json", summary, token=token)
167
+ # Minimal state fallback: progress polling can treat it as running until
168
+ # the worker writes a definitive state.json.
169
+ write_json(
170
+ f"{paths.root}/state.json",
171
+ {
172
+ "run_id": run_id,
173
+ "kind": summary["kind"],
174
+ "status": summary["status"],
175
+ "model_id": summary["model_id"],
176
+ "target_space": summary["target_space"],
177
+ "target_space_url": summary["target_space_url"],
178
+ "job_id": summary["job_id"],
179
+ "job_url": summary["job_url"],
180
+ "created_by": summary["created_by"],
181
+ "created_at": summary["created_at"],
182
+ "updated_at": summary["updated_at"],
183
+ },
184
+ token=token,
185
+ )
186
 
187
 
188
  def read_events(run_id: str, *, bucket_source: str, token: str | None = None) -> list[dict[str, Any]]:
 
279
 
280
 
281
  def summarize_run_bundle(run_id: str, bundle: dict[str, Any], *, bucket_source: str) -> dict[str, Any]:
282
+ summary_file = bundle.get("summary_file") or {}
283
  state = bundle.get("state") or {}
284
  launch = bundle.get("launch") or {}
285
  gate = bundle.get("inference_gate") or {}
286
  smoke = bundle.get("generation_smoke") or {}
287
  hardware = bundle.get("hardware_strategy") or {}
288
+ target_space = state.get("target_space") or launch.get("target_space") or summary_file.get("target_space") or ""
289
+ status = state.get("status") or launch.get("status") or summary_file.get("status") or gate.get("status") or smoke.get("status") or "unknown"
290
  return {
291
  "run_id": run_id,
292
+ "kind": state.get("kind") or launch.get("kind") or summary_file.get("kind") or "unknown",
293
  "status": status,
294
+ "model_id": state.get("model_id") or launch.get("model_id") or summary_file.get("model_id") or state.get("model") or "",
295
  "target_space": target_space,
296
+ "target_space_url": state.get("target_space_url") or launch.get("target_space_url") or summary_file.get("target_space_url") or (f"https://huggingface.co/spaces/{target_space}" if target_space else ""),
297
+ "job_id": state.get("job_id") or launch.get("job_id") or summary_file.get("job_id") or "",
298
+ "job_url": state.get("job_url") or launch.get("job_url") or summary_file.get("job_url") or _job_url_from_launch_or_state(launch={**summary_file, **launch}, state=state),
299
+ "created_at": state.get("created_at") or launch.get("created_at") or summary_file.get("created_at") or "",
300
+ "updated_at": state.get("updated_at") or state.get("created_at") or launch.get("updated_at") or launch.get("created_at") or summary_file.get("updated_at") or summary_file.get("created_at") or "",
301
+ "selected_hardware": hardware.get("selected_hardware") or state.get("selected_hardware") or launch.get("preferred_space_hardware") or summary_file.get("selected_hardware") or state.get("hardware") or "",
302
  "manual_hardware_required": bool(gate.get("manual_hardware_required") or hardware.get("manual_action_required") or launch.get("manual_hardware_required")),
303
  "health_passed": bool((gate.get("implementation_signals") or {}).get("health_passed") or smoke.get("health_passed")),
304
  "smoke_test_passed": bool(smoke.get("ok") or smoke.get("status") == "success"),
 
317
  "events": paths.events,
318
  "report": paths.report,
319
  },
320
+ "summary_file": _safe_read_json(f"{paths.root}/summary.json", token=token),
321
  "launch": _safe_read_json(f"{paths.root}/launch.json", token=token),
322
  "state": read_json(paths.state, token=token) or {},
323
  "events": read_events(run_id, bucket_source=bucket_source, token=token),
 
339
  return path.rstrip('/').split('/')[-1]
340
 
341
 
342
+ def _discover_run_ids(root: str, *, token: str | None = None, limit: int = 300) -> list[str]:
343
+ """Discover run ids in a bucket even when the run folder is partial.
344
+
345
+ HfFileSystem may expose object-store prefixes slightly differently between
346
+ Buckets/runtime versions. Use ls plus a few targeted globs so running Jobs
347
+ with only launch.json/summary.json are still shown in the Run Explorer.
348
+ """
349
+ fs = _fs(token)
350
+ run_ids: set[str] = set()
351
+
352
+ def add_from_path(path: str) -> None:
353
+ if "/runs/" in path:
354
+ tail = path.split("/runs/", 1)[1]
355
+ else:
356
+ tail = path.replace(root.rstrip("/") + "/", "", 1)
357
+ run_id = tail.strip("/").split("/", 1)[0]
358
+ if run_id and run_id != "runs":
359
+ run_ids.add(run_id)
360
+
361
+ try:
362
+ for entry in fs.ls(root, detail=True):
363
+ name = entry.get("name") if isinstance(entry, dict) else str(entry)
364
+ if name:
365
+ add_from_path(name)
366
+ except Exception:
367
+ pass
368
+
369
+ for pattern in (f"{root}/*", f"{root}/*/summary.json", f"{root}/*/launch.json", f"{root}/*/state.json"):
370
+ try:
371
+ for path in fs.glob(pattern):
372
+ add_from_path(str(path))
373
+ if len(run_ids) >= limit:
374
+ break
375
+ except Exception:
376
+ continue
377
+
378
+ return sorted(run_ids, reverse=True)[:limit]
379
+
380
+
381
  def list_recent_runs(
382
  *,
383
  bucket_source: str,
 
388
  ) -> list[dict[str, Any]]:
389
  """List recent run summaries from a user's bucket.
390
 
391
+ Best effort: a run can be visible as soon as launch.json/summary.json exists,
392
+ before the worker writes full state/events. This keeps running Jobs visible
393
+ and makes Job links available immediately.
394
  """
 
395
  root = f"{bucket_uri_from_source(bucket_source)}/runs"
396
+ run_ids = _discover_run_ids(root, token=token)
397
+ if not run_ids:
 
 
 
398
  return []
399
 
400
  runs: list[dict[str, Any]] = []
401
+ for run_id in run_ids:
402
+ summary_file = read_json(f"{root}/{run_id}/summary.json", token=token) or {}
 
 
 
 
 
403
  launch = read_json(f"{root}/{run_id}/launch.json", token=token) or {}
404
  state = read_json(f"{root}/{run_id}/state.json", token=token) or {}
405
  gate = read_json(f"{root}/{run_id}/inference_gate.json", token=token) or {}
406
  smoke = read_json(f"{root}/{run_id}/tests/generation_smoke.json", token=token) or read_json(f"{root}/{run_id}/generation_smoke.json", token=token) or {}
407
  hardware = read_json(f"{root}/{run_id}/hardware_strategy.json", token=token) or {}
408
+ partial_bundle = {
409
+ "summary_file": summary_file,
410
+ "launch": launch,
411
+ "state": state,
412
+ "inference_gate": gate,
413
+ "generation_smoke": smoke,
414
+ "hardware_strategy": hardware,
415
+ }
416
  item = summarize_run_bundle(run_id, partial_bundle, bucket_source=bucket_source)
417
  item["bucket_source"] = bucket_source
418
+ haystack = " ".join(str(item.get(k, "")) for k in ["run_id", "model_id", "target_space", "status", "kind", "job_id"]).lower()
419
  if query and query.lower() not in haystack:
420
  continue
421
  if status and status not in {"all", ""} and item["status"] != status: