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.
Files changed (227) hide show
  1. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/PKG-INFO +1 -1
  2. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/__init__.py +1 -1
  3. openrunner_sdk-2.41.1/openrunner/_retry_transport.py +94 -0
  4. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/datasets.py +2 -0
  5. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/api_integration.py +16 -27
  6. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/runner.py +18 -33
  7. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/install_commands.py +8 -6
  8. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/pyproject.toml +1 -1
  9. openrunner_sdk-2.41.1/tests/test_gpu/test_ovh_training.py +29 -0
  10. openrunner_sdk-2.41.1/tests/test_retry_transport.py +66 -0
  11. openrunner_sdk-2.40.1/tests/test_gpu/test_ovh_training.py +0 -47
  12. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/.gitignore +0 -0
  13. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/=6.0 +0 -0
  14. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/=8.1 +0 -0
  15. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/README.md +0 -0
  16. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/CLAUDE.md +0 -0
  17. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/_safepath.py +0 -0
  18. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/_secret_rules.py +0 -0
  19. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/api_client.py +0 -0
  20. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/artifact.py +0 -0
  21. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/autoresearch.py +0 -0
  22. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/buffer.py +0 -0
  23. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/cache.py +0 -0
  24. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/cli.py +0 -0
  25. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/config.py +0 -0
  26. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/cost.py +0 -0
  27. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/dataset.py +0 -0
  28. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/environment.py +0 -0
  29. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/evaluation.py +0 -0
  30. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/feedback.py +0 -0
  31. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/git_info.py +0 -0
  32. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/__init__.py +0 -0
  33. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/cost_guard.py +0 -0
  34. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/gpu_types.py +0 -0
  35. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/price_compare.py +0 -0
  36. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/provider_base.py +0 -0
  37. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/providers/__init__.py +0 -0
  38. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/providers/forge_provider.py +0 -0
  39. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/providers/lambda_cloud.py +0 -0
  40. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/providers/modal_provider.py +0 -0
  41. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/providers/ovhcloud.py +0 -0
  42. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/providers/runpod.py +0 -0
  43. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/providers/vastai.py +0 -0
  44. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/gpu/workspace_sync.py +0 -0
  45. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/guardrails.py +0 -0
  46. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/handover.py +0 -0
  47. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/hooks/patent-lab-post-commit +0 -0
  48. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/__init__.py +0 -0
  49. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/accelerate.py +0 -0
  50. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/anthropic_tracer.py +0 -0
  51. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/catboost.py +0 -0
  52. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/diffusers.py +0 -0
  53. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/fastai.py +0 -0
  54. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/forced_alignment.py +0 -0
  55. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/gladia.py +0 -0
  56. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/gymnasium.py +0 -0
  57. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/huggingface.py +0 -0
  58. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/hydra.py +0 -0
  59. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/ignite.py +0 -0
  60. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/jax.py +0 -0
  61. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/keras.py +0 -0
  62. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/langchain.py +0 -0
  63. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/lightgbm.py +0 -0
  64. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/lightning.py +0 -0
  65. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/llamaindex.py +0 -0
  66. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/openai_finetune.py +0 -0
  67. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/openai_tracer.py +0 -0
  68. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/optuna.py +0 -0
  69. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/pytorch.py +0 -0
  70. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/sb3.py +0 -0
  71. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/sklearn.py +0 -0
  72. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/skypilot.py +0 -0
  73. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/tensorflow.py +0 -0
  74. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/trl.py +0 -0
  75. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/tts.py +0 -0
  76. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/ultralytics.py +0 -0
  77. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/voice_agent.py +0 -0
  78. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/whisper.py +0 -0
  79. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/integration/xgboost.py +0 -0
  80. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/launch.py +0 -0
  81. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/machine.py +0 -0
  82. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/mcp_server.py +0 -0
  83. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/media.py +0 -0
  84. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/migrate.py +0 -0
  85. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/model.py +0 -0
  86. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/model_card.py +0 -0
  87. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/offline.py +0 -0
  88. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/patent_scan.py +0 -0
  89. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/pii.py +0 -0
  90. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/plot.py +0 -0
  91. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/projects.py +0 -0
  92. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/prompt.py +0 -0
  93. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/query_api.py +0 -0
  94. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/redact.py +0 -0
  95. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/reload.py +0 -0
  96. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/research.py +0 -0
  97. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/research_policy.py +0 -0
  98. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/run.py +0 -0
  99. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/scorers.py +0 -0
  100. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/sender.py +0 -0
  101. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/session.py +0 -0
  102. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/settings.py +0 -0
  103. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/summary.py +0 -0
  104. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/sweep.py +0 -0
  105. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/sync.py +0 -0
  106. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/system_metrics.py +0 -0
  107. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/tensorboard.py +0 -0
  108. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/tools/__init__.py +0 -0
  109. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/tools/patent_report.py +0 -0
  110. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/trace.py +0 -0
  111. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/transcript_formatter.py +0 -0
  112. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/wal.py +0 -0
  113. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/wandb_compat/__init__.py +0 -0
  114. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/wandb_compat/_shim.py +0 -0
  115. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/openrunner/wer.py +0 -0
  116. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/scripts/gen_betterleaks_rules.py +0 -0
  117. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/__init__.py +0 -0
  118. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/conftest.py +0 -0
  119. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-aaa/config.json +0 -0
  120. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-aaa/metadata.json +0 -0
  121. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-aaa/metrics.jsonl +0 -0
  122. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-aaa/summary.json +0 -0
  123. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/model-checkpoint/manifest.json +0 -0
  124. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/model-checkpoint/weights.bin +0 -0
  125. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/sample-image/manifest.json +0 -0
  126. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/sample-image/preview.png +0 -0
  127. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-bbb/config.json +0 -0
  128. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-bbb/metadata.json +0 -0
  129. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-bbb/metrics.jsonl +0 -0
  130. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/fixtures/wandb-export-mini/run-bbb/summary.json +0 -0
  131. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/integration/__init__.py +0 -0
  132. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/integration/conftest.py +0 -0
  133. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/integration/test_artifact_round_trip.py +0 -0
  134. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_alert.py +0 -0
  135. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_aliases.py +0 -0
  136. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_api_client.py +0 -0
  137. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_api_client_methods.py +0 -0
  138. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_api_client_transport.py +0 -0
  139. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_artifact.py +0 -0
  140. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_buffer.py +0 -0
  141. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_cache.py +0 -0
  142. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_class_scorers.py +0 -0
  143. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_cli.py +0 -0
  144. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_cli_artifact_sweep.py +0 -0
  145. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_cli_datasets.py +0 -0
  146. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_cli_gpu_registry_deploy.py +0 -0
  147. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_cli_infra.py +0 -0
  148. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_cli_remainder.py +0 -0
  149. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_cli_topup.py +0 -0
  150. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_config.py +0 -0
  151. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_datasets_module.py +0 -0
  152. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_evaluation.py +0 -0
  153. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_finish.py +0 -0
  154. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_git_info.py +0 -0
  155. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/__init__.py +0 -0
  156. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/conftest.py +0 -0
  157. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_cost_guard.py +0 -0
  158. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_engine_serverside.py +0 -0
  159. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_forge.py +0 -0
  160. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_governed_jobs.py +0 -0
  161. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_gpu_cluster.py +0 -0
  162. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_gpu_types.py +0 -0
  163. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_lambda.py +0 -0
  164. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_modal.py +0 -0
  165. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_ovhcloud.py +0 -0
  166. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_price_compare.py +0 -0
  167. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_provider_base.py +0 -0
  168. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_runner.py +0 -0
  169. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_runpod.py +0 -0
  170. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_sdk_gpu_api.py +0 -0
  171. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_vastai.py +0 -0
  172. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu/test_workspace_sync.py +0 -0
  173. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_gpu_modal_creds.py +0 -0
  174. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_init.py +0 -0
  175. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_init_facade.py +0 -0
  176. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_install_commands.py +0 -0
  177. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_fastai.py +0 -0
  178. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_huggingface.py +0 -0
  179. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_keras.py +0 -0
  180. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_langchain.py +0 -0
  181. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_lightning.py +0 -0
  182. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_pytorch.py +0 -0
  183. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_setA.py +0 -0
  184. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_setB.py +0 -0
  185. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_setC.py +0 -0
  186. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_sklearn.py +0 -0
  187. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_integration_xgboost.py +0 -0
  188. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_launch.py +0 -0
  189. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_log.py +0 -0
  190. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_log_code.py +0 -0
  191. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_machine_migrate.py +0 -0
  192. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_mcp_decisions.py +0 -0
  193. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_mcp_monitor.py +0 -0
  194. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_mcp_predictions.py +0 -0
  195. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_mcp_server.py +0 -0
  196. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_media.py +0 -0
  197. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_media_extra.py +0 -0
  198. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_migrate.py +0 -0
  199. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_offline.py +0 -0
  200. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_offline_sync.py +0 -0
  201. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_patent_scan.py +0 -0
  202. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_pii.py +0 -0
  203. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_plot.py +0 -0
  204. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_plot_media_extra2.py +0 -0
  205. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_prompt_module.py +0 -0
  206. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_pure_modules.py +0 -0
  207. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_query_api.py +0 -0
  208. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_redact.py +0 -0
  209. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_reload_module.py +0 -0
  210. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_research_module.py +0 -0
  211. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_resume.py +0 -0
  212. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_run_module.py +0 -0
  213. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_sdk_cluster_j1.py +0 -0
  214. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_sdk_cluster_j2.py +0 -0
  215. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_sdk_cluster_j3.py +0 -0
  216. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_sdk_cluster_j4.py +0 -0
  217. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_sdk_core_edges.py +0 -0
  218. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_sdk_features.py +0 -0
  219. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_sender.py +0 -0
  220. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_session_module.py +0 -0
  221. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_session_sync.py +0 -0
  222. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_summary.py +0 -0
  223. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_sweep.py +0 -0
  224. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_system_metrics.py +0 -0
  225. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_topup_hi.py +0 -0
  226. {openrunner_sdk-2.40.1 → openrunner_sdk-2.41.1}/tests/test_trace.py +0 -0
  227. {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.40.1
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.40.1"
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 ovh_training_run(
412
- self, *, image: str, command: str, gpu: int = 1,
413
- name: str | None = None, project_id: str | None = None,
414
- ) -> dict[str, Any] | None:
415
- """POST /api/v1/gpu/ai-training/run -- submit an OVH AI Training job
416
- server-side (the OVH credential never leaves the API). Returns
417
- {job_id,status} or None."""
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
- try:
425
- r = self._client.post("/gpu/ai-training/run", params={"org_id": org}, json=body)
426
- r.raise_for_status()
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
- return None
441
- return r.json()
442
- except Exception as e:
443
- logger.warning("ovh_training_status failed: %s", e)
444
- return None
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. Submits a job (the OVH keys
540
- stay in the API vault, never on this machine), then polls status + logs until
541
- it finishes. COMMAND runs in IMAGE on `gpu` GPUs. Data comes from OpenRunner
542
- (datasets/artifacts) — no OVH object-storage volumes. Returns
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] submitting OVH AI Training job (image={image}, gpu={gpu}) "
552
- "— OVH keys stay server-side ...")
553
- res = client.ovh_training_run(image=image, command=command, gpu=gpu,
554
- name=name, project_id=project)
555
- if not res or not res.get("job_id"):
556
- raise RuntimeError(
557
- "failed to submit OVH AI Training job — need an org 'ovhcloud' "
558
- "credential (openrunner gpu credentials add --provider ovhcloud ...)."
559
- )
560
- jid = res["job_id"]
561
- say(f"[openrunner] OVH job {jid} ({res.get('status')}) — streaming ...")
562
-
563
- seen = 0
564
- while True:
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, submitted
3603
- SERVER-SIDE with the org's OVH credential (the `ovhcloud` app/secret/consumer
3604
- keys, never on the client). Run:
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; poll streams logs; it auto-stops when
3607
- the command exits. **Do NOT use OVH object-storage `--volume` mounts** -- data
3608
- comes from OpenRunner (datasets/artifacts). Needs an `ovhcloud` credential.
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openrunner-sdk"
3
- version = "2.40.1"
3
+ version = "2.41.1"
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,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