cmpnd 0.45.0__tar.gz → 0.46.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.46.0}/PKG-INFO +1 -1
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/__init__.py +4 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/_authored_import.py +2 -4
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/_http.py +45 -1
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/dataset_sync.py +2 -4
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/datasets.py +2 -4
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/deployment.py +20 -8
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/deployment_runs.py +1 -1
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/mcp_servers.py +2 -3
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/optimization.py +18 -21
- {cmpnd-0.45.0 → cmpnd-0.46.0}/docs/cli.md +10 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/docs/sdk-instrumentation.md +9 -0
- cmpnd-0.46.0/ingest/cmpnd_ingest/client_floor.py +101 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/ingest/cmpnd_ingest/exporter_http.py +4 -3
- {cmpnd-0.45.0 → cmpnd-0.46.0}/pyproject.toml +1 -1
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/integration/conftest.py +13 -12
- cmpnd-0.46.0/tests/test_client_floor.py +181 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_dataset_sync.py +5 -5
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_deploy.py +48 -4
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_optimize.py +93 -5
- cmpnd-0.46.0/tests/test_upload_auth_headers.py +59 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/uv.lock +1 -1
- {cmpnd-0.45.0 → cmpnd-0.46.0}/.gitignore +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/AGENTS.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/CLAUDE.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/README.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/_assessments.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/_deployments.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/_gepa_patch.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/_runtime_tools.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/_traces.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/_valsets.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/authored.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/callback.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/commands/assessments.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/commands/org.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/commands/programs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/commands/signatures.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/commands/top.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/commands/traces.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/commands/valsets.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/shell.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/cli/verbs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/client.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/configuration.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/context.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/decorators.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/deployment_metadata.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/execution.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/exporter.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/helpers.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/identity.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/imaging.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/lm_configuration.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/models.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/named_programs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/packaging.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/programs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/references.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/retry.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/cmpnd/traces.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/docs/client.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/docs/custom-proposer.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/docs/reading-traces.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/examples/local_ollama.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/ingest/README.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/ingest/cmpnd_ingest/program_names.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/ingest/pyproject.toml +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/scripts/capture_review_run.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/scripts/check-pip-install.sh +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/scripts/release-stress.sh +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/scripts/test-in-linux.sh +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/stubs/dspy/utils/dummies.pyi +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/_deployment_signature_helpers.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/_fake_clock.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/_resolve_refs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/_traced_run.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/conftest.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/helpers.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_deploy_pandas.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_deploy_programs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/e2e/test_valsets_cli.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/integration/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/integration/hosted_optimize_probe.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/integration/test_logs_renders_traces.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/load/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/load/__main__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/load/drain.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/load/ingest.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/load/rlm_size_report.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/load/sweep.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/load/test_rlm_size_report.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/parity/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/parity/conftest.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/parity/test_health.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/span_reference_fixture.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/_provider.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/_readiness.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/_result.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/test_deploy_programs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/test_multi_client_stress.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/test_optimize_loop.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/test_optimize_stress.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/test_provider_roles.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/test_readiness.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/test_real_provider_twins.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/test_rlm_wasm.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_adapter_reentrancy.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_assessments_api.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_authored.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_authored_metric_contract.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_authored_named_models.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_authored_optimize.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_callback.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_assessments.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_deployment_runs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_main.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_org.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_programs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_shell.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_signatures.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_top.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_traces.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_traces_search_wire.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cli/test_valsets.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_client.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_client_program_names.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_configuration.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_context.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_datasets.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_decision_client_spans.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_decorators.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_deploy_program_id.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_deployment_metadata.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_deployment_runs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_deployments_read.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_eval.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_eval_start.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_execute.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_execute_run_identity.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_exporter.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_exporter_program_names.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_gepa_output_normalization.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_identity.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_imaging.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_import_weight.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_integration.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_lm15_native_calls.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_lm_configuration.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_lm_usage_shapes.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_models.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_multi_client_stress_readiness.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_packaging.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_poll_until.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_program_identity.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_programs_create.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_project_payload.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_real_provider_twins_readiness.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_reflector_properties.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_release_stress_script.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_retry.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_run_attribution_paths.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_runtime_tools.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_schemas.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_signature_inputs.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_span_references.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_system_one.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_trace_context_concurrency.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_trace_fact_wire_contract.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_trace_facts.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_trace_render.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_traces_read.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_truncation.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.0}/tests/test_valsets_api.py +0 -0
- {cmpnd-0.45.0 → cmpnd-0.46.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,
|
|
@@ -17,7 +17,18 @@ from typing import Any
|
|
|
17
17
|
|
|
18
18
|
import httpx
|
|
19
19
|
|
|
20
|
-
from . import
|
|
20
|
+
from . import (
|
|
21
|
+
_http,
|
|
22
|
+
authored,
|
|
23
|
+
configuration,
|
|
24
|
+
deployment_metadata,
|
|
25
|
+
execution,
|
|
26
|
+
identity,
|
|
27
|
+
mcp_servers,
|
|
28
|
+
models,
|
|
29
|
+
packaging,
|
|
30
|
+
retry,
|
|
31
|
+
)
|
|
21
32
|
|
|
22
33
|
logger = logging.getLogger(__name__)
|
|
23
34
|
|
|
@@ -427,7 +438,7 @@ def deploy(module, metric=None, timeout=300, metadata=None, *, program_id=None):
|
|
|
427
438
|
references = _mcp_tool_references(module)
|
|
428
439
|
pinned_mcp_tools: dict[tuple[str, str], Any] = {}
|
|
429
440
|
if references:
|
|
430
|
-
with
|
|
441
|
+
with _http.new_client(timeout=httpx.Timeout(_TIMEOUT_SECONDS, connect=_CONNECT_TIMEOUT_SECONDS)) as client:
|
|
431
442
|
pinned_mcp_tools = _resolve_mcp_tool_references(client, config, references)
|
|
432
443
|
|
|
433
444
|
# Package the module
|
|
@@ -539,7 +550,7 @@ def _deploy_prepared(
|
|
|
539
550
|
"""Create, upload, and poll one fully prepared source bundle."""
|
|
540
551
|
headers = {"X-API-Key": config.api_key, "Content-Type": "application/json"}
|
|
541
552
|
|
|
542
|
-
with
|
|
553
|
+
with _http.new_client(timeout=httpx.Timeout(_TIMEOUT_SECONDS, connect=_CONNECT_TIMEOUT_SECONDS)) as client:
|
|
543
554
|
created = _send_retrying(
|
|
544
555
|
lambda: client.post(
|
|
545
556
|
f"{config.endpoint}/api/v1/deployments",
|
|
@@ -568,12 +579,13 @@ def _deploy_prepared(
|
|
|
568
579
|
)
|
|
569
580
|
for warning in answer.get("warnings") or []:
|
|
570
581
|
logger.warning("deploy: %s", warning)
|
|
582
|
+
upload_url = answer["upload_url"]
|
|
583
|
+
upload_headers = {
|
|
584
|
+
"Content-Type": "application/zip",
|
|
585
|
+
**_http.upload_auth_headers(upload_url, config.endpoint, config.api_key),
|
|
586
|
+
}
|
|
571
587
|
uploaded = _send_retrying(
|
|
572
|
-
lambda: client.put(
|
|
573
|
-
answer["upload_url"],
|
|
574
|
-
content=zip_bytes,
|
|
575
|
-
headers={"Content-Type": "application/zip"},
|
|
576
|
-
),
|
|
588
|
+
lambda: client.put(upload_url, content=zip_bytes, headers=upload_headers),
|
|
577
589
|
"upload deployment ZIP",
|
|
578
590
|
may_resend_with_no_answer=lambda _: True,
|
|
579
591
|
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
|
|
@@ -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,26 +1414,21 @@ def _initiate_step(
|
|
|
1414
1414
|
)
|
|
1415
1415
|
|
|
1416
1416
|
|
|
1417
|
-
def _upload_bundle(
|
|
1417
|
+
def _upload_bundle(
|
|
1418
|
+
client: httpx.Client, initiated: dict[str, Any], bundle: bytes, *, endpoint: str, api_key: str
|
|
1419
|
+
) -> None:
|
|
1418
1420
|
"""Upload the optimization bundle to the slot the initiate step returned.
|
|
1419
1421
|
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
- **PUT** (the on-prem worker-sandbox): no form fields — the URL *is* the
|
|
1427
|
-
slot. The bundle is the raw PUT body.
|
|
1428
|
-
|
|
1429
|
-
serve normalizes the awaiting_data response so both keys are always present
|
|
1430
|
-
(``upload_method`` defaulting to PUT, ``upload_fields`` to ``{}``), so the
|
|
1431
|
-
branch here is just "are there presigned fields to post."
|
|
1422
|
+
Serve answers an ``awaiting_data`` initiate with ``upload_url``, ``upload_method:
|
|
1423
|
+
"PUT"`` and empty ``upload_fields``, and the bundle goes out as the raw PUT body.
|
|
1424
|
+
Serve authenticates that route with the API key, which rides the PUT only when
|
|
1425
|
+
``upload_url`` is on ``endpoint``'s origin (``_http.upload_auth_headers``). A
|
|
1426
|
+
non-empty ``upload_fields`` is a presigned form, which no current server sends.
|
|
1427
|
+
The bundle then rides as the ``file`` part of a multipart POST.
|
|
1432
1428
|
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
transport blip alike.
|
|
1429
|
+
The slot pins its destination, so re-sending the same bytes writes the same
|
|
1430
|
+
object. That makes this call idempotent, so it rides through a transient answer
|
|
1431
|
+
and a transport blip alike.
|
|
1437
1432
|
"""
|
|
1438
1433
|
upload_url = initiated.get("upload_url")
|
|
1439
1434
|
if not upload_url:
|
|
@@ -1441,6 +1436,7 @@ def _upload_bundle(client: httpx.Client, initiated: dict[str, Any], bundle: byte
|
|
|
1441
1436
|
upload_fields = initiated.get("upload_fields")
|
|
1442
1437
|
method = (initiated.get("upload_method") or ("POST" if upload_fields else "PUT")).upper()
|
|
1443
1438
|
what = "upload optimization bundle"
|
|
1439
|
+
auth = _http.upload_auth_headers(upload_url, endpoint, api_key)
|
|
1444
1440
|
|
|
1445
1441
|
if upload_fields:
|
|
1446
1442
|
resp = _request(
|
|
@@ -1448,6 +1444,7 @@ def _upload_bundle(client: httpx.Client, initiated: dict[str, Any], bundle: byte
|
|
|
1448
1444
|
upload_url,
|
|
1449
1445
|
data=dict(upload_fields),
|
|
1450
1446
|
files={"file": ("data.zip", bundle, "application/zip")},
|
|
1447
|
+
headers=auth,
|
|
1451
1448
|
),
|
|
1452
1449
|
what,
|
|
1453
1450
|
)
|
|
@@ -1457,7 +1454,7 @@ def _upload_bundle(client: httpx.Client, initiated: dict[str, Any], bundle: byte
|
|
|
1457
1454
|
method,
|
|
1458
1455
|
upload_url,
|
|
1459
1456
|
content=bundle,
|
|
1460
|
-
headers={"Content-Type": "application/zip"},
|
|
1457
|
+
headers={"Content-Type": "application/zip", **auth},
|
|
1461
1458
|
),
|
|
1462
1459
|
what,
|
|
1463
1460
|
)
|
|
@@ -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.46.0"
|
|
9
9
|
description = "DSPy observability and deployment SDK for cmpnd"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.11"
|
|
@@ -27,6 +27,8 @@ import os
|
|
|
27
27
|
import httpx
|
|
28
28
|
import pytest
|
|
29
29
|
|
|
30
|
+
from cmpnd._http import upload_auth_headers
|
|
31
|
+
|
|
30
32
|
CMPND_API_KEY = os.environ.get("CMPND_API_KEY")
|
|
31
33
|
CMPND_ENDPOINT = os.environ.get("CMPND_ENDPOINT", "http://localhost:3000")
|
|
32
34
|
CMPND_STUB_URL = os.environ.get("CMPND_STUB_URL", "http://localhost:9292")
|
|
@@ -77,18 +79,17 @@ def parser_running(stub_health: dict) -> None:
|
|
|
77
79
|
|
|
78
80
|
|
|
79
81
|
def upload_deploy_zip(create_resp: dict, zip_bytes: bytes) -> None:
|
|
80
|
-
"""Upload a deploy ZIP
|
|
82
|
+
"""Upload a deploy ZIP to the PUT slot the create response names.
|
|
81
83
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
``cmpnd.deploy``'s upload step so integration tests can drive it without
|
|
86
|
-
the SDK's full deploy() wrapper.
|
|
84
|
+
Mirrors ``cmpnd.deploy``'s upload step, API key included when ``upload_url``
|
|
85
|
+
is on the endpoint's origin, so integration tests can drive it without the
|
|
86
|
+
SDK's full deploy() wrapper.
|
|
87
87
|
"""
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
upload_url = create_resp["upload_url"]
|
|
89
|
+
headers = {
|
|
90
|
+
"Content-Type": "application/zip",
|
|
91
|
+
**upload_auth_headers(upload_url, CMPND_ENDPOINT, CMPND_API_KEY or ""),
|
|
92
|
+
}
|
|
93
|
+
with httpx.Client(timeout=30.0) as client:
|
|
94
|
+
resp = client.put(upload_url, content=zip_bytes, headers=headers)
|
|
94
95
|
resp.raise_for_status()
|