git clone https://github.com/exprtec/instantlyai-python-sdk.git
cd instantlyai-python-sdk
uv sync # installs the package + dev/codegen/docs dependency groups
uv run pre-commit install # runs ruff, ty, and the docs sync check on every commituv sync installs everything needed for development, tests, and docs (see
[dependency-groups] in pyproject.toml). No separate pip install -e .[dev] step.
pre-commit install (from Setup, above) runs lint/format/type-check automatically
on every commit. To run them on demand, or to run what CI runs exactly:
uv run pre-commit run --all-files # ruff check --fix, ruff format, ty check, docs sync check
uv run pytest # extra args forwarded, e.g. uv run pytest -k campaigns
uv run python scripts/docs.py build # sync docs, then `mkdocs build --strict`.pre-commit-config.yaml is the single source of truth for lint/format/type-check --
CI's lint job runs pre-commit run --all-files too, so there's nothing to keep in
sync by hand.
src/instantlyai/models/-- generated from Instantly's OpenAPI spec. Never hand-edit_generated.py-- re-runuv run scripts/generate_models.pyinstead (regenerates from the live spec by default; pass a local path to use a saved copy).src/instantlyai/resources/-- hand-written thin wrappers, one module per API resource group.campaigns.pyandaccounts.pyare the reference implementations; match their conventions (see the module docstring inresources/_base.py) when adding or touching a resource.docs_src/*.py-- runnable example scripts. These are executed in CI (tests/test_examples.py) and embedded intoREADME.md/docs/tutorial/*.mdvia<!-- docs_src: name.py -->markers, kept in sync byscripts/docs.py sync.tests/mirrorssrc/and usesrespxto mock httpx -- no test ever hits the live API.
- Check the endpoint's shape in the OpenAPI spec
(
https://api.instantly.ai/openapi/api_v2.json). - Add the method to both the sync and async class in the resource module, matching
the existing style (
NOT_GIVENsentinel for optional params, typed nested request bodies using generated models where they exist,JSONObject/_list[JSONObject]for genuinely untyped ad-hoc responses). - If the method is a true cursor-paginated list (
{"items": [...], "next_starting_after": ...}), name itlist, define it last in the class body, and useself._paginate(...). A class with alist()method needs the_list = listalias (see any existing resource file) to avoid a real static-analysis gotcha wherelistas a method name shadows the builtin for every other annotation in that class body. - Add or update tests in
tests/test_resources.py. - Run
uv run pre-commit run --all-filesanduv run pytestbefore opening a PR.
- Prose goes in
docs/tutorial/; generated API reference goes indocs/reference/(mkdocstrings, pulling from docstrings -- write docstrings for extraction, Google style). - Any code shown in a doc page that's meant to be runnable belongs in
docs_src/as a real.pyfile, referenced from the Markdown via<!-- docs_src: name.py -->immediately above a fenced code block. Runuv run python scripts/docs.py syncafter editing adocs_srcfile to update the embedded copies.
Single source of truth: src/instantlyai/_version.py. Releases are cut locally, not
via CI (see scripts/release.sh):
- Bump
src/instantlyai/_version.py. - Move the relevant
[Unreleased]entries inCHANGELOG.mdunder a new## [X.Y.Z] - YYYY-MM-DDheading. - Commit and merge that bump via a normal PR.
- From
main, locally:scripts/release.sh-- runs lint/tests, builds, publishes to TestPyPI, waits for you to confirm the TestPyPI install works, publishes to PyPI, then tagsvX.Y.Zand pushes the tag (which triggersdocs.ymlto deploy versioned docs viamike).
Requires TESTPYPI_TOKEN / PYPI_TOKEN API tokens (scoped to the instantlyai
project) exported in your shell -- never committed.
- Keep PRs focused; one logical change per PR.
- Update
CHANGELOG.mdunder[Unreleased]for any user-facing change. - CI must be green (
test.yml) before merge.