Skip to main content

threecommon

PyPI Python License: MIT

Official Python client for the 3Common Public API. Sync and async, fully type-checked, Pydantic v2 models.

Install

pip install threecommon

Requires Python ≥ 3.10.

Quick start (sync)

from threecommon import ThreeCommon
from threecommon.events import ListParams, UpdateBody

with ThreeCommon(api_key="3co_...") as client:
    # List
    result = client.events.list(ListParams(status="open", page_size=50))

    # Retrieve
    ev = client.events.retrieve("evt_123")

    # Update
    updated = client.events.update("evt_123", UpdateBody(name="New name"))

    # Auto-paginate
    for ev in client.events.list_auto_paginate(ListParams(status="open")):
        print(ev.name)

Quick start (async)

import asyncio
from threecommon import AsyncThreeCommon
from threecommon.events import ListParams

async def main() -> None:
    async with AsyncThreeCommon(api_key="3co_...") as client:
        result = await client.events.list(ListParams(status="open"))
        async for ev in client.events.list_auto_paginate(ListParams(status="open")):
            print(ev.name)

asyncio.run(main())

The API key may also be supplied via the THREECOMMON_API_KEY environment variable.

Configuration

from threecommon import ThreeCommon, RetryDelay

client = ThreeCommon(
    api_key="3co_...",                        # required (or via env var)
    base_url="https://api.3common.com",       # default
    api_version="2026-04-29",                 # pinned API version
    timeout_seconds=30.0,                     # per-request deadline
    max_retries=3,                            # automatic retries on 408/425/429/5xx
    retry_delay=RetryDelay(
        initial_seconds=0.5,
        max_seconds=8.0,
        jitter=True,
    ),
    telemetry=True,                           # opt-out of anonymous telemetry
)

Error handling

Every error raised by the SDK inherits from threecommon.APIError. Catch the typed subclass you care about:

from threecommon import (
    NotFoundError,
    AuthError,
    RateLimitError,
    ConnectionError,
)

try:
    client.events.retrieve("evt_missing")
except NotFoundError as e:
    # 404 — e.request_id, e.code, e.details
    ...
except AuthError as e:
    # 401 — bad or expired API key
    ...
except RateLimitError as e:
    # 429 — e.retry_after_seconds tells you when to retry
    ...
except ConnectionError as e:
    # network error; original cause via e.__cause__
    ...

Every error carries code, message, http_status, request_id, details, and raw_response. The default str(e) format includes the request ID for log correlation:

[not_found] Event evt_missing not found (request_id=req-dfx-abc)

Pagination

Two flavors:

# One page at a time
result = client.events.list(ListParams(page_size=50))

# All pages, lazy
for ev in client.events.list_auto_paginate(ListParams(status="open")):
    print(ev.name)

# Async
async for ev in async_client.events.list_auto_paginate(ListParams(status="open")):
    print(ev.name)

Filters

The filters subpackage provides a typed builder for the API's filters query parameter — never write the JSON by hand:

from threecommon import filters
from threecommon.events import ListParams

f = filters.and_(
    filters.field("status").is_any_of(["open"]),
    filters.field("ticket_sum").is_greater_than(10),
)

result = client.events.list(ListParams(filters=f.serialize()))

The full operator set is enumerated in threecommon.filters.types.

Retries

Idempotent methods (GET, PATCH, PUT) retry automatically on 408, 425, 429, 500, 502, 503, 504 and on network errors. Backoff is exponential with full jitter, capped at RetryDelay.max_seconds. The SDK honors a server-provided Retry-After header on 429.

POST and DELETE do not retry by default; pass an idempotency_key via per-request options to opt in (forward-compat — no v1 endpoints currently use this).

Telemetry

The SDK sends a small, anonymized Threecommon-Client-Telemetry header on every request (SDK version, language, last-request latency). This helps debug performance reports from real customers without instrumenting their code. Disable globally:

client = ThreeCommon(api_key="...", telemetry=False)

Or at runtime:

client.disable_telemetry()

The header never contains your API key, request bodies, or response bodies.

Repository layout

The package layout mirrors the Node and Go SDKs so behavior changes can be paired across languages:

sdk-python/
├── pyproject.toml
├── src/threecommon/
│   ├── __init__.py             # public surface re-exports
│   ├── py.typed                # PEP 561 marker
│   ├── client.py               # ThreeCommon + AsyncThreeCommon
│   ├── config.py               # ClientConfig, RetryDelay, defaults
│   ├── api_version.py          # pinned API version + path
│   ├── version.py              # SDK package version
│   ├── helpers.py              # small utility helpers
│   ├── errors/                 # exception tree
│   │   ├── base.py             # APIError
│   │   └── classes.py          # AuthError, NotFoundError, RateLimitError, ...
│   ├── pagination/             # auto-paginating iterators
│   │   └── auto_paginator.py   # Iter[T] + AsyncIter[T]
│   ├── filters/                # typed filter builder (shared across resources)
│   ├── events/                 # events resource (sync + async + Pydantic types)
│   ├── invoices/               # invoices resource (sync + async + Pydantic types)
│   ├── _core/                  # private HTTP machinery (decomposed)
│   └── _generated/             # datamodel-code-generator output (re-run via `make gen`)
├── examples/
│   ├── events/
│   └── invoices/
└── tests/

Development

The project uses uv for venv + dependency management, ruff for lint and format, mypy and pyright for type checking, and pytest for testing.

Set up the dev environment

macOS and Linux:

# One-time: create a venv, activate, install runtime deps + dev tools
uv venv --python 3.10 .venv
source .venv/bin/activate
uv pip install -e ".[dev]"

