openrunner-sdk 2.40.1__tar.gz → 2.41.1__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.40.1 → openrunner_sdk-2.41.1}/PKG-INFO +1 -1
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/__init__.py +1 -1
- openrunner_sdk-2.41.1/openrunner/_retry_transport.py +94 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/datasets.py +2 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/api_integration.py +16 -27
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/runner.py +18 -33
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/install_commands.py +8 -6
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/pyproject.toml +1 -1
- openrunner_sdk-2.41.1/tests/test_gpu/test_ovh_training.py +29 -0
- openrunner_sdk-2.41.1/tests/test_retry_transport.py +66 -0
- openrunner_sdk-2.40.1/tests/test_gpu/test_ovh_training.py +0 -47
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/.gitignore +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/=6.0 +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/=8.1 +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/README.md +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/CLAUDE.md +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/_safepath.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/_secret_rules.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/api_client.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/artifact.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/autoresearch.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/buffer.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/cache.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/cli.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/config.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/cost.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/dataset.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/environment.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/evaluation.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/feedback.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/git_info.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/__init__.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/cost_guard.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/gpu_types.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/price_compare.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/provider_base.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/providers/__init__.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/providers/forge_provider.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/providers/lambda_cloud.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/providers/modal_provider.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/providers/ovhcloud.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/providers/runpod.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/providers/vastai.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/workspace_sync.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/guardrails.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/handover.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/hooks/patent-lab-post-commit +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/__init__.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/accelerate.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/anthropic_tracer.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/catboost.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/diffusers.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/fastai.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/forced_alignment.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/gladia.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/gymnasium.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/huggingface.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/hydra.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/ignite.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/jax.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/keras.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/langchain.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/lightgbm.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/lightning.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/llamaindex.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/openai_finetune.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/openai_tracer.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/optuna.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/pytorch.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/sb3.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/sklearn.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/skypilot.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/tensorflow.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/trl.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/tts.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/ultralytics.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/voice_agent.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/whisper.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/xgboost.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/launch.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/machine.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/mcp_server.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/media.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/migrate.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/model.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/model_card.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/offline.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/patent_scan.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/pii.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/plot.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/projects.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/prompt.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/query_api.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/redact.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/reload.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/research.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/research_policy.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/run.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/scorers.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/sender.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/session.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/settings.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/summary.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/sweep.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/sync.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/system_metrics.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/tensorboard.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/tools/__init__.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/tools/patent_report.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/trace.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/transcript_formatter.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/wal.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/wandb_compat/__init__.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/wandb_compat/_shim.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/wer.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/scripts/gen_betterleaks_rules.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/__init__.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/conftest.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-aaa/config.json +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-aaa/metadata.json +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-aaa/metrics.jsonl +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-aaa/summary.json +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/model-checkpoint/manifest.json +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/model-checkpoint/weights.bin +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/sample-image/manifest.json +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/sample-image/preview.png +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-bbb/config.json +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-bbb/metadata.json +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-bbb/metrics.jsonl +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-bbb/summary.json +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/integration/__init__.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/integration/conftest.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/integration/test_artifact_round_trip.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_alert.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_aliases.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_api_client.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_api_client_methods.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_api_client_transport.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_artifact.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_buffer.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_cache.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_class_scorers.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_cli.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_cli_artifact_sweep.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_cli_datasets.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_cli_gpu_registry_deploy.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_cli_infra.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_cli_remainder.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_cli_topup.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_config.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_datasets_module.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_evaluation.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_finish.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_git_info.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/__init__.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/conftest.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_cost_guard.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_engine_serverside.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_forge.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_governed_jobs.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_gpu_cluster.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_gpu_types.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_lambda.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_modal.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_ovhcloud.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_price_compare.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_provider_base.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_runner.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_runpod.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_sdk_gpu_api.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_vastai.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_workspace_sync.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu_modal_creds.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_init.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_init_facade.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_install_commands.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_fastai.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_huggingface.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_keras.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_langchain.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_lightning.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_pytorch.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_setA.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_setB.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_setC.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_sklearn.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_xgboost.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_launch.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_log.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_log_code.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_machine_migrate.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_mcp_decisions.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_mcp_monitor.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_mcp_predictions.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_mcp_server.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_media.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_media_extra.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_migrate.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_offline.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_offline_sync.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_patent_scan.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_pii.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_plot.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_plot_media_extra2.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_prompt_module.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_pure_modules.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_query_api.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_redact.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_reload_module.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_research_module.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_resume.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_run_module.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_sdk_cluster_j1.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_sdk_cluster_j2.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_sdk_cluster_j3.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_sdk_cluster_j4.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_sdk_core_edges.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_sdk_features.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_sender.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_session_module.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_session_sync.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_summary.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_sweep.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_system_metrics.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_topup_hi.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_trace.py +0 -0
- {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/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.41.1
|
|
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
|
|
@@ -181,7 +181,7 @@ launch.from_run = _launch_from_run # type: ignore[attr-defined]
|
|
|
181
181
|
# openrunner.trace.patch_openai() syntax
|
|
182
182
|
trace.patch_openai = _patch_openai # type: ignore[attr-defined]
|
|
183
183
|
|
|
184
|
-
__version__ = "2.
|
|
184
|
+
__version__ = "2.41.1"
|
|
185
185
|
|
|
186
186
|
logger = logging.getLogger("openrunner")
|
|
187
187
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""A drop-in httpx transport that transparently retries transient failures.
|
|
2
|
+
|
|
3
|
+
Wraps the default ``httpx.HTTPTransport`` and retries:
|
|
4
|
+
* connection/timeout/protocol errors (the request never reached the app), and
|
|
5
|
+
* ``502 / 503`` responses (bad gateway / unavailable — e.g. the API container
|
|
6
|
+
being recreated by a deploy, which the app also never processed), plus
|
|
7
|
+
* ``504`` and ``429`` — ``504`` only for idempotent methods (GET/HEAD/PUT/
|
|
8
|
+
DELETE/OPTIONS) so a POST that may have executed is never silently repeated;
|
|
9
|
+
``429`` honours ``Retry-After``.
|
|
10
|
+
|
|
11
|
+
Backoff is exponential with jitter, capped. This lets long-running SDK jobs
|
|
12
|
+
(dataset materialise, bulk upload, run logging) ride out a transient blip instead
|
|
13
|
+
of crashing — the failure mode that corrupted an EarningsCall fetch mid-download.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import random
|
|
18
|
+
import time
|
|
19
|
+
|
|
20
|
+
import httpx
|
|
21
|
+
|
|
22
|
+
_IDEMPOTENT = frozenset({"GET", "HEAD", "PUT", "DELETE", "OPTIONS", "TRACE"})
|
|
23
|
+
# 502/503: app never handled the request -> safe to retry for any method.
|
|
24
|
+
# 504: request may have executed -> retry idempotent methods only.
|
|
25
|
+
_RETRY_ALWAYS = frozenset({502, 503})
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class RetryTransport(httpx.BaseTransport):
|
|
29
|
+
"""httpx transport retrying transient 5xx/429/network errors with backoff."""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
wrapped: httpx.BaseTransport | None = None,
|
|
34
|
+
*,
|
|
35
|
+
max_retries: int = 4,
|
|
36
|
+
base_delay: float = 1.0,
|
|
37
|
+
max_delay: float = 30.0,
|
|
38
|
+
) -> None:
|
|
39
|
+
self._wrapped = wrapped if wrapped is not None else httpx.HTTPTransport()
|
|
40
|
+
self.max_retries = max_retries
|
|
41
|
+
self.base_delay = base_delay
|
|
42
|
+
self.max_delay = max_delay
|
|
43
|
+
|
|
44
|
+
def _sleep(self, attempt: int, retry_after: float = 0.0) -> None:
|
|
45
|
+
delay = min(self.base_delay * (2 ** attempt), self.max_delay)
|
|
46
|
+
delay = max(delay, min(retry_after, self.max_delay))
|
|
47
|
+
time.sleep(delay * (0.5 + random.random())) # 0.5x–1.5x jitter
|
|
48
|
+
|
|
49
|
+
def _should_retry_status(self, code: int, method: str) -> bool:
|
|
50
|
+
if code in _RETRY_ALWAYS or code == 429:
|
|
51
|
+
return True
|
|
52
|
+
if code == 504 and method.upper() in _IDEMPOTENT:
|
|
53
|
+
return True
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
def handle_request(self, request: httpx.Request) -> httpx.Response:
|
|
57
|
+
last_exc: Exception | None = None
|
|
58
|
+
for attempt in range(self.max_retries + 1):
|
|
59
|
+
try:
|
|
60
|
+
response = self._wrapped.handle_request(request)
|
|
61
|
+
except (
|
|
62
|
+
httpx.ConnectError,
|
|
63
|
+
httpx.ConnectTimeout,
|
|
64
|
+
httpx.ReadTimeout,
|
|
65
|
+
httpx.WriteTimeout,
|
|
66
|
+
httpx.PoolTimeout,
|
|
67
|
+
httpx.RemoteProtocolError,
|
|
68
|
+
) as exc:
|
|
69
|
+
last_exc = exc
|
|
70
|
+
if attempt >= self.max_retries:
|
|
71
|
+
raise
|
|
72
|
+
self._sleep(attempt)
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
if attempt < self.max_retries and self._should_retry_status(
|
|
76
|
+
response.status_code, request.method
|
|
77
|
+
):
|
|
78
|
+
retry_after = 0.0
|
|
79
|
+
try:
|
|
80
|
+
retry_after = float(response.headers.get("Retry-After", 0) or 0)
|
|
81
|
+
except (TypeError, ValueError):
|
|
82
|
+
retry_after = 0.0
|
|
83
|
+
response.close()
|
|
84
|
+
self._sleep(attempt, retry_after)
|
|
85
|
+
continue
|
|
86
|
+
return response
|
|
87
|
+
|
|
88
|
+
# Only reached if the last attempt raised then the loop fell through.
|
|
89
|
+
if last_exc is not None:
|
|
90
|
+
raise last_exc
|
|
91
|
+
raise httpx.HTTPError("request failed after retries") # pragma: no cover
|
|
92
|
+
|
|
93
|
+
def close(self) -> None:
|
|
94
|
+
self._wrapped.close()
|
|
@@ -194,10 +194,12 @@ class DatasetsClient:
|
|
|
194
194
|
or _g.get("base_url") or "http://localhost:8000")
|
|
195
195
|
if not key:
|
|
196
196
|
raise ValueError("API key required. Set OPENRUNNER_API_KEY, pass api_key=, or run `openrunner login`.")
|
|
197
|
+
from ._retry_transport import RetryTransport
|
|
197
198
|
self._http = httpx.Client(
|
|
198
199
|
base_url=f"{url.rstrip('/')}/api/v1",
|
|
199
200
|
headers={"Authorization": f"Bearer {key}"},
|
|
200
201
|
timeout=timeout,
|
|
202
|
+
transport=RetryTransport(),
|
|
201
203
|
)
|
|
202
204
|
|
|
203
205
|
# -- resolution ----------------------------------------------------------
|
|
@@ -408,40 +408,29 @@ class GpuAPIClient:
|
|
|
408
408
|
logger.warning("get_job failed: %s", e)
|
|
409
409
|
return None
|
|
410
410
|
|
|
411
|
-
def
|
|
412
|
-
self, *, image: str, command: str, gpu: int = 1,
|
|
413
|
-
|
|
414
|
-
)
|
|
415
|
-
"""POST /api/v1/gpu/ai-training/run
|
|
416
|
-
server
|
|
417
|
-
|
|
411
|
+
def ovh_training_run_stream(
|
|
412
|
+
self, *, image: str, command: str, gpu: int = 1, name: str | None = None,
|
|
413
|
+
project_id: str | None = None,
|
|
414
|
+
):
|
|
415
|
+
"""POST /api/v1/gpu/ai-training/run and YIELD the job's log lines. The
|
|
416
|
+
server runs `ovhai job run` with the org's stored user/password (never
|
|
417
|
+
sent here) and streams logs. A ``__ENGINE_EXIT__ <code>`` line ends it."""
|
|
418
418
|
org = self.org_id()
|
|
419
419
|
body: dict[str, Any] = {"image": image, "command": command, "gpu": gpu}
|
|
420
420
|
if name:
|
|
421
421
|
body["name"] = name
|
|
422
422
|
if project_id:
|
|
423
423
|
body["project_id"] = project_id
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
return r.json()
|
|
428
|
-
except Exception as e:
|
|
429
|
-
detail = getattr(getattr(e, "response", None), "text", "") or str(e)
|
|
430
|
-
logger.warning("ovh_training_run failed: %s", detail)
|
|
431
|
-
return None
|
|
432
|
-
|
|
433
|
-
def ovh_training_status(self, job_id: str) -> dict[str, Any] | None:
|
|
434
|
-
"""GET /api/v1/gpu/ai-training/{id} -- poll status + logs."""
|
|
435
|
-
try:
|
|
436
|
-
r = self._client.get(
|
|
437
|
-
f"/gpu/ai-training/{job_id}", params={"org_id": self.org_id()}
|
|
438
|
-
)
|
|
424
|
+
with self._client.stream(
|
|
425
|
+
"POST", "/gpu/ai-training/run", params={"org_id": org}, json=body,
|
|
426
|
+
) as r:
|
|
439
427
|
if r.status_code >= 400:
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
428
|
+
r.read()
|
|
429
|
+
yield f"[openrunner] server error {r.status_code}: {r.text[:300]}"
|
|
430
|
+
yield "__ENGINE_EXIT__ 1"
|
|
431
|
+
return
|
|
432
|
+
for line in r.iter_lines():
|
|
433
|
+
yield line
|
|
445
434
|
|
|
446
435
|
# ------------------------------------------------------------------
|
|
447
436
|
# Lifecycle
|
|
@@ -534,13 +534,11 @@ def run_ovh_training_sync(
|
|
|
534
534
|
project: str | None = None,
|
|
535
535
|
name: str | None = None,
|
|
536
536
|
emit: Callable[[str], None] | None = None,
|
|
537
|
-
poll_interval: float = 5.0,
|
|
538
537
|
) -> dict[str, Any]:
|
|
539
|
-
"""Governed OVH AI Training run — SERVER-SIDE.
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
``{"job_id","exit_code"}``."""
|
|
538
|
+
"""Governed OVH AI Training run — SERVER-SIDE. The API runs `ovhai job run`
|
|
539
|
+
on the engine-executor with the org's stored user/password (never on this
|
|
540
|
+
machine) and streams the job's logs. COMMAND runs in IMAGE on `gpu` GPUs. Data
|
|
541
|
+
comes from OpenRunner (no OVH object-storage volumes). Returns {"exit_code"}."""
|
|
544
542
|
from openrunner.gpu import _get_api_client
|
|
545
543
|
|
|
546
544
|
say = emit or (lambda m: print(m, flush=True))
|
|
@@ -548,33 +546,20 @@ def run_ovh_training_sync(
|
|
|
548
546
|
if client is None:
|
|
549
547
|
raise RuntimeError("Not logged in. Run 'openrunner login' first.")
|
|
550
548
|
|
|
551
|
-
say(f"[openrunner]
|
|
552
|
-
"— OVH
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
time.sleep(poll_interval)
|
|
566
|
-
st = client.ovh_training_status(jid) or {}
|
|
567
|
-
logs = st.get("logs") or ""
|
|
568
|
-
if len(logs) > seen:
|
|
569
|
-
say(logs[seen:].rstrip("\n"))
|
|
570
|
-
seen = len(logs)
|
|
571
|
-
status = st.get("status")
|
|
572
|
-
if status in ("terminated", "error"):
|
|
573
|
-
code = st.get("exit_code")
|
|
574
|
-
if code is None:
|
|
575
|
-
code = 0 if status == "terminated" else 1
|
|
576
|
-
say(f"[openrunner] OVH AI Training job {status} (exit={code}).")
|
|
577
|
-
return {"job_id": jid, "exit_code": int(code)}
|
|
549
|
+
say(f"[openrunner] launching OVH AI Training (image={image}, gpu={gpu}) "
|
|
550
|
+
"server-side — OVH user/password stays in OpenRunner ...")
|
|
551
|
+
exit_code = 1
|
|
552
|
+
for line in client.ovh_training_run_stream(
|
|
553
|
+
image=image, command=command, gpu=gpu, name=name, project_id=project,
|
|
554
|
+
):
|
|
555
|
+
if line.startswith("__ENGINE_EXIT__"):
|
|
556
|
+
try:
|
|
557
|
+
exit_code = int(line.split()[1])
|
|
558
|
+
except Exception:
|
|
559
|
+
exit_code = 1
|
|
560
|
+
continue
|
|
561
|
+
say(line.rstrip("\n"))
|
|
562
|
+
return {"exit_code": exit_code}
|
|
578
563
|
|
|
579
564
|
|
|
580
565
|
def run_engine_serverside_sync(
|
|
@@ -3599,13 +3599,15 @@ mentions a "distant GPU":
|
|
|
3599
3599
|
SCRIPT (GPU is declared in `@app.function(gpu=...)`, so no `--gpu`), the Run
|
|
3600
3600
|
auto-terminates when the function returns. Don't hunt for an SSH host/runner.
|
|
3601
3601
|
For native Forge use, the `forge-deploy` skill covers install/auth/write/run.
|
|
3602
|
-
- OVHcloud **AI Training** (managed GPU batch jobs): governed,
|
|
3603
|
-
|
|
3604
|
-
|
|
3602
|
+
- OVHcloud **AI Training** (managed GPU batch jobs): governed, run SERVER-SIDE
|
|
3603
|
+
via the `ovhai` CLI on the engine-executor using the org's stored OVH
|
|
3604
|
+
**user/password** (an OpenStack user with the AI Training Operator +
|
|
3605
|
+
ObjectStore operator roles) -- the password never reaches the client. Store it
|
|
3606
|
+
in Settings (provider `ovhai`: username + password), then run:
|
|
3605
3607
|
`openrunner gpu run --provider ovhai --image <img> --gpu-count N "<train cmd>"`
|
|
3606
|
-
-- COMMAND runs in the image on N GPUs;
|
|
3607
|
-
the command exits. **Do NOT use OVH object-storage `--volume`
|
|
3608
|
-
comes from OpenRunner (datasets/artifacts).
|
|
3608
|
+
-- COMMAND runs in the image on N GPUs; the job's logs stream back; it
|
|
3609
|
+
auto-stops when the command exits. **Do NOT use OVH object-storage `--volume`
|
|
3610
|
+
mounts** -- data comes from OpenRunner (datasets/artifacts).
|
|
3609
3611
|
- Provider keys are stored encrypted with `openrunner gpu credentials add`
|
|
3610
3612
|
(e.g. `--provider forge --api-key fg_..`); list/kill pods with
|
|
3611
3613
|
`openrunner gpu ls` / `openrunner gpu terminate`. Governed compute -- the key
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""SDK OVH AI Training governed run — streams the server's job logs."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
from openrunner.gpu import runner as R
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class _Fake:
|
|
8
|
+
def __init__(self, lines): self.args=None; self._lines=lines
|
|
9
|
+
def ovh_training_run_stream(self, **kwargs):
|
|
10
|
+
self.args=kwargs
|
|
11
|
+
yield from self._lines
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_ovh_training_streams_and_parses_exit():
|
|
15
|
+
fake=_Fake(["epoch 1","epoch 2","__ENGINE_EXIT__ 0"])
|
|
16
|
+
out=[]
|
|
17
|
+
with patch("openrunner.gpu._get_api_client", return_value=fake):
|
|
18
|
+
res=R.run_ovh_training_sync(command="python train.py",
|
|
19
|
+
image="ovhcom/ai-training-pytorch", gpu=2, emit=out.append)
|
|
20
|
+
assert res=={"exit_code":0}
|
|
21
|
+
assert fake.args["image"]=="ovhcom/ai-training-pytorch" and fake.args["gpu"]==2
|
|
22
|
+
assert fake.args["command"]=="python train.py"
|
|
23
|
+
assert any("epoch 2" in l for l in out) and not any("__ENGINE_EXIT__" in l for l in out)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_ovh_training_nonzero_exit():
|
|
27
|
+
with patch("openrunner.gpu._get_api_client", return_value=_Fake(["boom","__ENGINE_EXIT__ 4"])):
|
|
28
|
+
res=R.run_ovh_training_sync(command="x", image="i", emit=lambda _l:None)
|
|
29
|
+
assert res["exit_code"]==4
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Tests for RetryTransport — the SDK-level transient-failure retry."""
|
|
2
|
+
import httpx
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from openrunner._retry_transport import RetryTransport
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _client(responder, **kw):
|
|
9
|
+
mock = httpx.MockTransport(responder)
|
|
10
|
+
return httpx.Client(base_url="http://t", transport=RetryTransport(mock, base_delay=0.0, **kw))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_retries_502_then_succeeds():
|
|
14
|
+
calls = {"n": 0}
|
|
15
|
+
def responder(req):
|
|
16
|
+
calls["n"] += 1
|
|
17
|
+
return httpx.Response(502 if calls["n"] < 3 else 200, text="ok")
|
|
18
|
+
r = _client(responder).get("/x")
|
|
19
|
+
assert r.status_code == 200 and calls["n"] == 3
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_gives_up_after_max_retries():
|
|
23
|
+
calls = {"n": 0}
|
|
24
|
+
def responder(req):
|
|
25
|
+
calls["n"] += 1
|
|
26
|
+
return httpx.Response(503)
|
|
27
|
+
r = _client(responder, max_retries=2).get("/x")
|
|
28
|
+
assert r.status_code == 503 and calls["n"] == 3 # 1 + 2 retries
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_4xx_not_retried():
|
|
32
|
+
calls = {"n": 0}
|
|
33
|
+
def responder(req):
|
|
34
|
+
calls["n"] += 1
|
|
35
|
+
return httpx.Response(404)
|
|
36
|
+
r = _client(responder).get("/x")
|
|
37
|
+
assert r.status_code == 404 and calls["n"] == 1
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_504_retried_for_get_but_not_post():
|
|
41
|
+
for method, expect in [("GET", 3), ("POST", 1)]:
|
|
42
|
+
calls = {"n": 0}
|
|
43
|
+
def responder(req):
|
|
44
|
+
calls["n"] += 1
|
|
45
|
+
return httpx.Response(504)
|
|
46
|
+
_client(responder, max_retries=2).request(method, "/x")
|
|
47
|
+
assert calls["n"] == expect, method
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_connect_error_retried_then_raises():
|
|
51
|
+
calls = {"n": 0}
|
|
52
|
+
def responder(req):
|
|
53
|
+
calls["n"] += 1
|
|
54
|
+
raise httpx.ConnectError("boom")
|
|
55
|
+
with pytest.raises(httpx.ConnectError):
|
|
56
|
+
_client(responder, max_retries=2).get("/x")
|
|
57
|
+
assert calls["n"] == 3
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_429_honored_and_retried():
|
|
61
|
+
calls = {"n": 0}
|
|
62
|
+
def responder(req):
|
|
63
|
+
calls["n"] += 1
|
|
64
|
+
return httpx.Response(429, headers={"Retry-After": "0"}) if calls["n"] < 2 else httpx.Response(200)
|
|
65
|
+
r = _client(responder).get("/x")
|
|
66
|
+
assert r.status_code == 200 and calls["n"] == 2
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
"""SDK OVH AI Training governed run (server-side; OVH keys never on client)."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from unittest.mock import patch
|
|
6
|
-
|
|
7
|
-
from openrunner.gpu import runner as R
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class _Fake:
|
|
11
|
-
def __init__(self, statuses):
|
|
12
|
-
self.run_args = None
|
|
13
|
-
self._st = list(statuses)
|
|
14
|
-
|
|
15
|
-
def ovh_training_run(self, **kwargs):
|
|
16
|
-
self.run_args = kwargs
|
|
17
|
-
return {"job_id": "ai-1", "status": "queued"}
|
|
18
|
-
|
|
19
|
-
def ovh_training_status(self, job_id):
|
|
20
|
-
return self._st.pop(0) if self._st else {"status": "terminated", "exit_code": 0}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def test_ovh_training_submits_and_streams():
|
|
24
|
-
fake = _Fake([
|
|
25
|
-
{"status": "running", "logs": "epoch 1\n"},
|
|
26
|
-
{"status": "running", "logs": "epoch 1\nepoch 2\n"},
|
|
27
|
-
{"status": "terminated", "exit_code": 0, "logs": "epoch 1\nepoch 2\ndone\n"},
|
|
28
|
-
])
|
|
29
|
-
lines: list[str] = []
|
|
30
|
-
with patch("openrunner.gpu._get_api_client", return_value=fake):
|
|
31
|
-
res = R.run_ovh_training_sync(
|
|
32
|
-
command="python train.py", image="ovhcom/ai-training-pytorch",
|
|
33
|
-
gpu=2, emit=lines.append, poll_interval=0,
|
|
34
|
-
)
|
|
35
|
-
assert res == {"job_id": "ai-1", "exit_code": 0}
|
|
36
|
-
assert fake.run_args["image"] == "ovhcom/ai-training-pytorch"
|
|
37
|
-
assert fake.run_args["gpu"] == 2
|
|
38
|
-
assert fake.run_args["command"] == "python train.py"
|
|
39
|
-
assert any("done" in ln for ln in lines)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def test_ovh_training_nonzero_exit():
|
|
43
|
-
fake = _Fake([{"status": "error", "exit_code": 7, "logs": "boom\n"}])
|
|
44
|
-
with patch("openrunner.gpu._get_api_client", return_value=fake):
|
|
45
|
-
res = R.run_ovh_training_sync(command="x", image="i", emit=lambda _l: None,
|
|
46
|
-
poll_interval=0)
|
|
47
|
-
assert res["exit_code"] == 7
|
|
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
|