Skip to content

Latest commit

 

History

History
97 lines (60 loc) · 7.4 KB

CONTRIBUTING.md

File metadata and controls

97 lines (60 loc) · 7.4 KB

Contributing to the Didit Python SDK

Thank you for helping. Read docs/DESIGN.md first: it is the shared contract of all seven Didit server SDKs, and when the code and the design disagree, the code is wrong.

Setup

You need uv. It installs the Python version from .python-version and the locked dependencies from uv.lock.

uv sync --frozen

Run every command with uv run so that it uses the project environment.

The two layers

Generated layer: src/didit/types, src/didit/resources and tests/generated. These files are produced by codegen/ from the OpenAPI documents in openapi/ and start with Code generated by codegen from openapi/*.json. DO NOT EDIT. Never edit them by hand. To change them, change the generator in codegen/ or openapi/overlay.json, then regenerate:

uv run python codegen/generate.py

CI runs uv run python codegen/generate.py --check, which regenerates into a temporary directory and fails when the result differs from what is committed. A hand edit, or a generator or overlay change committed without regenerating, fails that check.

Core layer: everything else under src/didit (client, transport, retries, errors, pagination, files, webhooks, Authentication). It is hand-written, small and fully tested.

The OpenAPI documents

  • openapi/verification.json, openapi/account.json and openapi/webhook-vectors.json are upstream copies. Never edit them by hand; only the spec sync workflow (.github/workflows/spec-sync.yml) updates them.
  • openapi/overlay.json holds the SDK naming (x-didit-sdk, DESIGN section 4) for operations whose upstream definition does not carry it yet. Keys are "<METHOD> <path>" for verification.json and "account <METHOD> <path>" for account.json; each value has resource (dot path of snake_case names, one nested accessor per segment), method, auth, server and, for application-scoped paths, scope. When upstream carries x-didit-sdk for an operation, the upstream value wins and the overlay entry is deleted.
  • A new server operation without SDK naming makes the generator fail with a message that names the operation. The fix is an overlay entry (or x-didit-sdk upstream).

The spec sync runs daily. When upstream changed, it regenerates the code, runs the checks and opens a pull request titled chore(spec): sync API spec YYYY-MM-DD. Its body lists added, removed, renamed and changed operations and schemas; field-level differences (nested fields, types, enum values, nullability, required fields) that can break code written against the SDK are listed under "Breaking changes". A download that is not an OpenAPI 3 document with paths is refused and nothing is written.

When a sync fails a check, the pull request opens as a draft; push the fix (usually an openapi/overlay.json entry) to its branch chore/spec-sync. While that branch carries a commit that is not from the sync, later syncs leave it alone, so the fix is never overwritten: merge or close the pull request and the next sync picks up anything that changed since.

Deprecations

The sync never deletes a public method silently (DESIGN section 3.1). When an operation is removed upstream, or its x-didit-sdk naming changes (a rename), codegen/spec_sync.py records the old method in openapi/deprecated-operations.json, keyed by resource.method, with a dereferenced copy of the operation and remove_in, the next minor version. The generator keeps it as a method marked @deprecated (a renamed method's old name calls the same endpoint as the new one). An operation that moves to another path with the same naming is not deprecated; the summary compares the old and new operation. Two cases are breaking and are not kept: a name that now belongs to a different operation (swapped or reused), and an operation that becomes a device or end-user token operation (server: false), which server SDKs never expose. An operation that loses its naming makes the generator fail until openapi/overlay.json names it again.

  • When the release that reaches remove_in is prepared (the release-please pull request), tests/test_contract.py fails until the entry is deleted: delete it, run uv run python codegen/generate.py and push to the release pull request.
  • If a deprecated name becomes live again (for example an overlay entry gives a new path the old name), the generator says so; delete the entry.

Checks

CI runs all of these; run them before you open a pull request.

Check Command
Lint uv run ruff check .
Formatting uv run ruff format --check . (fix with uv run ruff format .)
Types uv run mypy and uv run pyright
Generated code uv run python codegen/generate.py --check
Tests uv run pytest
Core coverage (90 % or more) uv run pytest --cov --cov-report=term-missing --cov-fail-under=90
Build uv build

Unit tests never touch the network. Fixtures are synthetic: never commit API keys, secrets or real customer data. CI also scans the full git history with gitleaks (.gitleaks.toml).

Live smoke tests

tests/test_live.py talks to a Didit sandbox application and runs only when DIDIT_SANDBOX_API_KEY is set:

DIDIT_SANDBOX_API_KEY=... uv run pytest -m live

Optional: DIDIT_SANDBOX_WORKFLOW_ID (enables the session test) and DIDIT_SANDBOX_BASE_URL. They never run in the default CI. A maintainer can start them from the CI workflow (Run workflow) when the repository secret is set.

Commits, versions and the changelog

Pull request titles and commits follow Conventional Commits: fix: is a patch, feat: a minor version, and feat!: or a BREAKING CHANGE: footer a major version (a minor version while the SDK is 0.x). Pick the type that matches the semver impact of the change.

Releases are cut by release-please from those commits. It updates the version and generates CHANGELOG.md; never edit the changelog by hand. Squash-merge pull requests so that their conventional title becomes the commit release-please reads. .release-please-manifest.json holds the released version.

release-please runs on every push to main. Merging the release pull request is the release approval (Alex, 2026-10-29); nobody tags or publishes by hand. The next run creates the vX.Y.Z tag and the GitHub release and starts release.yml, which publishes to PyPI with trusted publishing (no stored token).

Keep the SDKs in sync

Didit has seven server SDKs: sdk-python, sdk-node, sdk-go, sdk-java, sdk-php, sdk-ruby and sdk-dotnet.

  • Any public API change in service-didit-verification or service-didit-auth must reach the OpenAPI documents in mintlify-docs, and from there every server SDK through the spec sync pull request.
  • A change to hand-written core behaviour that DESIGN.md describes (client options, errors, retries, pagination, webhooks, Authentication) is made in all seven repositories, one pull request each, and DESIGN.md is updated in all seven in the same words.
  • Feature parity: a capability shipped in one server SDK ships in the other six.

Every pull request states its parity: the same change in the other six repositories, or why it does not apply.

Public API changes

A change to the public SDK surface needs tests, a README update and a conventional commit type that matches its semver impact.

Security

Report vulnerabilities privately, never in an issue. See SECURITY.md.