openrunner-sdk 2.38.3__tar.gz → 2.40.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.38.3 → openrunner_sdk-2.40.0}/PKG-INFO +1 -1
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/__init__.py +1 -1
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/cli.py +25 -17
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/api_integration.py +77 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/runner.py +160 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/install_commands.py +10 -5
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/pyproject.toml +1 -1
- openrunner_sdk-2.40.0/tests/test_gpu/test_engine_serverside.py +30 -0
- openrunner_sdk-2.40.0/tests/test_gpu/test_governed_jobs.py +74 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/.gitignore +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/=6.0 +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/=8.1 +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/README.md +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/CLAUDE.md +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/_safepath.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/_secret_rules.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/api_client.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/artifact.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/autoresearch.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/buffer.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/cache.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/config.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/cost.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/dataset.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/datasets.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/environment.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/evaluation.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/feedback.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/git_info.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/__init__.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/cost_guard.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/gpu_types.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/price_compare.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/provider_base.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/providers/__init__.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/providers/forge_provider.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/providers/lambda_cloud.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/providers/modal_provider.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/providers/ovhcloud.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/providers/runpod.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/providers/vastai.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/workspace_sync.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/guardrails.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/handover.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/hooks/patent-lab-post-commit +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/__init__.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/accelerate.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/anthropic_tracer.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/catboost.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/diffusers.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/fastai.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/forced_alignment.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/gladia.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/gymnasium.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/huggingface.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/hydra.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/ignite.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/jax.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/keras.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/langchain.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/lightgbm.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/lightning.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/llamaindex.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/openai_finetune.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/openai_tracer.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/optuna.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/pytorch.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/sb3.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/sklearn.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/skypilot.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/tensorflow.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/trl.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/tts.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/ultralytics.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/voice_agent.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/whisper.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/xgboost.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/launch.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/machine.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/mcp_server.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/media.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/migrate.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/model.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/model_card.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/offline.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/patent_scan.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/pii.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/plot.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/projects.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/prompt.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/query_api.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/redact.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/reload.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/research.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/research_policy.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/run.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/scorers.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/sender.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/session.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/settings.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/summary.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/sweep.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/sync.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/system_metrics.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/tensorboard.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/tools/__init__.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/tools/patent_report.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/trace.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/transcript_formatter.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/wal.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/wandb_compat/__init__.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/wandb_compat/_shim.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/wer.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/scripts/gen_betterleaks_rules.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/__init__.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/conftest.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-aaa/config.json +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-aaa/metadata.json +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-aaa/metrics.jsonl +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-aaa/summary.json +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/model-checkpoint/manifest.json +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/model-checkpoint/weights.bin +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/sample-image/manifest.json +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/sample-image/preview.png +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-bbb/config.json +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-bbb/metadata.json +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-bbb/metrics.jsonl +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-bbb/summary.json +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/integration/__init__.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/integration/conftest.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/integration/test_artifact_round_trip.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_alert.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_aliases.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_api_client.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_api_client_methods.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_api_client_transport.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_artifact.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_buffer.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_cache.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_class_scorers.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_cli.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_cli_artifact_sweep.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_cli_datasets.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_cli_gpu_registry_deploy.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_cli_infra.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_cli_remainder.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_cli_topup.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_config.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_datasets_module.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_evaluation.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_finish.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_git_info.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/__init__.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/conftest.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_cost_guard.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_forge.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_gpu_cluster.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_gpu_types.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_lambda.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_modal.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_ovhcloud.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_price_compare.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_provider_base.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_runner.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_runpod.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_sdk_gpu_api.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_vastai.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_workspace_sync.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu_modal_creds.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_init.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_init_facade.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_install_commands.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_fastai.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_huggingface.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_keras.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_langchain.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_lightning.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_pytorch.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_setA.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_setB.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_setC.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_sklearn.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_xgboost.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_launch.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_log.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_log_code.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_machine_migrate.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_mcp_decisions.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_mcp_monitor.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_mcp_predictions.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_mcp_server.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_media.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_media_extra.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_migrate.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_offline.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_offline_sync.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_patent_scan.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_pii.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_plot.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_plot_media_extra2.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_prompt_module.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_pure_modules.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_query_api.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_redact.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_reload_module.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_research_module.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_resume.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_run_module.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_sdk_cluster_j1.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_sdk_cluster_j2.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_sdk_cluster_j3.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_sdk_cluster_j4.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_sdk_core_edges.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_sdk_features.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_sender.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_session_module.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_session_sync.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_summary.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_sweep.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_system_metrics.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_topup_hi.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_trace.py +0 -0
- {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.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.40.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
|
|
@@ -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.40.0"
|
|
185
185
|
|
|
186
186
|
logger = logging.getLogger("openrunner")
|
|
187
187
|
|
|
@@ -4244,8 +4244,8 @@ def gpu_run(command, provider, gpu, gpu_count, image, disk_gb, env, ssh_keys, pr
|
|
|
4244
4244
|
"""
|
|
4245
4245
|
from openrunner.gpu.runner import run_governed_sync, run_on_gpu_sync
|
|
4246
4246
|
|
|
4247
|
-
if provider
|
|
4248
|
-
click.echo("Error: --gpu is required (except for forge).", err=True)
|
|
4247
|
+
if provider not in ("forge", "modal") and not gpu:
|
|
4248
|
+
click.echo("Error: --gpu is required (except for forge/modal).", err=True)
|
|
4249
4249
|
raise SystemExit(1)
|
|
4250
4250
|
|
|
4251
4251
|
settings = _load_settings()
|
|
@@ -4289,22 +4289,30 @@ def gpu_run(command, provider, gpu, gpu_count, image, disk_gb, env, ssh_keys, pr
|
|
|
4289
4289
|
except Exception: # noqa: BLE001
|
|
4290
4290
|
pass
|
|
4291
4291
|
|
|
4292
|
-
# Forge
|
|
4293
|
-
#
|
|
4294
|
-
#
|
|
4295
|
-
#
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4292
|
+
# Forge/Modal are serverless client-side engines — no pod to provision. By
|
|
4293
|
+
# default they run GOVERNED: submit a runner job whose engine token is
|
|
4294
|
+
# injected server-side (the token never touches this machine). COMMAND is the
|
|
4295
|
+
# App script. A caller who explicitly supplies the token via -e (FORGE_TOKEN /
|
|
4296
|
+
# MODAL_TOKEN_SECRET) opts into running the engine client locally instead.
|
|
4297
|
+
if provider in ("forge", "modal"):
|
|
4298
|
+
_DEFAULT_IMAGE = "pytorch/pytorch:2.4.0-cuda12.4-cudnn9-runtime"
|
|
4299
|
+
local_tok = env_dict.get("FORGE_TOKEN") if provider == "forge" else env_dict.get("MODAL_TOKEN_SECRET")
|
|
4299
4300
|
try:
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4301
|
+
if provider == "forge" and local_tok:
|
|
4302
|
+
from openrunner.gpu.runner import run_forge_sync
|
|
4303
|
+
result = run_forge_sync(
|
|
4304
|
+
command=command, api_key=env_dict.get("FORGE_TOKEN"),
|
|
4305
|
+
api_url=env_dict.get("FORGE_API_URL"), project=project,
|
|
4306
|
+
env=env_dict, emit=lambda m: click.echo(m),
|
|
4307
|
+
)
|
|
4308
|
+
else:
|
|
4309
|
+
# Governed: the SERVER runs the engine via the engine-executor
|
|
4310
|
+
# (token stays in OpenRunner; nothing runs here, no runner needed).
|
|
4311
|
+
from openrunner.gpu.runner import run_engine_serverside_sync
|
|
4312
|
+
result = run_engine_serverside_sync(
|
|
4313
|
+
provider=provider, command=command, project=project,
|
|
4314
|
+
emit=lambda m: click.echo(m),
|
|
4315
|
+
)
|
|
4308
4316
|
except Exception as exc: # noqa: BLE001
|
|
4309
4317
|
click.echo(f"gpu run failed: {exc}", err=True)
|
|
4310
4318
|
raise SystemExit(1)
|
|
@@ -331,6 +331,83 @@ class GpuAPIClient:
|
|
|
331
331
|
logger.warning("get_credentials failed: %s", e)
|
|
332
332
|
return None
|
|
333
333
|
|
|
334
|
+
# ------------------------------------------------------------------
|
|
335
|
+
# Governed runner jobs (client-side engines: forge/modal). The engine
|
|
336
|
+
# token is injected server-side into the runner's claim response — the
|
|
337
|
+
# client submits the job WITHOUT it and never sees it.
|
|
338
|
+
# ------------------------------------------------------------------
|
|
339
|
+
|
|
340
|
+
def submit_job(
|
|
341
|
+
self,
|
|
342
|
+
*,
|
|
343
|
+
provider: str | None,
|
|
344
|
+
command: str,
|
|
345
|
+
image: str,
|
|
346
|
+
env: dict[str, str] | None = None,
|
|
347
|
+
target_labels: list[str] | None = None,
|
|
348
|
+
project_id: str | None = None,
|
|
349
|
+
run_id: str | None = None,
|
|
350
|
+
gpu_count: int = 0,
|
|
351
|
+
) -> dict[str, Any] | None:
|
|
352
|
+
"""POST /api/v1/jobs -- enqueue a runner job. For a governed engine pass
|
|
353
|
+
``provider`` ('forge'/'modal'); the API injects the org's decrypted token
|
|
354
|
+
into the runner's env at claim time. Returns the job dict or None."""
|
|
355
|
+
org = self.org_id()
|
|
356
|
+
if not org:
|
|
357
|
+
logger.warning("submit_job: no org resolved")
|
|
358
|
+
return None
|
|
359
|
+
body: dict[str, Any] = {
|
|
360
|
+
"type": "exec", "image": image, "command": command,
|
|
361
|
+
"gpu_count": gpu_count, "target_labels": target_labels or [],
|
|
362
|
+
}
|
|
363
|
+
if provider:
|
|
364
|
+
body["provider"] = provider
|
|
365
|
+
if env:
|
|
366
|
+
body["env"] = env
|
|
367
|
+
if project_id:
|
|
368
|
+
body["project_id"] = project_id
|
|
369
|
+
if run_id:
|
|
370
|
+
body["run_id"] = run_id
|
|
371
|
+
try:
|
|
372
|
+
r = self._client.post("/jobs", params={"org_id": org}, json=body)
|
|
373
|
+
r.raise_for_status()
|
|
374
|
+
return r.json()
|
|
375
|
+
except Exception as e:
|
|
376
|
+
detail = getattr(getattr(e, "response", None), "text", "") or str(e)
|
|
377
|
+
logger.warning("submit_job failed: %s", detail)
|
|
378
|
+
return None
|
|
379
|
+
|
|
380
|
+
def engine_run_stream(
|
|
381
|
+
self, *, engine: str, script_b64: str, name: str, args: list[str] | None = None,
|
|
382
|
+
):
|
|
383
|
+
"""POST /api/v1/gpu/engine/run and YIELD stdout lines. The server runs the
|
|
384
|
+
engine (Forge/Modal) via the executor with the org's decrypted token — the
|
|
385
|
+
token never comes here. A ``__ENGINE_EXIT__ <code>`` sentinel line ends it."""
|
|
386
|
+
org = self.org_id()
|
|
387
|
+
body = {"engine": engine, "script_b64": script_b64, "name": name,
|
|
388
|
+
"args": args or []}
|
|
389
|
+
with self._client.stream(
|
|
390
|
+
"POST", "/gpu/engine/run", params={"org_id": org}, json=body,
|
|
391
|
+
) as r:
|
|
392
|
+
if r.status_code >= 400:
|
|
393
|
+
r.read()
|
|
394
|
+
yield f"[openrunner] server error {r.status_code}: {r.text[:300]}"
|
|
395
|
+
yield "__ENGINE_EXIT__ 1"
|
|
396
|
+
return
|
|
397
|
+
for line in r.iter_lines():
|
|
398
|
+
yield line
|
|
399
|
+
|
|
400
|
+
def get_job(self, job_id: str) -> dict[str, Any] | None:
|
|
401
|
+
"""GET /api/v1/jobs/{id} -- poll a job's status/logs_tail/exit_code."""
|
|
402
|
+
try:
|
|
403
|
+
r = self._client.get(f"/jobs/{job_id}", params={"org_id": self.org_id()})
|
|
404
|
+
if r.status_code >= 400:
|
|
405
|
+
return None
|
|
406
|
+
return r.json()
|
|
407
|
+
except Exception as e:
|
|
408
|
+
logger.warning("get_job failed: %s", e)
|
|
409
|
+
return None
|
|
410
|
+
|
|
334
411
|
# ------------------------------------------------------------------
|
|
335
412
|
# Lifecycle
|
|
336
413
|
# ------------------------------------------------------------------
|
|
@@ -411,3 +411,163 @@ def run_forge_sync(
|
|
|
411
411
|
exit_code = forge_exec(command, run_env, say)
|
|
412
412
|
say(f"[openrunner] forge run finished (exit={exit_code}); serverless — nothing to terminate.")
|
|
413
413
|
return {"exit_code": exit_code, "terminated": False}
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
# ---------------------------------------------------------------------------
|
|
417
|
+
# Governed client-side engines (Forge, Modal) — run on a runner, token injected
|
|
418
|
+
# server-side. The user's machine never holds the engine token.
|
|
419
|
+
# ---------------------------------------------------------------------------
|
|
420
|
+
|
|
421
|
+
# Per-engine container recipe. The token env vars are injected by the API at
|
|
422
|
+
# claim time (FORGE_TOKEN / MODAL_TOKEN_ID+SECRET) — not set here.
|
|
423
|
+
_ENGINE_JOB_SPEC: dict[str, dict[str, str]] = {
|
|
424
|
+
"forge": {
|
|
425
|
+
"image": "python:3.13-slim",
|
|
426
|
+
"bootstrap": "pip install -q forge-shadow --extra-index-url https://forge.shadow-paas.com/sdk/",
|
|
427
|
+
"run": "forge run",
|
|
428
|
+
},
|
|
429
|
+
"modal": {
|
|
430
|
+
"image": "python:3.11-slim",
|
|
431
|
+
"bootstrap": "pip install -q modal",
|
|
432
|
+
"run": "modal run",
|
|
433
|
+
},
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def run_engine_job_sync(
|
|
438
|
+
*,
|
|
439
|
+
provider: str,
|
|
440
|
+
command: str,
|
|
441
|
+
project: str | None = None,
|
|
442
|
+
image: str | None = None,
|
|
443
|
+
target_labels: list[str] | None = None,
|
|
444
|
+
env: dict[str, str] | None = None,
|
|
445
|
+
emit: Callable[[str], None] | None = None,
|
|
446
|
+
poll_interval: float = 3.0,
|
|
447
|
+
queue_warn_after: float = 20.0,
|
|
448
|
+
) -> dict[str, Any]:
|
|
449
|
+
"""Governed run of a client-side engine (Forge/Modal) via the runner-job
|
|
450
|
+
system. ``command`` is ``<local_script.py> [args...]``; the script is shipped
|
|
451
|
+
inline (base64) and run with ``<engine> run <script>`` inside the engine's
|
|
452
|
+
container ON A RUNNER. The API injects the org's decrypted token into the
|
|
453
|
+
runner's env at claim time — this machine never holds it. Returns
|
|
454
|
+
``{"job_id","exit_code"}``.
|
|
455
|
+
"""
|
|
456
|
+
import base64 as _b64
|
|
457
|
+
import os as _os
|
|
458
|
+
|
|
459
|
+
from openrunner.gpu import _get_api_client
|
|
460
|
+
|
|
461
|
+
say = emit or (lambda m: print(m, flush=True))
|
|
462
|
+
spec = _ENGINE_JOB_SPEC.get(provider)
|
|
463
|
+
if spec is None:
|
|
464
|
+
raise RuntimeError(f"'{provider}' is not a governed client-side engine")
|
|
465
|
+
|
|
466
|
+
parts = shlex.split(command)
|
|
467
|
+
if not parts:
|
|
468
|
+
raise RuntimeError("no script given")
|
|
469
|
+
script_path, args = parts[0], parts[1:]
|
|
470
|
+
try:
|
|
471
|
+
with open(script_path, "rb") as fh:
|
|
472
|
+
script_b64 = _b64.b64encode(fh.read()).decode()
|
|
473
|
+
except OSError as exc:
|
|
474
|
+
raise RuntimeError(f"cannot read script '{script_path}': {exc}") from exc
|
|
475
|
+
name = _os.path.basename(script_path) or "app.py"
|
|
476
|
+
arg_str = (" " + " ".join(shlex.quote(a) for a in args)) if args else ""
|
|
477
|
+
# Runner runs this via `sh -c`. Clean dir (engine snapshots cwd), install the
|
|
478
|
+
# client, materialize the script, run it.
|
|
479
|
+
container_cmd = (
|
|
480
|
+
f"set -e; mkdir -p /w && cd /w; {spec['bootstrap']}; "
|
|
481
|
+
f"printf %s '{script_b64}' | base64 -d > {shlex.quote(name)}; "
|
|
482
|
+
f"{spec['run']} {shlex.quote(name)}{arg_str}"
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
client = _get_api_client()
|
|
486
|
+
if client is None:
|
|
487
|
+
raise RuntimeError("Not logged in. Run 'openrunner login' first.")
|
|
488
|
+
|
|
489
|
+
say(f"[openrunner] submitting governed {provider} job (token injected "
|
|
490
|
+
"server-side; this machine never holds it) ...")
|
|
491
|
+
job = client.submit_job(
|
|
492
|
+
provider=provider, command=container_cmd, image=image or spec["image"],
|
|
493
|
+
env=env or None, target_labels=target_labels, project_id=project,
|
|
494
|
+
gpu_count=0, # the entrypoint runs on CPU; the engine's fleet has the GPU
|
|
495
|
+
)
|
|
496
|
+
if not job or not job.get("id"):
|
|
497
|
+
raise RuntimeError(
|
|
498
|
+
f"failed to submit {provider} job — need an org {provider} credential "
|
|
499
|
+
f"(openrunner gpu credentials add --provider {provider} ...) and a "
|
|
500
|
+
"registered runner (openrunner runner start)."
|
|
501
|
+
)
|
|
502
|
+
jid = job["id"]
|
|
503
|
+
say(f"[openrunner] job {jid} queued — waiting for a runner to claim it ...")
|
|
504
|
+
|
|
505
|
+
seen = 0
|
|
506
|
+
warned = False
|
|
507
|
+
start = time.monotonic()
|
|
508
|
+
while True:
|
|
509
|
+
time.sleep(poll_interval)
|
|
510
|
+
j = client.get_job(jid) or {}
|
|
511
|
+
logs = j.get("logs_tail") or ""
|
|
512
|
+
if len(logs) > seen:
|
|
513
|
+
say(logs[seen:].rstrip("\n"))
|
|
514
|
+
seen = len(logs)
|
|
515
|
+
status = j.get("status")
|
|
516
|
+
if status in ("succeeded", "failed", "canceled"):
|
|
517
|
+
code = j.get("exit_code")
|
|
518
|
+
if code is None:
|
|
519
|
+
code = 0 if status == "succeeded" else 1
|
|
520
|
+
say(f"[openrunner] {provider} job {status} (exit={code}).")
|
|
521
|
+
return {"job_id": jid, "exit_code": int(code)}
|
|
522
|
+
if (not warned and status in (None, "queued")
|
|
523
|
+
and time.monotonic() - start > queue_warn_after):
|
|
524
|
+
warned = True
|
|
525
|
+
say("[openrunner] still queued — is a runner online? "
|
|
526
|
+
"`openrunner runner start` on a box in this org (needs Docker).")
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def run_engine_serverside_sync(
|
|
530
|
+
*,
|
|
531
|
+
provider: str,
|
|
532
|
+
command: str,
|
|
533
|
+
project: str | None = None,
|
|
534
|
+
emit: Callable[[str], None] | None = None,
|
|
535
|
+
) -> dict[str, Any]:
|
|
536
|
+
"""Governed run of a client-side engine (Forge/Modal) where the SERVER runs
|
|
537
|
+
it. Ships the App script to the API, which launches `forge run`/`modal run`
|
|
538
|
+
on the internal engine-executor with the org's decrypted token and streams
|
|
539
|
+
stdout back. The token never touches this machine and no runner is needed.
|
|
540
|
+
``command`` is ``<local_script.py> [args...]``. Returns {"exit_code"}."""
|
|
541
|
+
import base64 as _b64
|
|
542
|
+
import os as _os
|
|
543
|
+
|
|
544
|
+
from openrunner.gpu import _get_api_client
|
|
545
|
+
|
|
546
|
+
say = emit or (lambda m: print(m, flush=True))
|
|
547
|
+
parts = shlex.split(command)
|
|
548
|
+
if not parts:
|
|
549
|
+
raise RuntimeError("no script given")
|
|
550
|
+
script_path, args = parts[0], parts[1:]
|
|
551
|
+
try:
|
|
552
|
+
with open(script_path, "rb") as fh:
|
|
553
|
+
b64 = _b64.b64encode(fh.read()).decode()
|
|
554
|
+
except OSError as exc:
|
|
555
|
+
raise RuntimeError(f"cannot read script '{script_path}': {exc}") from exc
|
|
556
|
+
name = _os.path.basename(script_path) or "app.py"
|
|
557
|
+
|
|
558
|
+
client = _get_api_client()
|
|
559
|
+
if client is None:
|
|
560
|
+
raise RuntimeError("Not logged in. Run 'openrunner login' first.")
|
|
561
|
+
|
|
562
|
+
say(f"[openrunner] launching {provider} server-side "
|
|
563
|
+
"(token stays in OpenRunner; nothing runs on this machine) ...")
|
|
564
|
+
exit_code = 1
|
|
565
|
+
for line in client.engine_run_stream(engine=provider, script_b64=b64, name=name, args=args):
|
|
566
|
+
if line.startswith("__ENGINE_EXIT__"):
|
|
567
|
+
try:
|
|
568
|
+
exit_code = int(line.split()[1])
|
|
569
|
+
except Exception:
|
|
570
|
+
exit_code = 1
|
|
571
|
+
continue
|
|
572
|
+
say(line.rstrip("\n"))
|
|
573
|
+
return {"exit_code": exit_code}
|
|
@@ -3209,13 +3209,18 @@ Forge (shadow-paas.com) is a **serverless GPU fleet** -- like Modal. You deploy
|
|
|
3209
3209
|
auto-terminates. It is NOT an ssh box or a registered runner.
|
|
3210
3210
|
|
|
3211
3211
|
## Run an App
|
|
3212
|
-
- Governed (token
|
|
3212
|
+
- Governed (DEFAULT — token never touches your machine): store once with
|
|
3213
3213
|
`openrunner gpu credentials add --provider forge --api-key fg_..`, then
|
|
3214
|
-
`openrunner gpu run --provider forge <script.py
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3214
|
+
`openrunner gpu run --provider forge <script.py>`. This submits a runner job;
|
|
3215
|
+
the API injects the org's decrypted token into the runner server-side. COMMAND
|
|
3216
|
+
is the App SCRIPT (GPU declared inside it, so no `--gpu`); the Run
|
|
3217
|
+
auto-terminates. Needs a **registered runner** in the org
|
|
3218
|
+
(`openrunner runner start`, needs Docker) — else the job sits queued.
|
|
3219
|
+
- Native client (runs locally, token on this box): `forge run <script.py>` (needs
|
|
3220
|
+
the `forge-shadow` client on Python 3.13 + `FORGE_TOKEN`; install:
|
|
3218
3221
|
`pip install forge-shadow --extra-index-url https://forge.shadow-paas.com/sdk/`).
|
|
3222
|
+
`openrunner gpu run --provider forge -e FORGE_TOKEN=fg_.. <script>` also runs
|
|
3223
|
+
local.
|
|
3219
3224
|
|
|
3220
3225
|
## How the function runs (mental model)
|
|
3221
3226
|
`forge run script.py` runs `@app.local_entrypoint()` on YOUR machine; each
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""SDK server-side engine run: streams the server's stdout, token never local."""
|
|
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 engine_run_stream(self, **kwargs):
|
|
10
|
+
self.args=kwargs
|
|
11
|
+
yield from self._lines
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_serverside_streams_and_parses_exit(tmp_path):
|
|
15
|
+
s=tmp_path/"app.py"; s.write_text("print('x')\n")
|
|
16
|
+
fake=_Fake(["step 1","step 2","__ENGINE_EXIT__ 0"])
|
|
17
|
+
out=[]
|
|
18
|
+
with patch("openrunner.gpu._get_api_client", return_value=fake):
|
|
19
|
+
res=R.run_engine_serverside_sync(provider="forge", command=str(s), emit=out.append)
|
|
20
|
+
assert res=={"exit_code":0}
|
|
21
|
+
assert fake.args["engine"]=="forge" and fake.args["name"]=="app.py"
|
|
22
|
+
assert "script_b64" in fake.args # script shipped, no token
|
|
23
|
+
assert "step 2" in out and not any("__ENGINE_EXIT__" in l for l in out)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_serverside_nonzero_exit(tmp_path):
|
|
27
|
+
s=tmp_path/"m.py"; s.write_text("x=1\n")
|
|
28
|
+
with patch("openrunner.gpu._get_api_client", return_value=_Fake(["boom","__ENGINE_EXIT__ 5"])):
|
|
29
|
+
res=R.run_engine_serverside_sync(provider="modal", command=str(s), emit=lambda _l:None)
|
|
30
|
+
assert res["exit_code"]==5
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""SDK governed engine run: submits a runner job (no token) + streams logs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
from unittest.mock import patch
|
|
7
|
+
|
|
8
|
+
from openrunner.gpu import runner as R
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class _FakeClient:
|
|
12
|
+
def __init__(self, statuses):
|
|
13
|
+
self.submitted = None
|
|
14
|
+
self._statuses = list(statuses)
|
|
15
|
+
|
|
16
|
+
def submit_job(self, **kwargs):
|
|
17
|
+
self.submitted = kwargs
|
|
18
|
+
return {"id": "job-1"}
|
|
19
|
+
|
|
20
|
+
def get_job(self, job_id):
|
|
21
|
+
return self._statuses.pop(0) if self._statuses else {"status": "succeeded", "exit_code": 0}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_run_engine_job_builds_inline_script_and_streams(tmp_path):
|
|
25
|
+
script = tmp_path / "app.py"
|
|
26
|
+
script.write_text("print('hi')\n")
|
|
27
|
+
fake = _FakeClient([
|
|
28
|
+
{"status": "running", "logs_tail": "step 1\n"},
|
|
29
|
+
{"status": "running", "logs_tail": "step 1\nstep 2\n"},
|
|
30
|
+
{"status": "succeeded", "exit_code": 0, "logs_tail": "step 1\nstep 2\ndone\n"},
|
|
31
|
+
])
|
|
32
|
+
lines: list[str] = []
|
|
33
|
+
with patch("openrunner.gpu._get_api_client", return_value=fake):
|
|
34
|
+
result = R.run_engine_job_sync(
|
|
35
|
+
provider="forge", command=str(script),
|
|
36
|
+
emit=lines.append, poll_interval=0,
|
|
37
|
+
)
|
|
38
|
+
assert result["exit_code"] == 0 and result["job_id"] == "job-1"
|
|
39
|
+
# Governed: provider marker set, NO token in the submitted env, gpu_count 0.
|
|
40
|
+
s = fake.submitted
|
|
41
|
+
assert s["provider"] == "forge"
|
|
42
|
+
assert s["gpu_count"] == 0
|
|
43
|
+
assert "FORGE_TOKEN" not in (s.get("env") or {})
|
|
44
|
+
# Script shipped inline (base64) + run via the forge client in a clean dir.
|
|
45
|
+
b64 = base64.b64encode(b"print('hi')\n").decode()
|
|
46
|
+
assert b64 in s["command"]
|
|
47
|
+
assert "forge run app.py" in s["command"]
|
|
48
|
+
assert s["image"] == "python:3.13-slim"
|
|
49
|
+
# Incremental log deltas streamed (not the whole buffer each poll).
|
|
50
|
+
assert any("done" in ln for ln in lines)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_run_engine_job_modal_defaults(tmp_path):
|
|
54
|
+
script = tmp_path / "m.py"
|
|
55
|
+
script.write_text("x=1\n")
|
|
56
|
+
fake = _FakeClient([{"status": "failed", "exit_code": 3, "logs_tail": "boom\n"}])
|
|
57
|
+
with patch("openrunner.gpu._get_api_client", return_value=fake):
|
|
58
|
+
result = R.run_engine_job_sync(provider="modal", command=str(script),
|
|
59
|
+
emit=lambda _l: None, poll_interval=0)
|
|
60
|
+
assert result["exit_code"] == 3
|
|
61
|
+
assert fake.submitted["image"] == "python:3.11-slim"
|
|
62
|
+
assert "modal run m.py" in fake.submitted["command"]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_run_engine_job_missing_script_errors():
|
|
66
|
+
fake = _FakeClient([])
|
|
67
|
+
with patch("openrunner.gpu._get_api_client", return_value=fake):
|
|
68
|
+
try:
|
|
69
|
+
R.run_engine_job_sync(provider="forge", command="/no/such.py",
|
|
70
|
+
emit=lambda _l: None, poll_interval=0)
|
|
71
|
+
except RuntimeError as e:
|
|
72
|
+
assert "cannot read script" in str(e)
|
|
73
|
+
else:
|
|
74
|
+
raise AssertionError("expected RuntimeError")
|
|
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
|