openrunner-sdk 2.41.7__tar.gz → 2.41.8__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 (233) hide show
  1. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/.gitignore +2 -0
  2. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/PKG-INFO +6 -6
  3. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/README.md +1 -1
  4. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/__init__.py +17 -3
  5. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/artifact.py +49 -0
  6. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/cli.py +121 -31
  7. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/datasets.py +139 -2
  8. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/gpu/api_integration.py +29 -3
  9. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/gpu/runner.py +14 -2
  10. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/install_commands.py +318 -2
  11. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/migrate.py +29 -7
  12. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/run.py +22 -3
  13. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/pyproject.toml +4 -4
  14. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/integration/test_artifact_round_trip.py +1 -1
  15. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_artifact.py +59 -0
  16. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_datasets_module.py +82 -0
  17. openrunner_sdk-2.41.8/tests/test_gpu/test_engine_serverside.py +62 -0
  18. openrunner_sdk-2.41.8/tests/test_gpu/test_engine_stream_keepalive.py +61 -0
  19. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_init_facade.py +8 -2
  20. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_machine_migrate.py +47 -3
  21. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_run_module.py +40 -0
  22. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_sdk_cluster_j3.py +5 -1
  23. openrunner_sdk-2.41.7/tests/test_gpu/test_engine_serverside.py +0 -30
  24. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/=6.0 +0 -0
  25. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/=8.1 +0 -0
  26. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/CLAUDE.md +0 -0
  27. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/_native.py +0 -0
  28. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/_retry_transport.py +0 -0
  29. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/_safepath.py +0 -0
  30. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/_secret_rules.py +0 -0
  31. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/api_client.py +0 -0
  32. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/autoresearch.py +0 -0
  33. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/buffer.py +0 -0
  34. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/cache.py +0 -0
  35. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/config.py +0 -0
  36. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/cost.py +0 -0
  37. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/dataset.py +0 -0
  38. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/environment.py +0 -0
  39. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/evaluation.py +0 -0
  40. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/feedback.py +0 -0
  41. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/git_info.py +0 -0
  42. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/gpu/__init__.py +0 -0
  43. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/gpu/cost_guard.py +0 -0
  44. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/gpu/gpu_types.py +0 -0
  45. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/gpu/price_compare.py +0 -0
  46. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/gpu/provider_base.py +0 -0
  47. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/gpu/providers/__init__.py +0 -0
  48. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/gpu/providers/forge_provider.py +0 -0
  49. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/gpu/providers/lambda_cloud.py +0 -0
  50. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/gpu/providers/modal_provider.py +0 -0
  51. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/gpu/providers/ovhcloud.py +0 -0
  52. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/gpu/providers/runpod.py +0 -0
  53. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/gpu/providers/vastai.py +0 -0
  54. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/gpu/workspace_sync.py +0 -0
  55. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/guardrails.py +0 -0
  56. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/handover.py +0 -0
  57. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/hooks/patent-lab-post-commit +0 -0
  58. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/__init__.py +0 -0
  59. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/accelerate.py +0 -0
  60. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/anthropic_tracer.py +0 -0
  61. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/catboost.py +0 -0
  62. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/diffusers.py +0 -0
  63. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/fastai.py +0 -0
  64. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/forced_alignment.py +0 -0
  65. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/gladia.py +0 -0
  66. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/gymnasium.py +0 -0
  67. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/huggingface.py +0 -0
  68. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/hydra.py +0 -0
  69. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/ignite.py +0 -0
  70. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/jax.py +0 -0
  71. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/keras.py +0 -0
  72. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/langchain.py +0 -0
  73. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/lightgbm.py +0 -0
  74. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/lightning.py +0 -0
  75. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/llamaindex.py +0 -0
  76. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/openai_finetune.py +0 -0
  77. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/openai_tracer.py +0 -0
  78. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/optuna.py +0 -0
  79. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/pytorch.py +0 -0
  80. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/sb3.py +0 -0
  81. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/sklearn.py +0 -0
  82. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/skypilot.py +0 -0
  83. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/tensorflow.py +0 -0
  84. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/trl.py +0 -0
  85. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/tts.py +0 -0
  86. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/ultralytics.py +0 -0
  87. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/voice_agent.py +0 -0
  88. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/whisper.py +0 -0
  89. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/integration/xgboost.py +0 -0
  90. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/launch.py +0 -0
  91. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/llms-full.txt +0 -0
  92. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/llms.py +0 -0
  93. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/llms.txt +0 -0
  94. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/machine.py +0 -0
  95. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/mcp_server.py +0 -0
  96. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/media.py +0 -0
  97. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/model.py +0 -0
  98. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/model_card.py +0 -0
  99. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/offline.py +0 -0
  100. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/patent_scan.py +0 -0
  101. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/pii.py +0 -0
  102. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/plot.py +0 -0
  103. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/projects.py +0 -0
  104. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/prompt.py +0 -0
  105. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/query_api.py +0 -0
  106. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/redact.py +0 -0
  107. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/reload.py +0 -0
  108. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/research.py +0 -0
  109. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/research_policy.py +0 -0
  110. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/scorers.py +0 -0
  111. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/sender.py +0 -0
  112. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/session.py +0 -0
  113. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/settings.py +0 -0
  114. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/summary.py +0 -0
  115. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/sweep.py +0 -0
  116. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/sync.py +0 -0
  117. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/system_metrics.py +0 -0
  118. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/tensorboard.py +0 -0
  119. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/tools/__init__.py +0 -0
  120. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/tools/patent_report.py +0 -0
  121. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/trace.py +0 -0
  122. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/transcript_formatter.py +0 -0
  123. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/wal.py +0 -0
  124. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/wandb_compat/__init__.py +0 -0
  125. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/wandb_compat/_shim.py +0 -0
  126. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/openrunner/wer.py +0 -0
  127. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/scripts/gen_betterleaks_rules.py +0 -0
  128. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/__init__.py +0 -0
  129. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/conftest.py +0 -0
  130. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-aaa/config.json +0 -0
  131. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-aaa/metadata.json +0 -0
  132. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-aaa/metrics.jsonl +0 -0
  133. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-aaa/summary.json +0 -0
  134. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/model-checkpoint/manifest.json +0 -0
  135. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/model-checkpoint/weights.bin +0 -0
  136. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/sample-image/manifest.json +0 -0
  137. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/sample-image/preview.png +0 -0
  138. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-bbb/config.json +0 -0
  139. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-bbb/metadata.json +0 -0
  140. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-bbb/metrics.jsonl +0 -0
  141. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-bbb/summary.json +0 -0
  142. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/integration/__init__.py +0 -0
  143. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/integration/conftest.py +0 -0
  144. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_alert.py +0 -0
  145. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_aliases.py +0 -0
  146. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_api_client.py +0 -0
  147. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_api_client_methods.py +0 -0
  148. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_api_client_transport.py +0 -0
  149. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_buffer.py +0 -0
  150. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_cache.py +0 -0
  151. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_class_scorers.py +0 -0
  152. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_cli.py +0 -0
  153. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_cli_artifact_sweep.py +0 -0
  154. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_cli_datasets.py +0 -0
  155. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_cli_gpu_registry_deploy.py +0 -0
  156. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_cli_infra.py +0 -0
  157. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_cli_remainder.py +0 -0
  158. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_cli_topup.py +0 -0
  159. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_config.py +0 -0
  160. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_evaluation.py +0 -0
  161. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_finish.py +0 -0
  162. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_git_info.py +0 -0
  163. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu/__init__.py +0 -0
  164. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu/conftest.py +0 -0
  165. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu/test_cost_guard.py +0 -0
  166. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu/test_forge.py +0 -0
  167. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu/test_governed_jobs.py +0 -0
  168. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu/test_gpu_cluster.py +0 -0
  169. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu/test_gpu_types.py +0 -0
  170. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu/test_lambda.py +0 -0
  171. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu/test_modal.py +0 -0
  172. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu/test_ovh_training.py +0 -0
  173. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu/test_ovhcloud.py +0 -0
  174. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu/test_price_compare.py +0 -0
  175. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu/test_provider_base.py +0 -0
  176. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu/test_runner.py +0 -0
  177. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu/test_runpod.py +0 -0
  178. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu/test_sdk_gpu_api.py +0 -0
  179. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu/test_vastai.py +0 -0
  180. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu/test_workspace_sync.py +0 -0
  181. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_gpu_modal_creds.py +0 -0
  182. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_init.py +0 -0
  183. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_install_commands.py +0 -0
  184. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_integration_fastai.py +0 -0
  185. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_integration_huggingface.py +0 -0
  186. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_integration_keras.py +0 -0
  187. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_integration_langchain.py +0 -0
  188. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_integration_lightning.py +0 -0
  189. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_integration_pytorch.py +0 -0
  190. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_integration_setA.py +0 -0
  191. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_integration_setB.py +0 -0
  192. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_integration_setC.py +0 -0
  193. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_integration_sklearn.py +0 -0
  194. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_integration_xgboost.py +0 -0
  195. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_launch.py +0 -0
  196. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_llms.py +0 -0
  197. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_log.py +0 -0
  198. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_log_code.py +0 -0
  199. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_mcp_decisions.py +0 -0
  200. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_mcp_monitor.py +0 -0
  201. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_mcp_predictions.py +0 -0
  202. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_mcp_server.py +0 -0
  203. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_media.py +0 -0
  204. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_media_extra.py +0 -0
  205. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_migrate.py +0 -0
  206. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_offline.py +0 -0
  207. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_offline_sync.py +0 -0
  208. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_patent_scan.py +0 -0
  209. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_pii.py +0 -0
  210. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_plot.py +0 -0
  211. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_plot_media_extra2.py +0 -0
  212. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_prompt_module.py +0 -0
  213. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_pure_modules.py +0 -0
  214. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_query_api.py +0 -0
  215. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_redact.py +0 -0
  216. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_reload_module.py +0 -0
  217. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_research_module.py +0 -0
  218. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_resume.py +0 -0
  219. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_retry_transport.py +0 -0
  220. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_sdk_cluster_j1.py +0 -0
  221. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_sdk_cluster_j2.py +0 -0
  222. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_sdk_cluster_j4.py +0 -0
  223. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_sdk_core_edges.py +0 -0
  224. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_sdk_features.py +0 -0
  225. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_sender.py +0 -0
  226. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_session_module.py +0 -0
  227. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_session_sync.py +0 -0
  228. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_summary.py +0 -0
  229. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_sweep.py +0 -0
  230. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_system_metrics.py +0 -0
  231. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_topup_hi.py +0 -0
  232. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_trace.py +0 -0
  233. {openrunner_sdk-2.41.7 → openrunner_sdk-2.41.8}/tests/test_wandb_compat.py +0 -0
