openrunner-sdk 2.2.0__tar.gz → 2.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.
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/PKG-INFO +1 -1
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/api_client.py +106 -10
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/run.py +13 -3
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/sender.py +8 -4
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/pyproject.toml +1 -1
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_alert.py +1 -1
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_integration_sklearn.py +7 -7
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_media.py +12 -4
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_plot.py +7 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_system_metrics.py +2 -2
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/.gitignore +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/=6.0 +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/=8.1 +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/README.md +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/__init__.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/artifact.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/buffer.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/cache.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/cli.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/config.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/cost.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/dataset.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/environment.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/evaluation.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/feedback.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/git_info.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/guardrails.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/__init__.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/accelerate.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/anthropic_tracer.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/catboost.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/diffusers.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/fastai.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/forced_alignment.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/gladia.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/gymnasium.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/huggingface.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/hydra.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/ignite.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/jax.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/keras.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/langchain.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/lightgbm.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/lightning.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/llamaindex.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/openai_finetune.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/openai_tracer.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/optuna.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/pytorch.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/sb3.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/sklearn.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/tensorflow.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/trl.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/tts.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/ultralytics.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/voice_agent.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/whisper.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/integration/xgboost.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/launch.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/media.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/migrate.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/model.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/offline.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/pii.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/plot.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/prompt.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/query_api.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/scorers.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/settings.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/summary.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/sweep.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/system_metrics.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/tensorboard.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/trace.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/transcript_formatter.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/wal.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/wandb_compat/__init__.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/openrunner/wandb_compat/_shim.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/__init__.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/conftest.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_aliases.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_api_client.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_artifact.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_buffer.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_cache.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_class_scorers.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_cli.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_config.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_evaluation.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_finish.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_git_info.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_init.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_integration_fastai.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_integration_huggingface.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_integration_keras.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_integration_langchain.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_integration_lightning.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_integration_pytorch.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_integration_xgboost.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_launch.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_log.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_log_code.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_migrate.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_offline.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_offline_sync.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_pii.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_query_api.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_resume.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_sdk_features.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_sender.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_summary.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_sweep.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_trace.py +0 -0
- {openrunner_sdk-2.2.0 → openrunner_sdk-2.4.0}/tests/test_wandb_compat.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openrunner-sdk
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: OpenRunner SDK - W&B-compatible ML experiment tracking client
|
|
5
5
|
Project-URL: Homepage, https://github.com/jqueguiner/openrunner
|
|
6
6
|
Project-URL: Repository, https://github.com/jqueguiner/openrunner
|
|
@@ -490,6 +490,86 @@ class APIClient:
|
|
|
490
490
|
logger.warning("use_artifact failed: %s", e)
|
|
491
491
|
return None
|
|
492
492
|
|
|
493
|
+
def download_artifact(
|
|
494
|
+
self,
|
|
495
|
+
run_id: str,
|
|
496
|
+
artifact_name: str,
|
|
497
|
+
dest_dir: str = ".",
|
|
498
|
+
version: int | None = None,
|
|
499
|
+
alias: str | None = None,
|
|
500
|
+
) -> str | None:
|
|
501
|
+
"""Download all files from an artifact version to a local directory.
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
run_id: Run ID that used/created the artifact
|
|
505
|
+
artifact_name: Artifact name (or "name:alias")
|
|
506
|
+
dest_dir: Local directory to save files (created if needed)
|
|
507
|
+
version: Specific version number (optional)
|
|
508
|
+
alias: Alias name like "latest", "best" (optional)
|
|
509
|
+
|
|
510
|
+
Returns:
|
|
511
|
+
Path to the download directory, or None on failure.
|
|
512
|
+
|
|
513
|
+
Example:
|
|
514
|
+
path = client.download_artifact(run_id, "model-checkpoint", "./models")
|
|
515
|
+
# Files saved to ./models/model-checkpoint/v3/...
|
|
516
|
+
"""
|
|
517
|
+
import os
|
|
518
|
+
from pathlib import Path
|
|
519
|
+
|
|
520
|
+
info = self.use_artifact(run_id, artifact_name, version=version, alias=alias)
|
|
521
|
+
if not info:
|
|
522
|
+
logger.warning("download_artifact: use_artifact returned no data")
|
|
523
|
+
return None
|
|
524
|
+
|
|
525
|
+
# Extract version info and files
|
|
526
|
+
ver = info.get("version", version or 1)
|
|
527
|
+
files = info.get("files", [])
|
|
528
|
+
if not files:
|
|
529
|
+
logger.warning("download_artifact: no files in artifact")
|
|
530
|
+
return None
|
|
531
|
+
|
|
532
|
+
# Create destination directory
|
|
533
|
+
name_clean = artifact_name.split(":")[0].replace("/", "_")
|
|
534
|
+
out_dir = Path(dest_dir) / name_clean / f"v{ver}"
|
|
535
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
536
|
+
|
|
537
|
+
downloaded = 0
|
|
538
|
+
for f in files:
|
|
539
|
+
url = f.get("presigned_url") or f.get("url") or f.get("download_url")
|
|
540
|
+
fname = f.get("name") or f.get("path") or f"file_{downloaded}"
|
|
541
|
+
if not url:
|
|
542
|
+
continue
|
|
543
|
+
|
|
544
|
+
# Try presigned URL first, fall back to proxy
|
|
545
|
+
data = self.download_file_from_presigned_url(url)
|
|
546
|
+
if data is None and "localhost" in url:
|
|
547
|
+
# Try proxy
|
|
548
|
+
key = f.get("storage_key") or f.get("key")
|
|
549
|
+
if key:
|
|
550
|
+
try:
|
|
551
|
+
resp = self._request("GET", f"/storage/download?key={key}")
|
|
552
|
+
if resp.status_code == 200:
|
|
553
|
+
data = resp.content
|
|
554
|
+
except Exception:
|
|
555
|
+
pass
|
|
556
|
+
|
|
557
|
+
if data:
|
|
558
|
+
file_path = out_dir / fname
|
|
559
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
560
|
+
file_path.write_bytes(data)
|
|
561
|
+
downloaded += 1
|
|
562
|
+
logger.info("downloaded: %s (%d bytes)", fname, len(data))
|
|
563
|
+
else:
|
|
564
|
+
logger.warning("failed to download: %s", fname)
|
|
565
|
+
|
|
566
|
+
if downloaded == 0:
|
|
567
|
+
logger.warning("download_artifact: no files downloaded")
|
|
568
|
+
return None
|
|
569
|
+
|
|
570
|
+
logger.info("artifact downloaded: %d files → %s", downloaded, out_dir)
|
|
571
|
+
return str(out_dir)
|
|
572
|
+
|
|
493
573
|
def set_alias(
|
|
494
574
|
self,
|
|
495
575
|
artifact_id: str,
|
|
@@ -684,21 +764,37 @@ class APIClient:
|
|
|
684
764
|
presigned_url: str,
|
|
685
765
|
data_bytes: bytes,
|
|
686
766
|
content_type: str = "image/png",
|
|
767
|
+
run_id: str = "",
|
|
768
|
+
key: str = "",
|
|
687
769
|
) -> bool:
|
|
688
770
|
"""PUT bytes to a presigned URL with Content-Type header.
|
|
689
771
|
|
|
690
|
-
|
|
691
|
-
|
|
772
|
+
Falls back to API proxy upload if presigned URL is unreachable
|
|
773
|
+
(e.g., MinIO not exposed publicly).
|
|
692
774
|
"""
|
|
693
775
|
try:
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
776
|
+
try:
|
|
777
|
+
resp = httpx.put(
|
|
778
|
+
presigned_url,
|
|
779
|
+
content=data_bytes,
|
|
780
|
+
headers={"Content-Type": content_type},
|
|
781
|
+
timeout=300.0,
|
|
782
|
+
)
|
|
783
|
+
resp.raise_for_status()
|
|
784
|
+
return True
|
|
785
|
+
except (httpx.ConnectError, httpx.ConnectTimeout):
|
|
786
|
+
# Presigned URL unreachable — use API proxy
|
|
787
|
+
if run_id and key:
|
|
788
|
+
import os
|
|
789
|
+
fname = os.path.basename(key) or key.replace("/", "_")
|
|
790
|
+
proxy_resp = self._request(
|
|
791
|
+
"PUT",
|
|
792
|
+
f"/runs/{run_id}/files/media/{fname}",
|
|
793
|
+
content=data_bytes,
|
|
794
|
+
headers={"Content-Type": content_type},
|
|
795
|
+
)
|
|
796
|
+
return proxy_resp.status_code == 200
|
|
797
|
+
return False
|
|
702
798
|
except Exception as e:
|
|
703
799
|
logger.warning("upload_media_bytes failed: %s", e)
|
|
704
800
|
return False
|
|
@@ -1154,18 +1154,28 @@ class Run:
|
|
|
1154
1154
|
name: str,
|
|
1155
1155
|
version: int | None = None,
|
|
1156
1156
|
alias: str | None = None,
|
|
1157
|
-
|
|
1158
|
-
|
|
1157
|
+
dest_dir: str = "./artifacts",
|
|
1158
|
+
) -> str | None:
|
|
1159
|
+
"""Download a model artifact to disk.
|
|
1159
1160
|
|
|
1160
1161
|
Args:
|
|
1161
1162
|
name: Model artifact name (supports "name:alias" syntax).
|
|
1162
1163
|
version: Specific version number, or None for latest.
|
|
1163
1164
|
alias: Alias name to resolve.
|
|
1165
|
+
dest_dir: Local directory for downloaded files.
|
|
1164
1166
|
|
|
1165
1167
|
Returns:
|
|
1166
1168
|
Path to the local artifact directory, or None on failure.
|
|
1167
1169
|
"""
|
|
1168
|
-
|
|
1170
|
+
if not self._client:
|
|
1171
|
+
return None
|
|
1172
|
+
return self._client.download_artifact(
|
|
1173
|
+
run_id=self._run_id,
|
|
1174
|
+
artifact_name=name,
|
|
1175
|
+
dest_dir=dest_dir,
|
|
1176
|
+
version=version,
|
|
1177
|
+
alias=alias,
|
|
1178
|
+
)
|
|
1169
1179
|
|
|
1170
1180
|
def link_model(
|
|
1171
1181
|
self,
|
|
@@ -265,7 +265,8 @@ class Sender:
|
|
|
265
265
|
if result and result.get("presigned_url"):
|
|
266
266
|
# Upload the bytes to the presigned URL
|
|
267
267
|
self._client.upload_media_bytes(
|
|
268
|
-
result["presigned_url"], img_bytes, content_type
|
|
268
|
+
result["presigned_url"], img_bytes, content_type,
|
|
269
|
+
run_id=self._run_id, key=item["key"],
|
|
269
270
|
)
|
|
270
271
|
|
|
271
272
|
elif media_type == "table":
|
|
@@ -293,7 +294,8 @@ class Sender:
|
|
|
293
294
|
)
|
|
294
295
|
if result and result.get("presigned_url"):
|
|
295
296
|
self._client.upload_media_bytes(
|
|
296
|
-
result["presigned_url"], audio_bytes, content_type
|
|
297
|
+
result["presigned_url"], audio_bytes, content_type,
|
|
298
|
+
run_id=self._run_id, key=item["key"],
|
|
297
299
|
)
|
|
298
300
|
|
|
299
301
|
elif media_type == "video":
|
|
@@ -311,7 +313,8 @@ class Sender:
|
|
|
311
313
|
)
|
|
312
314
|
if result and result.get("presigned_url"):
|
|
313
315
|
self._client.upload_media_bytes(
|
|
314
|
-
result["presigned_url"], video_bytes, content_type
|
|
316
|
+
result["presigned_url"], video_bytes, content_type,
|
|
317
|
+
run_id=self._run_id, key=item["key"],
|
|
315
318
|
)
|
|
316
319
|
|
|
317
320
|
elif media_type == "histogram":
|
|
@@ -408,7 +411,8 @@ class Sender:
|
|
|
408
411
|
)
|
|
409
412
|
if result and result.get("presigned_url"):
|
|
410
413
|
self._client.upload_media_bytes(
|
|
411
|
-
result["presigned_url"], img_bytes, content_type
|
|
414
|
+
result["presigned_url"], img_bytes, content_type,
|
|
415
|
+
run_id=self._run_id, key=item["key"],
|
|
412
416
|
)
|
|
413
417
|
|
|
414
418
|
elif media_type == "molecule":
|
|
@@ -192,7 +192,7 @@ class TestModuleLevelAlert:
|
|
|
192
192
|
result = openrunner.alert("Title", text="Body", level="WARN")
|
|
193
193
|
assert result == {"id": "x"}
|
|
194
194
|
mock_run.alert.assert_called_once_with(
|
|
195
|
-
title="Title", text="Body", level="WARN"
|
|
195
|
+
title="Title", text="Body", level="WARN", wait_duration=None
|
|
196
196
|
)
|
|
197
197
|
finally:
|
|
198
198
|
openrunner._active_run = original
|
|
@@ -70,15 +70,15 @@ class TestLogModel:
|
|
|
70
70
|
from openrunner.integration.sklearn import log_model
|
|
71
71
|
import openrunner
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
monkeypatch.setattr("openrunner.
|
|
73
|
+
mock_config = MagicMock()
|
|
74
|
+
monkeypatch.setattr("openrunner.config", mock_config)
|
|
75
75
|
monkeypatch.setattr("openrunner._active_run", MagicMock())
|
|
76
76
|
|
|
77
77
|
model = _make_model(params={"n_estimators": 100, "max_depth": 5, "random_state": 42})
|
|
78
78
|
log_model(model)
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
logged =
|
|
80
|
+
mock_config.update.assert_called_once()
|
|
81
|
+
logged = mock_config.update.call_args[0][0]
|
|
82
82
|
assert logged["model/n_estimators"] == 100
|
|
83
83
|
assert logged["model/max_depth"] == 5
|
|
84
84
|
assert logged["model/random_state"] == 42
|
|
@@ -88,12 +88,12 @@ class TestLogModel:
|
|
|
88
88
|
from openrunner.integration.sklearn import log_model
|
|
89
89
|
import openrunner
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
monkeypatch.setattr("openrunner.
|
|
91
|
+
mock_config = MagicMock()
|
|
92
|
+
monkeypatch.setattr("openrunner.config", mock_config)
|
|
93
93
|
monkeypatch.setattr("openrunner._active_run", None)
|
|
94
94
|
|
|
95
95
|
log_model(_make_model())
|
|
96
|
-
|
|
96
|
+
mock_config.update.assert_not_called()
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
class TestLogClassificationReport:
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import pytest
|
|
5
6
|
import io
|
|
6
7
|
import json
|
|
7
8
|
import struct
|
|
@@ -586,16 +587,18 @@ class TestHtml:
|
|
|
586
587
|
"""Tests for the Html class."""
|
|
587
588
|
|
|
588
589
|
def test_html_basic(self) -> None:
|
|
589
|
-
"""Html with raw string serializes correctly."""
|
|
590
|
+
"""Html with raw string serializes correctly (inject=True prepends style)."""
|
|
590
591
|
html = Html("<div>Hello World</div>")
|
|
591
592
|
result = html._serialize()
|
|
592
|
-
assert result
|
|
593
|
+
assert result["html"].endswith("<div>Hello World</div>")
|
|
594
|
+
assert "<style>" in result["html"]
|
|
593
595
|
|
|
594
596
|
def test_html_with_caption(self) -> None:
|
|
595
597
|
"""Html with caption includes it in serialized output."""
|
|
596
598
|
html = Html("<p>Report</p>", caption="Training Report")
|
|
597
599
|
result = html._serialize()
|
|
598
|
-
assert result
|
|
600
|
+
assert result["html"].endswith("<p>Report</p>")
|
|
601
|
+
assert result["caption"] == "Training Report"
|
|
599
602
|
assert html.caption == "Training Report"
|
|
600
603
|
|
|
601
604
|
def test_html_no_caption(self) -> None:
|
|
@@ -616,7 +619,7 @@ class TestHtml:
|
|
|
616
619
|
)
|
|
617
620
|
html = Html(content, caption="complex")
|
|
618
621
|
result = html._serialize()
|
|
619
|
-
assert result["html"]
|
|
622
|
+
assert result["html"].endswith(content)
|
|
620
623
|
assert result["caption"] == "complex"
|
|
621
624
|
|
|
622
625
|
|
|
@@ -627,6 +630,11 @@ class TestHtml:
|
|
|
627
630
|
class TestMatplotlibFigure:
|
|
628
631
|
"""Tests for the MatplotlibFigure class."""
|
|
629
632
|
|
|
633
|
+
pytestmark = pytest.mark.skipif(
|
|
634
|
+
not __import__("importlib").util.find_spec("matplotlib"),
|
|
635
|
+
reason="matplotlib not installed",
|
|
636
|
+
)
|
|
637
|
+
|
|
630
638
|
def test_matplotlib_explicit_figure(self) -> None:
|
|
631
639
|
"""MatplotlibFigure from explicit figure serializes to PNG bytes."""
|
|
632
640
|
import matplotlib
|
|
@@ -208,6 +208,11 @@ class TestLineSeries:
|
|
|
208
208
|
# pr_curve()
|
|
209
209
|
# ---------------------------------------------------------------------------
|
|
210
210
|
|
|
211
|
+
import importlib.util
|
|
212
|
+
_has_sklearn = importlib.util.find_spec("sklearn") is not None
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@pytest.mark.skipif(not _has_sklearn, reason="scikit-learn not installed")
|
|
211
216
|
class TestPRCurve:
|
|
212
217
|
"""Tests for plot.pr_curve()."""
|
|
213
218
|
|
|
@@ -272,6 +277,7 @@ class TestPRCurve:
|
|
|
272
277
|
# roc_curve()
|
|
273
278
|
# ---------------------------------------------------------------------------
|
|
274
279
|
|
|
280
|
+
@pytest.mark.skipif(not _has_sklearn, reason="scikit-learn not installed")
|
|
275
281
|
class TestROCCurve:
|
|
276
282
|
"""Tests for plot.roc_curve()."""
|
|
277
283
|
|
|
@@ -336,6 +342,7 @@ class TestROCCurve:
|
|
|
336
342
|
# confusion_matrix()
|
|
337
343
|
# ---------------------------------------------------------------------------
|
|
338
344
|
|
|
345
|
+
@pytest.mark.skipif(not _has_sklearn, reason="scikit-learn not installed")
|
|
339
346
|
class TestConfusionMatrix:
|
|
340
347
|
"""Tests for plot.confusion_matrix()."""
|
|
341
348
|
|
|
@@ -82,8 +82,8 @@ class TestGPUMetrics:
|
|
|
82
82
|
assert result["system.gpu.0.gpu"] == 75.0
|
|
83
83
|
assert "system.gpu.0.memory" in result
|
|
84
84
|
assert result["system.gpu.0.memory"] == 50.0
|
|
85
|
-
assert "system.gpu.0.
|
|
86
|
-
assert result["system.gpu.0.
|
|
85
|
+
assert "system.gpu.0.memoryAllocatedMB" in result
|
|
86
|
+
assert result["system.gpu.0.memoryAllocatedMB"] == 4_000_000_000.0 / (1024 * 1024)
|
|
87
87
|
|
|
88
88
|
|
|
89
89
|
# ---------------------------------------------------------------------------
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|