Skip to main content

Service layer base class with domain error hierarchy

Project description

csrd.service

Service-layer boilerplate for the CSRD pattern (Controller → Service → Repository / Delegate).

What it provides

Component Purpose
BaseService Lightweight base class with per-class logger and request-context accessors (current_user(), current_request_id())
ServiceError Base domain exception — keeps business logic free of HTTPException
NotFoundError 404 — resource doesn't exist
ConflictError 409 — duplicate, version mismatch, etc.
ValidationError 422 — business-rule validation (not schema validation)
AuthorizationError 403 — authenticated user lacks permission
DownstreamError 502 — a delegate call to another service failed
service_exception_handler FastAPI handler that maps any ServiceError → structured APIErrorResponse JSON

Consumer Feature Matrix (BaseService + ServiceError)

Capability area Common expectation Actual support How to use / notes
Request context access Service can read current user and request id ✅ Built-in BaseService.current_user() and BaseService.current_request_id()
Service base class Lightweight base for domain services ✅ Built-in Inherit from BaseService; inject repo/delegate dependencies in your constructor
Domain error taxonomy Business errors map to consistent HTTP statuses ✅ Built-in Use NotFoundError (404), ConflictError (409), ValidationError (422), AuthorizationError (403), DownstreamError (502)
Structured API error responses Service exceptions become API error payloads ✅ Built-in (with registration) Register service_exception_handler for ServiceError
Per-error status override Override default status code per raise ✅ Built-in raise ValidationError(..., status_code=409)
Error metadata Include detail/code fields for clients ✅ Built-in detail and code are preserved by the handler
Logging integration Auto logging from service methods ⚙️ Via composition Mix in csrd.logging.LoggingMixin; not part of BaseService itself
Transport-agnostic domain logic Avoid HTTPException in business layer ✅ Built-in pattern Raise ServiceError subclasses and keep HTTP concerns in handler layer
Dependency injection wiring FastAPI DI helper exists in package ⚙️ Pattern-based Use your own factory + Depends; package documents the pattern
Transactions / unit-of-work Automatic transactional boundaries ❌ Not built-in Handle in repository/DB layer
Idempotency utilities Built-in idempotency keys/replay protection ❌ Not built-in Implement per endpoint/service policy
Policy engine / RBAC framework Full authorization policy runtime ❌ Not built-in Use claim checks in service code and/or external policy layer

Dependency tier

Tier 1 (standalone)     csrd.models · csrd.lifespan · csrd.context
Tier 2 (uses Tier 1)    csrd.delegate  csrd.repository  csrd.service
Tier 3 (uses Tier 1+2)  csrd.versioning

csrd.service depends on csrd.context and csrd.models only. It does not import csrd.repository or csrd.delegate — your services accept those as constructor params.

Quick start

from csrd.service import BaseService, NotFoundError, DownstreamError


class OrderService(BaseService):
    def __init__(self, repo: OrderRepository, payments: PaymentsDelegate):
        super().__init__()
        self._repo = repo
        self._payments = payments

    async def get_order(self, order_id: int) -> Order:
        order = await self._repo.get_by_id(order_id)
        if order is None:
            raise NotFoundError("Order not found", detail=f"order_id={order_id}")
        return order

    async def place_order(self, cart: Cart) -> Order:
        order = await self._repo.create(cart)
        try:
            await self._payments.charge(order)
        except Exception as exc:
            raise DownstreamError("Payment failed", cause=exc) from exc
        return order

Register the exception handler

With csrd.versioning:

from csrd.service import ServiceError, service_exception_handler
from csrd.versioning import VersionedApiConfig

configure_versioned_api(
    app,
    version_mapping=VERSIONS,
    config=VersionedApiConfig(
        ex_handlers=[(ServiceError, service_exception_handler)],
    ),
)

Or on a plain FastAPI app:

app.add_exception_handler(ServiceError, service_exception_handler)

Wire into FastAPI DI

from functools import cache
from typing import Annotated
from fastapi import Depends

@cache
def order_service_factory(repo: OrderRepository, payments: PaymentsDelegate):
    return OrderService(repo, payments)

OrderServiceDep = Annotated[OrderService, Depends(order_service_factory)]

# In your endpoint:
@router.get("/orders/{order_id}")
async def get_order(service: OrderServiceDep, order_id: int):
    return await service.get_order(order_id)

Installation

uv add csrd-service  # or: pip install csrd-service

Project details


Release history Release notifications | RSS feed

This version

0.5.6

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

csrd_service-0.5.6.tar.gz (8.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

csrd_service-0.5.6-py3-none-any.whl (7.2 kB view details)

Uploaded Python 3

File details

Details for the file csrd_service-0.5.6.tar.gz.

File metadata

  • Download URL: csrd_service-0.5.6.tar.gz
  • Upload date:
  • Size: 8.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for csrd_service-0.5.6.tar.gz
Algorithm Hash digest
SHA256 37c4116bab5fb49c22539352cb53ec2aa94df2654347b47cd2035d8843fb0b58
MD5 46d72c24ade23870cb7683c707ce2cfc
BLAKE2b-256 f3128b0c3c8f2322a4a64189e02dbb02ebeea92aaa8108beeae2553a5b065f74

See more details on using hashes here.

Provenance

The following attestation bundles were made for csrd_service-0.5.6.tar.gz:

Publisher: release.yml on csrd-api/fastapi-common

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file csrd_service-0.5.6-py3-none-any.whl.

File metadata

  • Download URL: csrd_service-0.5.6-py3-none-any.whl
  • Upload date:
  • Size: 7.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for csrd_service-0.5.6-py3-none-any.whl
Algorithm Hash digest
SHA256 1d14cb54591b54a14644bf48a3f676ac11434c0470c56e088aa6ef7fc8211340
MD5 03c8b32c083d8b4c641bc8215ce09c07
BLAKE2b-256 dc2de869ecfa854996a1b43ced2a8efb135fb636d49013f397c10e6fb5da6117

See more details on using hashes here.

Provenance

The following attestation bundles were made for csrd_service-0.5.6-py3-none-any.whl:

Publisher: release.yml on csrd-api/fastapi-common

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page