Python standards
The reference that reviewers — human and AI — hold plans and diffs against. Goal: every new module models data with the strongest structure the runtime allows, and every design decision survives production load.
|
Runtime constraint: Python 3.9 ( |
Pick the strongest data structure that fits
| Use case | Structure | Why |
|---|---|---|
API request/response bodies |
pydantic |
Validation at the boundary, self-documenting OpenAPI, no hand-rolled |
Internal domain objects (task inputs/outputs, session records, result mappings) |
|
Immutable, hashable, typo-proof; replaces raw dicts crossing function boundaries. |
Closed sets of values (statuses, tool types, decisions) |
|
The repo already does this ( |
Dict-shaped data you cannot restructure yet (DynamoDB items, legacy payloads) |
|
Documents the shape and lets a type checker catch key errors. |
Fixed small records in hot paths |
|
Cheap, immutable, destructurable. |
Duck-typed injection seams (clients passed into |
|
Lets tasks depend on an interface rather than a vendor class, and makes mockito tests honest. |
Constants |
|
No magic numbers or strings inline. |
Rules of thumb:
-
No raw
dictacross a boundary. A dict may exist inside a function; the moment it is returned, passed or persisted, give it a dataclass/TypedDict/pydantic shape. Existing code violates this in places — do not add new violations. -
Parse, don’t validate. Convert external shapes (webhook payloads, DynamoDB items, vendor JSON) into typed objects once, at the edge, then pass the typed object around.
-
Immutable by default.
frozen=Truedataclasses; mutate withdataclasses.replace. -
Enums own their mapping logic. Follow
ToolType.get_by_process_name— classmethods on the enum, not dict lookups scattered around callers.
Production and scaling patterns
-
DynamoDB: query, never scan, in request paths.
ToolTablecurrently usesscanwith filters (get_by_supplier_and_type,get_all_by_supplier) even thoughsupplier_keyis the partition key — a known hot spot. New access patterns must useKey(…)conditions against the PK/sort key or a GSI, and must paginate onLastEvaluatedKey(seeFinalStatusTable.get_query_resultsorTaskExecutionTable.get_query_resultsfor the correct loop). -
Idempotency is mandatory for webhooks and events. Handlers keyed by
correlation_id/workflow_run_idmust tolerate redelivery: a repeated payload must not double-write S3, double-emit EventBridge, or flip a terminal status backwards. Use status-transition guards or conditional writes — the Entrust task’sdocuments_uploaded/documents_approvedflags and itsprocessedshort-circuit are the reference implementation. -
correlation_idis the join key acrossTaskExecutionTable,FinalStatusTable,ToolTable, S3 keys and EventBridge payloads. Preserve it end to end; never mint a second one mid-flow. -
HTTP out: sessions, timeouts, retries. Reuse a
requests.Sessionper client, set an explicit timeout on every call (no barerequests.post), and use the retry helper or anHTTPAdapterretry for polling.EntrustClientshows the pattern;Au10tixClientandThomsonReutersClientpredate it and lack both session reuse and timeouts — do not copy them. -
The Web API stays synchronous, thin and vendor-light. Anything slow belongs on the worker. The two exceptions (Entrust session creation, HubSpot sync) are fast single calls, and the webhook hands off rather than processing.
-
Fail loudly, once. Side-effecting task logic goes through
task_failure_handlingso failures mark the taskfailedand reach Sentry. Do not swallow exceptions to "keep the loop alive". The one intentional exception is HubSpot, which is explicitly best-effort and documented as such. -
Configuration through Dynaconf
settingsonly. New settings insettings_setup.py, secrets in Secrets Manager viaconfig_setup.py. Neveros.getenvin app code, never a hardcoded table name, URL or account id. -
S3 layout is part of the contract. Datalake keys follow
<vendor>/<process>/supplier_key=<k>/person_id=<p>/<timestamp>.json; downstream consumers parse these paths.
Conventions specific to this repository
-
Tasks live in packages with the exact file layout in Domain model: processes & tasks (
implementation.py,input.py,output.py, optionalinput_validator.py). -
No direct I/O in
task/— do not importrequestsorboto3there. Use the injectedcomponents/clients. -
Private methods use the double-underscore name-mangled form (
self.__do_thing) throughout; match the surrounding style. -
Endpoint handlers subclass
BaseEndpointHandler, log the request, and useStopExecutionfor early exits rather than returning from nested helpers. -
When you add a dependency to a task, wire it into every
TaskFactoryconstruction site —swf_app.pyplus the Web API handlers that build one.
Definition of done
-
Code follows the conventions above.
-
Unit tests added or updated under
development/unit_tests/and passing, coverage ≥ 80% (pytest, see Testing). -
Any affected page in this documentation component updated (Writing these docs).
-
No secrets or generated configuration committed.