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.
Files changed (223) hide show
  1. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/PKG-INFO +1 -1
  2. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/__init__.py +1 -1
  3. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/cli.py +25 -17
  4. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/api_integration.py +77 -0
  5. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/runner.py +160 -0
  6. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/install_commands.py +10 -5
  7. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/pyproject.toml +1 -1
  8. openrunner_sdk-2.40.0/tests/test_gpu/test_engine_serverside.py +30 -0
  9. openrunner_sdk-2.40.0/tests/test_gpu/test_governed_jobs.py +74 -0
  10. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/.gitignore +0 -0
  11. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/=6.0 +0 -0
  12. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/=8.1 +0 -0
  13. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/README.md +0 -0
  14. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/CLAUDE.md +0 -0
  15. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/_safepath.py +0 -0
  16. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/_secret_rules.py +0 -0
  17. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/api_client.py +0 -0
  18. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/artifact.py +0 -0
  19. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/autoresearch.py +0 -0
  20. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/buffer.py +0 -0
  21. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/cache.py +0 -0
  22. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/config.py +0 -0
  23. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/cost.py +0 -0
  24. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/dataset.py +0 -0
  25. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/datasets.py +0 -0
  26. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/environment.py +0 -0
  27. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/evaluation.py +0 -0
  28. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/feedback.py +0 -0
  29. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/git_info.py +0 -0
  30. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/__init__.py +0 -0
  31. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/cost_guard.py +0 -0
  32. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/gpu_types.py +0 -0
  33. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/price_compare.py +0 -0
  34. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/provider_base.py +0 -0
  35. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/providers/__init__.py +0 -0
  36. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/providers/forge_provider.py +0 -0
  37. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/providers/lambda_cloud.py +0 -0
  38. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/providers/modal_provider.py +0 -0
  39. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/providers/ovhcloud.py +0 -0
  40. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/providers/runpod.py +0 -0
  41. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/providers/vastai.py +0 -0
  42. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/gpu/workspace_sync.py +0 -0
  43. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/guardrails.py +0 -0
  44. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/handover.py +0 -0
  45. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/hooks/patent-lab-post-commit +0 -0
  46. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/__init__.py +0 -0
  47. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/accelerate.py +0 -0
  48. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/anthropic_tracer.py +0 -0
  49. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/catboost.py +0 -0
  50. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/diffusers.py +0 -0
  51. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/fastai.py +0 -0
  52. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/forced_alignment.py +0 -0
  53. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/gladia.py +0 -0
  54. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/gymnasium.py +0 -0
  55. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/huggingface.py +0 -0
  56. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/hydra.py +0 -0
  57. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/ignite.py +0 -0
  58. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/jax.py +0 -0
  59. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/keras.py +0 -0
  60. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/langchain.py +0 -0
  61. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/lightgbm.py +0 -0
  62. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/lightning.py +0 -0
  63. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/llamaindex.py +0 -0
  64. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/openai_finetune.py +0 -0
  65. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/openai_tracer.py +0 -0
  66. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/optuna.py +0 -0
  67. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/pytorch.py +0 -0
  68. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/sb3.py +0 -0
  69. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/sklearn.py +0 -0
  70. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/skypilot.py +0 -0
  71. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/tensorflow.py +0 -0
  72. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/trl.py +0 -0
  73. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/tts.py +0 -0
  74. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/ultralytics.py +0 -0
  75. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/voice_agent.py +0 -0
  76. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/whisper.py +0 -0
  77. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/integration/xgboost.py +0 -0
  78. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/launch.py +0 -0
  79. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/machine.py +0 -0
  80. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/mcp_server.py +0 -0
  81. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/media.py +0 -0
  82. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/migrate.py +0 -0
  83. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/model.py +0 -0
  84. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/model_card.py +0 -0
  85. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/offline.py +0 -0
  86. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/patent_scan.py +0 -0
  87. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/pii.py +0 -0
  88. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/plot.py +0 -0
  89. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/projects.py +0 -0
  90. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/prompt.py +0 -0
  91. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/query_api.py +0 -0
  92. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/redact.py +0 -0
  93. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/reload.py +0 -0
  94. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/research.py +0 -0
  95. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/research_policy.py +0 -0
  96. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/run.py +0 -0
  97. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/scorers.py +0 -0
  98. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/sender.py +0 -0
  99. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/session.py +0 -0
  100. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/settings.py +0 -0
  101. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/summary.py +0 -0
  102. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/sweep.py +0 -0
  103. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/sync.py +0 -0
  104. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/system_metrics.py +0 -0
  105. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/tensorboard.py +0 -0
  106. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/tools/__init__.py +0 -0
  107. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/tools/patent_report.py +0 -0
  108. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/trace.py +0 -0
  109. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/transcript_formatter.py +0 -0
  110. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/wal.py +0 -0
  111. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/wandb_compat/__init__.py +0 -0
  112. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/wandb_compat/_shim.py +0 -0
  113. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/openrunner/wer.py +0 -0
  114. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/scripts/gen_betterleaks_rules.py +0 -0
  115. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/__init__.py +0 -0
  116. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/conftest.py +0 -0
  117. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-aaa/config.json +0 -0
  118. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-aaa/metadata.json +0 -0
  119. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-aaa/metrics.jsonl +0 -0
  120. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-aaa/summary.json +0 -0
  121. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/model-checkpoint/manifest.json +0 -0
  122. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/model-checkpoint/weights.bin +0 -0
  123. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/sample-image/manifest.json +0 -0
  124. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/sample-image/preview.png +0 -0
  125. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-bbb/config.json +0 -0
  126. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-bbb/metadata.json +0 -0
  127. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-bbb/metrics.jsonl +0 -0
  128. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/fixtures/wandb-export-mini/run-bbb/summary.json +0 -0
  129. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/integration/__init__.py +0 -0
  130. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/integration/conftest.py +0 -0
  131. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/integration/test_artifact_round_trip.py +0 -0
  132. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_alert.py +0 -0
  133. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_aliases.py +0 -0
  134. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_api_client.py +0 -0
  135. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_api_client_methods.py +0 -0
  136. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_api_client_transport.py +0 -0
  137. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_artifact.py +0 -0
  138. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_buffer.py +0 -0
  139. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_cache.py +0 -0
  140. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_class_scorers.py +0 -0
  141. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_cli.py +0 -0
  142. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_cli_artifact_sweep.py +0 -0
  143. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_cli_datasets.py +0 -0
  144. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_cli_gpu_registry_deploy.py +0 -0
  145. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_cli_infra.py +0 -0
  146. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_cli_remainder.py +0 -0
  147. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_cli_topup.py +0 -0
  148. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_config.py +0 -0
  149. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_datasets_module.py +0 -0
  150. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_evaluation.py +0 -0
  151. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_finish.py +0 -0
  152. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_git_info.py +0 -0
  153. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/__init__.py +0 -0
  154. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/conftest.py +0 -0
  155. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_cost_guard.py +0 -0
  156. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_forge.py +0 -0
  157. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_gpu_cluster.py +0 -0
  158. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_gpu_types.py +0 -0
  159. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_lambda.py +0 -0
  160. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_modal.py +0 -0
  161. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_ovhcloud.py +0 -0
  162. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_price_compare.py +0 -0
  163. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_provider_base.py +0 -0
  164. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_runner.py +0 -0
  165. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_runpod.py +0 -0
  166. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_sdk_gpu_api.py +0 -0
  167. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_vastai.py +0 -0
  168. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu/test_workspace_sync.py +0 -0
  169. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_gpu_modal_creds.py +0 -0
  170. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_init.py +0 -0
  171. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_init_facade.py +0 -0
  172. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_install_commands.py +0 -0
  173. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_fastai.py +0 -0
  174. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_huggingface.py +0 -0
  175. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_keras.py +0 -0
  176. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_langchain.py +0 -0
  177. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_lightning.py +0 -0
  178. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_pytorch.py +0 -0
  179. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_setA.py +0 -0
  180. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_setB.py +0 -0
  181. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_setC.py +0 -0
  182. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_sklearn.py +0 -0
  183. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_integration_xgboost.py +0 -0
  184. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_launch.py +0 -0
  185. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_log.py +0 -0
  186. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_log_code.py +0 -0
  187. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_machine_migrate.py +0 -0
  188. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_mcp_decisions.py +0 -0
  189. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_mcp_monitor.py +0 -0
  190. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_mcp_predictions.py +0 -0
  191. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_mcp_server.py +0 -0
  192. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_media.py +0 -0
  193. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_media_extra.py +0 -0
  194. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_migrate.py +0 -0
  195. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_offline.py +0 -0
  196. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_offline_sync.py +0 -0
  197. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_patent_scan.py +0 -0
  198. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_pii.py +0 -0
  199. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_plot.py +0 -0
  200. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_plot_media_extra2.py +0 -0
  201. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_prompt_module.py +0 -0
  202. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_pure_modules.py +0 -0
  203. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_query_api.py +0 -0
  204. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_redact.py +0 -0
  205. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_reload_module.py +0 -0
  206. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_research_module.py +0 -0
  207. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_resume.py +0 -0
  208. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_run_module.py +0 -0
  209. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_sdk_cluster_j1.py +0 -0
  210. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_sdk_cluster_j2.py +0 -0
  211. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_sdk_cluster_j3.py +0 -0
  212. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_sdk_cluster_j4.py +0 -0
  213. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_sdk_core_edges.py +0 -0
  214. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_sdk_features.py +0 -0
  215. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_sender.py +0 -0
  216. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_session_module.py +0 -0
  217. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_session_sync.py +0 -0
  218. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_summary.py +0 -0
  219. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_sweep.py +0 -0
  220. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_system_metrics.py +0 -0
  221. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_topup_hi.py +0 -0
  222. {openrunner_sdk-2.38.3 → openrunner_sdk-2.40.0}/tests/test_trace.py +0 -0
  223. {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.38.3
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.38.3"
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 != "forge" and not gpu:
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 is serverless — it has no pod to provision or SSH into. Dispatch to
4293
- # the native forge client (COMMAND is the App script); the fleet Run auto-
4294
- # terminates when the function returns. Credentials come from the org's stored
4295
- # Forge credential (or FORGE_TOKEN / -e FORGE_TOKEN=..).
4296
- if provider == "forge":
4297
- from openrunner.gpu.runner import run_forge_sync
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
- result = run_forge_sync(
4301
- command=command,
4302
- api_key=env_dict.get("FORGE_TOKEN"),
4303
- api_url=env_dict.get("FORGE_API_URL"),
4304
- project=project,
4305
- env=env_dict,
4306
- emit=lambda m: click.echo(m),
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 stored server-side, cost-tracked): store once with
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>` -- COMMAND is the App SCRIPT
3215
- (the GPU is declared inside it, so no `--gpu`); the Run auto-terminates.
3216
- - Native client: `forge run <script.py>` (needs the `forge-shadow` client on a
3217
- Python 3.13 interpreter + `FORGE_TOKEN`; install:
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openrunner-sdk"
3
- version = "2.38.3"
3
+ version = "2.40.0"
4
4
  description = "OpenRunner SDK - W&B-compatible ML experiment tracking client"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -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