# Verify:
uv pip list | grep threecommon       # should print: threecommon  0.0.0.dev0  /path/to/sdk-python
pytest -q                            # all tests pass

Windows:

# One-time: create a venv, activate, install runtime deps + dev tools
uv venv --python 3.10 .venv
.\.venv\Scripts\activate.ps1
uv pip install -e ".[dev]"

# Verify:
uv pip list | Select-String threecommon # should print: threecommon  0.0.0.dev0  \path\to\sdk-python
pytest -q                               # all tests pass

If activation fails with an execution-policy error, run Set-ExecutionPolicy -Scope CurrentUser RemoteSigned once and retry.

Note: with the virtual environment active, all further bash snippets should work as-is in PowerShell on Windows, except where a separate PowerShell snippet is provided.

Run tests

pytest                                              # all tests
pytest tests/test_events.py                         # one file
pytest tests/test_events.py::test_list_decodes_response   # one test
pytest -k "conformance"                             # match by name
pytest -q                                           # quiet output

The conformance harness (tests/test_conformance.py) parametrizes over the shared YAML scenarios at ../conformance/scenarios/*.yaml and runs each one against both the sync and async clients.

Coverage

pytest --cov=src/threecommon --cov-report=term      # term summary
pytest --cov=src/threecommon --cov-report=html      # HTML report at htmlcov/
pytest --cov=src/threecommon --cov-fail-under=90    # CI gate (≥ 90% line + branch)

Lint and format

ruff check .                       # lint
ruff check --fix .                 # auto-fix
ruff format .                      # format
ruff format --check .              # CI-style check (no changes)

Type check

Both run in CI; either failing blocks the PR.

mypy src/threecommon tests scripts        # mypy --strict via pyproject
pyright src/threecommon scripts           # pyright with project config

Regenerate OpenAPI models

src/threecommon/_generated/models.py is produced from ../openapi/spec.yaml. Re-run after every spec update:

macOS and Linux:

datamodel-codegen \
  --input ../openapi/spec.yaml \
  --input-file-type openapi \
  --output src/threecommon/_generated/models.py \
  --output-model-type pydantic_v2.BaseModel \
  --target-python-version 3.10 \
  --use-standard-collections --use-union-operator --use-double-quotes \
  --field-constraints --use-schema-description --capitalise-enum-members \
  --reuse-model --openapi-scopes paths schemas parameters

Windows:

datamodel-codegen `
  --input ..\openapi\spec.yaml `
  --input-file-type openapi `
  --output .\src\threecommon\_generated\models.py `
  --output-model-type pydantic_v2.BaseModel `
  --target-python-version 3.10 `
  --use-standard-collections --use-union-operator --use-double-quotes `
  --field-constraints --use-schema-description --capitalise-enum-members `
  --reuse-model --openapi-scopes paths schemas parameters

The generated package is treated as a contract reference; customer-facing types are hand-curated under src/threecommon/<resource>/types.py.

Live smoke (maintainer-only)

macOS and Linux:

THREECOMMON_API_KEY=3co_real_key \
SMOKE_EVENT_ID=evt_known \
python scripts/livesmoke.py

Windows:

$env:THREECOMMON_API_KEY = "3co_real_key"
$env:SMOKE_EVENT_ID = "evt_known"
python .\scripts\livesmoke.py

Runs ≤ 10 real API calls and verifies the happy path + 401/404 error paths. Set THREECOMMON_BASE_URL to override the default https://api.3common.com.

Build a wheel locally

uv build            # produces sdist + wheel under dist/

Versioning

The SDK follows SemVer. The pinned API version (sent as Threecommon-Version) is independent — the API can evolve without breaking already-deployed SDKs. Bump api_version to opt into newer server behavior.

PyPI distribution: threecommon. Tags use the path-prefixed form sdk-python/vX.Y.Z to share the monorepo with the Node and Go SDKs.

Examples

End-to-end runnable examples live under examples/events/:

python examples/events/list_sync.py
python examples/events/list_async.py
python examples/events/retrieve.py
python examples/events/update.py
python examples/events/auto_paginate.py
python examples/events/error_handling.py
python examples/events/filters_demo.py

Replace 3co_your_api_key_here and evt_replace_with_real_id with real values before running.

Contributing

See the repository CONTRIBUTING guide. Issues and PRs welcome.

License

MIT

Release files for threecommon 0.12.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for threecommon 0.12.0
File Size Uploaded
threecommon-0.12.0.tar.gz 54.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for threecommon 0.12.0
File Interpreter ABI Platform
threecommon-0.12.0-py3-none-any.whl Python 3 none any Details

Total release size: 132.7 kB

Release files / threecommon-0.12.0.tar.gz

Download URL threecommon-0.12.0.tar.gz
Size 54.8 kB
Tags Source
SHA-256 checksum
How to use checksums
bc26fb1850e066ba1b27cdaff9e144d785dd2e84ce3ee9058a8f8d183a99413c
BLAKE2b-256 checksum
How to use checksums
2c65741ad48af165de081a198e56d334c3eecbf32854d6f1679e589398a73129
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 30, 2026.

Transparency log

Release files / threecommon-0.12.0-py3-none-any.whl

Download URL threecommon-0.12.0-py3-none-any.whl
Size 77.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5fdc307867e353c217b7ddad6e77ece7d330907ef879d67bc2a6190275535f9c
BLAKE2b-256 checksum
How to use checksums
f7849bf59860dae71c50d8f4b9879a3329c9af884e7b92e71e7481df6c15d695
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 30, 2026.

Transparency log

Release history Release notifications | RSS feed

0.13.0

2 release files

This release

0.12.0 This release

2 release files

0.11.0

2 release files

0.10.0

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page