cmpnd 0.40.1__tar.gz → 0.42.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 (370) hide show
  1. {cmpnd-0.40.1 → cmpnd-0.42.0}/PKG-INFO +8 -7
  2. {cmpnd-0.40.1 → cmpnd-0.42.0}/README.md +1 -1
  3. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/commands/top.py +14 -21
  4. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/shell.py +4 -4
  5. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/trace_render.py +2 -2
  6. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/deployment.py +40 -18
  7. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/optimization.py +10 -22
  8. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/packaging.py +2 -2
  9. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/programs.py +17 -1
  10. {cmpnd-0.40.1 → cmpnd-0.42.0}/docs/cli.md +9 -4
  11. {cmpnd-0.40.1 → cmpnd-0.42.0}/docs/sdk-instrumentation.md +9 -6
  12. {cmpnd-0.40.1 → cmpnd-0.42.0}/ingest/pyproject.toml +1 -1
  13. {cmpnd-0.40.1 → cmpnd-0.42.0}/pyproject.toml +17 -18
  14. cmpnd-0.42.0/scripts/check-pip-install.sh +34 -0
  15. {cmpnd-0.40.1 → cmpnd-0.42.0}/scripts/require_version_bump.py +2 -11
  16. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/integration/test_unhappy_path.py +1 -2
  17. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/_readiness.py +1 -1
  18. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_shell.py +12 -6
  19. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_top.py +21 -12
  20. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_traces_search_wire.py +2 -4
  21. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_deploy_program_id.py +56 -47
  22. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_programs_create.py +15 -0
  23. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_require_version_bump.py +0 -7
  24. {cmpnd-0.40.1 → cmpnd-0.42.0}/uv.lock +484 -941
  25. {cmpnd-0.40.1 → cmpnd-0.42.0}/.gitignore +0 -0
  26. {cmpnd-0.40.1 → cmpnd-0.42.0}/AGENTS.md +0 -0
  27. {cmpnd-0.40.1 → cmpnd-0.42.0}/CLAUDE.md +0 -0
  28. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/__init__.py +0 -0
  29. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/_assessments.py +0 -0
  30. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/_authored_import.py +0 -0
  31. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/_deployments.py +0 -0
  32. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/_exporter_pool.py +0 -0
  33. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/_gepa_patch.py +0 -0
  34. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/_http.py +0 -0
  35. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/_program_patch.py +0 -0
  36. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/_rlm_patch.py +0 -0
  37. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/_runtime_tools.py +0 -0
  38. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/_timespec.py +0 -0
  39. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/_traces.py +0 -0
  40. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/_valsets.py +0 -0
  41. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/authored.py +0 -0
  42. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/callback.py +0 -0
  43. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/__init__.py +0 -0
  44. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/api_client.py +0 -0
  45. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/commands/__init__.py +0 -0
  46. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/commands/admin.py +0 -0
  47. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/commands/assessments.py +0 -0
  48. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/commands/auth.py +0 -0
  49. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/commands/datasets.py +0 -0
  50. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/commands/deployments.py +0 -0
  51. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/commands/evals.py +0 -0
  52. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/commands/login.py +0 -0
  53. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/commands/optimizations.py +0 -0
  54. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/commands/org.py +0 -0
  55. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/commands/programs.py +0 -0
  56. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
  57. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/commands/signatures.py +0 -0
  58. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/commands/tool_servers.py +0 -0
  59. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/commands/traces.py +0 -0
  60. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/commands/valsets.py +0 -0
  61. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/credentials.py +0 -0
  62. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/deprecation.py +0 -0
  63. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/pagination.py +0 -0
  64. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/tree_help.py +0 -0
  65. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/cli/verbs.py +0 -0
  66. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/client.py +0 -0
  67. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/configuration.py +0 -0
  68. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/context.py +0 -0
  69. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/dataset_sync.py +0 -0
  70. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/datasets.py +0 -0
  71. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/decorators.py +0 -0
  72. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/deployment_metadata.py +0 -0
  73. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/deployment_runs.py +0 -0
  74. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/eval_handler.py +0 -0
  75. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/execution.py +0 -0
  76. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/exporter.py +0 -0
  77. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/exporter_http.py +0 -0
  78. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/helpers.py +0 -0
  79. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/identity.py +0 -0
  80. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/imaging.py +0 -0
  81. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/ir/__init__.py +0 -0
  82. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/ir/decode.py +0 -0
  83. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/ir/encode.py +0 -0
  84. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/ir/hash.py +0 -0
  85. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/ir/program.py +0 -0
  86. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/ir/reflection.py +0 -0
  87. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/ir/types.py +0 -0
  88. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/ir/xxh64.py +0 -0
  89. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/lm_configuration.py +0 -0
  90. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/mcp_servers.py +0 -0
  91. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/models.py +0 -0
  92. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/optimizers/__init__.py +0 -0
  93. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/optimizers/gepa.py +0 -0
  94. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/optimizers/gepa_callback.py +0 -0
  95. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/optimizers/resume.py +0 -0
  96. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/optimizers/tracker.py +0 -0
  97. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/references.py +0 -0
  98. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/retry.py +0 -0
  99. {cmpnd-0.40.1 → cmpnd-0.42.0}/cmpnd/traces.py +0 -0
  100. {cmpnd-0.40.1 → cmpnd-0.42.0}/docs/2026-04-02-code-review.md +0 -0
  101. {cmpnd-0.40.1 → cmpnd-0.42.0}/docs/client.md +0 -0
  102. {cmpnd-0.40.1 → cmpnd-0.42.0}/docs/custom-proposer.md +0 -0
  103. {cmpnd-0.40.1 → cmpnd-0.42.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  104. {cmpnd-0.40.1 → cmpnd-0.42.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  105. {cmpnd-0.40.1 → cmpnd-0.42.0}/docs/reading-traces.md +0 -0
  106. {cmpnd-0.40.1 → cmpnd-0.42.0}/examples/generalizing_proposer.py +0 -0
  107. {cmpnd-0.40.1 → cmpnd-0.42.0}/examples/local_ollama.py +0 -0
  108. {cmpnd-0.40.1 → cmpnd-0.42.0}/ingest/LINEAGE.md +0 -0
  109. {cmpnd-0.40.1 → cmpnd-0.42.0}/ingest/README.md +0 -0
  110. {cmpnd-0.40.1 → cmpnd-0.42.0}/ingest/cmpnd_ingest/__init__.py +0 -0
  111. {cmpnd-0.40.1 → cmpnd-0.42.0}/ingest/cmpnd_ingest/bodies.py +0 -0
  112. {cmpnd-0.40.1 → cmpnd-0.42.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
  113. {cmpnd-0.40.1 → cmpnd-0.42.0}/ingest/cmpnd_ingest/protocols.py +0 -0
  114. {cmpnd-0.40.1 → cmpnd-0.42.0}/ingest/cmpnd_ingest/py.typed +0 -0
  115. {cmpnd-0.40.1 → cmpnd-0.42.0}/ingest/cmpnd_ingest/request_log.py +0 -0
  116. {cmpnd-0.40.1 → cmpnd-0.42.0}/ingest/cmpnd_ingest/retry.py +0 -0
  117. {cmpnd-0.40.1 → cmpnd-0.42.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
  118. {cmpnd-0.40.1 → cmpnd-0.42.0}/scripts/audit-sdist.sh +0 -0
  119. {cmpnd-0.40.1 → cmpnd-0.42.0}/scripts/capture_review_run.py +0 -0
  120. {cmpnd-0.40.1 → cmpnd-0.42.0}/scripts/check_release_artifacts.py +0 -0
  121. {cmpnd-0.40.1 → cmpnd-0.42.0}/scripts/fixtures/review_run.json.gz +0 -0
  122. {cmpnd-0.40.1 → cmpnd-0.42.0}/scripts/gepa_resume_bug.py +0 -0
  123. {cmpnd-0.40.1 → cmpnd-0.42.0}/scripts/release-stress.sh +0 -0
  124. {cmpnd-0.40.1 → cmpnd-0.42.0}/scripts/seed_review_run.py +0 -0
  125. {cmpnd-0.40.1 → cmpnd-0.42.0}/scripts/test-in-linux.sh +0 -0
  126. {cmpnd-0.40.1 → cmpnd-0.42.0}/stubs/dspy/__init__.pyi +0 -0
  127. {cmpnd-0.40.1 → cmpnd-0.42.0}/stubs/dspy/primitives/__init__.pyi +0 -0
  128. {cmpnd-0.40.1 → cmpnd-0.42.0}/stubs/dspy/primitives/prediction.pyi +0 -0
  129. {cmpnd-0.40.1 → cmpnd-0.42.0}/stubs/dspy/utils/__init__.pyi +0 -0
  130. {cmpnd-0.40.1 → cmpnd-0.42.0}/stubs/dspy/utils/callback.pyi +0 -0
  131. {cmpnd-0.40.1 → cmpnd-0.42.0}/stubs/dspy/utils/dummies.pyi +0 -0
  132. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/__init__.py +0 -0
  133. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/_deployment_signature_helpers.py +0 -0
  134. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/_exporter_helpers.py +0 -0
  135. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/_fake_clock.py +0 -0
  136. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/_identity_helpers.py +0 -0
  137. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/_resolve_refs.py +0 -0
  138. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/_traced_run.py +0 -0
  139. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/conftest.py +0 -0
  140. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/__init__.py +0 -0
  141. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/committee_task.py +0 -0
  142. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/conftest.py +0 -0
  143. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
  144. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
  145. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/fake_lm.py +0 -0
  146. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/helpers.py +0 -0
  147. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/target_capabilities.py +0 -0
  148. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_assessments_cli.py +0 -0
  149. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_capture_prompts.py +0 -0
  150. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_composite_module.py +0 -0
  151. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
  152. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_deploy_execute.py +0 -0
  153. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_deploy_pandas.py +0 -0
  154. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_deploy_programs.py +0 -0
  155. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_edge_cases.py +0 -0
  156. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_example_hash.py +0 -0
  157. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_gepa_optimization.py +0 -0
  158. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_interpreter_spans.py +0 -0
  159. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_lm_accuracy.py +0 -0
  160. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_module_tracing.py +0 -0
  161. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_optimization_lineage.py +0 -0
  162. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
  163. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_optimize_failures.py +0 -0
  164. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_optimize_loop.py +0 -0
  165. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_optimize_review_committee.py +0 -0
  166. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
  167. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_parallel_export.py +0 -0
  168. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_project_attribution.py +0 -0
  169. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_react_tool_spans.py +0 -0
  170. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_save_load.py +0 -0
  171. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_trace_structure.py +0 -0
  172. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_traces_read.py +0 -0
  173. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_user_attribution.py +0 -0
  174. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/e2e/test_valsets_cli.py +0 -0
  175. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/identity_vectors/01_predict_qa.json +0 -0
  176. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
  177. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
  178. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
  179. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
  180. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
  181. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
  182. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/identity_vectors/08_opaque_module.json +0 -0
  183. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
  184. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/identity_vectors/10_react.json +0 -0
  185. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/identity_vectors/11_refine.json +0 -0
  186. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/identity_vectors/12_retrieve.json +0 -0
  187. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/identity_vectors/13_knn.json +0 -0
  188. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/identity_vectors/14_parallel.json +0 -0
  189. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/identity_vectors/README.md +0 -0
  190. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/ingest_conformance.py +0 -0
  191. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/integration/__init__.py +0 -0
  192. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/integration/authored_optimize/judge.py +0 -0
  193. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
  194. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/integration/conftest.py +0 -0
  195. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
  196. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/integration/hosted_optimize_probe.py +0 -0
  197. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/integration/optimization_run_probe.py +0 -0
  198. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
  199. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/integration/test_logs_renders_traces.py +0 -0
  200. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/integration/test_optimization_run.py +0 -0
  201. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/integration/test_optimizing_progress.py +0 -0
  202. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/load/__init__.py +0 -0
  203. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/load/__main__.py +0 -0
  204. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/load/drain.py +0 -0
  205. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/load/ingest.py +0 -0
  206. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/load/latency_transport.py +0 -0
  207. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/load/rlm_shape.py +0 -0
  208. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/load/rlm_size_report.py +0 -0
  209. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/load/sweep.py +0 -0
  210. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/load/test_rlm_shape.py +0 -0
  211. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/load/test_rlm_size_report.py +0 -0
  212. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/parity/__init__.py +0 -0
  213. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/parity/conftest.py +0 -0
  214. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/parity/seed_parity.py +0 -0
  215. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/parity/test_auth.py +0 -0
  216. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/parity/test_datasets.py +0 -0
  217. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/parity/test_deployments.py +0 -0
  218. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/parity/test_evals.py +0 -0
  219. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/parity/test_health.py +0 -0
  220. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/parity/test_modules.py +0 -0
  221. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/parity/test_optimizations.py +0 -0
  222. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/parity/test_programs.py +0 -0
  223. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/parity/test_signatures.py +0 -0
  224. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/parity/test_trace_streaming.py +0 -0
  225. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/parity/test_traces.py +0 -0
  226. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/span_reference_fixture.py +0 -0
  227. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/__init__.py +0 -0
  228. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/_provider.py +0 -0
  229. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/_reliability.py +0 -0
  230. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/_result.py +0 -0
  231. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/mint_bedrock_token.py +0 -0
  232. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/test_deploy_programs.py +0 -0
  233. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/test_multi_client_stress.py +0 -0
  234. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/test_optimize_client_committee.py +0 -0
  235. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/test_optimize_loop.py +0 -0
  236. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
  237. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/test_optimize_stress.py +0 -0
  238. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/test_provider_roles.py +0 -0
  239. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/test_readiness.py +0 -0
  240. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/test_real_provider_twins.py +0 -0
  241. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/test_reliability.py +0 -0
  242. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/test_reliability_classify.py +0 -0
  243. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/test_rlm_wasm.py +0 -0
  244. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/stress/test_trace_search.py +0 -0
  245. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_adapter_reentrancy.py +0 -0
  246. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_assessments_api.py +0 -0
  247. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_authored.py +0 -0
  248. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_authored_bundle_collisions.py +0 -0
  249. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_authored_metric_contract.py +0 -0
  250. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_authored_named_models.py +0 -0
  251. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_authored_optimize.py +0 -0
  252. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_callback.py +0 -0
  253. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_candidate_lineage.py +0 -0
  254. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/__init__.py +0 -0
  255. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/conftest.py +0 -0
  256. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_admin.py +0 -0
  257. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_api_client.py +0 -0
  258. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_assessments.py +0 -0
  259. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_auth.py +0 -0
  260. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_credentials.py +0 -0
  261. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_datasets.py +0 -0
  262. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_deployment_runs.py +0 -0
  263. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_deployments.py +0 -0
  264. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_evals.py +0 -0
  265. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_login.py +0 -0
  266. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_main.py +0 -0
  267. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_optimizations.py +0 -0
  268. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_org.py +0 -0
  269. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_pagination.py +0 -0
  270. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_parity.py +0 -0
  271. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_programs.py +0 -0
  272. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_provider_credentials.py +0 -0
  273. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_schema_sample.py +0 -0
  274. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_signatures.py +0 -0
  275. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_tool_servers.py +0 -0
  276. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_traces.py +0 -0
  277. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_tree_help.py +0 -0
  278. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cli/test_valsets.py +0 -0
  279. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_client.py +0 -0
  280. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_clustering/__init__.py +0 -0
  281. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_clustering/conftest.py +0 -0
  282. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_clustering/test_edge_cases.py +0 -0
  283. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_clustering/test_hash_consistency.py +0 -0
  284. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_clustering/test_type_conversion.py +0 -0
  285. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_cmp551_grouping_identity.py +0 -0
  286. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_configuration.py +0 -0
  287. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_context.py +0 -0
  288. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_context_propagation.py +0 -0
  289. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_dataset_polling.py +0 -0
  290. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_dataset_sync.py +0 -0
  291. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_datasets.py +0 -0
  292. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_decorators.py +0 -0
  293. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_deploy.py +0 -0
  294. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_deploy_mcp_tool_references.py +0 -0
  295. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_deployment_metadata.py +0 -0
  296. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_deployment_runs.py +0 -0
  297. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_deployments_read.py +0 -0
  298. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_eval.py +0 -0
  299. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_eval_example_attribution.py +0 -0
  300. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_eval_start.py +0 -0
  301. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_eval_trace_grouping.py +0 -0
  302. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_exception_paths.py +0 -0
  303. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_execute.py +0 -0
  304. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_execute_run_identity.py +0 -0
  305. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_exporter.py +0 -0
  306. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_exporter_queue.py +0 -0
  307. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_exporter_real.py +0 -0
  308. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_fake_lm.py +0 -0
  309. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_gepa_callback.py +0 -0
  310. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_gepa_integration.py +0 -0
  311. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_gepa_output_normalization.py +0 -0
  312. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_gepa_resume.py +0 -0
  313. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_identity.py +0 -0
  314. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_imaging.py +0 -0
  315. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_import_weight.py +0 -0
  316. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_ingest_conformance.py +0 -0
  317. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_ingest_second_producer.py +0 -0
  318. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_integration.py +0 -0
  319. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_interpreter_callback.py +0 -0
  320. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_ir_encoder_properties.py +0 -0
  321. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_ir_golden_vectors.py +0 -0
  322. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_ir_roundtrip.py +0 -0
  323. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_lm15_native_calls.py +0 -0
  324. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_lm_configuration.py +0 -0
  325. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_lm_usage_shapes.py +0 -0
  326. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_mcp_tools.py +0 -0
  327. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_models.py +0 -0
  328. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_multi_client_stress_readiness.py +0 -0
  329. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_optimization_progress.py +0 -0
  330. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_optimization_start.py +0 -0
  331. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_optimize.py +0 -0
  332. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_optimize_budget.py +0 -0
  333. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_optimize_lift.py +0 -0
  334. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_optimize_lms.py +0 -0
  335. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_optimize_proposer.py +0 -0
  336. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_optimize_reflection_lm.py +0 -0
  337. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_optimize_terminal_failure.py +0 -0
  338. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_packaging.py +0 -0
  339. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_packaging_callable_source.py +0 -0
  340. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_packaging_mcp_tools.py +0 -0
  341. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_packaging_program_source.py +0 -0
  342. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_poll_until.py +0 -0
  343. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_program_identity.py +0 -0
  344. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_program_lineage_persist.py +0 -0
  345. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_project_payload.py +0 -0
  346. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_real_provider_twins_readiness.py +0 -0
  347. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_reflector_properties.py +0 -0
  348. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_reflector_smoke.py +0 -0
  349. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_release_stress_script.py +0 -0
  350. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_repro_reactv2_bugs.py +0 -0
  351. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_retry.py +0 -0
  352. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_reversible_failures.py +0 -0
  353. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_rlm_patch.py +0 -0
  354. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_run_attribution_paths.py +0 -0
  355. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_runtime_tools.py +0 -0
  356. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_schemas.py +0 -0
  357. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_seed_review_run.py +0 -0
  358. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_signature_inputs.py +0 -0
  359. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_span_references.py +0 -0
  360. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_span_sent_once.py +0 -0
  361. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_trace_context_concurrency.py +0 -0
  362. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_trace_fact_wire_contract.py +0 -0
  363. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_trace_facts.py +0 -0
  364. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_trace_render.py +0 -0
  365. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_traces_read.py +0 -0
  366. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_tracked_gepa.py +0 -0
  367. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_tracker_enrich.py +0 -0
  368. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_truncation.py +0 -0
  369. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_valsets_api.py +0 -0
  370. {cmpnd-0.40.1 → cmpnd-0.42.0}/tests/test_xxh64.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cmpnd
3
- Version: 0.40.1
3
+ Version: 0.42.0
4
4
  Summary: DSPy observability and deployment SDK for cmpnd
5
5
  Project-URL: Homepage, https://cmpnd.ai
6
6
  Author-email: cmpnd <hello@cmpnd.ai>
@@ -10,13 +10,13 @@ Classifier: Development Status :: 3 - Alpha
10
10
  Classifier: Intended Audience :: Developers
11
11
  Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.10
14
13
  Classifier: Programming Language :: Python :: 3.11
15
14
  Classifier: Programming Language :: Python :: 3.12
16
15
  Classifier: Programming Language :: Python :: 3.13
17
16
  Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Programming Language :: Python :: 3.15
18
18
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
- Requires-Python: >=3.10
19
+ Requires-Python: >=3.11
20
20
  Requires-Dist: dspy>=3.1.3
21
21
  Requires-Dist: httpx>=0.27.0
22
22
  Requires-Dist: ipython>=8.20
@@ -27,10 +27,11 @@ Requires-Dist: xxhash>=3.0.0
27
27
  Provides-Extra: dev
28
28
  Requires-Dist: basedpyright>=1.39; extra == 'dev'
29
29
  Requires-Dist: boto3>=1.34; extra == 'dev'
30
+ Requires-Dist: gepa; extra == 'dev'
30
31
  Requires-Dist: hypothesis>=6.100; extra == 'dev'
31
32
  Requires-Dist: jsonschema>=4.0; extra == 'dev'
32
- Requires-Dist: numpy>=1.26; (python_version < '3.11') and extra == 'dev'
33
- Requires-Dist: numpy>=2.3; (python_version >= '3.11') and extra == 'dev'
33
+ Requires-Dist: litellm; extra == 'dev'
34
+ Requires-Dist: numpy>=2.3; extra == 'dev'
34
35
  Requires-Dist: pandas>=2.2; extra == 'dev'
35
36
  Requires-Dist: pillow>=10.0; extra == 'dev'
36
37
  Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
@@ -40,7 +41,7 @@ Requires-Dist: pytest>=8.0; extra == 'dev'
40
41
  Requires-Dist: ruff>=0.4.0; extra == 'dev'
41
42
  Requires-Dist: twine>=7.0; extra == 'dev'
42
43
  Provides-Extra: fake-provider
43
- Requires-Dist: cmpnd-fake-provider; (python_version >= '3.11') and extra == 'fake-provider'
44
+ Requires-Dist: cmpnd-fake-provider; extra == 'fake-provider'
44
45
  Provides-Extra: images
45
46
  Requires-Dist: pillow>=10.0; extra == 'images'
46
47
  Provides-Extra: mcp
@@ -161,7 +162,7 @@ cmpnd.deploy(
161
162
  metric=None, # optional: a metric callable, packaged for optimization
162
163
  timeout=300, # optional: seconds to wait for the parser after the upload
163
164
  metadata=None, # optional: a JSON-serializable dict, at most 64 KiB, stored with the deployment
164
- program_id="p_…", # required unless configured: the program the deployment belongs to
165
+ program_id="p_…", # the program the deployment belongs to; serve requires it after self-host 2026-001
165
166
  )
166
167
  ```
167
168
 
@@ -111,7 +111,7 @@ cmpnd.deploy(
111
111
  metric=None, # optional: a metric callable, packaged for optimization
112
112
  timeout=300, # optional: seconds to wait for the parser after the upload
113
113
  metadata=None, # optional: a JSON-serializable dict, at most 64 KiB, stored with the deployment
114
- program_id="p_…", # required unless configured: the program the deployment belongs to
114
+ program_id="p_…", # the program the deployment belongs to; serve requires it after self-host 2026-001
115
115
  )
116
116
  ```
117
117
 
@@ -83,8 +83,7 @@ def _parse_iso(ts: str | None) -> datetime | None:
83
83
  if not ts:
84
84
  return None
85
85
  try:
86
- # Rails serializes with "Z"; Python <3.11 parsed via fromisoformat needed +00:00
87
- return datetime.fromisoformat(ts.replace("Z", "+00:00"))
86
+ return datetime.fromisoformat(ts)
88
87
  except (ValueError, TypeError):
89
88
  return None
90
89
 
@@ -811,11 +810,12 @@ def _parse_deploy_metadata(raw: str | None) -> dict[str, Any] | None:
811
810
  raise api_client.CliError(f"--metadata is invalid: {exc}")
812
811
 
813
812
 
814
- def _program_id_arg(value: str | None, *, required: bool) -> str | None:
813
+ def _program_id_arg(value: str | None) -> str | None:
815
814
  """The program id a deploy or optimize names: ``--program-id``, else ``CMPND_PROGRAM_ID``.
816
815
 
817
- Checked here, before a file is staged, so a missing or mistyped id is a usage
818
- error naming the flag rather than a refusal after work was done.
816
+ Checked here, before a file is staged, so a mistyped id is a usage error naming
817
+ its source rather than a refusal after work was done. serve decides whether a
818
+ deployment needs an id, so a missing one is not refused here.
819
819
  """
820
820
  from ... import identity
821
821
 
@@ -823,12 +823,6 @@ def _program_id_arg(value: str | None, *, required: bool) -> str | None:
823
823
  if value is None and os.environ.get("CMPND_PROGRAM_ID"):
824
824
  value, source = os.environ["CMPND_PROGRAM_ID"], "CMPND_PROGRAM_ID"
825
825
  if value is None:
826
- if required:
827
- raise api_client.CliError(
828
- "--program-id is required: a deployment always names its program. "
829
- "Create one with `cmpnd programs create`, then pass --program-id p_… "
830
- "(or set CMPND_PROGRAM_ID)."
831
- )
832
826
  return None
833
827
  try:
834
828
  return identity.validate_program_id(value, source=source)
@@ -855,9 +849,7 @@ def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
855
849
 
856
850
  from ... import authored, configuration, deployment
857
851
 
858
- # Refused before the file is staged or anything is sent: a deployment always
859
- # names its program, and the server would refuse the create anyway.
860
- program_id = _program_id_arg(getattr(args, "program_id", None), required=True)
852
+ program_id = _program_id_arg(getattr(args, "program_id", None))
861
853
  configuration.configure(api_key=client._api_key, endpoint=client._endpoint, program_id=program_id)
862
854
  try:
863
855
  if args.source == "-" or args.source is None:
@@ -881,7 +873,10 @@ def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
881
873
 
882
874
  metadata = _parse_deploy_metadata(getattr(args, "metadata", None))
883
875
  print(_c("deploying…", "dim"), file=sys.stderr)
884
- deployed = deployment.deploy_bundle(prepared.bundle, prepared.facts, metadata=metadata, program_id=program_id)
876
+ try:
877
+ deployed = deployment.deploy_bundle(prepared.bundle, prepared.facts, metadata=metadata, program_id=program_id)
878
+ except ValueError as exc:
879
+ raise api_client.CliError(str(exc)) from exc
885
880
 
886
881
  deployment_id = deployed.deployment_id
887
882
  print(deployment_id)
@@ -946,9 +941,7 @@ def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
946
941
  from ... import authored, configuration, optimization
947
942
 
948
943
  deployment_id = getattr(args, "deployment_id", None)
949
- # Required only when the file's program is deployed here; a reused deployment
950
- # already names its program.
951
- program_id = _program_id_arg(getattr(args, "program_id", None), required=deployment_id is None)
944
+ program_id = _program_id_arg(getattr(args, "program_id", None))
952
945
  configuration.configure(api_key=client._api_key, endpoint=client._endpoint, program_id=program_id)
953
946
  try:
954
947
  if args.source == "-" or args.source is None:
@@ -1266,7 +1259,7 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
1266
1259
  dest="program_id",
1267
1260
  default=None,
1268
1261
  help="Program id (p_…) the deployment belongs to, from `cmpnd programs create`. "
1269
- "Required; defaults to CMPND_PROGRAM_ID",
1262
+ "Defaults to CMPND_PROGRAM_ID. Required by every serve after self-host release 2026-001",
1270
1263
  )
1271
1264
  dp.add_argument(
1272
1265
  "--entrypoint",
@@ -1351,8 +1344,8 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
1351
1344
  dest="program_id",
1352
1345
  default=None,
1353
1346
  help="Program id (p_…) being optimized, from `cmpnd programs create`; defaults to "
1354
- "CMPND_PROGRAM_ID. Required when the file's program is deployed here, optional "
1355
- "with --deployment-id",
1347
+ "CMPND_PROGRAM_ID. Required by every serve after self-host release 2026-001 when "
1348
+ "the file's program is deployed here, optional with --deployment-id",
1356
1349
  )
1357
1350
  opt.add_argument(
1358
1351
  "--reflection-model",
@@ -490,8 +490,8 @@ def _make_namespace(client: api_client.ApiClient, endpoint: str) -> dict[str, An
490
490
  """Deploy authored Python from a file or ``"-"`` for stdin.
491
491
 
492
492
  ``program_id`` (``p_…``, from ``cmpnd programs create``) names the program
493
- the deployment belongs to; it defaults to ``CMPND_PROGRAM_ID`` and one of
494
- the two is required. A fresh child imports the staged bundle. The shell
493
+ the deployment belongs to; it defaults to ``CMPND_PROGRAM_ID``. serve
494
+ decides whether a deployment needs one. A fresh child imports the staged bundle. The shell
495
495
  does not execute the file in its own process. Returns the
496
496
  new Deployment entity so you can chain ``.watch()`` / ``.run({...})``
497
497
  directly; also invalidates ``deps`` so the new deployment shows up
@@ -499,7 +499,7 @@ def _make_namespace(client: api_client.ApiClient, endpoint: str) -> dict[str, An
499
499
  """
500
500
  from .. import authored, configuration, deployment
501
501
 
502
- declared = commands.top._program_id_arg(program_id, required=True)
502
+ declared = commands.top._program_id_arg(program_id)
503
503
  configuration.configure(api_key=client._api_key, endpoint=client._endpoint, program_id=declared)
504
504
  if source in ("-", None):
505
505
  prepared = authored.prepare_stdin(
@@ -644,7 +644,7 @@ def _enable_readline(namespace: dict[str, Any]) -> None:
644
644
  def _launch_repl(namespace: dict[str, Any], banner: str) -> None:
645
645
  """Prefer IPython (Jedi completion, history, multi-line); fall back to stdlib."""
646
646
  try:
647
- from IPython import start_ipython # pyright: ignore[reportAttributeAccessIssue]
647
+ from IPython import start_ipython
648
648
  from traitlets.config import Config
649
649
  except ImportError:
650
650
  import code
@@ -194,8 +194,8 @@ _TS_RE = re.compile(r"^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(\.\d+)?(Z|[+-]\d{2}
194
194
 
195
195
  def _parse_ts(value: Any) -> datetime.datetime | None:
196
196
  """Parse an RFC3339 timestamp tolerantly. Server emits nanosecond (9-digit)
197
- fractions that datetime.fromisoformat rejects, so truncate the fraction to
198
- microseconds and normalize a trailing Z. Returns None on anything unparsable
197
+ fractions, so truncate the fraction to microseconds, the precision datetime
198
+ holds, and normalize a trailing Z. Returns None on anything unparsable
199
199
  — the caller degrades (offset 0) rather than crashing."""
200
200
  if not isinstance(value, str):
201
201
  return None
@@ -346,21 +346,35 @@ def _resolve_mcp_tool_references(client: httpx.Client, config, references: list[
346
346
 
347
347
 
348
348
  MISSING_PROGRAM_ID = (
349
- "deploying needs a program id: pass program_id= (`cmpnd deploy --program-id`), or set "
349
+ "this server requires a program id: pass program_id= (`cmpnd deploy --program-id`), or set "
350
350
  "cmpnd.configure(program_id=...) or CMPND_PROGRAM_ID. Create one with `cmpnd programs create`."
351
351
  )
352
352
 
353
+ # The error serve answers, with a 400, to a create that names no program. A serve
354
+ # from before program ids accepts the create instead. serve pins this prefix in
355
+ # deploymentsapi.missingProgramIDError.
356
+ _SERVE_MISSING_PROGRAM_ID = "program_id is required"
353
357
 
354
- def require_program_id(explicit: str | None, config: configuration.Config) -> str:
358
+
359
+ def declared_program_id(explicit: str | None, config: configuration.Config) -> str | None:
355
360
  """The program a deployment is created under: the explicit id, else the configured one.
356
361
 
357
- A deployment always names its program, so a missing id is refused here, before
358
- anything is packaged or sent, rather than by the server after a create.
362
+ A declared id is checked for shape before anything is packaged or sent. serve
363
+ decides whether a deployment needs an id, so a missing one is not refused here.
359
364
  """
360
365
  chosen = explicit if explicit is not None else config.program_id
361
- if chosen is None:
362
- raise ValueError(MISSING_PROGRAM_ID)
363
- return identity.validate_program_id(chosen)
366
+ return identity.validate_program_id(chosen) if chosen is not None else None
367
+
368
+
369
+ def _refused_for_missing_program_id(response: httpx.Response, deploy_body: dict[str, Any]) -> bool:
370
+ """Whether serve refused a create that named no program because it needs one."""
371
+ if response.status_code != 400 or "program_id" in deploy_body:
372
+ return False
373
+ try:
374
+ error = response.json().get("error")
375
+ except (ValueError, AttributeError):
376
+ return False
377
+ return isinstance(error, str) and error.startswith(_SERVE_MISSING_PROGRAM_ID)
364
378
 
365
379
 
366
380
  def deploy(module, metric=None, timeout=300, metadata=None, *, program_id=None):
@@ -387,22 +401,24 @@ def deploy(module, metric=None, timeout=300, metadata=None, *, program_id=None):
387
401
  with string keys, within 64 KiB. Read it back via the deployment
388
402
  record (``cmpnd.status`` / the deployment GET API).
389
403
  program_id: The program (``p_…``) the deployment belongs to, as
390
- ``cmpnd programs create`` printed it. Required: when omitted, the
391
- configured id (``cmpnd.configure(program_id=...)`` or
392
- ``CMPND_PROGRAM_ID``) is used, and with neither the deploy is
393
- refused before anything is packaged.
404
+ ``cmpnd programs create`` printed it. When omitted, the configured
405
+ id (``cmpnd.configure(program_id=...)`` or ``CMPND_PROGRAM_ID``) is
406
+ used. With neither, the create names no program, and serve decides:
407
+ every serve with program ids refuses it, and a serve from before
408
+ them, such as self-host release 2026-001, accepts it.
394
409
 
395
410
  Returns:
396
411
  A DeployedProgram that can be called with the same inputs as the
397
412
  original module. Has a .deployment_id attribute and str() returns it.
398
413
 
399
414
  Raises:
400
- ValueError: If no program id is given or configured, or it is malformed.
415
+ ValueError: If the program id is malformed, or if serve requires one and
416
+ none was given or configured.
401
417
  RuntimeError: If cmpnd is not configured, or if deployment fails.
402
418
  TimeoutError: If the program isn't ready within timeout seconds.
403
419
  """
404
420
  config = configuration.require_config()
405
- declared_program_id = require_program_id(program_id, config)
421
+ program = declared_program_id(program_id, config)
406
422
 
407
423
  # Resolve the program's MCP tool references against the org's registry, before
408
424
  # anything is packaged and before a deployment row exists. A program with no MCP
@@ -423,12 +439,15 @@ def deploy(module, metric=None, timeout=300, metadata=None, *, program_id=None):
423
439
  program_signature_hash = identity.compute_program_hash(module) or 0
424
440
 
425
441
  deploy_body: dict[str, Any] = {
426
- "program_id": declared_program_id,
427
442
  "program_signature_hash": identity.format_signature_hash(program_signature_hash),
428
443
  # Declare the ZIP's exact length so the server can size-bound the upload
429
444
  # slot and reject an oversized deploy before signing it (Slice E).
430
445
  "declared_content_length": len(zip_bytes),
431
446
  }
447
+ # Left off when undeclared, so a serve from before program ids reads the body it
448
+ # always read and a newer serve answers with its own refusal.
449
+ if program is not None:
450
+ deploy_body["program_id"] = program
432
451
  # Carry the caller's metadata blob (validated and made jsonb-storable at the
433
452
  # call site) as a reserved top-level body key, committed with the deployment
434
453
  # record server-side.
@@ -496,13 +515,14 @@ def deploy_bundle(
496
515
  ) -> execution.DeployedProgram:
497
516
  """Upload a checked authored-source bundle without packaging a live module.
498
517
 
499
- ``program_id`` is required as it is for :func:`deploy`: the explicit id, else
500
- the configured one, else a ``ValueError`` before anything is sent.
518
+ ``program_id`` works as it does for :func:`deploy`: the explicit id, else the
519
+ configured one, else none, and serve decides whether a deployment needs one.
501
520
  """
502
521
  config = configuration.require_config()
503
- declared_program_id = require_program_id(program_id, config)
522
+ program = declared_program_id(program_id, config)
504
523
  deploy_body = facts.deploy_body()
505
- deploy_body["program_id"] = declared_program_id
524
+ if program is not None:
525
+ deploy_body["program_id"] = program
506
526
  deploy_body["source_format"] = "authored"
507
527
  deploy_body["declared_content_length"] = len(zip_bytes)
508
528
  if metadata is not None:
@@ -531,6 +551,8 @@ def _deploy_prepared(
531
551
  may_resend_with_no_answer=retry.never_left_the_client,
532
552
  may_resend_after_answer=lambda _: False,
533
553
  )
554
+ if _refused_for_missing_program_id(created, deploy_body):
555
+ raise ValueError(MISSING_PROGRAM_ID)
534
556
  _raise_http_error(created, "create deployment")
535
557
  answer = created.json()
536
558
  deployment_id = answer["deployment_id"]
@@ -38,7 +38,7 @@ from typing import TYPE_CHECKING, Any, Callable, cast
38
38
  import httpx
39
39
  from httpx import HTTPError
40
40
 
41
- from . import _deployments, _http, authored, configuration, deployment, execution, identity, packaging, retry
41
+ from . import _deployments, _http, authored, configuration, deployment, execution, packaging, retry
42
42
  from .deployment_metadata import DeploymentMetadataError, DeploymentSignatureResponse
43
43
 
44
44
  if TYPE_CHECKING:
@@ -831,10 +831,11 @@ def optimize(
831
831
  deployment_id: Skip the initial deploy by reusing an existing ready
832
832
  deployment as program A.
833
833
  program_id: The program (``p_…``) being optimized. Defaults to the
834
- configured id. Required when the program is deployed here (no
835
- ``deployment_id``), because a deployment always names its program;
836
- optional otherwise, since the deployment already names one. Sent on
837
- the first optimize step, the one that optimizes your deployment.
834
+ configured id. When the program is deployed here (no
835
+ ``deployment_id``), the deploy carries it, and serve decides whether
836
+ that deploy needs one. A reused deployment already names its program.
837
+ Sent on the first optimize step, the one that optimizes your
838
+ deployment.
838
839
  reflection_lm: **Required** — the model that proposes new instructions.
839
840
  The search is only as good as it is, so this is a strong model,
840
841
  typically not the one being optimized. Pass the ``dspy.LM`` you would
@@ -925,7 +926,7 @@ def optimize(
925
926
  _apply_budget(config, max_metric_calls=max_metric_calls, auto=auto)
926
927
 
927
928
  cfg = configuration.require_config()
928
- program_id = _declared_program_id(program_id, cfg, deploys=deployment_id is None)
929
+ program_id = deployment.declared_program_id(program_id, cfg)
929
930
 
930
931
  from . import lm_configuration
931
932
 
@@ -957,19 +958,6 @@ def optimize(
957
958
  return _deployed_program(best_deployment_id, cfg, score=best_score)
958
959
 
959
960
 
960
- def _declared_program_id(explicit: str | None, cfg: configuration.Config, *, deploys: bool) -> str | None:
961
- """The program an optimization names: the explicit id, else the configured one.
962
-
963
- Required when this call deploys the program, since a deployment always names its
964
- program, and refused before anything is sent. Optional when an existing
965
- deployment is reused, because that deployment already names one.
966
- """
967
- if deploys:
968
- return deployment.require_program_id(explicit, cfg)
969
- chosen = explicit if explicit is not None else cfg.program_id
970
- return identity.validate_program_id(chosen) if chosen is not None else None
971
-
972
-
973
961
  def _run_steps(
974
962
  cfg: configuration.Config,
975
963
  bundle: bytes,
@@ -1094,8 +1082,8 @@ def optimize_authored(
1094
1082
  one directory on the executor, so a path they disagree about is refused, and it is
1095
1083
  worth refusing here where both files are in reach.
1096
1084
 
1097
- ``program_id`` is :func:`optimize`'s: required when the file's program is deployed
1098
- here, optional when ``deployment_id`` names the program.
1085
+ ``program_id`` is :func:`optimize`'s: the deploy of the file's program carries it,
1086
+ and serve decides whether that deploy needs one.
1099
1087
  """
1100
1088
  check_optimizer(optimizer)
1101
1089
  config = dict(config or {})
@@ -1111,7 +1099,7 @@ def optimize_authored(
1111
1099
  _apply_budget(config, max_metric_calls=max_metric_calls, auto=auto)
1112
1100
 
1113
1101
  cfg = configuration.require_config()
1114
- program_id = _declared_program_id(program_id, cfg, deploys=prepared.program_bundle is not None)
1102
+ program_id = deployment.declared_program_id(program_id, cfg)
1115
1103
 
1116
1104
  facts = prepared.facts.program
1117
1105
  if prepared.program_bundle is not None:
@@ -460,8 +460,8 @@ def _collect_inlined_base_imports(
460
460
  if typing_extensions.is_typeddict(ann_type):
461
461
  # A TypedDict subclass reports `dict` as its base — the name its source
462
462
  # actually spells survives only in the text. Use typing_extensions for
463
- # the bare spelling because Pydantic requires it on Python 3.10 and
464
- # 3.11. Import both modules to cover either qualified spelling.
463
+ # the bare spelling because Pydantic requires it on Python 3.11.
464
+ # Import both modules to cover either qualified spelling.
465
465
  imports.add(ImportEntry("typing_extensions", "TypedDict"))
466
466
  imports.add(ImportEntry("typing_extensions"))
467
467
  imports.add(ImportEntry("typing"))
@@ -19,6 +19,11 @@ from . import _http, configuration
19
19
 
20
20
  DEFAULT_ENDPOINT = "https://platform.cmpnd.ai"
21
21
 
22
+ # Self-host release 2026-001 answers a programs create with 405, because it routes
23
+ # GET requests under /api/v1/programs/ and has no create. Every newer serve accepts
24
+ # the create, so on this one route a 405 means a serve from before program ids.
25
+ UNSUPPORTED_STATUS = 405
26
+
22
27
 
23
28
  def create(
24
29
  name: str | None = None,
@@ -61,7 +66,18 @@ def send_create(
61
66
  body["name"] = name
62
67
  if optimized_from_run_id is not None:
63
68
  body["optimized_from_run_id"] = optimized_from_run_id
64
- return client.post("/api/v1/programs", json=body)
69
+ try:
70
+ return client.post("/api/v1/programs", json=body)
71
+ except _http.ApiError as exc:
72
+ if exc.status_code == UNSUPPORTED_STATUS:
73
+ raise _http.ApiError(
74
+ "This server does not support program ids. A serve from before program ids, such as "
75
+ "self-host release 2026-001, deploys without one: run `cmpnd deploy` without "
76
+ f"--program-id.\n {exc}",
77
+ exc.exit_code,
78
+ status_code=exc.status_code,
79
+ ) from exc
80
+ raise
65
81
 
66
82
 
67
83
  def _client(api_key: str | None, endpoint: str | None) -> _http.ApiClient:
@@ -19,10 +19,15 @@ PROGRAM_ID=$(cmpnd programs create --name triage)
19
19
  ```
20
20
 
21
21
  The command reports what it created on stderr, and `--json` prints the whole
22
- registry row instead. `cmpnd deploy` requires the id. `cmpnd optimize` takes
23
- it as `--program-id` too. It is required when the command deploys the file's
24
- program first, and optional with `--deployment-id`, because that deployment
25
- already names its program. Each call creates a new program. The command never
22
+ registry row instead. `cmpnd deploy` takes the id as `--program-id`, and so
23
+ does `cmpnd optimize` when it deploys the file's program first. With
24
+ `--deployment-id`, the id is optional because that deployment already names its
25
+ program. The CLI checks a given id's shape and leaves the requirement to serve.
26
+ Every serve after self-host release 2026-001 refuses a deploy with no id, and
27
+ the CLI then names `--program-id` and `cmpnd programs create`. Self-host release
28
+ 2026-001 has no program ids, so a deploy there omits `--program-id`, and
29
+ `cmpnd programs create` reports that the server does not support them. Each
30
+ call creates a new program. The command never
26
31
  retries, because a second send would create a second program.
27
32
  `cmpnd.programs.create(name=...)` makes the same call from Python and returns
28
33
  the row.
@@ -444,7 +444,7 @@ base written inside a function belongs to no module's globals, so a bare name
444
444
  falls back to the runtime base at that position. A `TypedDict` subclass is the
445
445
  one base read off the class rather than the text, because it reports `dict`:
446
446
  `typing_extensions` supplies the name, which is what pydantic requires on Python
447
- 3.10 and 3.11.
447
+ 3.11.
448
448
 
449
449
  Two shapes refuse the package rather than deploying a program that raises
450
450
  `NameError` on its first call, which is well after deploy reported it ready:
@@ -757,11 +757,14 @@ the key is left off the wire rather than sent as `null`, so an undeclared trace
757
757
  it did before ids existed. The finalize body carries no id, because it restates no
758
758
  creation field.
759
759
 
760
- A deployment always names its program. `deploy()` and `deploy_bundle()` take
761
- `program_id=` and fall back to the configured id; with neither they raise `ValueError`
762
- before anything is packaged or sent (`deployment.require_program_id`). `optimize()` and
763
- `optimize_authored()` need the id only when they deploy the program themselves. A reused
764
- `deployment_id` already names its program, so the id is optional there. Later steps
760
+ `deploy()` and `deploy_bundle()` take `program_id=` and fall back to the configured id
761
+ (`deployment.declared_program_id`). A given id is checked for shape before anything is
762
+ packaged or sent. With no id the create body leaves the key off, and serve decides.
763
+ Every serve after self-host release 2026-001 refuses that create with a `400`, which the
764
+ SDK raises as `ValueError` naming `cmpnd programs create`. Self-host release 2026-001
765
+ has no program ids and accepts it. `optimize()` and `optimize_authored()` pass the id to
766
+ the deploy when they deploy the program themselves. A reused `deployment_id` already
767
+ names its program, so the id is optional there. Later steps
765
768
  optimize a result deployment the platform created and whose program it allocated, so
766
769
  the caller's id rides only the steps on the caller's own deployment.
767
770
 
@@ -11,7 +11,7 @@ description = "The cmpnd ingest transport: the HTTP client that pushes traces, o
11
11
  readme = "README.md"
12
12
  # `cmpnd`'s floor: these modules ship inside its wheel, so they must import on
13
13
  # every Python it supports.
14
- requires-python = ">=3.10"
14
+ requires-python = ">=3.11"
15
15
  license = {text = "MIT"}
16
16
  authors = [
17
17
  {name = "cmpnd", email = "hello@cmpnd.ai"}
@@ -5,10 +5,10 @@ name = "cmpnd"
5
5
  # it's new) — no git tags. Bump with `uv version --bump {minor,patch}` or by
6
6
  # hand. Runtime `cmpnd.__version__` reads installed metadata, so it tracks
7
7
  # this automatically. See docs/plans/2026-07-02-sdk-pypi-tag-free-publish.md.
8
- version = "0.40.1"
8
+ version = "0.42.0"
9
9
  description = "DSPy observability and deployment SDK for cmpnd"
10
10
  readme = "README.md"
11
- requires-python = ">=3.10"
11
+ requires-python = ">=3.11"
12
12
  license = {text = "MIT"}
13
13
  authors = [
14
14
  {name = "cmpnd", email = "hello@cmpnd.ai"}
@@ -19,11 +19,11 @@ classifiers = [
19
19
  "Intended Audience :: Developers",
20
20
  "License :: OSI Approved :: MIT License",
21
21
  "Programming Language :: Python :: 3",
22
- "Programming Language :: Python :: 3.10",
23
22
  "Programming Language :: Python :: 3.11",
24
23
  "Programming Language :: Python :: 3.12",
25
24
  "Programming Language :: Python :: 3.13",
26
25
  "Programming Language :: Python :: 3.14",
26
+ "Programming Language :: Python :: 3.15",
27
27
  "Topic :: Software Development :: Libraries :: Python Modules",
28
28
  ]
29
29
 
@@ -46,7 +46,7 @@ dependencies = [
46
46
  "pydantic>=2.0",
47
47
  # Detects TypedDicts from both typing and typing_extensions, and supplies
48
48
  # the spelling Pydantic requires when generated programs run on Python
49
- # 3.10 or 3.11.
49
+ # 3.11.
50
50
  "typing-extensions>=4.1.0",
51
51
  # Still required, and still the fast path — but `cmpnd/ir/xxh64.py` no
52
52
  # longer *needs* it: it falls back to a pure-Python xxh64 that the tests
@@ -56,9 +56,8 @@ dependencies = [
56
56
  # install should have it.
57
57
  "xxhash>=3.0.0",
58
58
  # Powers the interactive `cmpnd` shell (Jedi completion, multi-line,
59
- # history search, syntax highlighting). No upper cap: pip/uv resolves
60
- # 8.x on Python 3.10 and 9.x on 3.11+. The shell falls back to stdlib
61
- # `code.interact` + rlcompleter if IPython somehow can't import.
59
+ # history search, syntax highlighting). No upper cap. The shell falls back
60
+ # to stdlib `code.interact` + rlcompleter if IPython somehow can't import.
62
61
  "ipython>=8.20",
63
62
  ]
64
63
 
@@ -95,16 +94,20 @@ dev = [
95
94
  "ruff>=0.4.0",
96
95
  "basedpyright>=1.39",
97
96
  "jsonschema>=4.0",
97
+ # The tests import litellm (tests/e2e/fake_lm.py and the modules that reuse it) and
98
+ # gepa directly. dspy 3.4.1 installs both only below Python 3.15, because both declare
99
+ # requires-python <3.15. uv ignores that upper bound, so the 3.15 test leg still
100
+ # installs them.
101
+ "litellm",
102
+ "gepa",
98
103
  # litellm's bedrock/ provider imports boto3 in-process, so the client-side
99
104
  # Bedrock stress tests (committee twins) need it in the test env.
100
105
  "boto3>=1.34",
101
106
  # test_reflector_smoke.py builds numpy fixtures. dspy <=3.2.x required numpy
102
107
  # unconditionally so it rode in transitively; dspy 3.3.0 moved numpy behind a
103
108
  # `numpy` extra, so declare it directly as the test dependency it always was.
104
- # numpy 2.3 dropped Python 3.10 and is the first release with cp314 wheels. Split the
105
- # requirement so the >=3.10 lock keeps 2.2.x for 3.10 only and 3.11+ installs from wheels.
106
- "numpy>=1.26; python_version < '3.11'",
107
- "numpy>=2.3; python_version >= '3.11'",
109
+ # numpy 2.3 is the first release with cp314 wheels.
110
+ "numpy>=2.3",
108
111
  # tests/e2e/test_deploy_pandas.py deploys a program and a metric that import pandas,
109
112
  # because the worker's kernel ships it. The SDK reads a function's imports from the
110
113
  # live module before packaging it, so pandas has to be importable on the client.
@@ -124,12 +127,8 @@ dev = [
124
127
  # integration stack points programs at. Carried as an SDK extra so a single
125
128
  # `uv run --project python-sdk --extra fake-provider` brings up the SDK and the
126
129
  # provider together — no separate venv to forget about.
127
- #
128
- # It targets python >=3.11, so the marker is gated: without it, resolution for
129
- # the (3.10-supporting) SDK fails. The extra is dev-only and we always run it on
130
- # 3.11+ (bin/setup uses 3.11).
131
130
  fake-provider = [
132
- "cmpnd-fake-provider; python_version >= '3.11'",
131
+ "cmpnd-fake-provider",
133
132
  ]
134
133
 
135
134
  [tool.uv.sources]
@@ -224,7 +223,7 @@ markers = [
224
223
 
225
224
  [tool.ruff]
226
225
  line-length = 120
227
- target-version = "py310"
226
+ target-version = "py311"
228
227
 
229
228
  [tool.ruff.lint]
230
229
  select = ["E", "F", "I", "W"]
@@ -256,7 +255,7 @@ select = ["E", "F", "I", "W"]
256
255
  # no relaxations, so it gets no executionEnvironment of its own.
257
256
  include = ["cmpnd", "ingest/cmpnd_ingest", "examples", "tests/stress", "tests/e2e", "tests/load"]
258
257
  stubPath = "stubs"
259
- pythonVersion = "3.10"
258
+ pythonVersion = "3.11"
260
259
  reportAny = false
261
260
  reportExplicitAny = false
262
261
  reportUnknownMemberType = false
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bash
2
+ # Install cmpnd with pip's own resolver and check that the installed package
3
+ # imports and instruments DSPy. Called by .github/workflows/ci-sdk.yml (the
4
+ # sdk-test leg for Python 3.15).
5
+ #
6
+ # `pip install cmpnd` is the install PyPI advertises. pip honours the
7
+ # requires-python upper bounds that uv ignores, so this check fails when no
8
+ # dspy release admits the Python version. PYO3_USE_ABI3_FORWARD_COMPATIBILITY
9
+ # is set to 0, so an extension without a wheel for this Python fails the
10
+ # install and does not build from source.
11
+ #
12
+ # Usage: scripts/check-pip-install.sh <python-version> <dspy-version>
13
+ # e.g. scripts/check-pip-install.sh 3.15 3.4.1
14
+ set -euo pipefail
15
+
16
+ if [ "$#" -ne 2 ]; then
17
+ echo "usage: $0 <python-version> <dspy-version>" >&2
18
+ exit 2
19
+ fi
20
+ python_version=$1
21
+ dspy_version=$2
22
+
23
+ sdk_dir=$(cd "$(dirname "$0")/.." && pwd)
24
+ venv_dir=$(mktemp -d)
25
+ trap 'rm -rf "$venv_dir"' EXIT
26
+
27
+ uv venv --seed --python "$python_version" "$venv_dir"
28
+ PYO3_USE_ABI3_FORWARD_COMPATIBILITY=0 \
29
+ "$venv_dir/bin/python" -m pip install "$sdk_dir" "dspy==$dspy_version"
30
+
31
+ # -I keeps the checkout off sys.path, so the import reads the installed
32
+ # package and not the source tree.
33
+ cd "$venv_dir"
34
+ "$venv_dir/bin/python" -I -c "import cmpnd; cmpnd.configure(api_key='ci'); cmpnd.auto_instrument(); print('cmpnd', cmpnd.__file__)"