Skip to content

Latest commit

 

History

History
103 lines (63 loc) · 10 KB

CONTRIBUTING.md

File metadata and controls

103 lines (63 loc) · 10 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, openapi/authentication.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. The generator reads verification.json and account.json; authentication.json holds the hand-written Authentication module to its contract (tests/test_authentication_contract.py).
  • 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 <type>(spec): sync API spec YYYY-MM-DD. The type is feat(spec) when a caller can observe the change (a minor release, even when it can break typed code) and chore(spec) when only descriptions, examples or other documentation changed (no release on its own); never ! or a BREAKING CHANGE: footer (DESIGN sections 3.1 and 13). A download that is not an OpenAPI 3 document with paths is refused and nothing is written.

The body opens with "Breaking changes", or "None detected": every change the rules of DESIGN section 3.1 count, in parameters, request bodies and 2xx responses, following $refs and array items. Then it lists added, removed, renamed and changed operations and schemas. It ends with a BEGIN_COMMIT_OVERRIDE block that release-please reads instead of the merged commit: the sync line plus one nested api(spec): commit per breaking change, which release-please-config.json lists under "API changes that can break typed code". Merge sync pull requests with Squash and merge and keep that block: after a merge commit release-please reads it once per commit and every line appears twice. When the lines do not fit in a pull request description (hundreds of breaking changes), the description says so and leaves the block out; then squash with the sync commit's message, which holds every line.

The rules are held by openapi/breaking-vectors.json, which the other six server SDKs copy byte for byte and run through their own detectors. codegen/breaking_vectors.py writes it from hand-written cases: two minimal documents, before and after, with the expected commit type and the counted changes, each reviewed against DESIGN section 3.1. It refuses to write the file while codegen/spec_sync.py disagrees with a case, and codegen/tests/test_breaking_vectors.py fails while the committed file is not what it writes. To change a rule, change DESIGN section 3.1, the detector and the cases in one pull request, run uv run python codegen/breaking_vectors.py, and then copy the file to the other six repositories.

openapi/authentication.json is synced like the others, but the didit.authentication module stays hand-written (DESIGN section 10). When a sync adds, removes or renames one of its server operations, the body lists it under "Authentication module" and tests/test_authentication_contract.py fails until the module follows on the sync branch.

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 not kept: a name that now belongs to a different operation (swapped or reused), which the summary flags for review, and an operation that becomes a device or end-user token operation (server: false), which server SDKs never expose and which the summary lists as a breaking change. 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:, perf:, deps: and docs: are a patch, feat: a minor version, and chore:, ci:, test:, build:, refactor: and style: release nothing on their own. ! or a BREAKING CHANGE: footer (a major version) is only for a change to the SDK's own design: renaming or removing a method or resource the API still has, client configuration, errors, retries, pagination or webhook verification behaviour, or a dropped Python version. A change that mirrors the API is a minor version even when it can break typed code (DESIGN section 13). 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.