openrunner-sdk 2.41.7__tar.gz → 2.41.9__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.41.7 → openrunner_sdk-2.41.9}/.gitignore +2 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/PKG-INFO +8 -8
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/README.md +1 -1
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/__init__.py +36 -5
- openrunner_sdk-2.41.9/openrunner/_multipart.py +116 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/api_client.py +181 -45
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/artifact.py +49 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/cli.py +280 -54
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/datasets.py +345 -24
- openrunner_sdk-2.41.9/openrunner/errors.py +41 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/gpu/api_integration.py +29 -3
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/gpu/runner.py +14 -2
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/install_commands.py +358 -18
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/mcp_server.py +38 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/media.py +5 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/migrate.py +29 -7
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/projects.py +51 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/run.py +94 -19
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/pyproject.toml +6 -6
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/integration/test_artifact_round_trip.py +1 -1
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_artifact.py +59 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_cli.py +4 -1
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_cli_artifact_sweep.py +3 -1
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_cli_infra.py +27 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_datasets_module.py +164 -2
- openrunner_sdk-2.41.9/tests/test_gpu/test_engine_serverside.py +62 -0
- openrunner_sdk-2.41.9/tests/test_gpu/test_engine_stream_keepalive.py +61 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_init_facade.py +8 -2
- openrunner_sdk-2.41.9/tests/test_large_file_uploads.py +393 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_machine_migrate.py +47 -3
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_media_extra.py +15 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_run_module.py +64 -5
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_sdk_cluster_j3.py +5 -1
- openrunner_sdk-2.41.7/tests/test_gpu/test_engine_serverside.py +0 -30
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/=6.0 +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/=8.1 +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/CLAUDE.md +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/_native.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/_retry_transport.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/_safepath.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/_secret_rules.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/autoresearch.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/buffer.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/cache.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/config.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/cost.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/dataset.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/environment.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/evaluation.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/feedback.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/git_info.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/gpu/__init__.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/gpu/cost_guard.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/gpu/gpu_types.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/gpu/price_compare.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/gpu/provider_base.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/gpu/providers/__init__.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/gpu/providers/forge_provider.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/gpu/providers/lambda_cloud.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/gpu/providers/modal_provider.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/gpu/providers/ovhcloud.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/gpu/providers/runpod.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/gpu/providers/vastai.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/gpu/workspace_sync.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/guardrails.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/handover.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/hooks/patent-lab-post-commit +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/__init__.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/accelerate.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/anthropic_tracer.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/catboost.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/diffusers.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/fastai.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/forced_alignment.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/gladia.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/gymnasium.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/huggingface.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/hydra.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/ignite.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/jax.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/keras.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/langchain.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/lightgbm.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/lightning.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/llamaindex.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/openai_finetune.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/openai_tracer.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/optuna.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/pytorch.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/sb3.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/sklearn.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/skypilot.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/tensorflow.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/trl.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/tts.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/ultralytics.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/voice_agent.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/whisper.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/integration/xgboost.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/launch.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/llms-full.txt +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/llms.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/llms.txt +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/machine.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/model.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/model_card.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/offline.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/patent_scan.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/pii.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/plot.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/prompt.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/query_api.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/redact.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/reload.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/research.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/research_policy.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/scorers.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/sender.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/session.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/settings.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/summary.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/sweep.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/sync.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/system_metrics.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/tensorboard.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/tools/__init__.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/tools/patent_report.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/trace.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/transcript_formatter.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/wal.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/wandb_compat/__init__.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/wandb_compat/_shim.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/openrunner/wer.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/scripts/gen_betterleaks_rules.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/__init__.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/conftest.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/fixtures/wandb-export-mini/run-aaa/config.json +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/fixtures/wandb-export-mini/run-aaa/metadata.json +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/fixtures/wandb-export-mini/run-aaa/metrics.jsonl +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/fixtures/wandb-export-mini/run-aaa/summary.json +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/model-checkpoint/manifest.json +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/model-checkpoint/weights.bin +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/sample-image/manifest.json +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/sample-image/preview.png +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/fixtures/wandb-export-mini/run-bbb/config.json +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/fixtures/wandb-export-mini/run-bbb/metadata.json +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/fixtures/wandb-export-mini/run-bbb/metrics.jsonl +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/fixtures/wandb-export-mini/run-bbb/summary.json +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/integration/__init__.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/integration/conftest.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_alert.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_aliases.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_api_client.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_api_client_methods.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_api_client_transport.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_buffer.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_cache.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_class_scorers.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_cli_datasets.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_cli_gpu_registry_deploy.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_cli_remainder.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_cli_topup.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_config.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_evaluation.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_finish.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_git_info.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu/__init__.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu/conftest.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu/test_cost_guard.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu/test_forge.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu/test_governed_jobs.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu/test_gpu_cluster.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu/test_gpu_types.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu/test_lambda.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu/test_modal.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu/test_ovh_training.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu/test_ovhcloud.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu/test_price_compare.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu/test_provider_base.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu/test_runner.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu/test_runpod.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu/test_sdk_gpu_api.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu/test_vastai.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu/test_workspace_sync.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_gpu_modal_creds.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_init.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_install_commands.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_integration_fastai.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_integration_huggingface.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_integration_keras.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_integration_langchain.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_integration_lightning.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_integration_pytorch.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_integration_setA.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_integration_setB.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_integration_setC.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_integration_sklearn.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_integration_xgboost.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_launch.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_llms.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_log.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_log_code.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_mcp_decisions.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_mcp_monitor.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_mcp_predictions.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_mcp_server.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_media.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_migrate.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_offline.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_offline_sync.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_patent_scan.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_pii.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_plot.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_plot_media_extra2.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_prompt_module.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_pure_modules.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_query_api.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_redact.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_reload_module.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_research_module.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_resume.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_retry_transport.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_sdk_cluster_j1.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_sdk_cluster_j2.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_sdk_cluster_j4.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_sdk_core_edges.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_sdk_features.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_sender.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_session_module.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_session_sync.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_summary.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_sweep.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_system_metrics.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_topup_hi.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_trace.py +0 -0
- {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.9}/tests/test_wandb_compat.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: openrunner-sdk
|
|
3
|
-
Version: 2.41.
|
|
3
|
+
Version: 2.41.9
|
|
4
4
|
Summary: OpenRunner SDK - W&B-compatible ML experiment tracking client
|
|
5
|
-
Project-URL: Homepage, https://github.com/
|
|
6
|
-
Project-URL: Repository, https://github.com/
|
|
7
|
-
Project-URL: Issues, https://github.com/
|
|
5
|
+
Project-URL: Homepage, https://github.com/gladiaio/openrunner
|
|
6
|
+
Project-URL: Repository, https://github.com/gladiaio/openrunner
|
|
7
|
+
Project-URL: Issues, https://github.com/gladiaio/openrunner/issues
|
|
8
8
|
Author-email: JL Queguiner <jl@gladia.io>
|
|
9
9
|
License: MIT
|
|
10
10
|
Keywords: experiment-tracking,invention-disclosure,machine-learning,ml,mlops,patent,wandb
|
|
@@ -23,7 +23,7 @@ Requires-Dist: click<9.0,>=8.1
|
|
|
23
23
|
Requires-Dist: httpx<1.0,>=0.27
|
|
24
24
|
Requires-Dist: mcp<2.0,>=1.0
|
|
25
25
|
Requires-Dist: nvidia-ml-py<14.0,>=12.0
|
|
26
|
-
Requires-Dist: pillow<
|
|
26
|
+
Requires-Dist: pillow<14,>=10.0
|
|
27
27
|
Requires-Dist: psutil<8.0,>=6.0
|
|
28
28
|
Provides-Extra: accelerate
|
|
29
29
|
Requires-Dist: accelerate<1.0,>=0.24; extra == 'accelerate'
|
|
@@ -35,7 +35,7 @@ Provides-Extra: dev
|
|
|
35
35
|
Requires-Dist: matplotlib<5.0,>=3.7; extra == 'dev'
|
|
36
36
|
Requires-Dist: num2words2<2.0,>=0.1; extra == 'dev'
|
|
37
37
|
Requires-Dist: numpy<3.0,>=1.24; extra == 'dev'
|
|
38
|
-
Requires-Dist: pillow<
|
|
38
|
+
Requires-Dist: pillow<14,>=10.0; extra == 'dev'
|
|
39
39
|
Requires-Dist: pytest-asyncio<1.0,>=0.23; extra == 'dev'
|
|
40
40
|
Requires-Dist: pytest-cov<6.0,>=5.0; extra == 'dev'
|
|
41
41
|
Requires-Dist: pytest<9.0,>=8.0; extra == 'dev'
|
|
@@ -656,7 +656,7 @@ W&B env vars (`WANDB_API_KEY`, `WANDB_BASE_URL`) are also supported as fallback
|
|
|
656
656
|
|
|
657
657
|
## Self-Hosting
|
|
658
658
|
|
|
659
|
-
OpenRunner is designed to be self-hosted. See the [main repo](https://github.com/
|
|
659
|
+
OpenRunner is designed to be self-hosted. See the [main repo](https://github.com/gladiaio/openrunner) for server setup with Docker Compose.
|
|
660
660
|
|
|
661
661
|
## License
|
|
662
662
|
|
|
@@ -541,7 +541,7 @@ W&B env vars (`WANDB_API_KEY`, `WANDB_BASE_URL`) are also supported as fallback
|
|
|
541
541
|
|
|
542
542
|
## Self-Hosting
|
|
543
543
|
|
|
544
|
-
OpenRunner is designed to be self-hosted. See the [main repo](https://github.com/
|
|
544
|
+
OpenRunner is designed to be self-hosted. See the [main repo](https://github.com/gladiaio/openrunner) for server setup with Docker Compose.
|
|
545
545
|
|
|
546
546
|
## License
|
|
547
547
|
|
|
@@ -72,6 +72,7 @@ from pathlib import Path
|
|
|
72
72
|
from typing import Any
|
|
73
73
|
|
|
74
74
|
from openrunner.artifact import Artifact
|
|
75
|
+
from openrunner.errors import ArtifactUploadError, DatasetUploadError, UploadError
|
|
75
76
|
from openrunner.model_card import (
|
|
76
77
|
MODEL_CARD_STANDARD,
|
|
77
78
|
model_card_standard,
|
|
@@ -93,6 +94,7 @@ import openrunner.datasets as datasets
|
|
|
93
94
|
from openrunner.datasets import (
|
|
94
95
|
DatasetInfo,
|
|
95
96
|
DatasetsClient,
|
|
97
|
+
DeriveIncomplete,
|
|
96
98
|
create_dataset,
|
|
97
99
|
dataset_info,
|
|
98
100
|
index_dataset,
|
|
@@ -101,6 +103,7 @@ from openrunner.datasets import (
|
|
|
101
103
|
link_project,
|
|
102
104
|
load_dataset,
|
|
103
105
|
add_derivation,
|
|
106
|
+
derive_by_ids,
|
|
104
107
|
derive_by_query,
|
|
105
108
|
derived_rows,
|
|
106
109
|
keyword_expand,
|
|
@@ -164,7 +167,7 @@ from openrunner.reload import (
|
|
|
164
167
|
dataset_handle,
|
|
165
168
|
)
|
|
166
169
|
from openrunner.sync import sync_environment, scan_environment
|
|
167
|
-
from openrunner.projects import create_project
|
|
170
|
+
from openrunner.projects import create_project, rename_project
|
|
168
171
|
from openrunner.run import Run
|
|
169
172
|
from openrunner.settings import SDKSettings, resolve_project, pin_project
|
|
170
173
|
from openrunner.summary import Summary
|
|
@@ -852,11 +855,24 @@ def alert(
|
|
|
852
855
|
return None
|
|
853
856
|
|
|
854
857
|
|
|
855
|
-
def log_artifact(
|
|
856
|
-
|
|
858
|
+
def log_artifact(
|
|
859
|
+
artifact: "Artifact | str | Path",
|
|
860
|
+
*,
|
|
861
|
+
name: str | None = None,
|
|
862
|
+
type: str | None = None,
|
|
863
|
+
aliases: list[str] | None = None,
|
|
864
|
+
) -> dict[str, Any] | None:
|
|
865
|
+
"""Upload an artifact to the active run.
|
|
866
|
+
|
|
867
|
+
Raises :class:`ArtifactUploadError` when the upload fails (unless
|
|
868
|
+
``OPENRUNNER_ARTIFACT_STRICT=0``); other SDK errors are logged, never raised.
|
|
857
869
|
|
|
858
870
|
Args:
|
|
859
|
-
artifact:
|
|
871
|
+
artifact: An Artifact with files added via add_file/add_dir, OR a path
|
|
872
|
+
to a file or directory (W&B-style), built into an Artifact for you.
|
|
873
|
+
name: Artifact name when ``artifact`` is a path.
|
|
874
|
+
type: Advisory type when ``artifact`` is a path.
|
|
875
|
+
aliases: Aliases to set on the created version.
|
|
860
876
|
|
|
861
877
|
Returns:
|
|
862
878
|
Server response dict with version info, or None on failure.
|
|
@@ -865,7 +881,12 @@ def log_artifact(artifact: Artifact) -> dict[str, Any] | None:
|
|
|
865
881
|
if _active_run is None:
|
|
866
882
|
logger.warning("openrunner.log_artifact(): no active run")
|
|
867
883
|
return None
|
|
868
|
-
return _active_run.log_artifact(
|
|
884
|
+
return _active_run.log_artifact(
|
|
885
|
+
artifact, name=name, type=type, aliases=aliases
|
|
886
|
+
)
|
|
887
|
+
except ArtifactUploadError:
|
|
888
|
+
# An upload failure must reach the caller (see OPENRUNNER_ARTIFACT_STRICT).
|
|
889
|
+
raise
|
|
869
890
|
except Exception as e:
|
|
870
891
|
logger.warning("openrunner.log_artifact() failed: %s", e)
|
|
871
892
|
return None
|
|
@@ -889,6 +910,8 @@ def finish_artifact(artifact: Artifact) -> dict[str, Any] | None:
|
|
|
889
910
|
logger.warning("openrunner.finish_artifact(): no active run")
|
|
890
911
|
return None
|
|
891
912
|
return _active_run.finish_artifact(artifact)
|
|
913
|
+
except ArtifactUploadError:
|
|
914
|
+
raise
|
|
892
915
|
except Exception as e:
|
|
893
916
|
logger.warning("openrunner.finish_artifact() failed: %s", e)
|
|
894
917
|
return None
|
|
@@ -914,6 +937,8 @@ def link_artifact(
|
|
|
914
937
|
logger.warning("openrunner.link_artifact(): no active run")
|
|
915
938
|
return None
|
|
916
939
|
return _active_run.link_artifact(artifact, aliases=aliases)
|
|
940
|
+
except ArtifactUploadError:
|
|
941
|
+
raise
|
|
917
942
|
except Exception as e:
|
|
918
943
|
logger.warning("openrunner.link_artifact() failed: %s", e)
|
|
919
944
|
return None
|
|
@@ -1065,6 +1090,10 @@ def push(
|
|
|
1065
1090
|
Returns:
|
|
1066
1091
|
The created version info dict, or None on failure.
|
|
1067
1092
|
|
|
1093
|
+
Raises:
|
|
1094
|
+
ArtifactUploadError: when any file fails to upload or the version
|
|
1095
|
+
cannot be confirmed (unless ``OPENRUNNER_ARTIFACT_STRICT=0``).
|
|
1096
|
+
|
|
1068
1097
|
Example::
|
|
1069
1098
|
|
|
1070
1099
|
openrunner.push("model.safetensors", "gladia/my-project/my-model",
|
|
@@ -1089,6 +1118,8 @@ def push(
|
|
|
1089
1118
|
except Exception as e: # noqa: BLE001
|
|
1090
1119
|
logger.warning("openrunner.push(): set_model_card failed: %s", e)
|
|
1091
1120
|
return result
|
|
1121
|
+
except ArtifactUploadError:
|
|
1122
|
+
raise
|
|
1092
1123
|
except Exception as e: # noqa: BLE001
|
|
1093
1124
|
logger.warning("openrunner.push() failed: %s", e)
|
|
1094
1125
|
return None
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Client side of S3 multipart uploads (presigned part URLs).
|
|
2
|
+
|
|
3
|
+
The server signs one PUT URL per part (``/.../multipart/start``); this module
|
|
4
|
+
PUTs the parts straight to object storage -- each part read from disk on
|
|
5
|
+
demand, retried independently, a few in parallel -- and returns the ETags the
|
|
6
|
+
server needs for ``CompleteMultipartUpload``. A single dropped connection now
|
|
7
|
+
costs one 100 MiB part instead of the whole multi-GB file.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
import random
|
|
15
|
+
import time
|
|
16
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
import httpx
|
|
20
|
+
|
|
21
|
+
from .errors import UploadError
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger("openrunner")
|
|
24
|
+
|
|
25
|
+
MIB = 1024 * 1024
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _env_mb(name: str, default: int) -> int:
|
|
29
|
+
try:
|
|
30
|
+
return max(1, int(os.environ.get(name, "") or default))
|
|
31
|
+
except ValueError:
|
|
32
|
+
return default
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def multipart_threshold() -> int:
|
|
36
|
+
"""Files at or above this size (bytes) use multipart. Env:
|
|
37
|
+
``OPENRUNNER_MULTIPART_THRESHOLD_MB`` (default 100)."""
|
|
38
|
+
return _env_mb("OPENRUNNER_MULTIPART_THRESHOLD_MB", 100) * MIB
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def part_size() -> int:
|
|
42
|
+
"""Requested part size (bytes). Env: ``OPENRUNNER_MULTIPART_PART_MB``
|
|
43
|
+
(default 100; the server clamps to S3 limits)."""
|
|
44
|
+
return _env_mb("OPENRUNNER_MULTIPART_PART_MB", 100) * MIB
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def part_workers() -> int:
|
|
48
|
+
"""Parts uploaded concurrently. Env: ``OPENRUNNER_MULTIPART_WORKERS``
|
|
49
|
+
(default 4 -> ~4 x part size of RAM)."""
|
|
50
|
+
return _env_mb("OPENRUNNER_MULTIPART_WORKERS", 4)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _put_part(url: str, data: bytes, headers: dict[str, str], retries: int,
|
|
54
|
+
timeout: httpx.Timeout) -> str:
|
|
55
|
+
last: Exception | None = None
|
|
56
|
+
for attempt in range(retries):
|
|
57
|
+
try:
|
|
58
|
+
resp = httpx.put(url, content=data, headers=headers, timeout=timeout)
|
|
59
|
+
if resp.status_code >= 400:
|
|
60
|
+
raise UploadError(
|
|
61
|
+
f"HTTP {resp.status_code}: {(resp.text or '')[:300]}"
|
|
62
|
+
)
|
|
63
|
+
etag = resp.headers.get("ETag") or resp.headers.get("etag")
|
|
64
|
+
if not etag:
|
|
65
|
+
raise UploadError(
|
|
66
|
+
"storage returned no ETag for the part (bucket CORS must "
|
|
67
|
+
"expose the ETag header)"
|
|
68
|
+
)
|
|
69
|
+
return etag
|
|
70
|
+
except Exception as e: # noqa: BLE001
|
|
71
|
+
last = e
|
|
72
|
+
if attempt < retries - 1:
|
|
73
|
+
delay = min(1.0 * (2 ** attempt), 20.0) * (0.5 + random.random())
|
|
74
|
+
logger.warning(
|
|
75
|
+
"part upload failed (attempt %d/%d), retrying in %.1fs: %s",
|
|
76
|
+
attempt + 1, retries, delay, e,
|
|
77
|
+
)
|
|
78
|
+
time.sleep(delay)
|
|
79
|
+
raise UploadError(str(last) if last else "part upload failed")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def upload_parts(
|
|
83
|
+
file_path: str,
|
|
84
|
+
parts: list[dict[str, Any]],
|
|
85
|
+
part_size: int,
|
|
86
|
+
*,
|
|
87
|
+
headers: dict[str, str] | None = None,
|
|
88
|
+
workers: int | None = None,
|
|
89
|
+
retries: int = 4,
|
|
90
|
+
) -> list[dict[str, Any]]:
|
|
91
|
+
"""PUT every part of ``file_path`` to its presigned URL.
|
|
92
|
+
|
|
93
|
+
``parts`` is the server's ``[{"part_number", "url"}, ...]``. Returns
|
|
94
|
+
``[{"part_number", "etag"}, ...]``; raises :class:`UploadError` naming the
|
|
95
|
+
first part that exhausted its retries.
|
|
96
|
+
"""
|
|
97
|
+
size = os.path.getsize(file_path)
|
|
98
|
+
hdrs = dict(headers or {})
|
|
99
|
+
timeout = httpx.Timeout(connect=30.0, read=300.0, write=300.0, pool=30.0)
|
|
100
|
+
|
|
101
|
+
def _one(p: dict[str, Any]) -> dict[str, Any]:
|
|
102
|
+
n = int(p["part_number"])
|
|
103
|
+
offset = (n - 1) * part_size
|
|
104
|
+
length = max(0, min(part_size, size - offset))
|
|
105
|
+
with open(file_path, "rb") as fh:
|
|
106
|
+
fh.seek(offset)
|
|
107
|
+
data = fh.read(length)
|
|
108
|
+
try:
|
|
109
|
+
etag = _put_part(p["url"], data, hdrs, retries, timeout)
|
|
110
|
+
except UploadError as e:
|
|
111
|
+
raise UploadError(f"part {n}/{len(parts)} of {os.path.basename(file_path)} failed: {e}") from e
|
|
112
|
+
return {"part_number": n, "etag": etag}
|
|
113
|
+
|
|
114
|
+
with ThreadPoolExecutor(max_workers=max(1, workers or part_workers())) as ex:
|
|
115
|
+
results = list(ex.map(_one, parts))
|
|
116
|
+
return sorted(results, key=lambda r: r["part_number"])
|
|
@@ -19,6 +19,8 @@ from typing import Any
|
|
|
19
19
|
|
|
20
20
|
import httpx
|
|
21
21
|
|
|
22
|
+
from . import _multipart as _mp
|
|
23
|
+
|
|
22
24
|
logger = logging.getLogger("openrunner")
|
|
23
25
|
|
|
24
26
|
# -- Retry configuration ------------------------------------------------------
|
|
@@ -77,6 +79,38 @@ def _retry_request(fn, *args, **kwargs):
|
|
|
77
79
|
raise last_error
|
|
78
80
|
|
|
79
81
|
|
|
82
|
+
def _raise_for_status(response: httpx.Response) -> None:
|
|
83
|
+
"""raise_for_status() that keeps the server's error detail in the message."""
|
|
84
|
+
if response.status_code < 400:
|
|
85
|
+
return
|
|
86
|
+
detail = ""
|
|
87
|
+
try:
|
|
88
|
+
body = response.json()
|
|
89
|
+
detail = body.get("detail", body) if isinstance(body, dict) else body
|
|
90
|
+
except Exception: # noqa: BLE001
|
|
91
|
+
detail = (response.text or "")[:300]
|
|
92
|
+
raise httpx.HTTPStatusError(
|
|
93
|
+
f"HTTP {response.status_code} for {response.request.method} "
|
|
94
|
+
f"{response.request.url.path}: {detail}",
|
|
95
|
+
request=response.request,
|
|
96
|
+
response=response,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _describe(e: BaseException) -> str:
|
|
101
|
+
"""Human-readable cause (``TimeoutException`` str() is often empty)."""
|
|
102
|
+
text = str(e).strip()
|
|
103
|
+
return f"{type(e).__name__}: {text}" if text else type(e).__name__
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _fmt_bytes(n: int) -> str:
|
|
107
|
+
for unit in ("B", "KB", "MB", "GB"):
|
|
108
|
+
if n < 1024 or unit == "GB":
|
|
109
|
+
return f"{n:.0f} {unit}" if unit == "B" else f"{n:.1f} {unit}"
|
|
110
|
+
n /= 1024
|
|
111
|
+
return f"{n} B"
|
|
112
|
+
|
|
113
|
+
|
|
80
114
|
class APIClient:
|
|
81
115
|
"""HTTP client for the OpenRunner server API.
|
|
82
116
|
|
|
@@ -779,6 +813,7 @@ class APIClient:
|
|
|
779
813
|
metadata: dict[str, Any] | None = None,
|
|
780
814
|
base_version_id: str | None = None,
|
|
781
815
|
message: str | None = None,
|
|
816
|
+
raise_on_error: bool = False,
|
|
782
817
|
) -> dict[str, Any] | None:
|
|
783
818
|
"""POST /runs/{run_id}/artifacts -- create a new artifact version.
|
|
784
819
|
|
|
@@ -807,27 +842,56 @@ class APIClient:
|
|
|
807
842
|
body["base_version_id"] = base_version_id
|
|
808
843
|
if message is not None:
|
|
809
844
|
body["message"] = message
|
|
810
|
-
|
|
811
|
-
|
|
845
|
+
# The server does per-file dedup lookups + layer building on this
|
|
846
|
+
# call; under load it can exceed the 30s default read timeout, and a
|
|
847
|
+
# timed-out POST that is retried races the still-running first
|
|
848
|
+
# request (version-number unique constraint -> 500). Give it room.
|
|
849
|
+
response = self._request(
|
|
850
|
+
"post", f"/runs/{run_id}/artifacts", json=body,
|
|
851
|
+
timeout=httpx.Timeout(30.0, read=180.0),
|
|
852
|
+
)
|
|
853
|
+
_raise_for_status(response)
|
|
812
854
|
return response.json()
|
|
813
855
|
except Exception as e:
|
|
856
|
+
if raise_on_error:
|
|
857
|
+
from .errors import ArtifactUploadError
|
|
858
|
+
|
|
859
|
+
raise ArtifactUploadError(
|
|
860
|
+
f"could not create artifact version {name!r}: {_describe(e)}"
|
|
861
|
+
) from e
|
|
814
862
|
logger.warning("create_artifact_version failed: %s", e)
|
|
815
863
|
return None
|
|
816
864
|
|
|
817
865
|
def confirm_artifact_version(
|
|
818
|
-
self, version_id: str
|
|
866
|
+
self, version_id: str, raise_on_error: bool = False
|
|
819
867
|
) -> dict[str, Any] | None:
|
|
820
868
|
"""POST /artifacts/versions/{version_id}/confirm -- confirm upload.
|
|
821
869
|
|
|
822
|
-
Returns {status, version}, or None on failure.
|
|
870
|
+
Returns {status, version}, or None on failure. With
|
|
871
|
+
``raise_on_error=True`` a failure (including a server response whose
|
|
872
|
+
status is not ``confirmed``, e.g. 409 missing objects) raises
|
|
873
|
+
:class:`~openrunner.errors.ArtifactUploadError`.
|
|
823
874
|
"""
|
|
824
875
|
try:
|
|
825
876
|
response = self._request("post",
|
|
826
|
-
f"/artifacts/versions/{version_id}/confirm"
|
|
877
|
+
f"/artifacts/versions/{version_id}/confirm",
|
|
878
|
+
timeout=httpx.Timeout(30.0, read=180.0),
|
|
827
879
|
)
|
|
828
|
-
response
|
|
829
|
-
|
|
830
|
-
|
|
880
|
+
_raise_for_status(response)
|
|
881
|
+
data = response.json()
|
|
882
|
+
if raise_on_error and isinstance(data, dict) and data.get("status") not in (
|
|
883
|
+
None, "confirmed"
|
|
884
|
+
):
|
|
885
|
+
raise RuntimeError(f"server left the version in state {data.get('status')!r}")
|
|
886
|
+
return data
|
|
887
|
+
except Exception as e:
|
|
888
|
+
if raise_on_error:
|
|
889
|
+
from .errors import ArtifactUploadError
|
|
890
|
+
|
|
891
|
+
raise ArtifactUploadError(
|
|
892
|
+
f"could not confirm artifact version {version_id}: {_describe(e)}",
|
|
893
|
+
version_id=version_id,
|
|
894
|
+
) from e
|
|
831
895
|
logger.warning("confirm_artifact_version failed: %s", e)
|
|
832
896
|
return None
|
|
833
897
|
|
|
@@ -1129,52 +1193,124 @@ class APIClient:
|
|
|
1129
1193
|
return []
|
|
1130
1194
|
|
|
1131
1195
|
def upload_file_to_presigned_url(
|
|
1132
|
-
self,
|
|
1196
|
+
self,
|
|
1197
|
+
presigned_url: str,
|
|
1198
|
+
file_path: str,
|
|
1199
|
+
run_id: str = "",
|
|
1200
|
+
*,
|
|
1201
|
+
version_id: str | None = None,
|
|
1202
|
+
name: str | None = None,
|
|
1203
|
+
raise_on_error: bool = False,
|
|
1133
1204
|
) -> bool:
|
|
1134
|
-
"""
|
|
1135
|
-
|
|
1136
|
-
Handles
|
|
1137
|
-
1.
|
|
1138
|
-
|
|
1139
|
-
|
|
1205
|
+
"""Upload a local file for an artifact version.
|
|
1206
|
+
|
|
1207
|
+
Handles:
|
|
1208
|
+
1. Large files (>= ``OPENRUNNER_MULTIPART_THRESHOLD_MB``, default 100)
|
|
1209
|
+
when ``version_id`` + ``name`` are given and storage is directly
|
|
1210
|
+
reachable: S3 multipart via presigned part URLs (parts retried
|
|
1211
|
+
independently). Falls back to a single PUT on servers without the
|
|
1212
|
+
multipart endpoints.
|
|
1213
|
+
2. Relative proxy URL (/api/v1/runs/...) — streamed PUT via the
|
|
1214
|
+
authenticated client.
|
|
1215
|
+
3. Absolute URL — streamed direct PUT, falling back to the API proxy on
|
|
1216
|
+
ConnectError.
|
|
1217
|
+
|
|
1218
|
+
Returns True on success. On failure returns False after logging the
|
|
1219
|
+
real cause at ERROR level, or raises
|
|
1220
|
+
:class:`~openrunner.errors.ArtifactUploadError` if ``raise_on_error``.
|
|
1140
1221
|
"""
|
|
1141
|
-
import os
|
|
1142
|
-
|
|
1143
1222
|
try:
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1223
|
+
size = os.path.getsize(file_path)
|
|
1224
|
+
if (
|
|
1225
|
+
version_id
|
|
1226
|
+
and name
|
|
1227
|
+
and not presigned_url.startswith("/")
|
|
1228
|
+
and size >= _mp.multipart_threshold()
|
|
1229
|
+
):
|
|
1230
|
+
if self._upload_multipart(version_id, name, file_path, size):
|
|
1231
|
+
return True
|
|
1232
|
+
self._upload_single(presigned_url, file_path, size, run_id)
|
|
1233
|
+
return True
|
|
1234
|
+
except Exception as e:
|
|
1235
|
+
label = name or os.path.basename(file_path)
|
|
1236
|
+
msg = f"upload of {label!r} ({_fmt_bytes(os.path.getsize(file_path)) if os.path.exists(file_path) else '?'}) failed: {_describe(e)}"
|
|
1237
|
+
if raise_on_error:
|
|
1238
|
+
from .errors import ArtifactUploadError
|
|
1151
1239
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1240
|
+
raise ArtifactUploadError(
|
|
1241
|
+
msg, version_id=version_id, failed_files=[label]
|
|
1242
|
+
) from e
|
|
1243
|
+
logger.error("upload_file_to_presigned_url: %s", msg)
|
|
1244
|
+
return False
|
|
1154
1245
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1246
|
+
def _upload_single(self, presigned_url: str, file_path: str, size: int, run_id: str) -> None:
|
|
1247
|
+
"""One streamed PUT (file read in chunks, never fully in RAM). Raises."""
|
|
1248
|
+
# Per-operation timeouts. The read timeout covers the wait for the
|
|
1249
|
+
# response AFTER the body is sent: the API proxy hashes the body and
|
|
1250
|
+
# copies it to storage before answering, so scale it with size.
|
|
1251
|
+
read_timeout = max(120.0, size / (2 * 1024 * 1024))
|
|
1252
|
+
timeout = httpx.Timeout(connect=30.0, read=read_timeout, write=300.0, pool=30.0)
|
|
1253
|
+
|
|
1254
|
+
def _put(client_put, url: str) -> httpx.Response:
|
|
1255
|
+
with open(file_path, "rb") as fh: # fresh handle per attempt
|
|
1256
|
+
return client_put(url, content=fh, timeout=timeout)
|
|
1160
1257
|
|
|
1161
|
-
|
|
1258
|
+
if presigned_url.startswith("/"):
|
|
1259
|
+
api_path = (
|
|
1260
|
+
presigned_url.replace("/api/v1/", "/", 1)
|
|
1261
|
+
if presigned_url.startswith("/api/v1/") else presigned_url
|
|
1262
|
+
)
|
|
1263
|
+
_raise_for_status(_put(self._client.put, api_path))
|
|
1264
|
+
return
|
|
1265
|
+
try:
|
|
1266
|
+
_raise_for_status(_put(httpx.put, presigned_url))
|
|
1267
|
+
except (httpx.ConnectError, httpx.ConnectTimeout):
|
|
1268
|
+
if not run_id:
|
|
1269
|
+
raise
|
|
1270
|
+
fname = os.path.basename(file_path)
|
|
1271
|
+
_raise_for_status(_put(self._client.put, f"/runs/{run_id}/files/{fname}"))
|
|
1272
|
+
|
|
1273
|
+
def _upload_multipart(self, version_id: str, name: str, file_path: str, size: int) -> bool:
|
|
1274
|
+
"""Multipart upload of one artifact file. Returns False when the server
|
|
1275
|
+
cannot do multipart (old server / proxy-only storage) so the caller
|
|
1276
|
+
falls back to a single PUT; raises on an actual upload failure."""
|
|
1277
|
+
base = f"/artifacts/versions/{version_id}/multipart"
|
|
1278
|
+
start = self._request(
|
|
1279
|
+
"post", f"{base}/start",
|
|
1280
|
+
json={"name": name, "size": size, "part_size": _mp.part_size()},
|
|
1281
|
+
)
|
|
1282
|
+
if start.status_code in (404, 405):
|
|
1283
|
+
detail = ""
|
|
1162
1284
|
try:
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
if run_id:
|
|
1169
|
-
fname = os.path.basename(file_path)
|
|
1170
|
-
proxy_resp = self._client.put(
|
|
1171
|
-
f"/runs/{run_id}/files/{fname}", content=data, timeout=upload_timeout
|
|
1172
|
-
)
|
|
1173
|
-
return proxy_resp.status_code == 200
|
|
1285
|
+
detail = str(start.json().get("detail", ""))
|
|
1286
|
+
except Exception: # noqa: BLE001
|
|
1287
|
+
pass
|
|
1288
|
+
if start.status_code == 405 or detail in ("", "Not Found"):
|
|
1289
|
+
logger.info("server has no multipart endpoint; using a single PUT for %s", name)
|
|
1174
1290
|
return False
|
|
1175
|
-
|
|
1176
|
-
|
|
1291
|
+
_raise_for_status(start)
|
|
1292
|
+
info = start.json()
|
|
1293
|
+
if not info.get("multipart"):
|
|
1177
1294
|
return False
|
|
1295
|
+
upload_id = info["upload_id"]
|
|
1296
|
+
logger.info(
|
|
1297
|
+
"uploading %s (%s) in %d parts", name, _fmt_bytes(size), len(info["parts"])
|
|
1298
|
+
)
|
|
1299
|
+
try:
|
|
1300
|
+
etags = _mp.upload_parts(file_path, info["parts"], int(info["part_size"]))
|
|
1301
|
+
done = self._request(
|
|
1302
|
+
"post", f"{base}/complete",
|
|
1303
|
+
json={"name": name, "upload_id": upload_id, "parts": etags},
|
|
1304
|
+
timeout=httpx.Timeout(30.0, read=300.0),
|
|
1305
|
+
)
|
|
1306
|
+
_raise_for_status(done)
|
|
1307
|
+
except Exception:
|
|
1308
|
+
try:
|
|
1309
|
+
self._client.post(f"{base}/abort", json={"name": name, "upload_id": upload_id})
|
|
1310
|
+
except Exception: # noqa: BLE001
|
|
1311
|
+
pass
|
|
1312
|
+
raise
|
|
1313
|
+
return True
|
|
1178
1314
|
|
|
1179
1315
|
def download_file_from_presigned_url(
|
|
1180
1316
|
self, presigned_url: str
|
|
@@ -29,6 +29,55 @@ def _sha256_file(path: Path, chunk_size: int = 8192) -> str:
|
|
|
29
29
|
return h.hexdigest()
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
def coerce_artifact(
|
|
33
|
+
source: "Artifact | str | Path",
|
|
34
|
+
*,
|
|
35
|
+
name: str | None = None,
|
|
36
|
+
type: str | None = None,
|
|
37
|
+
description: str | None = None,
|
|
38
|
+
metadata: dict[str, Any] | None = None,
|
|
39
|
+
) -> "Artifact":
|
|
40
|
+
"""Return an Artifact, building one from a path if that's what was given.
|
|
41
|
+
|
|
42
|
+
``log_artifact`` historically took a fully built ``Artifact``. Users coming
|
|
43
|
+
from W&B -- which this repo actively courts, see
|
|
44
|
+
``examples/migrate-from-wandb/`` -- write the path form instead::
|
|
45
|
+
|
|
46
|
+
run.log_artifact("out/final", name="my-model", type="model")
|
|
47
|
+
|
|
48
|
+
and used to get ``TypeError: unexpected keyword argument 'name'``. Accept
|
|
49
|
+
both. A directory becomes ``add_dir``, a file ``add_file``; ``name``
|
|
50
|
+
defaults to the path's own name so the common case needs no kwargs.
|
|
51
|
+
"""
|
|
52
|
+
if isinstance(source, Artifact):
|
|
53
|
+
# Already built: kwargs only fill gaps, they never silently override
|
|
54
|
+
# what the caller put on the object.
|
|
55
|
+
if name and not source.name:
|
|
56
|
+
source.name = name
|
|
57
|
+
if type and source.type is None:
|
|
58
|
+
source.type = type
|
|
59
|
+
return source
|
|
60
|
+
|
|
61
|
+
path = Path(source)
|
|
62
|
+
if not path.exists():
|
|
63
|
+
raise FileNotFoundError(f"artifact path does not exist: {path}")
|
|
64
|
+
art = Artifact(
|
|
65
|
+
name or path.name or path.stem,
|
|
66
|
+
type,
|
|
67
|
+
description,
|
|
68
|
+
metadata,
|
|
69
|
+
)
|
|
70
|
+
if path.is_dir():
|
|
71
|
+
# Existing add_dir semantics: files are prefixed with the directory
|
|
72
|
+
# name. Kept as-is so a coerced artifact lays out identically to a
|
|
73
|
+
# hand-built one -- changing it would silently move every existing
|
|
74
|
+
# caller's files.
|
|
75
|
+
art.add_dir(str(path))
|
|
76
|
+
else:
|
|
77
|
+
art.add_file(str(path))
|
|
78
|
+
return art
|
|
79
|
+
|
|
80
|
+
|
|
32
81
|
class Artifact:
|
|
33
82
|
"""An untyped, versioned bucket of files for experiment tracking.
|
|
34
83
|
|