generated from john/python-template
4.2 KiB
4.2 KiB
description, applyTo
| description | applyTo |
|---|---|
| Follow these guidelines when editing the services | src/transcription/services/*.py |
Services
Structure
- Project core data models defined in models
- 1 service class per data model
- Only services directly interact with the database, and only through async methods
- Services are completely independent of one another. Any operation that needs to use more than a single service, which is most of them, needs to have a separate orchestration function.
Error Handling
- Service-specific errors defined at the top of the respective module and inherit from
AppError - Use a context manager for large
try/exceptblocks like in transcription
Checklist
- Uses
ServiceBasefor common logic - CRUD methods created at the top
- Session kwarg for
AsyncSessionto pass in a session object to each method - Services use
self._session_scopein their methods to pass the session thru.- Multiple operations on the same object(s) require sharing a session between all the methods used.
CRUD Methods
- Create, read, update, and delete, created in that order
- Name format
<operation>_<model >, for examplecreate_documentorupdate_job - All services must define these 4 methods first, and in that order
Transaction Finalization
When a service method accepts an optional session kwarg, write methods must use self._finalize to finalize the transaction properly according to whether or not they are sharing a session.
- If
sessionisNone: the method owns the transaction and shouldcommit(). - If
sessionis provided: the method must not commit; it shouldflush()so IDs and FK values are available to the caller's transaction. - Use
refresh()on returned ORM objects when the caller needs DB-populated values (defaults, triggers, merged state).
Recommended helper behavior:
- Inputs: active session object, original
sessionarg (or a boolean ownership flag), and an optional list of objects to refresh. - Logic:
commitwhen service-owned session,flushwhen caller-owned session, then refresh requested objects.
This keeps orchestration functions atomic: they can pass one shared session across multiple services and commit exactly once at the workflow boundary.
Workflow Transaction Boundaries
For multi-step job lifecycles (for example queued transcription jobs), orchestration functions must use explicit transaction phases.
Required boundary model:
- Transaction A (claim): transition
JobStatus.QUEUED -> JobStatus.PROCESSINGand commit immediately. - Perform provider/network work outside database transactions.
- Transaction B (terminal success): write transcript content and set
JobStatus.TRANSCRIBEDin the same shared-session commit. - Transaction B (terminal failure): write transcript error detail and set
JobStatus.FAILEDin the same shared-session commit. - Transaction C (retry path): write transcript error detail, increment retry count, and set
JobStatus.QUEUEDin one shared-session commit.
Atomicity rules:
- Never commit transcript updates separately from the paired terminal/retry job status change.
- Terminal state (
TRANSCRIBEDorFAILED) and transcript row changes must succeed or roll back together. - Retry persistence (
QUEUED+ retry increment + error detail) must succeed or roll back together.
Separation of concerns:
- Worker modules should stay lightweight and delegate lifecycle transitions to service/workflow orchestration functions.
- In
workflows.py,process_queued_jobshould own one complete attempt lifecycle:QUEUED -> PROCESSING -> TRANSCRIBED|FAILED. - In
workflows.py,advance_jobshould coordinate broader status progression around attempts (for example retry scheduling fromFAILED -> QUEUED). - Services should expose session-aware write helpers (flush on caller-owned session) so orchestration controls commit boundaries.
- Backoff/sleep behavior must run outside transactional scopes.
Service Composition
Some operations, like uploading a picutre, require modifications to multiple tables, which can be done by composing methods from the service object into a separate function.