@@ -153,6 +153,8 @@ activemq-data/
153
153
  # Environments
154
154
  .env
155
155
  .envrc
156
+ .forge.env
157
+ .forge-venv/
156
158
 
157
159
  # Docker secrets (real values — only examples tracked)
158
160
  secrets/
@@ -1,10 +1,10 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: openrunner-sdk
3
- Version: 2.41.7
3
+ Version: 2.41.8
4
4
  Summary: OpenRunner SDK - W&B-compatible ML experiment tracking client
5
- Project-URL: Homepage, https://github.com/jqueguiner/openrunner
6
- Project-URL: Repository, https://github.com/jqueguiner/openrunner
7
- Project-URL: Issues, https://github.com/jqueguiner/openrunner/issues
5
+ Project-URL: Homepage, https://github.com/gladiaio/openrunner
6
+ Project-URL: Repository, https://github.com/gladiaio/openrunner
7
+ Project-URL: Issues, https://github.com/gladiaio/openrunner/issues
8
8
  Author-email: JL Queguiner <jl@gladia.io>
9
9
  License: MIT
10
10
  Keywords: experiment-tracking,invention-disclosure,machine-learning,ml,mlops,patent,wandb
@@ -656,7 +656,7 @@ W&B env vars (`WANDB_API_KEY`, `WANDB_BASE_URL`) are also supported as fallback
656
656
 
