Typed synchronous and asynchronous Python SDK for the ParamPilot API
Project description
ParamPilot Python SDK
parampilot is the typed public Python client for the ParamPilot programmatic
API. It provides native asynchronous and synchronous clients backed by HTTPX
and validates JSON contracts with Pydantic.
The SDK is standalone: installing it does not install or import the private ParamPilot backend, worker, Django, or the optimization runtime.
Start with the sync-first quickstart, then see workflows, jobs, and recovery.
Installation
python -m pip install parampilot
Authentication
Create a programmatic API token in the ParamPilot account interface. The raw
token is shown only when it is created. Pass it explicitly or store it in the
PARAMPILOT_API_TOKEN environment variable; the SDK never creates, persists,
or displays tokens.
Synchronous quick start
from parampilot import ParamPilot, TrainingRequiredError
from parampilot.models import CampaignCreateRequest
with ParamPilot(base_url="https://your-parampilot.example") as client:
campaign = client.campaigns.create(
CampaignCreateRequest(name="Esterification")
)
for summary in client.campaigns.iterate(limit=100):
print(summary.name)
try:
client.model_jobs.create_ask_job(campaign.id, n=5)
except TrainingRequiredError as error:
print(error.context["model_state"])
ParamPilot is a native blocking client backed by httpx.Client; it does not
create or manage an asyncio event loop. It exposes the same resource names,
method parameters, generated models, errors, retries, pagination, downloads,
and compatibility behavior as AsyncParamPilot. Blocking the calling thread
is expected; use AsyncParamPilot for high-concurrency applications. Both
clients accept the same timeout, max_retries, and retry_backoff options.
Asynchronous quick start
import asyncio
from parampilot import AsyncParamPilot
async def main() -> None:
"""Print the connected ParamPilot API version."""
async with AsyncParamPilot(
base_url="https://your-parampilot.example"
) as client:
availability = await client.get_availability()
print(availability.api_version)
asyncio.run(main())
Both clients expose typed resources for campaigns and configuration, campaign access and transfer links, experiments and effective-data exports, model jobs and observations, and model artifacts. Request bodies are generated Pydantic models; JSON responses are generated Pydantic results. The async equivalent of the synchronous resource example is:
from parampilot import AsyncParamPilot, TrainingRequiredError
from parampilot.models import CampaignCreateRequest
async def create_and_list() -> None:
"""Create a draft, then lazily list visible campaigns."""
async with AsyncParamPilot(
base_url="https://your-parampilot.example"
) as client:
campaign = await client.campaigns.create(
CampaignCreateRequest(name="Esterification")
)
async for summary in client.campaigns.iterate(limit=100):
print(summary.name)
try:
await client.model_jobs.create_ask_job(campaign.id, n=5)
except TrainingRequiredError as error:
print(error.context["model_state"])
Model training is never implicit. Experiment changes, candidate requests, and
predictions do not train a model. Only methods and workflows whose names
explicitly contain train or training may request training.
The sole training submission is a deliberate, visibly named call in either client:
training_job = client.model_jobs.train_model(campaign_id) # ParamPilot
training_job = await async_client.model_jobs.train_model( # AsyncParamPilot
campaign_id
)
Waiting for model jobs
Submission remains nonblocking by default. Set wait=True only when the
calling thread or coroutine should wait for the typed terminal result:
train_result = client.model_jobs.train_model(
campaign_id,
wait=True,
timeout=600,
)
ask_result = client.model_jobs.create_ask_job(
campaign_id,
n=5,
wait=True,
)
The asynchronous methods accept the same controls and are awaited. Ask and
Predict waiting never submit training; if the campaign needs a current model,
they preserve the server's TrainingRequiredError.
Use client.model_jobs.wait(campaign_id, job_id, ...) for an already submitted
job. Its progress callback receives validated PublicModelJobObservation
objects only when lifecycle, structured progress, liveness, or terminal state
meaningfully changes. A local timeout, KeyboardInterrupt, or asyncio task
cancellation leaves the remote job running. Remote cancellation occurs only
through client.model_jobs.cancel(...) or an explicit cancel_remote=True.
TrainingJobHandle, AskJobHandle, and PredictJobHandle are frozen,
serializable references containing only campaign ID, job ID, and job kind. Pass
a currently open client to their refresh, wait, result, or cancel
methods; the handles never retain or serialize a token or HTTP client.
Failed, canceled, timed-out, authentication-lost, compatibility-lost, generic
polling, and progress-callback failures use distinct JobWaitError subclasses.
They retain the public job identifiers and last validated observation for safe
recovery. Callback failures abort only the local wait unless remote cancellation
was explicitly requested.
Explicit composite workflow
The ergonomic one-call flow keeps training visible in its name:
result = client.workflows.add_experiments_train_and_ask(
campaign_id,
experiments,
n=5,
idempotency_key="optimization-round-0001",
)
It validates locally, atomically upserts the experiment batch, explicitly
submits and waits for Train, submits and waits for non-training Ask, then
returns complete suggested ExperimentResponse values in Ask order. The call
is not an atomic server transaction. Its typed checkpoint records partial
completion, deterministic operation subkeys, and Train/Ask job IDs.
AddExperimentsTrainAndAskError carries that checkpoint. Resume without
duplicating accepted rows, job submissions, or completed training:
result = client.workflows.resume_add_experiments_train_and_ask(
error.checkpoint,
experiments,
n=5,
)
Progress events distinguish upload, training, Ask, suggestion retrieval, and terminal completion. Callback failure never retries or remotely cancels work. The async methods expose the same contract and are awaited.
Collection list(...) methods fetch one typed bounded page. Matching
iterate(...) methods follow opaque cursors lazily without prefetching. Binary
exports and grid artifacts return Download or AsyncDownload; iterate their
chunks, call read() only when explicit buffering is intended, or stream to a
caller-chosen path:
download = client.experiments.export(campaign_id, format="csv")
download.write_to("experiments.csv")
download = await client.experiments.export(campaign_id, format="csv")
await download.write_to("experiments.csv")
Existing-resource mutations require the ETag returned by a metadata-aware read:
response = await client.campaigns.get_with_metadata(campaign_id)
updated = await client.campaigns.start(
campaign_id,
if_match=response.require_etag(),
)
Validate a complete configured-create request without creating a campaign, idempotency record, model job, or artifact:
validation = client.campaigns.validate_configured(configured_request)
canonical_request = validation.canonical_request
Validation returns the backend's canonical request and payload/schema digests;
it is not a receipt, so create_configured(...) validates again. Access-grant
mutations use the full grant-set revision returned by
client.campaign_access.list_with_metadata(campaign_id).require_etag().
Call create_configured_with_metadata(...),
replace_domain_with_metadata(...), or
replace_strategy_with_metadata(...) when the caller also needs the safe
request ID, returned ETag, or Idempotency-Replayed header. Their existing
non-metadata variants remain convenience methods returning CampaignResponse.
The same metadata contract is available for routine writes through
create_with_metadata(...), replace_additional_fields_with_metadata(...),
update_settings_with_metadata(...), batch_upsert_with_metadata(...), and
patch_with_metadata(...); the batch response may omit an ETag because it does
not replace one versioned resource.
High-impact writes likewise expose start_with_metadata(...),
unlock_with_metadata(...), archive_with_metadata(...),
replace_effects_with_metadata(...), access-grant
upsert_with_metadata(...)/delete_with_metadata(...), transfer-link
create_with_metadata(...)/delete_with_metadata(...), and experiment
delete_with_metadata(...). Empty archive and grant-delete responses retain
safe request/replay headers with data=None; existing methods keep their
original return values.
Nonwaiting model-job writes expose replay and request metadata through
train_model_with_metadata(...), create_ask_job_with_metadata(...),
create_prediction_job_with_metadata(...), and cancel_with_metadata(...).
Their existing convenience methods keep returning the typed job directly.
The client generates idempotency keys unless one is supplied, retries only
generated operations classified as safe reads or keyed mutations, never
follows redirects, and maps canonical errors to public exception types. The
default compatibility mode warns on a same-major schema-digest difference;
pass schema_compatibility="strict" to require the exact generated contract or
call check_compatibility(...) with required capability names explicitly.
ParamPilot.request(...) and AsyncParamPilot.request(...) are intentionally
untyped escape hatches. They are restricted to rooted /papi/v2/ paths and
retain the SDK's authentication, redirect, timeout, error, and redaction
behavior.
Development
The package uses uv and stores pytest configuration in pytest.toml:
uv sync --all-groups --locked
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run mypy
uv audit --locked
uv build --no-build-isolation
Generated contracts are derived solely from the committed public OpenAPI artifact. Generated files must not be edited by hand.
See contributing for the complete local quality gates, release and compatibility for the audited history-isolated extraction and rollback workflow, security for private vulnerability reporting, and the changelog for release contents.
To synchronize the private backend handoff and regenerate every public model, operation, export, and provenance artifact:
uv run python -m parampilot_codegen \
--schema-source <parampilot-backend>/schemas/programmatic-openapi.json
uv run python -m parampilot_codegen --check
License
Apache-2.0. See LICENSE and NOTICE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file parampilot-0.1.0.tar.gz.
File metadata
- Download URL: parampilot-0.1.0.tar.gz
- Upload date:
- Size: 333.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
003cc25c38a3e37f8900b2bda14332a07aa87551b900f04a7aee81c3407e4efd
|
|
| MD5 |
cf0aaef296bd99e3afa3620cdb6cdf7b
|
|
| BLAKE2b-256 |
90330aa69a16ec03ee99a9388b259622939691a268ce6509aa6bfd4e9202d5c7
|
File details
Details for the file parampilot-0.1.0-py3-none-any.whl.
File metadata
- Download URL: parampilot-0.1.0-py3-none-any.whl
- Upload date:
- Size: 171.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
18bf2cf7358e579139e14587efdf4c01010ade8fa6363fa5ce3a152742ef315a
|
|
| MD5 |
f0bec9a60fb28e61b61d121183a7bac3
|
|
| BLAKE2b-256 |
e7f30af7cd9844fe74e48fc64cb5347f6df76c17f7b81d6dc6b1dbd569b07ba8
|