cmpnd 0.59.0__tar.gz → 0.60.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.59.0 → cmpnd-0.60.0}/PKG-INFO +1 -1
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/commands/programs.py +124 -18
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/verbs.py +1 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/docs/cli.md +29 -7
- {cmpnd-0.59.0 → cmpnd-0.60.0}/pyproject.toml +1 -1
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_main.py +3 -1
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_programs.py +139 -26
- {cmpnd-0.59.0 → cmpnd-0.60.0}/uv.lock +1 -1
- {cmpnd-0.59.0 → cmpnd-0.60.0}/.gitignore +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/AGENTS.md +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/CLAUDE.md +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/README.md +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/__init__.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/_assessments.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/_authored_import.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/_deployments.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/_gepa_patch.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/_http.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/_runtime_tools.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/_traces.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/_valsets.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/authored.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/callback.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/__init__.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/commands/assessments.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/commands/org.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/commands/signatures.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/commands/top.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/commands/traces.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/commands/valsets.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/shell.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/client.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/configuration.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/context.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/credentials.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/dataset_sync.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/datasets.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/decorators.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/deployment.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/deployment_metadata.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/deployment_runs.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/execution.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/exporter.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/helpers.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/identity.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/imaging.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/lm_configuration.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/mcp_servers.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/models.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/named_programs.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/optimization.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/packaging.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/programs.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/references.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/retry.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/cmpnd/traces.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/docs/client.md +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/docs/custom-proposer.md +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/docs/reading-traces.md +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/docs/sdk-instrumentation.md +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/examples/local_ollama.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/ingest/README.md +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/program_names.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/ingest/pyproject.toml +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/scripts/capture_review_run.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/scripts/check-pip-install.sh +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/scripts/install-test-env.sh +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/scripts/release-stress.sh +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/scripts/selfhost_compat.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/scripts/test-in-linux.sh +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/stubs/dspy/utils/dummies.pyi +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/__init__.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/_credential_route.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/_deployment_signature_helpers.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/_fake_clock.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/_resolve_refs.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/_traced_run.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/conftest.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/helpers.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_deploy_pandas.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_deploy_programs.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/e2e/test_valsets_cli.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/integration/__init__.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/integration/conftest.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/integration/hosted_optimize_probe.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/integration/test_logs_renders_traces.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/load/__init__.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/load/__main__.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/load/drain.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/load/ingest.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/load/rlm_size_report.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/load/sweep.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/load/test_rlm_size_report.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/parity/__init__.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/parity/conftest.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/parity/test_health.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/span_reference_fixture.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/__init__.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/_provider.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/_readiness.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/_result.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/test_deploy_programs.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/test_multi_client_stress.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/test_optimize_loop.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/test_optimize_stress.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/test_provider_roles.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/test_readiness.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/test_real_provider_twins.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/test_rlm_wasm.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_adapter_reentrancy.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_assessments_api.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_authored.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_authored_binding_credentials.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_authored_metric_contract.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_authored_named_models.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_authored_optimize.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_callback.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_assessments.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_deployment_runs.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_org.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_shell.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_signatures.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_top.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_traces.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_traces_search_wire.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cli/test_valsets.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_client.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_client_floor.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_client_program_names.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_configuration.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_context.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_credential_kwargs.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_credential_selection.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_credential_values.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_dataset_sync.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_datasets.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_decision_client_spans.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_decorators.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_deploy.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_deploy_program_id.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_deployment_metadata.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_deployment_runs.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_deployments_read.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_eval.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_eval_start.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_execute.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_execute_run_identity.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_exporter.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_exporter_program_names.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_gepa_output_normalization.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_identity.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_imaging.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_import_weight.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_integration.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_lm15_native_calls.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_lm_configuration.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_lm_usage_shapes.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_models.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_multi_client_stress_readiness.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_optimize.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_packaging.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_poll_until.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_program_id_deprecation.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_program_identity.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_program_names_in_deploy.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_programs_create.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_project_payload.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_real_provider_twins_readiness.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_reflector_properties.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_release_stress_script.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_replaced_names.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_retry.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_run_attribution_paths.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_runtime_tools.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_schemas.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_seal_lm_credential.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_signature_inputs.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_span_references.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_system_one.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_trace_context_concurrency.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_trace_fact_wire_contract.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_trace_facts.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_trace_render.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_traces_read.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_truncation.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_upload_auth_headers.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_valsets_api.py +0 -0
- {cmpnd-0.59.0 → cmpnd-0.60.0}/tests/test_xxh64.py +0 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
|
+
import json
|
|
6
7
|
import sys
|
|
7
8
|
import urllib.parse
|
|
8
9
|
from argparse import ArgumentParser, _SubParsersAction
|
|
@@ -12,8 +13,8 @@ from .. import api_client, pagination
|
|
|
12
13
|
from . import provider_credentials
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
#
|
|
16
|
-
# which the routes resolve as a *compiled* program hash — they look it up
|
|
16
|
+
# The hash that show, traces, stats, usage and select take is the value `programs optimized list`
|
|
17
|
+
# prints as `program_hash`, which the routes resolve as a *compiled* program hash — they look it up
|
|
17
18
|
# against optimization runs and map it to a program signature hash internally.
|
|
18
19
|
# The two hashes are different values, so a real signature hash 404s here. The
|
|
19
20
|
# argument help says which one it wants rather than naming the concept the
|
|
@@ -48,34 +49,74 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
48
49
|
create.add_argument("--json", action="store_true", help="Emit the created program as JSON instead of its id")
|
|
49
50
|
create.set_defaults(func=handle_create)
|
|
50
51
|
|
|
51
|
-
ls = sub.add_parser(
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
ls = sub.add_parser(
|
|
53
|
+
"list",
|
|
54
|
+
help="List your organization's programs",
|
|
55
|
+
description=(
|
|
56
|
+
"List your organization's live programs, newest first, with each program's name and p_ id. A program "
|
|
57
|
+
"lists from its creation on, before anything runs under it. With --trashed, list the Trash instead."
|
|
58
|
+
),
|
|
59
|
+
)
|
|
54
60
|
ls.add_argument(
|
|
55
61
|
"--trashed",
|
|
56
62
|
action="store_true",
|
|
57
63
|
help="List the programs in the Trash instead, with when each is purged",
|
|
58
64
|
)
|
|
59
|
-
ls.add_argument("--order-by", default="run_count", help="Sort field (default: run_count)")
|
|
60
|
-
ls.add_argument("--order", default="desc", choices=["asc", "desc"], help="Sort direction")
|
|
61
65
|
pagination.add_args(ls, with_page_aliases=True, default_limit=50)
|
|
66
|
+
# The optimized-program filters and sort moved to `programs optimized list`. They stay declared so that a
|
|
67
|
+
# command that passes one gets an error naming the command that takes it.
|
|
68
|
+
for flag, dest in [
|
|
69
|
+
("--name", "program_name"),
|
|
70
|
+
("--model", "model_name"),
|
|
71
|
+
("--order-by", "order_by"),
|
|
72
|
+
("--order", "order"),
|
|
73
|
+
]:
|
|
74
|
+
ls.add_argument(flag, dest=dest, default=None, help=argparse.SUPPRESS)
|
|
62
75
|
ls.set_defaults(func=handle_list)
|
|
63
76
|
|
|
77
|
+
# The optimized programs are the compiled programs optimization runs produced, grouped by compiled program
|
|
78
|
+
# hash. They are different records from the org's programs, so they nest, as the registry does.
|
|
79
|
+
optimized = sub.add_parser("optimized", help="Browse the optimized programs that optimization runs produced")
|
|
80
|
+
optimized_sub = optimized.add_subparsers(dest="optimized_action")
|
|
81
|
+
opt_list = optimized_sub.add_parser(
|
|
82
|
+
"list",
|
|
83
|
+
help="List optimized programs by compiled program hash",
|
|
84
|
+
description=(
|
|
85
|
+
"List the compiled programs your optimization runs produced, one row per compiled program hash, with "
|
|
86
|
+
"their run counts and scores. `programs show`, `traces`, `stats` and `usage` take the hash from the "
|
|
87
|
+
"program_hash column."
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
opt_list.add_argument("--name", dest="program_name", help="Filter by program name")
|
|
91
|
+
opt_list.add_argument("--model", dest="model_name", help="Filter by model name")
|
|
92
|
+
opt_list.add_argument("--order-by", default="run_count", help="Sort field (default: run_count)")
|
|
93
|
+
opt_list.add_argument("--order", default="desc", choices=["asc", "desc"], help="Sort direction")
|
|
94
|
+
pagination.add_args(opt_list, with_page_aliases=True, default_limit=50)
|
|
95
|
+
opt_list.set_defaults(func=handle_optimized_list)
|
|
96
|
+
|
|
64
97
|
show = sub.add_parser("show", help="Show program detail")
|
|
65
|
-
show.add_argument(
|
|
98
|
+
show.add_argument(
|
|
99
|
+
"id", help="Compiled program hash, as `programs optimized list` prints it in the program_hash column"
|
|
100
|
+
)
|
|
66
101
|
show.set_defaults(func=handle_show)
|
|
67
102
|
|
|
68
103
|
tr = sub.add_parser("traces", help="List traces for a program")
|
|
69
|
-
tr.add_argument(
|
|
104
|
+
tr.add_argument(
|
|
105
|
+
"id", help="Compiled program hash, as `programs optimized list` prints it in the program_hash column"
|
|
106
|
+
)
|
|
70
107
|
pagination.add_args(tr, with_page_aliases=False, default_limit=50)
|
|
71
108
|
tr.set_defaults(func=handle_traces)
|
|
72
109
|
|
|
73
110
|
stats = sub.add_parser("stats", help="Show a program's execution statistics")
|
|
74
|
-
stats.add_argument(
|
|
111
|
+
stats.add_argument(
|
|
112
|
+
"id", help="Compiled program hash, as `programs optimized list` prints it in the program_hash column"
|
|
113
|
+
)
|
|
75
114
|
stats.set_defaults(func=handle_stats)
|
|
76
115
|
|
|
77
116
|
usage = sub.add_parser("usage", help="Show a program's trace volume, tokens and cost over time")
|
|
78
|
-
usage.add_argument(
|
|
117
|
+
usage.add_argument(
|
|
118
|
+
"id", help="Compiled program hash, as `programs optimized list` prints it in the program_hash column"
|
|
119
|
+
)
|
|
79
120
|
usage.add_argument("--hours", type=int, default=24, help="Rolling window in hours (default %(default)s)")
|
|
80
121
|
# Constrained on purpose: an unrecognised period buckets hourly server-side
|
|
81
122
|
# rather than failing, so a typo would answer a different question silently.
|
|
@@ -132,7 +173,9 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
132
173
|
purge.set_defaults(func=handle_purge)
|
|
133
174
|
|
|
134
175
|
select = sub.add_parser("select", help="Pin an optimization run as this program's chosen version")
|
|
135
|
-
select.add_argument(
|
|
176
|
+
select.add_argument(
|
|
177
|
+
"id", help="Compiled program hash, as `programs optimized list` prints it in the program_hash column"
|
|
178
|
+
)
|
|
136
179
|
select.add_argument("--run", dest="run_id", required=True, help="Optimization run ID (op_...) to pin")
|
|
137
180
|
select.set_defaults(func=handle_select)
|
|
138
181
|
|
|
@@ -178,14 +221,24 @@ def handle_create(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
178
221
|
|
|
179
222
|
|
|
180
223
|
def handle_list(client: api_client.ApiClient, args: Any) -> Any:
|
|
224
|
+
"""List the org's live programs by name and p_ id, or the Trash with ``--trashed``."""
|
|
225
|
+
for attr, flag in [
|
|
226
|
+
("program_name", "--name"),
|
|
227
|
+
("model_name", "--model"),
|
|
228
|
+
("order_by", "--order-by"),
|
|
229
|
+
("order", "--order"),
|
|
230
|
+
]:
|
|
231
|
+
if getattr(args, attr, None) is not None:
|
|
232
|
+
from . import top
|
|
233
|
+
|
|
234
|
+
raise api_client.CliError(
|
|
235
|
+
f"{flag} filters the optimized programs, which `{top.hint_programs('optimized', 'list')}` lists. "
|
|
236
|
+
f"`{top.hint_programs('list')}` lists your organization's programs by name and p_ id."
|
|
237
|
+
)
|
|
181
238
|
if getattr(args, "trashed", False):
|
|
182
|
-
# The Trash is its own short list, unpaged and newest first, so
|
|
183
|
-
#
|
|
239
|
+
# The Trash is its own short list, unpaged and newest first, so paging does not apply. A flag left at its
|
|
240
|
+
# default cannot be told apart from one not passed.
|
|
184
241
|
for attr, flag, default in [
|
|
185
|
-
("program_name", "--name", None),
|
|
186
|
-
("model_name", "--model", None),
|
|
187
|
-
("order_by", "--order-by", "run_count"),
|
|
188
|
-
("order", "--order", "desc"),
|
|
189
242
|
("limit", "--limit", 50),
|
|
190
243
|
("offset", "--offset", 0),
|
|
191
244
|
("per_page", "--per-page", None),
|
|
@@ -194,6 +247,59 @@ def handle_list(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
194
247
|
if getattr(args, attr, default) != default:
|
|
195
248
|
raise api_client.CliError(f"{flag} does not apply to --trashed, which lists the whole Trash")
|
|
196
249
|
return client.get("/api/v1/programs/trash")
|
|
250
|
+
params = pagination.wire(args, "limit_offset", as_str=True)
|
|
251
|
+
# Read as text: a server from before the program list, such as self-host release 2026-001, redirects this
|
|
252
|
+
# path to its dispatcher, and the redirect's body is HTML.
|
|
253
|
+
try:
|
|
254
|
+
raw = client.get_text("/api/v1/programs", params=params)
|
|
255
|
+
except api_client.CliError as exc:
|
|
256
|
+
if exc.status_code not in (404, 405):
|
|
257
|
+
raise
|
|
258
|
+
return _list_recorded(client, params)
|
|
259
|
+
try:
|
|
260
|
+
listed = json.loads(raw)
|
|
261
|
+
except ValueError:
|
|
262
|
+
listed = None
|
|
263
|
+
if isinstance(listed, dict) and "programs" in listed:
|
|
264
|
+
return listed
|
|
265
|
+
return _list_recorded(client, params)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# GET /api/v1/modules on a server from before the program list answers at most this many programs
|
|
269
|
+
# (modulesPageCap in self-host release 2026-001), and nothing pages past them.
|
|
270
|
+
_RECORDED_PROGRAMS_CAP = 200
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _list_recorded(client: api_client.ApiClient, params: dict[str, str]) -> dict[str, Any]:
|
|
274
|
+
"""List the programs a server from before the program list recorded traces for, paged here.
|
|
275
|
+
|
|
276
|
+
GET /api/v1/modules answers them in one array, by name and legacy hash, up to its cap. A program with no
|
|
277
|
+
traces yet is not in it. A full array may be missing programs past the cap, so the answer then has no total
|
|
278
|
+
and says it is incomplete.
|
|
279
|
+
"""
|
|
280
|
+
rows = client.get("/api/v1/modules")
|
|
281
|
+
if not isinstance(rows, list):
|
|
282
|
+
raise api_client.CliError(
|
|
283
|
+
"this cmpnd server answered neither the program list nor the list of recorded programs"
|
|
284
|
+
)
|
|
285
|
+
complete = len(rows) < _RECORDED_PROGRAMS_CAP
|
|
286
|
+
note = "note: this cmpnd server predates the program list, so this lists the programs it recorded traces for."
|
|
287
|
+
if not complete:
|
|
288
|
+
note += f" It answers at most {_RECORDED_PROGRAMS_CAP} of them, so programs past those are missing."
|
|
289
|
+
print(note + " Upgrade the server to list every program by name and p_ id.", file=sys.stderr)
|
|
290
|
+
limit, offset = int(params["limit"]), int(params["offset"])
|
|
291
|
+
return {
|
|
292
|
+
"programs": rows[offset : offset + limit],
|
|
293
|
+
"total": len(rows) if complete else None,
|
|
294
|
+
"complete": complete,
|
|
295
|
+
"limit": limit,
|
|
296
|
+
"offset": offset,
|
|
297
|
+
"has_more": offset + limit < len(rows),
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def handle_optimized_list(client: api_client.ApiClient, args: Any) -> Any:
|
|
302
|
+
"""List the optimized programs, one row per compiled program hash."""
|
|
197
303
|
params: dict[str, str] = pagination.wire(args, "per_page", as_str=True)
|
|
198
304
|
params["order_by"] = args.order_by
|
|
199
305
|
params["order_direction"] = args.order
|
|
@@ -185,6 +185,7 @@ RELATION_NOUNS: dict[str, str] = {
|
|
|
185
185
|
"failed-examples": "the examples a compared candidate got wrong",
|
|
186
186
|
"examples": "the examples belonging to a dataset or an eval run",
|
|
187
187
|
"registry": "the registered programs or signatures, as distinct from the observed ones",
|
|
188
|
+
"optimized": "the compiled programs optimization runs produced, as distinct from the organization's programs",
|
|
188
189
|
"stats": "a program's execution statistics",
|
|
189
190
|
"usage": "a program's usage over time",
|
|
190
191
|
"openapi": "the OpenAPI document a deployment serves",
|
|
@@ -41,6 +41,27 @@ The CLI refuses `--program-id`, `cmpnd programs create` without a program
|
|
|
41
41
|
name, and its `--name`. These inputs are deprecated, and the error shows the
|
|
42
42
|
program name form.
|
|
43
43
|
|
|
44
|
+
`cmpnd programs list` lists your organization's live programs, newest first,
|
|
45
|
+
with each program's `name` and `program_id`. It reads `GET /api/v1/programs`,
|
|
46
|
+
which lists a program from its creation on, before anything runs under it. A
|
|
47
|
+
program with no name has a null `name`. The answer also holds `total`, the
|
|
48
|
+
count of live programs, and `has_more`. The command pages with `--limit` and
|
|
49
|
+
`--offset`, and needs `traces:read`.
|
|
50
|
+
|
|
51
|
+
A server from before the program list, such as self-host release 2026-001, has
|
|
52
|
+
no `GET /api/v1/programs`. Against one, `cmpnd programs list` says so on stderr
|
|
53
|
+
and lists the programs that server recorded traces for, from
|
|
54
|
+
`GET /api/v1/modules`, by name and legacy hash. A program with no traces yet is
|
|
55
|
+
not in that list. That route answers at most 200 programs, so when it is full
|
|
56
|
+
the answer has a null `total`, `complete` is false, and the note says programs
|
|
57
|
+
are missing.
|
|
58
|
+
|
|
59
|
+
`cmpnd programs optimized list` lists the compiled programs that optimization
|
|
60
|
+
runs produced, one row per compiled program hash, from
|
|
61
|
+
`GET /api/v1/programs/v2`. It takes `--name`, `--model`, `--order-by` and
|
|
62
|
+
`--order`. `cmpnd programs list` refuses those flags with an error that names
|
|
63
|
+
`cmpnd programs optimized list`.
|
|
64
|
+
|
|
44
65
|
## The Trash
|
|
45
66
|
|
|
46
67
|
A program in the Trash leaves every view, and its traces with it, until it is
|
|
@@ -281,10 +302,10 @@ Some older spellings still run as deprecated aliases and print a warning:
|
|
|
281
302
|
- `credentials add` for `create`
|
|
282
303
|
|
|
283
304
|
Every list command pages with `--limit` and `--offset`. The `traces`, `evals`,
|
|
284
|
-
`optimizations`, `deployments` and `programs` lists also
|
|
285
|
-
`--per-page` as deprecated aliases.
|
|
286
|
-
|
|
287
|
-
offsets. `deployments runs list` still accepts the deprecated `--cursor`, which
|
|
305
|
+
`optimizations`, `deployments`, `programs` and `programs optimized` lists also
|
|
306
|
+
accept `--page` and `--per-page` as deprecated aliases. Every one of those
|
|
307
|
+
except `programs list` pages by page number on the wire, so its `--offset` must
|
|
308
|
+
be a multiple of `--limit`, and the error names the nearest valid offsets. `deployments runs list` still accepts the deprecated `--cursor`, which
|
|
288
309
|
pages from a previous response's `next_cursor` and cannot be combined with
|
|
289
310
|
`--offset`.
|
|
290
311
|
|
|
@@ -333,9 +354,10 @@ The `signatures` and `programs` groups name programs differently.
|
|
|
333
354
|
hash, which identifies a program's inputs and outputs and everything recorded
|
|
334
355
|
under them: traces, examples, evals and revisions. The CLI passes an id
|
|
335
356
|
through unchanged, and serve tells the two apart by the `p_` prefix.
|
|
336
|
-
`cmpnd programs
|
|
337
|
-
|
|
338
|
-
|
|
357
|
+
`cmpnd programs show`, `traces`, `stats`, `usage` and `select` take a compiled
|
|
358
|
+
program hash, the value in the `program_hash` column of
|
|
359
|
+
`cmpnd programs optimized list`. `cmpnd programs list` prints names and ids, and
|
|
360
|
+
the Trash commands take a program name or id ("The Trash" above). Its routes resolve the hash
|
|
339
361
|
through optimization runs, so they do not find a signature hash. Each group's
|
|
340
362
|
`--help` names the column its IDs come from.
|
|
341
363
|
|
|
@@ -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.60.0"
|
|
9
9
|
description = "DSPy observability and deployment SDK for cmpnd"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.11"
|
|
@@ -103,7 +103,9 @@ class TestDevGate:
|
|
|
103
103
|
mock_response = {"programs": [], "total": 0}
|
|
104
104
|
with mock.patch("sys.argv", ["cmpnd", "programs", "list"]):
|
|
105
105
|
with mock.patch.dict(os.environ, env, clear=True):
|
|
106
|
-
with mock.patch(
|
|
106
|
+
with mock.patch(
|
|
107
|
+
"cmpnd.cli.api_client.ApiClient.get_text", autospec=True, return_value=json.dumps(mock_response)
|
|
108
|
+
):
|
|
107
109
|
main()
|
|
108
110
|
captured = capsys.readouterr()
|
|
109
111
|
output = json.loads(captured.out)
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
|
+
import json
|
|
7
|
+
from unittest import mock
|
|
6
8
|
|
|
9
|
+
import httpx
|
|
7
10
|
import pytest
|
|
8
11
|
|
|
9
|
-
from cmpnd.cli.api_client import CliError
|
|
12
|
+
from cmpnd.cli.api_client import ApiClient, CliError
|
|
10
13
|
from cmpnd.cli.commands import programs
|
|
11
14
|
from cmpnd.cli.commands.programs import (
|
|
12
|
-
handle_list,
|
|
13
15
|
handle_registry_list,
|
|
14
16
|
handle_registry_show,
|
|
15
17
|
handle_select,
|
|
@@ -28,44 +30,164 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
class TestProgramsList:
|
|
33
|
+
def test_lists_the_orgs_programs_by_limit_and_offset(self, mock_client):
|
|
34
|
+
listed = {"programs": [{"program_id": "p_0123456789Ab", "name": "triage"}], "total": 1}
|
|
35
|
+
mock_client.get_text.return_value = json.dumps(listed)
|
|
36
|
+
args = _build_parser().parse_args(["programs", "list"])
|
|
37
|
+
assert args.func(mock_client, args) == listed
|
|
38
|
+
mock_client.get_text.assert_called_once_with("/api/v1/programs", params={"limit": "50", "offset": "0"})
|
|
39
|
+
|
|
40
|
+
def test_pages_by_any_offset(self, mock_client):
|
|
41
|
+
mock_client.get_text.return_value = json.dumps({"programs": []})
|
|
42
|
+
args = _build_parser().parse_args(["programs", "list", "--limit", "10", "--offset", "5"])
|
|
43
|
+
args.func(mock_client, args)
|
|
44
|
+
assert mock_client.get_text.call_args[1]["params"] == {"limit": "10", "offset": "5"}
|
|
45
|
+
|
|
46
|
+
def test_deprecated_paging_still_works_and_warns(self, mock_client, capsys):
|
|
47
|
+
args = _build_parser().parse_args(["programs", "list", "--page", "2", "--per-page", "5"])
|
|
48
|
+
mock_client.get_text.return_value = json.dumps({"programs": []})
|
|
49
|
+
args.func(mock_client, args)
|
|
50
|
+
assert mock_client.get_text.call_args[1]["params"] == {"limit": "5", "offset": "5"}
|
|
51
|
+
assert "deprecated" in capsys.readouterr().err
|
|
52
|
+
|
|
53
|
+
@pytest.mark.parametrize(
|
|
54
|
+
"flag",
|
|
55
|
+
[["--name", "RAG"], ["--model", "gpt-4o"], ["--order-by", "best_score"], ["--order", "asc"]],
|
|
56
|
+
)
|
|
57
|
+
def test_optimized_program_flags_point_at_their_command(self, mock_client, flag):
|
|
58
|
+
args = _build_parser().parse_args(["programs", "list", *flag])
|
|
59
|
+
with pytest.raises(CliError, match=f"{flag[0]} .*`cmpnd programs optimized list`"):
|
|
60
|
+
args.func(mock_client, args)
|
|
61
|
+
mock_client.get.assert_not_called()
|
|
62
|
+
mock_client.get_text.assert_not_called()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class TestProgramsListOnAnOlderServer:
|
|
66
|
+
"""A server from before the program list, such as self-host release 2026-001, has no
|
|
67
|
+
GET /api/v1/programs. Its mux redirects the path to the dispatcher, which answers 404.
|
|
68
|
+
The CLI then lists the programs that server recorded traces for, from /api/v1/modules."""
|
|
69
|
+
|
|
70
|
+
MODULES = [
|
|
71
|
+
{"program_signature_hash": "08729442d1b3a3bf", "name": "Triage"},
|
|
72
|
+
{"program_signature_hash": "0000000000abcdef", "name": "Rag"},
|
|
73
|
+
{"program_signature_hash": "00000000000c0ffe", "name": "Summarize"},
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
def _over_the_wire(self, answers):
|
|
77
|
+
"""A real client over a transport that answers by path: `sent` collects the requests."""
|
|
78
|
+
sent: list[httpx.Request] = []
|
|
79
|
+
|
|
80
|
+
def handle(request: httpx.Request) -> httpx.Response:
|
|
81
|
+
sent.append(request)
|
|
82
|
+
return answers[request.url.path]
|
|
83
|
+
|
|
84
|
+
real = httpx.Client
|
|
85
|
+
patch = mock.patch("httpx.Client", lambda **kwargs: real(**kwargs, transport=httpx.MockTransport(handle)))
|
|
86
|
+
return sent, patch
|
|
87
|
+
|
|
88
|
+
def _list(self, answers, *argv: str):
|
|
89
|
+
sent, patch = self._over_the_wire(answers)
|
|
90
|
+
args = _build_parser().parse_args(["programs", "list", *argv])
|
|
91
|
+
with patch:
|
|
92
|
+
result = args.func(ApiClient(api_key="k", endpoint="http://serve.test"), args)
|
|
93
|
+
return sent, result
|
|
94
|
+
|
|
95
|
+
def test_a_redirect_falls_back_to_the_recorded_programs(self, capsys):
|
|
96
|
+
redirect = httpx.Response(
|
|
97
|
+
307,
|
|
98
|
+
headers={"Location": "/api/v1/programs/?limit=2&offset=1"},
|
|
99
|
+
text='<a href="/api/v1/programs/?limit=2&offset=1">Temporary Redirect</a>.',
|
|
100
|
+
)
|
|
101
|
+
sent, result = self._list(
|
|
102
|
+
{"/api/v1/programs": redirect, "/api/v1/modules": httpx.Response(200, json=self.MODULES)},
|
|
103
|
+
"--limit",
|
|
104
|
+
"2",
|
|
105
|
+
"--offset",
|
|
106
|
+
"1",
|
|
107
|
+
)
|
|
108
|
+
assert [r.url.path for r in sent] == ["/api/v1/programs", "/api/v1/modules"]
|
|
109
|
+
assert result == {
|
|
110
|
+
"programs": self.MODULES[1:3],
|
|
111
|
+
"total": 3,
|
|
112
|
+
"complete": True,
|
|
113
|
+
"limit": 2,
|
|
114
|
+
"offset": 1,
|
|
115
|
+
"has_more": False,
|
|
116
|
+
}
|
|
117
|
+
assert "predates the program list" in capsys.readouterr().err
|
|
118
|
+
|
|
119
|
+
def test_a_full_answer_has_no_total_and_says_programs_are_missing(self, capsys):
|
|
120
|
+
# Self-host release 2026-001 answers at most 200 recorded programs, and nothing pages past them.
|
|
121
|
+
recorded = [{"program_signature_hash": f"{n:016x}", "name": f"P{n}"} for n in range(200)]
|
|
122
|
+
_, result = self._list(
|
|
123
|
+
{
|
|
124
|
+
"/api/v1/programs": httpx.Response(404, json={"error": "not found"}),
|
|
125
|
+
"/api/v1/modules": httpx.Response(200, json=recorded),
|
|
126
|
+
},
|
|
127
|
+
"--limit",
|
|
128
|
+
"50",
|
|
129
|
+
"--offset",
|
|
130
|
+
"150",
|
|
131
|
+
)
|
|
132
|
+
assert result["programs"] == recorded[150:200]
|
|
133
|
+
assert result["total"] is None
|
|
134
|
+
assert result["complete"] is False
|
|
135
|
+
assert result["has_more"] is False
|
|
136
|
+
assert "at most 200" in capsys.readouterr().err
|
|
137
|
+
|
|
138
|
+
def test_a_not_found_falls_back_too(self):
|
|
139
|
+
sent, result = self._list(
|
|
140
|
+
{
|
|
141
|
+
"/api/v1/programs": httpx.Response(404, json={"error": "not found"}),
|
|
142
|
+
"/api/v1/modules": httpx.Response(200, json=self.MODULES),
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
assert [r.url.path for r in sent] == ["/api/v1/programs", "/api/v1/modules"]
|
|
146
|
+
assert result["total"] == 3
|
|
147
|
+
|
|
148
|
+
def test_a_current_server_answers_without_a_fallback(self):
|
|
149
|
+
listed = {"programs": [{"program_id": "p_0123456789Ab", "name": "triage"}], "total": 1}
|
|
150
|
+
sent, result = self._list({"/api/v1/programs": httpx.Response(200, json=listed)})
|
|
151
|
+
assert [r.url.path for r in sent] == ["/api/v1/programs"]
|
|
152
|
+
assert result == listed
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class TestProgramsOptimizedList:
|
|
31
156
|
def test_default_params(self, mock_client):
|
|
32
157
|
mock_client.get.return_value = {"programs": []}
|
|
33
|
-
args =
|
|
34
|
-
|
|
35
|
-
)
|
|
36
|
-
result = handle_list(mock_client, args)
|
|
158
|
+
args = _build_parser().parse_args(["programs", "optimized", "list"])
|
|
159
|
+
assert args.func(mock_client, args) == {"programs": []}
|
|
37
160
|
mock_client.get.assert_called_once_with(
|
|
38
161
|
"/api/v1/programs/v2",
|
|
39
162
|
params={"page": "1", "per_page": "50", "order_by": "run_count", "order_direction": "desc"},
|
|
40
163
|
)
|
|
41
|
-
assert result == {"programs": []}
|
|
42
164
|
|
|
43
165
|
def test_with_filters(self, mock_client):
|
|
44
166
|
mock_client.get.return_value = {"programs": []}
|
|
45
|
-
args =
|
|
46
|
-
|
|
167
|
+
args = _build_parser().parse_args(
|
|
168
|
+
["programs", "optimized", "list", "--name", "RAG", "--model", "gpt-4o", "--order", "asc"]
|
|
169
|
+
+ ["--limit", "10", "--offset", "10"]
|
|
47
170
|
)
|
|
48
|
-
|
|
171
|
+
args.func(mock_client, args)
|
|
49
172
|
params = mock_client.get.call_args[1]["params"]
|
|
50
173
|
assert params["program_name"] == "RAG"
|
|
51
174
|
assert params["model_name"] == "gpt-4o"
|
|
175
|
+
assert params["order_direction"] == "asc"
|
|
52
176
|
assert params["page"] == "2"
|
|
53
177
|
|
|
54
178
|
def test_deprecated_paging_still_works_and_warns(self, mock_client, capsys):
|
|
55
|
-
|
|
56
|
-
args = parser.parse_args(["programs", "list", "--page", "2", "--per-page", "5"])
|
|
179
|
+
args = _build_parser().parse_args(["programs", "optimized", "list", "--page", "2", "--per-page", "5"])
|
|
57
180
|
mock_client.get.return_value = {"programs": []}
|
|
58
|
-
|
|
181
|
+
args.func(mock_client, args)
|
|
59
182
|
params = mock_client.get.call_args[1]["params"]
|
|
60
183
|
assert params["page"] == "2"
|
|
61
184
|
assert params["per_page"] == "5"
|
|
62
185
|
assert "deprecated" in capsys.readouterr().err
|
|
63
186
|
|
|
64
187
|
def test_non_multiple_offset_errors(self, mock_client):
|
|
65
|
-
|
|
66
|
-
args = parser.parse_args(["programs", "list", "--limit", "10", "--offset", "5"])
|
|
188
|
+
args = _build_parser().parse_args(["programs", "optimized", "list", "--limit", "10", "--offset", "5"])
|
|
67
189
|
with pytest.raises(CliError):
|
|
68
|
-
|
|
190
|
+
args.func(mock_client, args)
|
|
69
191
|
|
|
70
192
|
|
|
71
193
|
class TestProgramsShow:
|
|
@@ -273,16 +395,7 @@ class TestProgramsListTrashed:
|
|
|
273
395
|
assert args.func(mock_client, args) == {"programs": [], "retention_days": 7}
|
|
274
396
|
mock_client.get.assert_called_once_with("/api/v1/programs/trash")
|
|
275
397
|
|
|
276
|
-
|
|
277
|
-
args = _build_parser().parse_args(["programs", "list", "--trashed", "--name", "x"])
|
|
278
|
-
with pytest.raises(CliError, match="--name does not apply"):
|
|
279
|
-
args.func(mock_client, args)
|
|
280
|
-
mock_client.get.assert_not_called()
|
|
281
|
-
|
|
282
|
-
@pytest.mark.parametrize(
|
|
283
|
-
"flag",
|
|
284
|
-
[["--order-by", "name"], ["--order", "asc"], ["--limit", "5"], ["--offset", "10"]],
|
|
285
|
-
)
|
|
398
|
+
@pytest.mark.parametrize("flag", [["--limit", "5"], ["--offset", "10"], ["--page", "2"]])
|
|
286
399
|
def test_paging_and_sort_do_not_apply(self, mock_client, flag):
|
|
287
400
|
args = _build_parser().parse_args(["programs", "list", "--trashed", *flag])
|
|
288
401
|
with pytest.raises(CliError, match=f"{flag[0]} does not apply"):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|