657
657
  ## Self-Hosting
658
658
 
659
- OpenRunner is designed to be self-hosted. See the [main repo](https://github.com/jqueguiner/openrunner) for server setup with Docker Compose.
659
+ OpenRunner is designed to be self-hosted. See the [main repo](https://github.com/gladiaio/openrunner) for server setup with Docker Compose.
660
660
 
661
661
  ## License
662
662
 
@@ -541,7 +541,7 @@ W&B env vars (`WANDB_API_KEY`, `WANDB_BASE_URL`) are also supported as fallback
541
541
 
542
542
  ## Self-Hosting
543
543
 
544
- OpenRunner is designed to be self-hosted. See the [main repo](https://github.com/jqueguiner/openrunner) for server setup with Docker Compose.
544
+ OpenRunner is designed to be self-hosted. See the [main repo](https://github.com/gladiaio/openrunner) for server setup with Docker Compose.
545
545
 
546
546
  ## License
547
547
 
@@ -93,6 +93,7 @@ import openrunner.datasets as datasets
93
93
  from openrunner.datasets import (
94
94
  DatasetInfo,
95
95
  DatasetsClient,
96
+ DeriveIncomplete,
96
97
  create_dataset,
97
98
  dataset_info,
98
99
  index_dataset,
@@ -101,6 +102,7 @@ from openrunner.datasets import (
101
102
  link_project,
102
103
  load_dataset,
103
104
  add_derivation,
105
+ derive_by_ids,
104
106
  derive_by_query,
105
107
  derived_rows,
106
108
  keyword_expand,
@@ -852,11 +854,21 @@ def alert(
852
854
  return None
853
855
 
854
856
 
855
- def log_artifact(artifact: Artifact) -> dict[str, Any] | None:
857
+ def log_artifact(
858
+ artifact: "Artifact | str | Path",
859
+ *,
860
+ name: str | None = None,
861
+ type: str | None = None,
862
+ aliases: list[str] | None = None,
863
+ ) -> dict[str, Any] | None:
856
864
  """Upload an artifact to the active run. Never raises.
857
865
 
858
866
  Args:
859
- artifact: The Artifact object with files added via add_file/add_dir.
867
+ artifact: An Artifact with files added via add_file/add_dir, OR a path
868
+ to a file or directory (W&B-style), built into an Artifact for you.
869
+ name: Artifact name when ``artifact`` is a path.
870
+ type: Advisory type when ``artifact`` is a path.
871
+ aliases: Aliases to set on the created version.
860
872
 
861
873
  Returns:
862
874
  Server response dict with version info, or None on failure.
@@ -865,7 +877,9 @@ def log_artifact(artifact: Artifact) -> dict[str, Any] | None:
865
877
  if _active_run is None:
866
878
  logger.warning("openrunner.log_artifact(): no active run")
867
879
  return None
868
- return _active_run.log_artifact(artifact)
880
+ return _active_run.log_artifact(
881
+ artifact, name=name, type=type, aliases=aliases
882
+ )
869
883
  except Exception as e:
870
884
  logger.warning("openrunner.log_artifact() failed: %s", e)
871
885
  return None
@@ -29,6 +29,55 @@ def _sha256_file(path: Path, chunk_size: int = 8192) -> str:
29
29
  return h.hexdigest()
30
30
 
31
31
 
32
+ def coerce_artifact(
33
+ source: "Artifact | str | Path",
34
+ *,
35
+ name: str | None = None,
36
+ type: str | None = None,
37
+ description: str | None = None,
38
+ metadata: dict[str, Any] | None = None,
39
+ ) -> "Artifact":
40
+ """Return an Artifact, building one from a path if that's what was given.
41
+
42
+ ``log_artifact`` historically took a fully built ``Artifact``. Users coming
43
+ from W&B -- which this repo actively courts, see
44
+ ``examples/migrate-from-wandb/`` -- write the path form instead::
45
+
46
+ run.log_artifact("out/final", name="my-model", type="model")
47
+
48
+ and used to get ``TypeError: unexpected keyword argument 'name'``. Accept
49
+ both. A directory becomes ``add_dir``, a file ``add_file``; ``name``
50
+ defaults to the path's own name so the common case needs no kwargs.
51
+ """
52
+ if isinstance(source, Artifact):
53
+ # Already built: kwargs only fill gaps, they never silently override
54
+ # what the caller put on the object.
55
+ if name and not source.name:
56
+ source.name = name
57
+ if type and source.type is None:
58
+ source.type = type
59
+ return source
60
+
61
+ path = Path(source)
62
+ if not path.exists():
63
+ raise FileNotFoundError(f"artifact path does not exist: {path}")
64
+ art = Artifact(
65
+ name or path.name or path.stem,
66
+ type,
67
+ description,
68
+ metadata,
69
+ )
70
+ if path.is_dir():
71
+ # Existing add_dir semantics: files are prefixed with the directory
72
+ # name. Kept as-is so a coerced artifact lays out identically to a
73
+ # hand-built one -- changing it would silently move every existing
74
+ # caller's files.
75
+ art.add_dir(str(path))
76
+ else:
77
+ art.add_file(str(path))
78
+ return art
79
+
80
+
32
81
  class Artifact:
33
82
  """An untyped, versioned bucket of files for experiment tracking.
34
83
 
@@ -145,6 +145,45 @@ def _save_settings(settings: dict[str, Any]) -> None:
145
145
  )
146
146
 
147
147
 
148
+ def _resolve_api_key(settings: dict[str, Any] | None = None) -> str:
149
+ """API key with the same precedence the SDK uses: env, then the
150
+ session-synced ~/.openrunner/session_config.json, then settings.json.
151
+
152
+ Historically the CLI read settings.json FIRST and most subcommands never
153
+ looked at session_config at all — so a stale key in settings.json made
154
+ `openrunner catalog ...` fail with "API key has expired" while
155
+ `openrunner datasets ...` (SDK-backed) worked with the fresh session key.
156
+ """
157
+ try:
158
+ from openrunner.settings import _global_creds
159
+ session = _global_creds()
160
+ except Exception:
161
+ session = {}
162
+ return (
163
+ os.environ.get("OPENRUNNER_API_KEY")
164
+ or os.environ.get("OPENRUN_API_KEY")
165
+ or session.get("api_key")
166
+ or (settings or _load_settings()).get("api_key")
167
+ or ""
168
+ )
169
+
170
+
171
+ def _resolve_base_url(settings: dict[str, Any] | None = None) -> str:
172
+ """Base URL with the same precedence chain as ``_resolve_api_key``."""
173
+ try:
174
+ from openrunner.settings import _global_creds
175
+ session = _global_creds()
176
+ except Exception:
177
+ session = {}
178
+ return (
179
+ os.environ.get("OPENRUNNER_BASE_URL")
180
+ or os.environ.get("OPENRUN_BASE_URL")
181
+ or session.get("base_url")
182
+ or (settings or _load_settings()).get("base_url")
183
+ or ""
184
+ )
185
+
186
+
148
187
  # ---------------------------------------------------------------------------
149
188
  # CLI group
150
189
  # ---------------------------------------------------------------------------
@@ -420,7 +459,7 @@ def sync(
420
459
  --sync-tensorboard Import TensorBoard event files as metrics
421
460
  """
422
461
  settings = _load_settings()
423
- api_key = settings.get("api_key")
462
+ api_key = _resolve_api_key(settings)
424
463
  base_url = settings.get("base_url", "http://localhost:8000")
425
464
 
426
465
  if not api_key:
@@ -545,7 +584,7 @@ def ls(project: str | None) -> None:
545
584
  in the specified project.
546
585
  """
547
586
  settings = _load_settings()
548
- api_key = settings.get("api_key")
587
+ api_key = _resolve_api_key(settings)
549
588
  base_url = settings.get("base_url", "http://localhost:8000")
550
589
 
551
590
  if not api_key:
@@ -607,7 +646,7 @@ def _get_client() -> "APIClient":
607
646
  import os
608
647
 
609
648
  settings = _load_settings()
610
- api_key = os.environ.get("OPENRUNNER_API_KEY") or settings.get("api_key")
649
+ api_key = _resolve_api_key(settings)
611
650
  base_url = os.environ.get("OPENRUNNER_BASE_URL") or settings.get(
612
651
  "base_url", "http://localhost:8000"
613
652
  )
@@ -1534,7 +1573,7 @@ def launch(
1534
1573
  from openrunner.launch import launch as launch_fn
1535
1574
 
1536
1575
  settings = _load_settings()
1537
- api_key = settings.get("api_key")
1576
+ api_key = _resolve_api_key(settings)
1538
1577
  if not api_key:
1539
1578
  click.echo("Error: Not logged in. Run 'openrunner login' first.", err=True)
1540
1579
  raise SystemExit(1)
@@ -1600,7 +1639,7 @@ def launch(
1600
1639
  def jobs(project: str, state: str | None) -> None:
1601
1640
  """List jobs for a project."""
1602
1641
  settings = _load_settings()
1603
- api_key = settings.get("api_key")
1642
+ api_key = _resolve_api_key(settings)
1604
1643
  base_url = settings.get("base_url", "http://localhost:8000")
1605
1644
 
1606
1645
  if not api_key:
@@ -1656,7 +1695,7 @@ def job_logs(job_id: str) -> None:
1656
1695
  JOB_ID is the UUID of the job.
1657
1696
  """
1658
1697
  settings = _load_settings()
1659
- api_key = settings.get("api_key")
1698
+ api_key = _resolve_api_key(settings)
1660
1699
  base_url = settings.get("base_url", "http://localhost:8000")
1661
1700
 
1662
1701
  if not api_key:
@@ -2244,7 +2283,7 @@ def docker(
2244
2283
  import subprocess
2245
2284
 
2246
2285
  settings = _load_settings()
2247
- api_key = os.environ.get("OPENRUNNER_API_KEY") or settings.get("api_key")
2286
+ api_key = _resolve_api_key(settings)
2248
2287
  base_url = os.environ.get("OPENRUNNER_BASE_URL") or settings.get(
2249
2288
  "base_url", "http://localhost:8000"
2250
2289
  )
@@ -3579,8 +3618,8 @@ def catalog_tables(org: str):
3579
3618
  """List catalog tables and their row counts."""
3580
3619
  settings = _load_settings()
3581
3620
  client = APIClient(
3582
- base_url=settings.get("base_url", os.environ.get("OPENRUNNER_BASE_URL", "")),
3583
- api_key=settings.get("api_key", os.environ.get("OPENRUNNER_API_KEY", "")),
3621
+ base_url=_resolve_base_url(settings),
3622
+ api_key=_resolve_api_key(settings),
3584
3623
  )
3585
3624
  resp = client._request("get", f"/organizations/{org}/catalog/status")
3586
3625
  if resp.status_code != 200:
@@ -3618,8 +3657,8 @@ def catalog_init(org: str):
3618
3657
  """Initialize catalog tables on the configured storage connection."""
3619
3658
  settings = _load_settings()
3620
3659
  client = APIClient(
3621
- base_url=settings.get("base_url", os.environ.get("OPENRUNNER_BASE_URL", "")),
3622
- api_key=settings.get("api_key", os.environ.get("OPENRUNNER_API_KEY", "")),
3660
+ base_url=_resolve_base_url(settings),
3661
+ api_key=_resolve_api_key(settings),
3623
3662
  )
3624
3663
  resp = client._request("post", f"/organizations/{org}/catalog/init")
3625
3664
  if resp.status_code != 200:
@@ -3640,8 +3679,8 @@ def catalog_query(org: str, table: str, where: str, limit: int, offset: int, fmt
3640
3679
  """Query rows from a catalog table."""
3641
3680
  settings = _load_settings()
3642
3681
  client = APIClient(
3643
- base_url=settings.get("base_url", os.environ.get("OPENRUNNER_BASE_URL", "")),
3644
- api_key=settings.get("api_key", os.environ.get("OPENRUNNER_API_KEY", "")),
3682
+ base_url=_resolve_base_url(settings),
3683
+ api_key=_resolve_api_key(settings),
3645
3684
  )
3646
3685
  params = {"limit": limit, "offset": offset}
3647
3686
  if where:
@@ -3687,8 +3726,8 @@ def catalog_export(org: str, table: str, fmt: str, where: str, limit: int, outpu
3687
3726
  """Export catalog table data to file or stdout."""
3688
3727
  settings = _load_settings()
3689
3728
  client = APIClient(
3690
- base_url=settings.get("base_url", os.environ.get("OPENRUNNER_BASE_URL", "")),
3691
- api_key=settings.get("api_key", os.environ.get("OPENRUNNER_API_KEY", "")),
3729
+ base_url=_resolve_base_url(settings),
3730
+ api_key=_resolve_api_key(settings),
3692
3731
  )
3693
3732
  params: dict[str, Any] = {"format": fmt}
3694
3733
  if where:
@@ -3730,8 +3769,8 @@ def catalog_upload(org: str, table: str, path: str, validate: bool):
3730
3769
 
3731
3770
  settings = _load_settings()
3732
3771
  client = APIClient(
3733
- base_url=settings.get("base_url", os.environ.get("OPENRUNNER_BASE_URL", "")),
3734
- api_key=settings.get("api_key", os.environ.get("OPENRUNNER_API_KEY", "")),
3772
+ base_url=_resolve_base_url(settings),
3773
+ api_key=_resolve_api_key(settings),
3735
3774
  )
3736
3775
 
3737
3776
  # Collect files
@@ -3792,6 +3831,56 @@ def catalog_upload(org: str, table: str, path: str, validate: bool):
3792
3831
  click.echo(f"\nDone. {total_rows} total rows uploaded to '{table}'.")
3793
3832
 
3794
3833
 
3834
+ @catalog.command("merge")
3835
+ @click.option("--org", required=True, help="Organization ID or slug")
3836
+ @click.option("--table", required=True, help="Table name (e.g. transcriptions)")
3837
+ @click.option("--key", default="request_id", show_default=True,
3838
+ help="Match column for the upsert")
3839
+ @click.option("--insert", is_flag=True, default=False,
3840
+ help="Also insert rows whose key has no match (default: skip them)")
3841
+ @click.option("--batch", default=500, show_default=True, help="Rows per request")
3842
+ @click.argument("path", type=click.Path(exists=True))
3843
+ def catalog_merge(org: str, table: str, key: str, insert: bool, batch: int, path: str):
3844
+ """Upsert JSONL rows into a catalog table by key (enrichment merge).
3845
+
3846
+ Unlike `catalog upload` (append-only), matched rows get ONLY the uploaded
3847
+ columns updated; new columns are created automatically (nullable, typed
3848
+ from the first non-null value). Nested values must be pre-serialized to
3849
+ JSON strings.
3850
+
3851
+ Example:
3852
+ openrunner catalog merge --org my-org --table transcriptions \\
3853
+ --key request_id enriched.jsonl
3854
+ """
3855
+ settings = _load_settings()
3856
+ client = APIClient(
3857
+ base_url=_resolve_base_url(settings),
3858
+ api_key=_resolve_api_key(settings),
3859
+ )
3860
+ lines = [ln for ln in Path(path).read_text(encoding="utf-8").splitlines() if ln.strip()]
3861
+ total = 0
3862
+ new_cols: set = set()
3863
+ for start in range(0, len(lines), max(1, batch)):
3864
+ chunk = lines[start:start + max(1, batch)]
3865
+ resp = client._request(
3866
+ "POST",
3867
+ f"/organizations/{org}/catalog/tables/{table}/merge?key={key}"
3868
+ + ("&insert=true" if insert else ""),
3869
+ content=("\n".join(chunk)).encode("utf-8"),
3870
+ headers={"Content-Type": "application/x-ndjson"},
3871
+ )
3872
+ if resp.status_code not in (200, 201):
3873
+ click.echo(f"Merge failed at row {start + 1}: {resp.text}", err=True)
3874
+ sys.exit(1)
3875
+ result = resp.json()
3876
+ total += result.get("rows_merged", 0)
3877
+ new_cols.update(result.get("new_columns", []))
3878
+ click.echo(f" merged {total}/{len(lines)}")
3879
+ if new_cols:
3880
+ click.echo(f"New columns created: {', '.join(sorted(new_cols))}")
3881
+ click.echo(f"Done. {total} rows merged into '{table}'.")
3882
+
3883
+
3795
3884
  # ---------------------------------------------------------------------------
3796
3885
  # patent-scan command
3797
3886
  # ---------------------------------------------------------------------------
@@ -3837,7 +3926,7 @@ def patent_scan(
3837
3926
  from openrunner.patent_scan import scan_commit, _run_git
3838
3927
 
3839
3928
  settings = _load_settings()
3840
- api_key = settings.get("api_key")
3929
+ api_key = _resolve_api_key(settings)
3841
3930
  base_url = settings.get("base_url", "https://api.openrun.gladia.io")
3842
3931
  project = project or settings.get("project")
3843
3932
 
@@ -3954,7 +4043,7 @@ def patent_lab(project: str | None, min_score: int, status: str | None, limit: i
3954
4043
  openrunner patent-lab --min-score 70 --status detected
3955
4044
  """
3956
4045
  settings = _load_settings()
3957
- api_key = settings.get("api_key")
4046
+ api_key = _resolve_api_key(settings)
3958
4047
  base_url = settings.get("base_url", "https://api.openrun.gladia.io")
3959
4048
  project = project or settings.get("project")
3960
4049
 
@@ -4372,7 +4461,7 @@ def gpu_credentials_add(org_id: str, provider: str, provider_key: str | None,
4372
4461
  from openrunner.gpu.api_integration import GpuAPIClient
4373
4462
 
4374
4463
  settings = _load_settings()
4375
- api_key = settings.get("api_key")
4464
+ api_key = _resolve_api_key(settings)
4376
4465
  base_url = settings.get("base_url", "http://localhost:8000")
4377
4466
  if not api_key:
4378
4467
  click.echo("Error: Not logged in. Run 'openrunner login' first.", err=True)
@@ -4393,7 +4482,7 @@ def gpu_credentials_ls() -> None:
4393
4482
  from openrunner.gpu.api_integration import GpuAPIClient
4394
4483
 
4395
4484
  settings = _load_settings()
4396
- api_key = settings.get("api_key")
4485
+ api_key = _resolve_api_key(settings)
4397
4486
  base_url = settings.get("base_url", "http://localhost:8000")
4398
4487
  if not api_key:
4399
4488
  click.echo("Error: Not logged in.", err=True)
@@ -4430,9 +4519,10 @@ def gpu_run(command, provider, gpu, gpu_count, image, disk_gb, env, ssh_keys, pr
4430
4519
 
4431
4520
  Forge is serverless (no pod / no SSH): COMMAND is the App script and it runs
4432
4521
  through the forge client, streaming the fleet's logs. The GPU is declared in
4433
- the script, so --gpu is not used.
4522
+ the script, so --gpu is not used. -e KEY=VALUE reaches the App's
4523
+ local_entrypoint as an environment variable.
4434
4524
 
4435
- openrunner gpu run --provider forge get_started.py
4525
+ openrunner gpu run --provider forge -e EPOCHS=3 get_started.py
4436
4526
 
4437
4527
  Example:
4438
4528
 
@@ -4447,7 +4537,7 @@ def gpu_run(command, provider, gpu, gpu_count, image, disk_gb, env, ssh_keys, pr
4447
4537
  raise SystemExit(1)
4448
4538
 
4449
4539
  settings = _load_settings()
4450
- api_key = settings.get("api_key")
4540
+ api_key = _resolve_api_key(settings)
4451
4541
  base_url = settings.get("base_url")
4452
4542
  if not api_key:
4453
4543
  click.echo("Error: Not logged in. Run 'openrunner login' first.", err=True)
@@ -4527,7 +4617,7 @@ def gpu_run(command, provider, gpu, gpu_count, image, disk_gb, env, ssh_keys, pr
4527
4617
  from openrunner.gpu.runner import run_engine_serverside_sync
4528
4618
  result = run_engine_serverside_sync(
4529
4619
  provider=provider, command=command, project=project,
4530
- emit=lambda m: click.echo(m),
4620
+ env=env_dict, emit=lambda m: click.echo(m),
4531
4621
  )
4532
4622
  except Exception as exc: # noqa: BLE001
4533
4623
  click.echo(f"gpu run failed: {exc}", err=True)
@@ -4617,7 +4707,7 @@ def _registry_http():
4617
4707
  import httpx
4618
4708
 
4619
4709
  settings = _load_settings()
4620
- api_key = settings.get("api_key")
4710
+ api_key = _resolve_api_key(settings)
4621
4711
  base_url = settings.get("base_url", "http://localhost:8000")
4622
4712
  if not api_key:
4623
4713
  click.echo("Error: Not logged in. Run 'openrunner login' first.", err=True)
@@ -4815,7 +4905,7 @@ def registry_logout() -> None:
4815
4905
  def _drift_client_project(project: str | None):
4816
4906
  """Return (APIClient, project_id) resolving project name/slug -> UUID."""
4817
4907
  settings = _load_settings()
4818
- api_key = settings.get("api_key")
4908
+ api_key = _resolve_api_key(settings)
4819
4909
  base_url = settings.get("base_url", "http://localhost:8000")
4820
4910
  if not api_key:
4821
4911
  click.echo("Error: Not logged in. Run 'openrunner login' first.", err=True)
@@ -5152,7 +5242,7 @@ def deploy_call(deployment_id, path, project, method, data, headers) -> None:
5152
5242
  openrunner deploy call <id> predict -p ORG/NAME -d '{"text":"hi"}'
5153
5243
  """
5154
5244
  settings = _load_settings()
5155
- api_key = settings.get("api_key")
5245
+ api_key = _resolve_api_key(settings)
5156
5246
  base_url = settings.get("base_url", "http://localhost:8000").rstrip("/")
5157
5247
  if not api_key:
5158
5248
  click.echo("Error: Not logged in.", err=True); raise SystemExit(1)
@@ -5196,7 +5286,7 @@ def deploy_call(deployment_id, path, project, method, data, headers) -> None:
5196
5286
  def _plain_client():
5197
5287
  """Build an APIClient from settings, or exit if not logged in."""
5198
5288
  settings = _load_settings()
5199
- api_key = settings.get("api_key")
5289
+ api_key = _resolve_api_key(settings)
5200
5290
  base_url = settings.get("base_url", "http://localhost:8000")
5201
5291
  if not api_key:
5202
5292
  click.echo("Error: Not logged in. Run 'openrunner login' first.", err=True)
@@ -5420,7 +5510,7 @@ def runner_start(org, name, labels, gpu, gpu_count, poll_interval, once, token,
5420
5510
  if not org or not name:
5421
5511
  click.echo("Error: pass --org and --name to register, or --token to run with a pre-minted token.", err=True)
5422
5512
  raise SystemExit(1)
5423
- api_key = settings.get("api_key")
5513
+ api_key = _resolve_api_key(settings)
5424
5514
  if not api_key:
5425
5515
  click.echo("Error: Not logged in. Run 'openrunner login' first (or pass --token).", err=True)
5426
5516
  raise SystemExit(1)
@@ -5677,7 +5767,7 @@ def runner_launch(org, name, provider, gpu, gpu_count, labels, image, web, poll_
5677
5767
  from openrunner.gpu.api_integration import GpuAPIClient
5678
5768
 
5679
5769
  settings = _load_settings()
5680
- api_key = settings.get("api_key")
5770
+ api_key = _resolve_api_key(settings)
5681
5771
  base_url = settings.get("base_url", "http://localhost:8000").rstrip("/")
5682
5772
  if not api_key:
5683
5773
  click.echo("Error: Not logged in. Run 'openrunner login' first.", err=True)
@@ -31,6 +31,20 @@ import httpx
31
31
  _UUID_RE = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.I)
32
32
 
33
33
 
34
+ class DeriveIncomplete(RuntimeError):
35
+ """A chunked ``derive_by_ids`` failed part-way through.
36
+
37
+ The dataset was created and holds ``ids[:resume_from]``. It is a valid
38
+ dataset, not a broken one — it is just short, and nothing about it says so.
39
+ Carries what is needed to append the remainder.
40
+ """
41
+
42
+ def __init__(self, msg: str, *, dataset_id: str, resume_from: int):
43
+ super().__init__(msg)
44
+ self.dataset_id = dataset_id
45
+ self.resume_from = resume_from
46
+
47
+
34
48
  def _check(r: "httpx.Response") -> "httpx.Response":
35
49
  """Raise with the API's actual error detail (FastAPI 422 lists the bad field)."""
36
50
  if r.is_success:
@@ -741,18 +755,39 @@ class DatasetsClient:
741
755
  _check(r)
742
756
  return r.json()
743
757
 
744
- def reconcile_shards(self, repo_id: str, *, timeout: float = 1800.0) -> dict:
758
+ def reconcile_shards(self, repo_id: str, *, timeout: float = 1800.0,
759
+ offset: int = 0, limit: int = 0) -> dict:
745
760
  """Rebuild the tar clip index (index.jsonl) by re-scanning shards on S3.
746
761
 
747
762
  Recovers byte offsets if index.jsonl was lost or an ``upload_sharded`` run
748
763
  was interrupted — the shard tars are the source of truth. Re-marks the
749
764
  dataset ``sharded`` so ``/clip`` resolves audio by Range GET.
765
+
766
+ ``offset``/``limit`` reconcile a slice of the (sorted) shards, MERGING into
767
+ the existing index — for datasets too large to scan in one request without
768
+ hitting the proxy timeout. ``limit=0`` scans everything at once.
750
769
  """
751
770
  ds = self._resolve(repo_id)
752
- r = self._http.post(f"/datasets/{ds.id}/shards/reconcile", timeout=timeout)
771
+ r = self._http.post(f"/datasets/{ds.id}/shards/reconcile",
772
+ params={"offset": offset, "limit": limit}, timeout=timeout)
753
773
  _check(r)
754
774
  return r.json()
755
775
 
776
+ def reconcile_shards_batched(self, repo_id: str, *, batch: int = 500,
777
+ timeout: float = 1800.0, progress=None) -> dict:
778
+ """Reconcile a large sharded dataset in ``batch``-shard slices so no single
779
+ request exceeds the proxy timeout. Loops until every shard is indexed."""
780
+ off, last = 0, None
781
+ while True:
782
+ last = self.reconcile_shards(repo_id, offset=off, limit=batch, timeout=timeout)
783
+ if progress:
784
+ progress(last)
785
+ nxt = last.get("next_offset")
786
+ if not nxt:
787
+ break
788
+ off = nxt
789
+ return last
790
+
756
791
  def set_storage_policy(self, repo_id: str, tier: str) -> dict:
757
792
  """1-click storage tier for a managed dataset's files.
758
793
 
@@ -908,6 +943,8 @@ class DatasetsClient:
908
943
  ) -> DatasetInfo:
909
944
  """Create (or append to) a dataset **derived from a query** over ``repo_id``.
910
945
 
946
+ For an explicit membership list rather than a query, see ``derive_by_ids``.
947
+
911
948
  ``mode``: "word" (``q`` = term / CSV; pairs with LLM ``keyword_expand``),
912
949
  "fts" (BM25), "exact" (phrase), "regex" (``q`` = a Python-``re`` pattern
913
950
  matched over the text column, bounded), "semantic" (vector — needs
@@ -936,6 +973,101 @@ class DatasetsClient:
936
973
  _check(r)
937
974
  return DatasetInfo._from_json(r.json())
938
975
 
976
+ def derive_by_ids(
977
+ self,
978
+ repo_id: str,
979
+ ids: "list[str]",
980
+ *,
981
+ name: str | None = None,
982
+ columns: "dict[str, list] | None" = None,
983
+ lang: str | None = None,
984
+ validate_ids: bool = True,
985
+ chunk_size: int = 50_000,
986
+ target_repo_id: str | None = None,
987
+ project_id: str | None = None,
988
+ ) -> DatasetInfo:
989
+ """Create (or append to) a dataset from an **explicit list of primary keys**.
990
+
991
+ For selections no query can express: rows picked by a boolean column the
992
+ search modes cannot reach, by an external model's output, or by a human
993
+ review pass. The alternative is reverse-engineering a query that happens
994
+ to return the same rows.
995
+
996
+ ``ids`` are the parent's primary keys (``debug_id`` for transcript
997
+ catalogs). Unknown ids are dropped server-side unless
998
+ ``validate_ids=False`` — pass that only when the ids were just read out
999
+ of the parent. Long lists are POSTed in ``chunk_size`` batches, the first
1000
+ creating the dataset and the rest appending to it, so the caller does not
1001
+ have to hold a 20 MB request body. Returns the dataset after the last
1002
+ batch; ``num_examples`` reflects the deduplicated total.
1003
+
1004
+ ``columns`` attaches per-id metadata as member columns, one list per
1005
+ column positionally aligned to ``ids``::
1006
+
1007
+ ds.derive_by_ids(parent, ids, name='org/medical',
1008
+ columns={'n_health': counts, 'source': sigs})
1009
+
1010
+ Types are preserved, so the ranking signal stays queryable: one dataset
1011
+ plus ``n_health >= 20`` replaces the three nested datasets you would
1012
+ otherwise have to build to express three thresholds over the same rows.
1013
+ Chunking splits the columns alongside the ids, so a batch never carries
1014
+ another batch's values.
1015
+ """
1016
+ if not ids:
1017
+ raise ValueError("derive_by_ids needs a non-empty ids list")
1018
+ if not (name or target_repo_id):
1019
+ raise ValueError("derive_by_ids needs either name= (create) or target_repo_id= (append)")
1020
+ if chunk_size < 1:
1021
+ raise ValueError("chunk_size must be >= 1")
1022
+ for _n, _v in (columns or {}).items():
1023
+ if len(_v) != len(ids):
1024
+ raise ValueError(
1025
+ f"columns[{_n!r}] has {len(_v)} values but ids has {len(ids)}; "
1026
+ "they are positional")
1027
+ ds = self._resolve(repo_id)
1028
+ nat = self._native_client()
1029
+ info: DatasetInfo | None = None
1030
+ target_id = str(self._resolve(target_repo_id).id) if target_repo_id else None
1031
+ for i in range(0, len(ids), chunk_size):
1032
+ batch = [str(x) for x in ids[i:i + chunk_size]]
1033
+ body: dict[str, Any] = {
1034
+ "mode": "ids", "ids": batch, "limit": len(batch),
1035
+ "validate_ids": validate_ids,
1036
+ }
1037
+ if columns:
1038
+ body["columns"] = {n: list(v[i:i + chunk_size])
1039
+ for n, v in columns.items()}
1040
+ if lang:
1041
+ body["lang"] = lang
1042
+ if target_id: # append into the existing target...
1043
+ body["target_dataset_id"] = target_id
1044
+ else: # ...or create on the first batch
1045
+ body["name"] = name
1046
+ if project_id:
1047
+ body["project_id"] = str(project_id)
1048
+ try:
1049
+ if nat is not None:
1050
+ info = DatasetInfo._from_json(nat.derive_dataset(str(ds.id), body))
1051
+ else:
1052
+ r = self._http.post(f"/datasets/{ds.id}/derive", json=body, timeout=600.0)
1053
+ _check(r)
1054
+ info = DatasetInfo._from_json(r.json())
1055
+ except Exception as e:
1056
+ # A mid-loop failure leaves a dataset that exists and looks
1057
+ # complete. Retrying the whole call re-sends batch 0 with name=
1058
+ # and trips the duplicate-name check, so it can never resume —
1059
+ # hand back what the caller needs to continue instead.
1060
+ if target_id is None:
1061
+ raise
1062
+ raise DeriveIncomplete(
1063
+ f"derive_by_ids failed on the batch starting at index {i} of "
1064
+ f"{len(ids)}: {e}. Dataset {target_id} holds ids[:{i}]; resume "
1065
+ f"with target_repo_id={target_id!r} and ids[{i}:].",
1066
+ dataset_id=target_id, resume_from=i,
1067
+ ) from e
1068
+ target_id = target_id or str(info.id) # subsequent batches append
1069
+ return info
1070
+
939
1071
  def derived_rows(self, repo_id: str, *, limit: int = 50, offset: int = 0) -> dict:
940
1072
  """Page a query-derived dataset's rows: member keys + inherited parent
941
1073
  fields (lineage-namespaced) + extra columns. Native core when available."""
@@ -1382,6 +1514,11 @@ def derive_by_query(repo_id: str, q: str, **kwargs: Any) -> "DatasetInfo":
1382
1514
  return _c().derive_by_query(repo_id, q, **kwargs)
1383
1515
 
1384
1516
 
1517
+ def derive_by_ids(repo_id: str, ids: "list[str]", **kwargs: Any) -> "DatasetInfo":
1518
+ """Create/append a dataset from an explicit list of parent primary keys."""
1519
+ return _c().derive_by_ids(repo_id, ids, **kwargs)
1520
+
1521
+
1385
1522
  def derived_rows(repo_id: str, **kwargs: Any) -> dict:
1386
1523
  """Page a query-derived dataset's rows (member keys + inherited parent fields)."""
1387
1524
  return _c().derived_rows(repo_id, **kwargs)