Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

parampilot-0.1.0.tar.gz (333.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

parampilot-0.1.0-py3-none-any.whl (171.7 kB view details)

Uploaded Python 3

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

Hashes for parampilot-0.1.0.tar.gz
Algorithm Hash digest
SHA256 003cc25c38a3e37f8900b2bda14332a07aa87551b900f04a7aee81c3407e4efd
MD5 cf0aaef296bd99e3afa3620cdb6cdf7b
BLAKE2b-256 90330aa69a16ec03ee99a9388b259622939691a268ce6509aa6bfd4e9202d5c7

See more details on using hashes here.

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

Hashes for parampilot-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 18bf2cf7358e579139e14587efdf4c01010ade8fa6363fa5ce3a152742ef315a
MD5 f0bec9a60fb28e61b61d121183a7bac3
BLAKE2b-256 e7f30af7cd9844fe74e48fc64cb5347f6df76c17f7b81d6dc6b1dbd569b07ba8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page