cmpnd 0.45.0__tar.gz → 0.47.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.
- {cmpnd-0.45.0 → cmpnd-0.47.0}/PKG-INFO +1 -1
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/__init__.py +4 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/_authored_import.py +2 -4
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/_http.py +45 -1
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/dataset_sync.py +2 -4
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/datasets.py +2 -4
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/deployment.py +24 -13
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/deployment_runs.py +1 -1
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/mcp_servers.py +2 -3
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/optimization.py +18 -45
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/packaging.py +1 -1
- {cmpnd-0.45.0 → cmpnd-0.47.0}/docs/cli.md +10 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/docs/sdk-instrumentation.md +9 -0
- cmpnd-0.47.0/ingest/cmpnd_ingest/client_floor.py +101 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/ingest/cmpnd_ingest/exporter_http.py +4 -3
- {cmpnd-0.45.0 → cmpnd-0.47.0}/pyproject.toml +1 -1
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_deploy_programs.py +5 -7
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_optimize_server_owned_completion.py +7 -12
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/integration/conftest.py +13 -12
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/integration/test_logs_renders_traces.py +1 -2
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/integration/test_unhappy_path.py +1 -1
- cmpnd-0.47.0/tests/test_client_floor.py +181 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_dataset_sync.py +5 -5
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_deploy.py +52 -8
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_optimize.py +123 -45
- cmpnd-0.47.0/tests/test_upload_auth_headers.py +59 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/uv.lock +1 -1
- {cmpnd-0.45.0 → cmpnd-0.47.0}/.gitignore +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/AGENTS.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/CLAUDE.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/README.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/_assessments.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/_deployments.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/_gepa_patch.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/_runtime_tools.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/_traces.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/_valsets.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/authored.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/callback.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/commands/assessments.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/commands/org.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/commands/programs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/commands/signatures.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/commands/top.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/commands/traces.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/commands/valsets.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/shell.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/cli/verbs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/client.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/configuration.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/context.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/decorators.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/deployment_metadata.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/execution.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/exporter.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/helpers.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/identity.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/imaging.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/lm_configuration.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/models.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/named_programs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/programs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/references.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/retry.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/cmpnd/traces.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/docs/client.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/docs/custom-proposer.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/docs/reading-traces.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/examples/local_ollama.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/ingest/README.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/ingest/cmpnd_ingest/program_names.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/ingest/pyproject.toml +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/scripts/capture_review_run.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/scripts/check-pip-install.sh +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/scripts/release-stress.sh +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/scripts/test-in-linux.sh +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/stubs/dspy/utils/dummies.pyi +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/_deployment_signature_helpers.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/_fake_clock.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/_resolve_refs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/_traced_run.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/conftest.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/helpers.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_deploy_pandas.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/e2e/test_valsets_cli.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/integration/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/integration/hosted_optimize_probe.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/load/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/load/__main__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/load/drain.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/load/ingest.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/load/rlm_size_report.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/load/sweep.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/load/test_rlm_size_report.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/parity/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/parity/conftest.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/parity/test_health.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/span_reference_fixture.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/_provider.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/_readiness.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/_result.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/test_deploy_programs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/test_multi_client_stress.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/test_optimize_loop.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/test_optimize_stress.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/test_provider_roles.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/test_readiness.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/test_real_provider_twins.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/test_rlm_wasm.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_adapter_reentrancy.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_assessments_api.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_authored.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_authored_metric_contract.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_authored_named_models.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_authored_optimize.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_callback.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_assessments.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_deployment_runs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_main.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_org.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_programs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_shell.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_signatures.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_top.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_traces.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_traces_search_wire.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cli/test_valsets.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_client.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_client_program_names.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_configuration.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_context.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_datasets.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_decision_client_spans.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_decorators.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_deploy_program_id.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_deployment_metadata.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_deployment_runs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_deployments_read.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_eval.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_eval_start.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_execute.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_execute_run_identity.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_exporter.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_exporter_program_names.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_gepa_output_normalization.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_identity.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_imaging.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_import_weight.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_integration.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_lm15_native_calls.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_lm_configuration.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_lm_usage_shapes.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_models.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_multi_client_stress_readiness.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_packaging.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_poll_until.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_program_identity.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_programs_create.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_project_payload.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_real_provider_twins_readiness.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_reflector_properties.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_release_stress_script.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_retry.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_run_attribution_paths.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_runtime_tools.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_schemas.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_signature_inputs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_span_references.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_system_one.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_trace_context_concurrency.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_trace_fact_wire_contract.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_trace_facts.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_trace_render.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_traces_read.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_truncation.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_valsets_api.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.47.0}/tests/test_xxh64.py +0 -0
|
@@ -15,6 +15,8 @@ Usage:
|
|
|
15
15
|
import importlib.metadata
|
|
16
16
|
from typing import TYPE_CHECKING
|
|
17
17
|
|
|
18
|
+
from cmpnd_ingest.client_floor import UpgradeRequiredWarning
|
|
19
|
+
|
|
18
20
|
from . import authored, programs, traces
|
|
19
21
|
from ._http import ApiError
|
|
20
22
|
from .callback import CmpndCallback, trace_id
|
|
@@ -111,6 +113,8 @@ __all__ = [
|
|
|
111
113
|
"program",
|
|
112
114
|
"authored",
|
|
113
115
|
"ApiError",
|
|
116
|
+
# Raised once when the server no longer supports this release
|
|
117
|
+
"UpgradeRequiredWarning",
|
|
114
118
|
# Types
|
|
115
119
|
"SpanType",
|
|
116
120
|
"TraceStatus",
|
|
@@ -118,16 +118,14 @@ def _program_facts(
|
|
|
118
118
|
) -> dict[str, Any]:
|
|
119
119
|
"""Everything the deployment create call reads off the imported program."""
|
|
120
120
|
|
|
121
|
-
from . import configuration, deployment, identity, mcp_servers, packaging
|
|
121
|
+
from . import _http, configuration, deployment, identity, mcp_servers, packaging
|
|
122
122
|
from .authored import ProgramFacts
|
|
123
123
|
|
|
124
124
|
references = deployment._mcp_tool_references(program)
|
|
125
125
|
pinned: dict[tuple[str, str], Any] = {}
|
|
126
126
|
if references:
|
|
127
|
-
import httpx
|
|
128
|
-
|
|
129
127
|
config = configuration.require_config()
|
|
130
|
-
with
|
|
128
|
+
with _http.new_client(timeout=30) as client:
|
|
131
129
|
pinned = deployment._resolve_mcp_tool_references(client, config, references)
|
|
132
130
|
tool_references = [
|
|
133
131
|
{
|
|
@@ -13,8 +13,10 @@ from __future__ import annotations
|
|
|
13
13
|
import os
|
|
14
14
|
import re
|
|
15
15
|
from typing import Any
|
|
16
|
+
from urllib.parse import urlsplit
|
|
16
17
|
|
|
17
18
|
import httpx
|
|
19
|
+
from cmpnd_ingest import client_floor
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
class ApiError(Exception):
|
|
@@ -76,6 +78,48 @@ def server_detail(response: httpx.Response) -> str:
|
|
|
76
78
|
return (response.text or "").strip()[:2000]
|
|
77
79
|
|
|
78
80
|
|
|
81
|
+
def new_client(**kwargs: Any) -> httpx.Client:
|
|
82
|
+
"""An ``httpx.Client`` that warns when the server no longer supports this
|
|
83
|
+
release (:mod:`cmpnd_ingest.client_floor`).
|
|
84
|
+
|
|
85
|
+
Every client the SDK builds comes from here, so every answer the platform
|
|
86
|
+
sends gets the check. A caller's own ``event_hooks`` run after it.
|
|
87
|
+
"""
|
|
88
|
+
hooks = client_floor.event_hooks()
|
|
89
|
+
for event, callers in (kwargs.pop("event_hooks", None) or {}).items():
|
|
90
|
+
hooks.setdefault(event, []).extend(callers)
|
|
91
|
+
return httpx.Client(event_hooks=hooks, **kwargs)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
_DEFAULT_PORTS = {"http": 80, "https": 443}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _origin(url: str) -> tuple[str, str, int] | None:
|
|
98
|
+
"""A URL's (scheme, host, port), or None when it cannot be read as one."""
|
|
99
|
+
try:
|
|
100
|
+
parts = urlsplit(url)
|
|
101
|
+
port = parts.port
|
|
102
|
+
except ValueError:
|
|
103
|
+
return None
|
|
104
|
+
scheme = parts.scheme.lower()
|
|
105
|
+
if scheme not in _DEFAULT_PORTS or not parts.hostname:
|
|
106
|
+
return None
|
|
107
|
+
return scheme, parts.hostname.lower(), port if port is not None else _DEFAULT_PORTS[scheme]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def upload_auth_headers(upload_url: str, endpoint: str, api_key: str) -> dict[str, str]:
|
|
111
|
+
"""The authentication header for a PUT to an ``upload_url`` serve advertised.
|
|
112
|
+
|
|
113
|
+
Serve authenticates its upload routes with the API key. The SDK sends the URL
|
|
114
|
+
as given, so the key goes only to a URL on the configured endpoint's origin:
|
|
115
|
+
the same scheme, host and port. Any other origin gets no key.
|
|
116
|
+
"""
|
|
117
|
+
own = _origin(endpoint)
|
|
118
|
+
if not api_key or own is None or _origin(upload_url) != own:
|
|
119
|
+
return {}
|
|
120
|
+
return {"X-API-Key": api_key}
|
|
121
|
+
|
|
122
|
+
|
|
79
123
|
class ApiClient:
|
|
80
124
|
"""Thin HTTP client for the cmpnd backend API."""
|
|
81
125
|
|
|
@@ -150,7 +194,7 @@ class ApiClient:
|
|
|
150
194
|
url = f"{self._endpoint}{path}"
|
|
151
195
|
timeout = self.configured_timeout()
|
|
152
196
|
try:
|
|
153
|
-
with
|
|
197
|
+
with new_client(
|
|
154
198
|
headers={"X-API-Key": self._api_key, "Content-Type": "application/json"},
|
|
155
199
|
timeout=timeout,
|
|
156
200
|
) as client:
|
|
@@ -10,9 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
import logging
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
from . import configuration, identity, models
|
|
13
|
+
from . import _http, configuration, identity, models
|
|
16
14
|
|
|
17
15
|
logger = logging.getLogger(__name__)
|
|
18
16
|
|
|
@@ -37,7 +35,7 @@ def check_dataset_exists(dataset_hash: str) -> bool:
|
|
|
37
35
|
url = f"{config.endpoint}/api/v1/datasets/check/{dataset_hash}"
|
|
38
36
|
|
|
39
37
|
try:
|
|
40
|
-
with
|
|
38
|
+
with _http.new_client(timeout=5.0) as client:
|
|
41
39
|
response = client.get(
|
|
42
40
|
url,
|
|
43
41
|
headers={"Authorization": f"Bearer {config.api_key}"},
|
|
@@ -8,9 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
from . import configuration
|
|
11
|
+
from . import _http, configuration
|
|
14
12
|
|
|
15
13
|
|
|
16
14
|
class DatasetNotFoundError(Exception):
|
|
@@ -114,7 +112,7 @@ def _fetch_dataset(
|
|
|
114
112
|
params["version"] = str(version)
|
|
115
113
|
|
|
116
114
|
# Make request to backend
|
|
117
|
-
with
|
|
115
|
+
with _http.new_client(timeout=30.0) as client:
|
|
118
116
|
response = client.get(
|
|
119
117
|
url,
|
|
120
118
|
params=params,
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
The deploy flow:
|
|
4
4
|
1. Package the module into a source ZIP
|
|
5
|
-
2. POST to the server to create a deployment (returns
|
|
6
|
-
3. PUT the ZIP to that slot
|
|
7
|
-
worker, raw body, no form)
|
|
5
|
+
2. POST to the server to create a deployment (returns an upload slot on serve)
|
|
6
|
+
3. PUT the ZIP to that slot as the raw body
|
|
8
7
|
4. Poll until the parser finishes (status becomes "ready" or "failed")
|
|
9
8
|
5. Return the deployment_id
|
|
10
9
|
"""
|
|
@@ -17,7 +16,18 @@ from typing import Any
|
|
|
17
16
|
|
|
18
17
|
import httpx
|
|
19
18
|
|
|
20
|
-
from . import
|
|
19
|
+
from . import (
|
|
20
|
+
_http,
|
|
21
|
+
authored,
|
|
22
|
+
configuration,
|
|
23
|
+
deployment_metadata,
|
|
24
|
+
execution,
|
|
25
|
+
identity,
|
|
26
|
+
mcp_servers,
|
|
27
|
+
models,
|
|
28
|
+
packaging,
|
|
29
|
+
retry,
|
|
30
|
+
)
|
|
21
31
|
|
|
22
32
|
logger = logging.getLogger(__name__)
|
|
23
33
|
|
|
@@ -427,7 +437,7 @@ def deploy(module, metric=None, timeout=300, metadata=None, *, program_id=None):
|
|
|
427
437
|
references = _mcp_tool_references(module)
|
|
428
438
|
pinned_mcp_tools: dict[tuple[str, str], Any] = {}
|
|
429
439
|
if references:
|
|
430
|
-
with
|
|
440
|
+
with _http.new_client(timeout=httpx.Timeout(_TIMEOUT_SECONDS, connect=_CONNECT_TIMEOUT_SECONDS)) as client:
|
|
431
441
|
pinned_mcp_tools = _resolve_mcp_tool_references(client, config, references)
|
|
432
442
|
|
|
433
443
|
# Package the module
|
|
@@ -440,8 +450,8 @@ def deploy(module, metric=None, timeout=300, metadata=None, *, program_id=None):
|
|
|
440
450
|
|
|
441
451
|
deploy_body: dict[str, Any] = {
|
|
442
452
|
"program_signature_hash": identity.format_signature_hash(program_signature_hash),
|
|
443
|
-
# Declare the ZIP's exact length so the server can
|
|
444
|
-
#
|
|
453
|
+
# Declare the ZIP's exact length so the server can reject an oversized
|
|
454
|
+
# deploy before the upload.
|
|
445
455
|
"declared_content_length": len(zip_bytes),
|
|
446
456
|
}
|
|
447
457
|
# Left off when undeclared, so a serve from before program ids reads the body it
|
|
@@ -539,7 +549,7 @@ def _deploy_prepared(
|
|
|
539
549
|
"""Create, upload, and poll one fully prepared source bundle."""
|
|
540
550
|
headers = {"X-API-Key": config.api_key, "Content-Type": "application/json"}
|
|
541
551
|
|
|
542
|
-
with
|
|
552
|
+
with _http.new_client(timeout=httpx.Timeout(_TIMEOUT_SECONDS, connect=_CONNECT_TIMEOUT_SECONDS)) as client:
|
|
543
553
|
created = _send_retrying(
|
|
544
554
|
lambda: client.post(
|
|
545
555
|
f"{config.endpoint}/api/v1/deployments",
|
|
@@ -568,12 +578,13 @@ def _deploy_prepared(
|
|
|
568
578
|
)
|
|
569
579
|
for warning in answer.get("warnings") or []:
|
|
570
580
|
logger.warning("deploy: %s", warning)
|
|
581
|
+
upload_url = answer["upload_url"]
|
|
582
|
+
upload_headers = {
|
|
583
|
+
"Content-Type": "application/zip",
|
|
584
|
+
**_http.upload_auth_headers(upload_url, config.endpoint, config.api_key),
|
|
585
|
+
}
|
|
571
586
|
uploaded = _send_retrying(
|
|
572
|
-
lambda: client.put(
|
|
573
|
-
answer["upload_url"],
|
|
574
|
-
content=zip_bytes,
|
|
575
|
-
headers={"Content-Type": "application/zip"},
|
|
576
|
-
),
|
|
587
|
+
lambda: client.put(upload_url, content=zip_bytes, headers=upload_headers),
|
|
577
588
|
"upload deployment ZIP",
|
|
578
589
|
may_resend_with_no_answer=lambda _: True,
|
|
579
590
|
may_resend_after_answer=_repeatable_answer,
|
|
@@ -327,7 +327,7 @@ class _RunClient:
|
|
|
327
327
|
self.retry_after = 0.0
|
|
328
328
|
|
|
329
329
|
def __enter__(self) -> _RunClient:
|
|
330
|
-
self.client =
|
|
330
|
+
self.client = _http.new_client(headers={"X-API-Key": self.api_key})
|
|
331
331
|
return self
|
|
332
332
|
|
|
333
333
|
def __exit__(self, *_: object) -> None:
|
|
@@ -669,15 +669,14 @@ def _registered_tools(url: str, timeout_s: float) -> list[PinnedTool] | None | o
|
|
|
669
669
|
the registry route, so notebook use keeps its existing direct connection.
|
|
670
670
|
``None`` means the configured registry answered and does not know this server.
|
|
671
671
|
"""
|
|
672
|
-
from . import configuration
|
|
672
|
+
from . import _http, configuration
|
|
673
673
|
|
|
674
674
|
config = configuration.get_config()
|
|
675
675
|
if config is None:
|
|
676
676
|
return _NO_PLATFORM_REGISTRY
|
|
677
|
-
import httpx
|
|
678
677
|
|
|
679
678
|
headers = {"X-API-Key": config.api_key}
|
|
680
|
-
with
|
|
679
|
+
with _http.new_client(timeout=timeout_s) as client:
|
|
681
680
|
listing = client.get(f"{config.endpoint}/api/v1/org/tool-servers", headers=headers)
|
|
682
681
|
if listing.status_code == 404:
|
|
683
682
|
return _NO_PLATFORM_REGISTRY
|
|
@@ -11,8 +11,8 @@ Flow per :func:`optimize` call:
|
|
|
11
11
|
1. Build the optimization bundle (``trainset.ndjson`` + ``valset.ndjson`` +
|
|
12
12
|
``metric.py``) and, if no ``deployment_id`` was given, :func:`deploy` the
|
|
13
13
|
program to materialize the starting deployment A.
|
|
14
|
-
2. Step 0 — ``data:"upload"``: POST to ``/optimize/{optimizer}`` to get
|
|
15
|
-
|
|
14
|
+
2. Step 0 — ``data:"upload"``: POST to ``/optimize/{optimizer}`` to get an
|
|
15
|
+
upload slot, PUT the bundle, then poll until the step completes. The
|
|
16
16
|
result becomes a first-class child deployment B.
|
|
17
17
|
3. Steps 1..n — ``data:"reuse"`` on the latest result deployment, advancing the
|
|
18
18
|
seed each step (the key quality lever). Track the best score; stop after
|
|
@@ -1008,7 +1008,7 @@ def _run_steps(
|
|
|
1008
1008
|
current_deployment_id = deployment_id
|
|
1009
1009
|
non_improving = 0
|
|
1010
1010
|
|
|
1011
|
-
with
|
|
1011
|
+
with _http.new_client(timeout=_HTTP_TIMEOUT_SECONDS) as client:
|
|
1012
1012
|
for step in range(max_steps):
|
|
1013
1013
|
data = "upload" if step == 0 else "reuse"
|
|
1014
1014
|
step_config = dict(config)
|
|
@@ -1031,7 +1031,7 @@ def _run_steps(
|
|
|
1031
1031
|
optimization_id = initiated["optimization_id"]
|
|
1032
1032
|
|
|
1033
1033
|
if data == "upload":
|
|
1034
|
-
_upload_bundle(client, initiated, bundle)
|
|
1034
|
+
_upload_bundle(client, initiated, bundle, endpoint=cfg.endpoint, api_key=cfg.api_key)
|
|
1035
1035
|
|
|
1036
1036
|
status = _poll_until_terminal(
|
|
1037
1037
|
client,
|
|
@@ -1243,7 +1243,7 @@ def _refuse_conflicting_members(
|
|
|
1243
1243
|
headers = {"X-API-Key": cfg.api_key}
|
|
1244
1244
|
conflicting: list[str] = []
|
|
1245
1245
|
try:
|
|
1246
|
-
with
|
|
1246
|
+
with _http.new_client(timeout=_HTTP_TIMEOUT_SECONDS) as client:
|
|
1247
1247
|
listing = client.get(f"{cfg.endpoint}/api/v1/deployments/{deployment_id}/source", headers=headers)
|
|
1248
1248
|
if not listing.is_success:
|
|
1249
1249
|
return
|
|
@@ -1414,53 +1414,26 @@ def _initiate_step(
|
|
|
1414
1414
|
)
|
|
1415
1415
|
|
|
1416
1416
|
|
|
1417
|
-
def _upload_bundle(
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
upload is:
|
|
1422
|
-
|
|
1423
|
-
- **Presigned POST** (the deploy Lambda stack): ``upload_fields`` is a
|
|
1424
|
-
non-empty form the destination key is pinned by; the bundle rides as the
|
|
1425
|
-
``file`` part of a multipart POST.
|
|
1426
|
-
- **PUT** (the on-prem worker-sandbox): no form fields — the URL *is* the
|
|
1427
|
-
slot. The bundle is the raw PUT body.
|
|
1417
|
+
def _upload_bundle(
|
|
1418
|
+
client: httpx.Client, initiated: dict[str, Any], bundle: bytes, *, endpoint: str, api_key: str
|
|
1419
|
+
) -> None:
|
|
1420
|
+
"""PUT the optimization bundle, raw, to the slot the initiate step returned.
|
|
1428
1421
|
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1422
|
+
Serve answers an ``awaiting_data`` initiate with an ``upload_url`` on its own
|
|
1423
|
+
bundle route, and authenticates that route with the API key. The key rides
|
|
1424
|
+
the PUT only when ``upload_url`` is on ``endpoint``'s origin
|
|
1425
|
+
(``_http.upload_auth_headers``).
|
|
1432
1426
|
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
transport blip alike.
|
|
1427
|
+
The slot pins its destination, so re-sending the same bytes writes the same
|
|
1428
|
+
object. That makes this call idempotent, so it rides through a transient answer
|
|
1429
|
+
and a transport blip alike.
|
|
1437
1430
|
"""
|
|
1438
1431
|
upload_url = initiated.get("upload_url")
|
|
1439
1432
|
if not upload_url:
|
|
1440
1433
|
raise OptimizeError("upload step did not return an upload_url")
|
|
1441
|
-
upload_fields = initiated.get("upload_fields")
|
|
1442
|
-
method = (initiated.get("upload_method") or ("POST" if upload_fields else "PUT")).upper()
|
|
1443
1434
|
what = "upload optimization bundle"
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
resp = _request(
|
|
1447
|
-
lambda: client.post(
|
|
1448
|
-
upload_url,
|
|
1449
|
-
data=dict(upload_fields),
|
|
1450
|
-
files={"file": ("data.zip", bundle, "application/zip")},
|
|
1451
|
-
),
|
|
1452
|
-
what,
|
|
1453
|
-
)
|
|
1454
|
-
else:
|
|
1455
|
-
resp = _request(
|
|
1456
|
-
lambda: client.request(
|
|
1457
|
-
method,
|
|
1458
|
-
upload_url,
|
|
1459
|
-
content=bundle,
|
|
1460
|
-
headers={"Content-Type": "application/zip"},
|
|
1461
|
-
),
|
|
1462
|
-
what,
|
|
1463
|
-
)
|
|
1435
|
+
headers = {"Content-Type": "application/zip", **_http.upload_auth_headers(upload_url, endpoint, api_key)}
|
|
1436
|
+
resp = _request(lambda: client.put(upload_url, content=bundle, headers=headers), what)
|
|
1464
1437
|
if not resp.is_success:
|
|
1465
1438
|
body = (resp.text or "").strip()[:400]
|
|
1466
1439
|
raise OptimizeError(f"failed to {what}: HTTP {resp.status_code}: {body}")
|
|
@@ -1428,7 +1428,7 @@ def generate_source_zip(
|
|
|
1428
1428
|
) -> bytes:
|
|
1429
1429
|
"""Build a ZIP archive with program source, optional metric, and manifest.
|
|
1430
1430
|
|
|
1431
|
-
The ZIP is
|
|
1431
|
+
The ZIP is the body the deploy API's source upload takes.
|
|
1432
1432
|
"""
|
|
1433
1433
|
import dspy
|
|
1434
1434
|
|
|
@@ -32,6 +32,16 @@ retries, because a second send would create a second program.
|
|
|
32
32
|
`cmpnd.programs.create(name=...)` makes the same call from Python and returns
|
|
33
33
|
the row.
|
|
34
34
|
|
|
35
|
+
## When the server no longer supports your release
|
|
36
|
+
|
|
37
|
+
Every answer from the platform names the oldest `cmpnd` release it supports.
|
|
38
|
+
When yours is older, the CLI and the SDK warn once per process and name the
|
|
39
|
+
upgrade, `pip install -U cmpnd`. The server still answers, so an older release
|
|
40
|
+
keeps working wherever nothing it relies on has changed. To stay on an older
|
|
41
|
+
release without the warning, set `CMPND_NO_UPGRADE_WARNING=1`. In code,
|
|
42
|
+
`warnings.filterwarnings("ignore", category=cmpnd.UpgradeRequiredWarning)`
|
|
43
|
+
does the same.
|
|
44
|
+
|
|
35
45
|
## Deploy flow (Docker-style)
|
|
36
46
|
|
|
37
47
|
These commands print human-readable output by default. Each takes `--json` for
|
|
@@ -306,6 +306,10 @@ Two callers implement the waiting:
|
|
|
306
306
|
- **`exporter_http._send_json_retrying`** waits on the shutdown event rather than sleeping, so a backoff can't hold the process open at exit (and the post-shutdown drain gets no retries at all, deliberately).
|
|
307
307
|
- **`optimization._request`** clips every wait by the caller's `timeout` deadline, which stays the outer bound of `optimize()`. See [the optimize loop](#the-optimize-loop-and-transient-tolerance) below.
|
|
308
308
|
|
|
309
|
+
### The server says when this release is too old
|
|
310
|
+
|
|
311
|
+
Every API answer names the oldest cmpnd release the server supports, in the `X-Cmpnd-Minimum-Client-Version` header. `cmpnd_ingest/client_floor.py` reads it in an httpx response hook. When the installed `cmpnd` is older, it raises an `UpgradeRequiredWarning` once per process that names both versions and the command `pip install -U cmpnd`. The server refuses nothing for a client's version, so a call below the floor still goes through, and the warning appears beside whatever it does. Every client the SDK builds comes from `cmpnd._http.new_client`, which installs the hook, and the exporter installs it in `_client_kwargs`. `tests/test_client_floor.py` fails on an `httpx.Client` built any other way in `cmpnd/`. An install of `cmpnd_ingest` without `cmpnd` has no version to compare and says nothing. `CMPND_NO_UPGRADE_WARNING=1` silences the warning for a user who stays on an older release on purpose, and the warning names that switch. `cmpnd.UpgradeRequiredWarning` is public, so a `warnings` filter silences it in code.
|
|
312
|
+
|
|
309
313
|
### The optimize loop and transient tolerance
|
|
310
314
|
|
|
311
315
|
`optimization._poll_until_terminal` is what a caller blocks on for the length of a run, so one transient answer or one reset connection must not destroy a healthy run. It rides through transient answers on the shared budget, resetting after each successful poll, and never extends past the caller's `timeout` (default 600s per step) — a backoff wait is clipped by the deadline rather than overshooting it.
|
|
@@ -354,6 +358,11 @@ done.
|
|
|
354
358
|
| `PUT upload_url` (the ZIP) | retried — the slot pins the key and the bytes are fixed, so a re-send overwrites the same object | retried, for the same reason |
|
|
355
359
|
| `GET /api/v1/deployments/{id}` (poll) | retried on the next tick — a read is repeatable | retried on the next tick |
|
|
356
360
|
|
|
361
|
+
Serve authenticates its upload routes with the API key. The deploy upload and
|
|
362
|
+
the optimization bundle upload send `X-API-Key` when `upload_url` has the
|
|
363
|
+
configured endpoint's scheme, host and port (`_http.upload_auth_headers`). A URL
|
|
364
|
+
on any other origin gets no key.
|
|
365
|
+
|
|
357
366
|
After the poll reports ready, the SDK reads
|
|
358
367
|
`GET /api/v1/deployments/{id}/signature`. This bounded read returns persisted
|
|
359
368
|
artifact metadata and never asks a worker or analytics store. A failed metadata
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Tell the user when the server no longer supports this cmpnd release.
|
|
2
|
+
|
|
3
|
+
The server names the oldest cmpnd release it supports on every API answer, in
|
|
4
|
+
the ``X-Cmpnd-Minimum-Client-Version`` header. A client older than that may
|
|
5
|
+
still work, and the server refuses nothing for it, but the server promises
|
|
6
|
+
nothing either. So the first answer that names a floor above this client's
|
|
7
|
+
version raises an :class:`UpgradeRequiredWarning` that says how to upgrade. It
|
|
8
|
+
appears once per process, beside whatever the call then does.
|
|
9
|
+
|
|
10
|
+
A user who stays on an older release on purpose silences the warning with
|
|
11
|
+
``CMPND_NO_UPGRADE_WARNING=1`` in the environment, or in code with
|
|
12
|
+
``warnings.filterwarnings("ignore", category=cmpnd.UpgradeRequiredWarning)``.
|
|
13
|
+
|
|
14
|
+
The check runs as an httpx response hook (:func:`event_hooks`) on every client
|
|
15
|
+
the SDK builds, so no call site has to remember it. It compares against the
|
|
16
|
+
installed ``cmpnd`` distribution and does nothing when ``cmpnd`` is not
|
|
17
|
+
installed, because the floor is about ``cmpnd``.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import importlib.metadata
|
|
23
|
+
import os
|
|
24
|
+
import re
|
|
25
|
+
import threading
|
|
26
|
+
import warnings
|
|
27
|
+
from collections.abc import Callable
|
|
28
|
+
|
|
29
|
+
import httpx
|
|
30
|
+
|
|
31
|
+
HEADER = "X-Cmpnd-Minimum-Client-Version"
|
|
32
|
+
|
|
33
|
+
# Set to "1" to silence the warning, matching the SDK's other switches.
|
|
34
|
+
SILENCE_ENV = "CMPND_NO_UPGRADE_WARNING"
|
|
35
|
+
|
|
36
|
+
_RELEASE = re.compile(r"(\d+)\.(\d+)\.(\d+)")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class UpgradeRequiredWarning(UserWarning):
|
|
40
|
+
"""The server supports no cmpnd release older than the one it names."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
_warned: set[str] = set()
|
|
44
|
+
_lock = threading.Lock()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _reset_lock_after_fork() -> None:
|
|
48
|
+
"""Give a forked child a lock of its own.
|
|
49
|
+
|
|
50
|
+
A fork copies ``_lock`` as it stood, and a thread that held it does not exist
|
|
51
|
+
in the child, so the child's first check would block forever. The exporter
|
|
52
|
+
resets its own locks after a fork for the same reason.
|
|
53
|
+
"""
|
|
54
|
+
global _lock
|
|
55
|
+
_lock = threading.Lock()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if hasattr(os, "register_at_fork"):
|
|
59
|
+
os.register_at_fork(after_in_child=_reset_lock_after_fork)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _installed_version() -> str | None:
|
|
63
|
+
try:
|
|
64
|
+
return importlib.metadata.version("cmpnd")
|
|
65
|
+
except importlib.metadata.PackageNotFoundError:
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _release(version: str) -> tuple[int, int, int] | None:
|
|
70
|
+
match = _RELEASE.match(version.strip())
|
|
71
|
+
if match is None:
|
|
72
|
+
return None
|
|
73
|
+
major, minor, patch = match.groups()
|
|
74
|
+
return int(major), int(minor), int(patch)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def check_client_floor(response: httpx.Response) -> None:
|
|
78
|
+
"""Warn once when the answer names a floor above this client's version."""
|
|
79
|
+
floor = response.headers.get(HEADER)
|
|
80
|
+
if not floor or os.environ.get(SILENCE_ENV) == "1":
|
|
81
|
+
return
|
|
82
|
+
mine = _installed_version()
|
|
83
|
+
floor_release = _release(floor)
|
|
84
|
+
mine_release = _release(mine) if mine else None
|
|
85
|
+
if floor_release is None or mine_release is None or mine_release >= floor_release:
|
|
86
|
+
return
|
|
87
|
+
with _lock:
|
|
88
|
+
if floor in _warned:
|
|
89
|
+
return
|
|
90
|
+
_warned.add(floor)
|
|
91
|
+
warnings.warn(
|
|
92
|
+
f"cmpnd {mine} is older than the oldest release this server supports ({floor.strip()}). "
|
|
93
|
+
f"Upgrade with: pip install -U cmpnd (or set {SILENCE_ENV}=1 to stay on this release quietly)",
|
|
94
|
+
UpgradeRequiredWarning,
|
|
95
|
+
stacklevel=2,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def event_hooks() -> dict[str, list[Callable[[httpx.Response], None]]]:
|
|
100
|
+
"""The ``event_hooks`` argument that installs the check on an httpx client."""
|
|
101
|
+
return {"response": [check_client_floor]}
|
|
@@ -34,7 +34,7 @@ from typing import Any, cast
|
|
|
34
34
|
|
|
35
35
|
import httpx
|
|
36
36
|
|
|
37
|
-
from . import program_names, request_log, retry
|
|
37
|
+
from . import client_floor, program_names, request_log, retry
|
|
38
38
|
from .bodies import EncodedTraceBody, TraceBody
|
|
39
39
|
from .protocols import (
|
|
40
40
|
EvalRunRecord,
|
|
@@ -1029,8 +1029,8 @@ class BatchExporter:
|
|
|
1029
1029
|
|
|
1030
1030
|
def _client_kwargs(self) -> dict[str, Any]:
|
|
1031
1031
|
"""Shared httpx.Client construction args for the background and sync
|
|
1032
|
-
clients, so both carry the same base URL, auth header,
|
|
1033
|
-
injected transport."""
|
|
1032
|
+
clients, so both carry the same base URL, auth header, client-floor
|
|
1033
|
+
check (``client_floor``) and, in tests, injected transport."""
|
|
1034
1034
|
kwargs: dict[str, Any] = {
|
|
1035
1035
|
"base_url": self._config.endpoint,
|
|
1036
1036
|
"headers": {
|
|
@@ -1038,6 +1038,7 @@ class BatchExporter:
|
|
|
1038
1038
|
"Content-Type": "application/json",
|
|
1039
1039
|
},
|
|
1040
1040
|
"timeout": self._http_timeout,
|
|
1041
|
+
"event_hooks": client_floor.event_hooks(),
|
|
1041
1042
|
}
|
|
1042
1043
|
if self._transport:
|
|
1043
1044
|
kwargs["transport"] = self._transport
|
|
@@ -5,7 +5,7 @@ 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.
|
|
8
|
+
version = "0.47.0"
|
|
9
9
|
description = "DSPy observability and deployment SDK for cmpnd"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.11"
|
|
@@ -212,11 +212,8 @@ class TestDeployBadImportRejected:
|
|
|
212
212
|
headers = {"X-API-Key": config.api_key, "Content-Type": "application/json"}
|
|
213
213
|
|
|
214
214
|
with httpx.Client(timeout=30.0) as client:
|
|
215
|
-
# Step 1: create deployment
|
|
216
|
-
#
|
|
217
|
-
# length, so it must be declared up front — exactly as cmpnd/deploy.py
|
|
218
|
-
# does, and a deployment names its program. This hand-crafted client
|
|
219
|
-
# mirrors that contract.
|
|
215
|
+
# Step 1: create the deployment, declaring the ZIP's length and
|
|
216
|
+
# naming its program as cmpnd.deploy does.
|
|
220
217
|
create_resp = client.post(
|
|
221
218
|
f"{config.endpoint}/api/v1/deployments",
|
|
222
219
|
json={
|
|
@@ -232,11 +229,12 @@ class TestDeployBadImportRejected:
|
|
|
232
229
|
deployment_id = data["deployment_id"]
|
|
233
230
|
upload_url = data["upload_url"]
|
|
234
231
|
|
|
235
|
-
# Step 2: PUT the bad ZIP to the
|
|
232
|
+
# Step 2: PUT the bad ZIP to the upload slot, which serve
|
|
233
|
+
# authenticates with the API key.
|
|
236
234
|
upload_resp = client.put(
|
|
237
235
|
upload_url,
|
|
238
236
|
content=zip_bytes,
|
|
239
|
-
headers={"Content-Type": "application/zip"},
|
|
237
|
+
headers={"X-API-Key": config.api_key, "Content-Type": "application/zip"},
|
|
240
238
|
)
|
|
241
239
|
upload_resp.raise_for_status()
|
|
242
240
|
|
|
@@ -106,18 +106,13 @@ def _initiate_and_abandon(deployment_id: str, trainset, valset, metric) -> str:
|
|
|
106
106
|
resp.raise_for_status()
|
|
107
107
|
initiated = resp.json()
|
|
108
108
|
|
|
109
|
-
# serve
|
|
110
|
-
#
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
data=dict(upload_fields),
|
|
117
|
-
files={"file": ("data.zip", bundle, "application/zip")},
|
|
118
|
-
)
|
|
119
|
-
else:
|
|
120
|
-
up = client.put(upload_url, content=bundle, headers={"Content-Type": "application/zip"})
|
|
109
|
+
# The slot is serve's bundle route, which takes the raw bundle as a PUT
|
|
110
|
+
# and authenticates it with the API key.
|
|
111
|
+
up = client.put(
|
|
112
|
+
initiated["upload_url"],
|
|
113
|
+
content=bundle,
|
|
114
|
+
headers={"X-API-Key": cfg.api_key, "Content-Type": "application/zip"},
|
|
115
|
+
)
|
|
121
116
|
up.raise_for_status()
|
|
122
117
|
|
|
123
118
|
# Deliberately NO status poll — the server-owned watcher must finish it.
|