Skip to content

Route Templates

AutoCRUD generates FastAPI endpoints by applying a set of route templates to each registered resource.

A route template is a small unit of endpoint generation logic:

  • It defines one “slice” of the API (e.g. create, list, read, update, patch, delete, switch, export/import…).
  • It is applied per model to a router/app.
  • Multiple templates compose into a complete CRUD surface.

What is a RouteTemplate?

A route template implements the IRouteTemplate interface:

  • apply(model_name, resource_manager, router): registers one or more FastAPI routes.
  • order: controls the application order across templates.
class IRouteTemplate(ABC):
    """Route template interface: defines how to generate one set of API routes."""

    @abstractmethod
    def apply(
        self,
        model_name: str,
        resource_manager: IResourceManager[T],
        router: APIRouter,
    ) -> None:
        """Apply this template to the given model and router."""
        ...

    @property
    @abstractmethod
    def order(self) -> int:
        """Ordering weight for route template sorting."""
        ...

AutoCRUD provides a shared base class BaseRouteTemplate:

  • Accepts a DependencyProvider to inject consistent dependencies (e.g. current user, current time).
  • Implements comparable ordering so templates can be sorted.
class BaseRouteTemplate(IRouteTemplate):
    def __init__(
        self,
        dependency_provider: DependencyProvider = None,
        order: int = 100,
    ):
        self.deps = dependency_provider or DependencyProvider()
        self._order = order

    @property
    def order(self) -> int:
        return self._order

    def __lt__(self, other: IRouteTemplate):
        return self.order < other.order

    def __le__(self, other: IRouteTemplate):
        return self.order <= other.order

DependencyProvider

Route templates typically need shared request-scoped inputs like:

  • current_user
  • current_time

AutoCRUD centralizes those as injectable FastAPI dependencies via DependencyProvider.

Default behavior (when not customized):

  • get_user()"anonymous"
  • get_now()datetime.now()
class DependencyProvider:
    """Provides dependency callables for user/time."""

    def __init__(self, get_user: Callable = None, get_now: Callable = None):
        self.get_user = get_user or self._create_default_user_dependency()
        self.get_now = get_now or self._create_default_now_dependency()

    def _create_default_user_dependency(self) -> Callable:
        def default_get_user() -> str:
            return "anonymous"
        return default_get_user

    def _create_default_now_dependency(self) -> Callable:
        def default_get_now() -> dt.datetime:
            return dt.datetime.now()
        return default_get_now

This makes templates reusable and consistent across apps, while still allowing users to override auth/time logic centrally.


How AutoCRUD applies route templates

AutoCRUD holds:

  • resource_managers: {resource_name -> ResourceManager}
  • route_templates: list of IRouteTemplate

When you call:

crud.apply(app)

AutoCRUD will:

  1. Validate Ref targets (warn on dangling refs)
  2. Install referential integrity handlers
  3. Sort templates by order
  4. Apply each template to each model (best-effort)
  5. Register custom create-action routes
  6. Register custom update-action routes
  7. Register ref-specific routes (referrers + relationships)
  8. Register global backup / restore routes

Key portion (simplified):

self.route_templates.sort()
for model_name, resource_manager in self.resource_managers.items():
    for route_template in self.route_templates:
        try:
            route_template.apply(model_name, resource_manager, router)
        except Exception:
            pass

Important note: template application is intentionally best-effort. A failing template will be skipped so that unrelated routes can still be generated.


Default templates

If you do not pass route_templates explicitly, AutoCRUD builds a default list:

[
  CreateRouteTemplate,
  ListRouteTemplate,
  ReadRouteTemplate,
  UpdateRouteTemplate,
  PatchRouteTemplate,
  SwitchRevisionRouteTemplate,
  RerunRouteTemplate,
  DeleteRouteTemplate,
  PermanentlyDeleteRouteTemplate,
  RestoreRouteTemplate,
  BatchDeleteRouteTemplate,
  BatchRestoreRouteTemplate,
  ExportRouteTemplate,
  ImportRouteTemplate,
]

You can also pass a dict {TemplateClass: kwargs} to configure defaults while still using the standard set.


Canonical endpoints generated by key templates

Below are the canonical endpoints (new “bare path” endpoints) used going forward. Deprecated aliases may exist (e.g. /data, /meta, /full), but should not be considered the primary API surface.

ListRouteTemplate (List resources)

GET /{model_name}

  • Canonical listing endpoint.
  • Supports returns to control response sections per item.
  • Supports standard filtering/sorting (including qb).

Example (from template):

  • GET /users
  • GET /users?returns=data
  • GET /users?limit=20&offset=40

ReadRouteTemplate (Read single resource)

GET /{model_name}/{resource_id}

  • Canonical read endpoint.
  • Supports returns=data,revision_info,meta.
  • Supports revision_id and partial / partial[].

Example:

  • GET /users/123
  • GET /users/123?returns=meta
  • GET /users/123?revision_id=users:123:2

CreateRouteTemplate (Create)

POST /{model_name}

Creates a new resource and returns RevisionInfo.

UpdateRouteTemplate (Update / Modify)

PUT /{model_name}/{resource_id}

  • mode=update (default): creates a new revision (append)
  • mode=modify: updates the current revision in-place (no new revision)
  • optional change_status only when mode=modify

PatchRouteTemplate (JSON Patch / Modify)

PATCH /{model_name}/{resource_id}

  • RFC 6902 JSON Patch support
  • mode=update (default): new revision
  • mode=modify: in-place update of current revision

SwitchRevisionRouteTemplate (Switch current revision)

POST /{model_name}/{resource_id}/switch/{revision_id}

Switches the resource’s current revision pointer to a specific revision.

Notes:

  • Does not create a new revision.
  • Revision history is preserved.
  • Similar to moving a “HEAD pointer” in a version graph.

RerunRouteTemplate (Job rerun; only when message queue exists)

POST /{model_name}/{resource_id}/rerun

Registered only when:

  • resource_manager.message_queue is not None

Behavior:

  • Only allows rerun when job status is completed or failed
  • Resets job to pending, clears retries/errmsg, enqueues again

ExportRouteTemplate / ImportRouteTemplate (Per-model backup)

  • GET /{model_name}/export → streaming .acbak
  • POST /{model_name}/import → import .acbak (supports on_duplicate)

Export supports the same query parameters as search/list (e.g. qb, is_deleted, time ranges) to filter what is included.


Custom templates

You can extend AutoCRUD by providing your own IRouteTemplate implementation:

  • Add completely new endpoints
  • Add “derived views” (e.g. /search, /stats, /summary)
  • Change behaviors by replacing default templates with custom ones

Recommended pattern:

  • Keep each template focused on one area (1 file, 1 concern)
  • Use DependencyProvider so auth/time logic is consistent
  • Choose order carefully to avoid route conflicts

Design rationale (why templates?)

Route templates exist to keep AutoCRUD:

  • Composable: add/remove route families without rewriting everything
  • Extensible: users can ship custom endpoints as templates
  • Testable: each template can be tested in isolation
  • Consistent: shared dependencies and response patterns across endpoints

This architecture is what allows AutoCRUD to grow beyond “CRUD” into:

  • versioning primitives (switch)
  • job workflows (rerun)
  • migration and schema evolution
  • bulk operations and backup/restore
  • reference graph utilities (ref routes)