mirror of
https://github.com/d3vyce/fastapi-toolsets.git
synced 2026-04-15 22:26:25 +02:00
1 line
160 KiB
JSON
1 line
160 KiB
JSON
{"config":{"separator":"[\\s\\-_,:!=\\[\\]()\\\\\"`/]+|\\.(?!\\d)"},"items":[{"location":"","level":1,"title":"FastAPI Toolsets","text":"<p>A modular collection of production-ready utilities for FastAPI. Install only what you need — from async CRUD and database helpers to CLI tooling, Prometheus metrics, and pytest fixtures. Each module is independently installable via optional extras, keeping your dependency footprint minimal.</p> <p> </p> <p>Documentation: https://fastapi-toolsets.d3vyce.fr</p> <p>Source Code: https://github.com/d3vyce/fastapi-toolsets</p>","path":["FastAPI Toolsets"],"tags":[]},{"location":"#installation","level":2,"title":"Installation","text":"<p>The base package includes the core modules (CRUD, database, schemas, exceptions, fixtures, dependencies, model mixins, logging):</p> <pre><code>uv add fastapi-toolsets\n</code></pre> <p>Install only the extras you need:</p> <pre><code>uv add \"fastapi-toolsets[cli]\"\nuv add \"fastapi-toolsets[metrics]\"\nuv add \"fastapi-toolsets[pytest]\"\n</code></pre> <p>Or install everything:</p> <pre><code>uv add \"fastapi-toolsets[all]\"\n</code></pre>","path":["FastAPI Toolsets"],"tags":[]},{"location":"#features","level":2,"title":"Features","text":"","path":["FastAPI Toolsets"],"tags":[]},{"location":"#core","level":3,"title":"Core","text":"<ul> <li>CRUD: Generic async CRUD operations with <code>CrudFactory</code>, built-in full-text/faceted search and Offset/Cursor pagination.</li> <li>Database: Session management, transaction helpers, table locking, and polling-based row change detection</li> <li>Dependencies: FastAPI dependency factories (<code>PathDependency</code>, <code>BodyDependency</code>) for automatic DB lookups from path or body parameters</li> <li>Fixtures: Fixture system with dependency management, context support, and pytest integration</li> <li>Model Mixins: SQLAlchemy mixins for common column patterns (<code>UUIDMixin</code>, <code>CreatedAtMixin</code>, <code>UpdatedAtMixin</code>, <code>TimestampMixin</code>)</li> <li>Standardized API Responses: Consistent response format with <code>Response</code>, <code>PaginatedResponse</code>, and <code>PydanticBase</code></li> <li>Exception Handling: Structured error responses with automatic OpenAPI documentation</li> <li>Logging: Logging configuration with uvicorn integration via <code>configure_logging</code> and <code>get_logger</code></li> </ul>","path":["FastAPI Toolsets"],"tags":[]},{"location":"#optional","level":3,"title":"Optional","text":"<ul> <li>CLI: Django-like command-line interface with fixture management and custom commands support</li> <li>Metrics: Prometheus metrics endpoint with provider/collector registry</li> <li>Pytest Helpers: Async test client, database session management, <code>pytest-xdist</code> support, and table cleanup utilities</li> </ul>","path":["FastAPI Toolsets"],"tags":[]},{"location":"#license","level":2,"title":"License","text":"<p>MIT License - see LICENSE for details.</p>","path":["FastAPI Toolsets"],"tags":[]},{"location":"#contributing","level":2,"title":"Contributing","text":"<p>Contributions are welcome! Please feel free to submit issues and pull requests.</p>","path":["FastAPI Toolsets"],"tags":[]},{"location":"examples/pagination-search/","level":1,"title":"Pagination & search","text":"<p>This example builds an articles listing endpoint that supports offset pagination, cursor pagination, full-text search, faceted filtering, and sorting — all from a single <code>CrudFactory</code> definition.</p>","path":["Examples","Pagination & search"],"tags":[]},{"location":"examples/pagination-search/#models","level":2,"title":"Models","text":"models.py<pre><code>import datetime\nimport uuid\n\nfrom sqlalchemy import Boolean, DateTime, ForeignKey, String, Text, func\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship\n\n\nclass Base(DeclarativeBase):\n pass\n\n\nclass Category(Base):\n __tablename__ = \"categories\"\n\n id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)\n name: Mapped[str] = mapped_column(String(64), unique=True)\n\n articles: Mapped[list[\"Article\"]] = relationship(back_populates=\"category\")\n\n\nclass Article(Base):\n __tablename__ = \"articles\"\n\n id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)\n created_at: Mapped[datetime.datetime] = mapped_column(\n DateTime(timezone=True), server_default=func.now()\n )\n title: Mapped[str] = mapped_column(String(256))\n body: Mapped[str] = mapped_column(Text)\n status: Mapped[str] = mapped_column(String(32))\n published: Mapped[bool] = mapped_column(Boolean, default=False)\n category_id: Mapped[uuid.UUID | None] = mapped_column(\n ForeignKey(\"categories.id\"), nullable=True\n )\n\n category: Mapped[\"Category | None\"] = relationship(back_populates=\"articles\")\n</code></pre>","path":["Examples","Pagination & search"],"tags":[]},{"location":"examples/pagination-search/#schemas","level":2,"title":"Schemas","text":"schemas.py<pre><code>import datetime\nimport uuid\n\nfrom fastapi_toolsets.schemas import PydanticBase\n\n\nclass ArticleRead(PydanticBase):\n id: uuid.UUID\n created_at: datetime.datetime\n title: str\n status: str\n published: bool\n category_id: uuid.UUID | None\n</code></pre>","path":["Examples","Pagination & search"],"tags":[]},{"location":"examples/pagination-search/#crud","level":2,"title":"Crud","text":"<p>Declare <code>searchable_fields</code>, <code>facet_fields</code>, and <code>order_fields</code> once on <code>CrudFactory</code>. All endpoints built from this class share the same defaults and can override them per call.</p> crud.py<pre><code>from fastapi_toolsets.crud import CrudFactory\n\nfrom .models import Article, Category\n\nArticleCrud = CrudFactory(\n model=Article,\n cursor_column=Article.created_at,\n searchable_fields=[ # default fields for full-text search\n Article.title,\n Article.body,\n (Article.category, Category.name),\n ],\n facet_fields=[ # fields exposed as filter dropdowns\n Article.status,\n (Article.category, Category.name),\n ],\n order_fields=[ # fields exposed for client-driven ordering\n Article.title,\n Article.created_at,\n ],\n)\n</code></pre>","path":["Examples","Pagination & search"],"tags":[]},{"location":"examples/pagination-search/#session-dependency","level":2,"title":"Session dependency","text":"db.py<pre><code>from typing import Annotated\n\nfrom fastapi import Depends\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\n\nfrom fastapi_toolsets.db import create_db_context, create_db_dependency\n\nDATABASE_URL = \"postgresql+asyncpg://postgres:postgres@localhost:5432/postgres\"\n\nengine = create_async_engine(url=DATABASE_URL, future=True)\nasync_session_maker = async_sessionmaker(bind=engine, expire_on_commit=False)\n\nget_db = create_db_dependency(session_maker=async_session_maker)\nget_db_context = create_db_context(session_maker=async_session_maker)\n\n\nSessionDep = Annotated[AsyncSession, Depends(get_db)]\n</code></pre> <p>Deploy a Postgres DB with docker</p> <pre><code>docker run -d --name postgres -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres -p 5432:5432 postgres:18-alpine\n</code></pre>","path":["Examples","Pagination & search"],"tags":[]},{"location":"examples/pagination-search/#app","level":2,"title":"App","text":"app.py<pre><code>from fastapi import FastAPI\n\nfrom fastapi_toolsets.exceptions import init_exceptions_handlers\n\nfrom .routes import router\n\napp = FastAPI()\ninit_exceptions_handlers(app=app)\napp.include_router(router=router)\n</code></pre>","path":["Examples","Pagination & search"],"tags":[]},{"location":"examples/pagination-search/#routes","level":2,"title":"Routes","text":"","path":["Examples","Pagination & search"],"tags":[]},{"location":"examples/pagination-search/#offset-pagination","level":3,"title":"Offset pagination","text":"<p>Best for admin panels or any UI that needs a total item count and numbered pages.</p> routes.py:1:36<pre><code>from typing import Annotated\n\nfrom fastapi import APIRouter, Depends, Query\n\nfrom fastapi_toolsets.crud import OrderByClause\nfrom fastapi_toolsets.schemas import PaginatedResponse\n\nfrom .crud import ArticleCrud\nfrom .db import SessionDep\nfrom .models import Article\nfrom .schemas import ArticleRead\n\nrouter = APIRouter(prefix=\"/articles\")\n\n\n@router.get(\"/offset\")\nasync def list_articles_offset(\n session: SessionDep,\n filter_by: Annotated[dict[str, list[str]], Depends(ArticleCrud.filter_params())],\n order_by: Annotated[\n OrderByClause | None,\n Depends(ArticleCrud.order_params(default_field=Article.created_at)),\n ],\n page: int = Query(1, ge=1),\n items_per_page: int = Query(20, ge=1, le=100),\n search: str | None = None,\n) -> PaginatedResponse[ArticleRead]:\n return await ArticleCrud.offset_paginate(\n session=session,\n page=page,\n items_per_page=items_per_page,\n search=search,\n filter_by=filter_by or None,\n order_by=order_by,\n schema=ArticleRead,\n )\n</code></pre> <p>Example request</p> <pre><code>GET /articles/offset?page=2&items_per_page=10&search=fastapi&status=published&order_by=title&order=asc\n</code></pre> <p>Example response</p> <pre><code>{\n \"status\": \"SUCCESS\",\n \"data\": [\n { \"id\": \"3f47ac69-...\", \"title\": \"FastAPI tips\", \"status\": \"published\", ... }\n ],\n \"pagination\": {\n \"total_count\": 42,\n \"page\": 2,\n \"items_per_page\": 10,\n \"has_more\": true\n },\n \"filter_attributes\": {\n \"status\": [\"archived\", \"draft\", \"published\"],\n \"name\": [\"backend\", \"frontend\", \"python\"]\n }\n}\n</code></pre> <p><code>filter_attributes</code> always reflects the values visible after applying the active filters. Use it to populate filter dropdowns on the client.</p>","path":["Examples","Pagination & search"],"tags":[]},{"location":"examples/pagination-search/#cursor-pagination","level":3,"title":"Cursor pagination","text":"<p>Best for feeds, infinite scroll, or any high-throughput API where offset performance degrades.</p> routes.py:39:59<pre><code>@router.get(\"/cursor\")\nasync def list_articles_cursor(\n session: SessionDep,\n filter_by: Annotated[dict[str, list[str]], Depends(ArticleCrud.filter_params())],\n order_by: Annotated[\n OrderByClause | None,\n Depends(ArticleCrud.order_params(default_field=Article.created_at)),\n ],\n cursor: str | None = None,\n items_per_page: int = Query(20, ge=1, le=100),\n search: str | None = None,\n) -> PaginatedResponse[ArticleRead]:\n return await ArticleCrud.cursor_paginate(\n session=session,\n cursor=cursor,\n items_per_page=items_per_page,\n search=search,\n filter_by=filter_by or None,\n order_by=order_by,\n schema=ArticleRead,\n )\n</code></pre> <p>Example request</p> <pre><code>GET /articles/cursor?items_per_page=10&status=published&order_by=created_at&order=desc\n</code></pre> <p>Example response</p> <pre><code>{\n \"status\": \"SUCCESS\",\n \"data\": [\n { \"id\": \"3f47ac69-...\", \"title\": \"FastAPI tips\", \"status\": \"published\", ... }\n ],\n \"pagination\": {\n \"next_cursor\": \"eyJ2YWx1ZSI6ICIzZjQ3YWM2OS0uLi4ifQ==\",\n \"prev_cursor\": null,\n \"items_per_page\": 10,\n \"has_more\": true\n },\n \"filter_attributes\": {\n \"status\": [\"published\"],\n \"name\": [\"backend\", \"python\"]\n }\n}\n</code></pre> <p>Pass <code>next_cursor</code> as the <code>cursor</code> query parameter on the next request to advance to the next page.</p>","path":["Examples","Pagination & search"],"tags":[]},{"location":"examples/pagination-search/#search-behaviour","level":2,"title":"Search behaviour","text":"<p>Both endpoints inherit the same <code>searchable_fields</code> declared on <code>ArticleCrud</code>:</p> <p>Search is case-insensitive and uses a <code>LIKE %query%</code> pattern. Pass a <code>SearchConfig</code> instead of a plain string to control case sensitivity or switch to <code>match_mode=\"all\"</code> (AND across all fields instead of OR).</p> <pre><code>from fastapi_toolsets.crud import SearchConfig\n\n# Both title AND body must contain \"fastapi\"\nresult = await ArticleCrud.offset_paginate(\n session,\n search=SearchConfig(query=\"fastapi\", case_sensitive=True, match_mode=\"all\"),\n search_fields=[Article.title, Article.body],\n)\n</code></pre>","path":["Examples","Pagination & search"],"tags":[]},{"location":"migration/v2/","level":1,"title":"Migrating to v2.0","text":"<p>This page covers every breaking change introduced in v2.0 and the steps required to update your code.</p>","path":["Migration","Migrating to v2.0"],"tags":[]},{"location":"migration/v2/#crud","level":2,"title":"CRUD","text":"","path":["Migration","Migrating to v2.0"],"tags":[]},{"location":"migration/v2/#schema-is-now-required-in-offset_paginate-and-cursor_paginate","level":3,"title":"<code>schema</code> is now required in <code>offset_paginate()</code> and <code>cursor_paginate()</code>","text":"<p>Calls that omit <code>schema</code> will now raise a <code>TypeError</code> at runtime.</p> <p>Previously <code>schema</code> was optional; omitting it returned raw SQLAlchemy model instances inside the response. It is now a required keyword argument and the response always contains serialized schema instances.</p> Before (<code>v1</code>)Now (<code>v2</code>) <pre><code># schema omitted — returned raw model instances\nresult = await UserCrud.offset_paginate(session=session, page=1)\nresult = await UserCrud.cursor_paginate(session=session, cursor=token)\n</code></pre> <pre><code>result = await UserCrud.offset_paginate(session=session, page=1, schema=UserRead)\nresult = await UserCrud.cursor_paginate(session=session, cursor=token, schema=UserRead)\n</code></pre>","path":["Migration","Migrating to v2.0"],"tags":[]},{"location":"migration/v2/#as_response-removed-from-create-get-and-update","level":3,"title":"<code>as_response</code> removed from <code>create()</code>, <code>get()</code>, and <code>update()</code>","text":"<p>Passing <code>as_response</code> to these methods will raise a <code>TypeError</code> at runtime.</p> <p>The <code>as_response=True</code> shorthand is replaced by passing a <code>schema</code> directly. The return value is a <code>Response[schema]</code> when <code>schema</code> is provided, or the raw model instance when it is not.</p> Before (<code>v1</code>)Now (<code>v2</code>) <pre><code>user = await UserCrud.create(session=session, obj=data, as_response=True)\nuser = await UserCrud.get(session=session, filters=filters, as_response=True)\nuser = await UserCrud.update(session=session, obj=data, filters, as_response=True)\n</code></pre> <pre><code>user = await UserCrud.create(session=session, obj=data, schema=UserRead)\nuser = await UserCrud.get(session=session, filters=filters, schema=UserRead)\nuser = await UserCrud.update(session=session, obj=data, filters, schema=UserRead)\n</code></pre>","path":["Migration","Migrating to v2.0"],"tags":[]},{"location":"migration/v2/#delete-as_response-renamed-and-return-type-changed","level":3,"title":"<code>delete()</code>: <code>as_response</code> renamed and return type changed","text":"<p><code>as_response</code> is gone, and the plain (non-response) call no longer returns <code>True</code>.</p> <p>Two changes were made to <code>delete()</code>:</p> <ol> <li>The <code>as_response</code> parameter is renamed to <code>return_response</code>.</li> <li>When called without <code>return_response=True</code>, the method now returns <code>None</code> on success instead of <code>True</code>.</li> </ol> Before (<code>v1</code>)Now (<code>v2</code>) <pre><code>ok = await UserCrud.delete(session=session, filters=filters)\nif ok: # True on success\n ...\n\nresponse = await UserCrud.delete(session=session, filters=filters, as_response=True)\n</code></pre> <pre><code>await UserCrud.delete(session=session, filters=filters) # returns None\n\nresponse = await UserCrud.delete(session=session, filters=filters, return_response=True)\n</code></pre>","path":["Migration","Migrating to v2.0"],"tags":[]},{"location":"migration/v2/#paginate-alias-removed","level":3,"title":"<code>paginate()</code> alias removed","text":"<p>Any call to <code>crud.paginate(...)</code> will raise <code>AttributeError</code> at runtime.</p> <p>The <code>paginate</code> shorthand was an alias for <code>offset_paginate</code>. It has been removed; call <code>offset_paginate</code> directly.</p> Before (<code>v1</code>)Now (<code>v2</code>) <pre><code>result = await UserCrud.paginate(session=session, page=2, items_per_page=20, schema=UserRead)\n</code></pre> <pre><code>result = await UserCrud.offset_paginate(session=session, page=2, items_per_page=20, schema=UserRead)\n</code></pre>","path":["Migration","Migrating to v2.0"],"tags":[]},{"location":"migration/v2/#exceptions","level":2,"title":"Exceptions","text":"","path":["Migration","Migrating to v2.0"],"tags":[]},{"location":"migration/v2/#missing-api_error-raises-typeerror-at-class-definition-time","level":3,"title":"Missing <code>api_error</code> raises <code>TypeError</code> at class definition time","text":"<p>Unfinished or stub exception subclasses that previously compiled fine will now fail on import.</p> <p>In <code>v1</code>, a subclass without <code>api_error</code> would only fail when the exception was raised. In <code>v2</code>, <code>__init_subclass__</code> validates this at class definition time.</p> Before (<code>v1</code>)Now (<code>v2</code>) <pre><code>class MyError(ApiException):\n pass # fine until raised\n</code></pre> <pre><code>class MyError(ApiException):\n pass # TypeError: MyError must define an 'api_error' class attribute.\n</code></pre> <p>For shared base classes that are not meant to be raised directly, use <code>abstract=True</code>:</p> <pre><code>class BillingError(ApiException, abstract=True):\n \"\"\"Base for all billing-related errors — not raised directly.\"\"\"\n\nclass PaymentRequiredError(BillingError):\n api_error = ApiError(code=402, msg=\"Payment Required\", desc=\"...\", err_code=\"BILLING-402\")\n</code></pre>","path":["Migration","Migrating to v2.0"],"tags":[]},{"location":"migration/v2/#schemas","level":2,"title":"Schemas","text":"","path":["Migration","Migrating to v2.0"],"tags":[]},{"location":"migration/v2/#pagination-alias-removed","level":3,"title":"<code>Pagination</code> alias removed","text":"<p><code>Pagination</code> was already deprecated in <code>v1</code> and is fully removed in <code>v2</code>, you now need to use <code>OffsetPagination</code> or <code>CursorPagination</code>.</p>","path":["Migration","Migrating to v2.0"],"tags":[]},{"location":"module/cli/","level":1,"title":"CLI","text":"<p>Typer-based command-line interface for managing your FastAPI application, with built-in fixture commands integration.</p>","path":["Modules","CLI"],"tags":[]},{"location":"module/cli/#installation","level":2,"title":"Installation","text":"uvpip <pre><code>uv add \"fastapi-toolsets[cli]\"\n</code></pre> <pre><code>pip install \"fastapi-toolsets[cli]\"\n</code></pre>","path":["Modules","CLI"],"tags":[]},{"location":"module/cli/#overview","level":2,"title":"Overview","text":"<p>The <code>cli</code> module provides a <code>manager</code> entry point built with Typer. It allow custom commands to be added in addition of the fixture commands when a <code>FixtureRegistry</code> and a database context are configured.</p>","path":["Modules","CLI"],"tags":[]},{"location":"module/cli/#configuration","level":2,"title":"Configuration","text":"<p>Configure the CLI in your <code>pyproject.toml</code>:</p> <pre><code>[tool.fastapi-toolsets]\ncli = \"myapp.cli:cli\" # Custom Typer app\nfixtures = \"myapp.fixtures:registry\" # FixtureRegistry instance\ndb_context = \"myapp.db:db_context\" # Async context manager for sessions\n</code></pre> <p>All fields are optional. Without configuration, the <code>manager</code> command still works but no command are available.</p>","path":["Modules","CLI"],"tags":[]},{"location":"module/cli/#usage","level":2,"title":"Usage","text":"<pre><code># Manager commands\nmanager --help\n\n Usage: manager [OPTIONS] COMMAND [ARGS]...\n\n FastAPI utilities CLI.\n\n╭─ Options ────────────────────────────────────────────────────────────────────────╮\n│ --install-completion Install completion for the current shell. │\n│ --show-completion Show completion for the current shell, to copy it │\n│ or customize the installation. │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────────╮\n│ check-db │\n│ fixtures Manage database fixtures. │\n╰──────────────────────────────────────────────────────────────────────────────────╯\n\n# Fixtures commands\nmanager fixtures --help\n\n Usage: manager fixtures [OPTIONS] COMMAND [ARGS]...\n\n Manage database fixtures.\n\n╭─ Options ────────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────────╮\n│ list List all registered fixtures. │\n│ load Load fixtures into the database. │\n╰──────────────────────────────────────────────────────────────────────────────────╯\n</code></pre>","path":["Modules","CLI"],"tags":[]},{"location":"module/cli/#custom-cli","level":2,"title":"Custom CLI","text":"<p>You can extend the CLI by providing your own Typer app. The <code>manager</code> entry point will merge your app's commands with the built-in ones:</p> <pre><code># myapp/cli.py\nimport typer\n\ncli = typer.Typer()\n\n@cli.command()\ndef hello():\n print(\"Hello from my app!\")\n</code></pre> <pre><code>[tool.fastapi-toolsets]\ncli = \"myapp.cli:cli\"\n</code></pre> <p> API Reference</p>","path":["Modules","CLI"],"tags":[]},{"location":"module/crud/","level":1,"title":"CRUD","text":"<p>Generic async CRUD operations for SQLAlchemy models with search, pagination, and many-to-many support.</p> <p>Info</p> <p>This module has been coded and tested to be compatible with PostgreSQL only.</p>","path":["Modules","CRUD"],"tags":[]},{"location":"module/crud/#overview","level":2,"title":"Overview","text":"<p>The <code>crud</code> module provides <code>AsyncCrud</code>, an abstract base class with a full suite of async database operations, and <code>CrudFactory</code>, a convenience function to instantiate it for a given model.</p>","path":["Modules","CRUD"],"tags":[]},{"location":"module/crud/#creating-a-crud-class","level":2,"title":"Creating a CRUD class","text":"<pre><code>from fastapi_toolsets.crud import CrudFactory\nfrom myapp.models import User\n\nUserCrud = CrudFactory(model=User)\n</code></pre> <p><code>CrudFactory</code> dynamically creates a class named <code>AsyncUserCrud</code> with <code>User</code> as its model.</p>","path":["Modules","CRUD"],"tags":[]},{"location":"module/crud/#basic-operations","level":2,"title":"Basic operations","text":"<pre><code># Create\nuser = await UserCrud.create(session=session, obj=UserCreateSchema(username=\"alice\"))\n\n# Get one (raises NotFoundError if not found)\nuser = await UserCrud.get(session=session, filters=[User.id == user_id])\n\n# Get first or None\nuser = await UserCrud.first(session=session, filters=[User.email == email])\n\n# Get multiple\nusers = await UserCrud.get_multi(session=session, filters=[User.is_active == True])\n\n# Update\nuser = await UserCrud.update(session=session, obj=UserUpdateSchema(username=\"bob\"), filters=[User.id == user_id])\n\n# Delete\nawait UserCrud.delete(session=session, filters=[User.id == user_id])\n\n# Count / exists\ncount = await UserCrud.count(session=session, filters=[User.is_active == True])\nexists = await UserCrud.exists(session=session, filters=[User.email == email])\n</code></pre>","path":["Modules","CRUD"],"tags":[]},{"location":"module/crud/#pagination","level":2,"title":"Pagination","text":"<p>Added in <code>v1.1</code> (only offset_pagination via <code>paginate</code> if <code><v1.1</code>)</p> <p>Two pagination strategies are available. Both return a <code>PaginatedResponse</code> but differ in how they navigate through results.</p> <code>offset_paginate</code> <code>cursor_paginate</code> Total count Yes No Jump to arbitrary page Yes No Performance on deep pages Degrades Constant Stable under concurrent inserts No Yes Search compatible Yes Yes Use case Admin panels, numbered pagination Feeds, APIs, infinite scroll","path":["Modules","CRUD"],"tags":[]},{"location":"module/crud/#offset-pagination","level":3,"title":"Offset pagination","text":"<pre><code>@router.get(\n \"\",\n response_model=PaginatedResponse[User],\n)\nasync def get_users(\n session: SessionDep,\n items_per_page: int = 50,\n page: int = 1,\n):\n return await crud.UserCrud.offset_paginate(\n session=session,\n items_per_page=items_per_page,\n page=page,\n )\n</code></pre> <p>The <code>offset_paginate</code> method returns a <code>PaginatedResponse</code> whose <code>pagination</code> field is an <code>OffsetPagination</code> object:</p> <pre><code>{\n \"status\": \"SUCCESS\",\n \"data\": [\"...\"],\n \"pagination\": {\n \"total_count\": 100,\n \"page\": 1,\n \"items_per_page\": 20,\n \"has_more\": true\n }\n}\n</code></pre>","path":["Modules","CRUD"],"tags":[]},{"location":"module/crud/#cursor-pagination","level":3,"title":"Cursor pagination","text":"<pre><code>@router.get(\n \"\",\n response_model=PaginatedResponse[UserRead],\n)\nasync def list_users(\n session: SessionDep,\n cursor: str | None = None,\n items_per_page: int = 20,\n):\n return await UserCrud.cursor_paginate(\n session=session,\n cursor=cursor,\n items_per_page=items_per_page,\n )\n</code></pre> <p>The <code>cursor_paginate</code> method returns a <code>PaginatedResponse</code> whose <code>pagination</code> field is a <code>CursorPagination</code> object:</p> <pre><code>{\n \"status\": \"SUCCESS\",\n \"data\": [\"...\"],\n \"pagination\": {\n \"next_cursor\": \"eyJ2YWx1ZSI6ICIzZjQ3YWM2OS0uLi4ifQ==\",\n \"prev_cursor\": null,\n \"items_per_page\": 20,\n \"has_more\": true\n }\n}\n</code></pre> <p>Pass <code>next_cursor</code> as the <code>cursor</code> query parameter on the next request to advance to the next page. <code>prev_cursor</code> is set on pages 2+ and points back to the first item of the current page. Both are <code>null</code> when there is no adjacent page.</p>","path":["Modules","CRUD"],"tags":[]},{"location":"module/crud/#choosing-a-cursor-column","level":4,"title":"Choosing a cursor column","text":"<p>The cursor column is set once on <code>CrudFactory</code> via the <code>cursor_column</code> parameter. It must be monotonically ordered for stable results:</p> <ul> <li>Auto-increment integer PKs</li> <li>UUID v7 PKs</li> <li>Timestamps</li> </ul> <p>Warning</p> <p>Random UUID v4 PKs are not suitable as cursor columns because their ordering is non-deterministic.</p> <p>Note</p> <p><code>cursor_column</code> is required. Calling <code>cursor_paginate</code> on a CRUD class that has no <code>cursor_column</code> configured raises a <code>ValueError</code>.</p> <p>The cursor value is base64-encoded when returned to the client and decoded back to the correct Python type on the next request. The following SQLAlchemy column types are supported:</p> SQLAlchemy type Python type <code>Integer</code>, <code>BigInteger</code>, <code>SmallInteger</code> <code>int</code> <code>Uuid</code> <code>uuid.UUID</code> <code>DateTime</code> <code>datetime.datetime</code> <code>Date</code> <code>datetime.date</code> <code>Float</code>, <code>Numeric</code> <code>decimal.Decimal</code> <pre><code># Paginate by the primary key\nPostCrud = CrudFactory(model=Post, cursor_column=Post.id)\n\n# Paginate by a timestamp column instead\nPostCrud = CrudFactory(model=Post, cursor_column=Post.created_at)\n</code></pre>","path":["Modules","CRUD"],"tags":[]},{"location":"module/crud/#search","level":2,"title":"Search","text":"<p>Two search strategies are available, both compatible with <code>offset_paginate</code> and <code>cursor_paginate</code>.</p> Full-text search Faceted search Input Free-text string Exact column values Relationship support Yes Yes Use case Search bars Filter dropdowns <p>You can use both search strategies in the same endpoint!</p>","path":["Modules","CRUD"],"tags":[]},{"location":"module/crud/#full-text-search","level":3,"title":"Full-text search","text":"<p>Declare <code>searchable_fields</code> on the CRUD class. Relationship traversal is supported via tuples:</p> <pre><code>PostCrud = CrudFactory(\n model=Post,\n searchable_fields=[\n Post.title,\n Post.content,\n (Post.author, User.username), # search across relationship\n ],\n)\n</code></pre> <p>You can override <code>searchable_fields</code> per call with <code>search_fields</code>:</p> <pre><code>result = await UserCrud.offset_paginate(\n session=session,\n search_fields=[User.country],\n)\n</code></pre> <p>This allows searching with both <code>offset_paginate</code> and <code>cursor_paginate</code>:</p> <pre><code>@router.get(\n \"\",\n response_model=PaginatedResponse[User],\n)\nasync def get_users(\n session: SessionDep,\n items_per_page: int = 50,\n page: int = 1,\n search: str | None = None,\n):\n return await crud.UserCrud.offset_paginate(\n session=session,\n items_per_page=items_per_page,\n page=page,\n search=search,\n )\n</code></pre> <pre><code>@router.get(\n \"\",\n response_model=PaginatedResponse[User],\n)\nasync def get_users(\n session: SessionDep,\n cursor: str | None = None,\n items_per_page: int = 50,\n search: str | None = None,\n):\n return await crud.UserCrud.cursor_paginate(\n session=session,\n items_per_page=items_per_page,\n cursor=cursor,\n search=search,\n )\n</code></pre>","path":["Modules","CRUD"],"tags":[]},{"location":"module/crud/#faceted-search","level":3,"title":"Faceted search","text":"<p>Added in <code>v1.2</code></p> <p>Declare <code>facet_fields</code> on the CRUD class to return distinct column values alongside paginated results. This is useful for populating filter dropdowns or building faceted search UIs.</p> <p>Facet fields use the same syntax as <code>searchable_fields</code> — direct columns or relationship tuples:</p> <pre><code>UserCrud = CrudFactory(\n model=User,\n facet_fields=[\n User.status,\n User.country,\n (User.role, Role.name), # value from a related model\n ],\n)\n</code></pre> <p>You can override <code>facet_fields</code> per call:</p> <pre><code>result = await UserCrud.offset_paginate(\n session=session,\n facet_fields=[User.country],\n)\n</code></pre> <p>The distinct values are returned in the <code>filter_attributes</code> field of <code>PaginatedResponse</code>:</p> <pre><code>{\n \"status\": \"SUCCESS\",\n \"data\": [\"...\"],\n \"pagination\": { \"...\" },\n \"filter_attributes\": {\n \"status\": [\"active\", \"inactive\"],\n \"country\": [\"DE\", \"FR\", \"US\"],\n \"name\": [\"admin\", \"editor\", \"viewer\"]\n }\n}\n</code></pre> <p>Use <code>filter_by</code> to pass the client's chosen filter values directly — no need to build SQLAlchemy conditions by hand. Any unknown key raises <code>InvalidFacetFilterError</code>.</p> <p>The keys in <code>filter_by</code> are the same keys the client received in <code>filter_attributes</code>.</p> <p>Keys are normally the terminal <code>column.key</code> (e.g. <code>\"name\"</code> for <code>Role.name</code>). When two facet fields share the same column key (e.g. <code>(Build.project, Project.name)</code> and <code>(Build.os, Os.name)</code>), the relationship name is prepended automatically: <code>\"project__name\"</code> and <code>\"os__name\"</code>.</p> <p><code>filter_by</code> and <code>filters</code> can be combined — both are applied with AND logic.</p> <p>Use <code>filter_params()</code> to generate a dict with the facet filter values from the query parameters:</p> <pre><code>from typing import Annotated\n\nfrom fastapi import Depends\n\nUserCrud = CrudFactory(\n model=User,\n facet_fields=[User.status, User.country, (User.role, Role.name)],\n)\n\n@router.get(\"\", response_model_exclude_none=True)\nasync def list_users(\n session: SessionDep,\n page: int = 1,\n filter_by: Annotated[dict[str, list[str]], Depends(UserCrud.filter_params())],\n) -> PaginatedResponse[UserRead]:\n return await UserCrud.offset_paginate(\n session=session,\n page=page,\n filter_by=filter_by,\n )\n</code></pre> <p>Both single-value and multi-value query parameters work:</p> <pre><code>GET /users?status=active → filter_by={\"status\": [\"active\"]}\nGET /users?status=active&country=FR → filter_by={\"status\": [\"active\"], \"country\": [\"FR\"]}\nGET /users?role=admin&role=editor → filter_by={\"role\": [\"admin\", \"editor\"]} (IN clause)\n</code></pre>","path":["Modules","CRUD"],"tags":[]},{"location":"module/crud/#sorting","level":2,"title":"Sorting","text":"<p>Added in <code>v1.3</code></p> <p>Declare <code>order_fields</code> on the CRUD class to expose client-driven column ordering via <code>order_by</code> and <code>order</code> query parameters.</p> <pre><code>UserCrud = CrudFactory(\n model=User,\n order_fields=[\n User.name,\n User.created_at,\n ],\n)\n</code></pre> <p>Call <code>order_params()</code> to generate a FastAPI dependency that maps the query parameters to an <code>OrderByClause</code> expression:</p> <pre><code>from typing import Annotated\n\nfrom fastapi import Depends\nfrom fastapi_toolsets.crud import OrderByClause\n\n@router.get(\"\")\nasync def list_users(\n session: SessionDep,\n order_by: Annotated[OrderByClause | None, Depends(UserCrud.order_params())],\n) -> PaginatedResponse[UserRead]:\n return await UserCrud.offset_paginate(session=session, order_by=order_by)\n</code></pre> <p>The dependency adds two query parameters to the endpoint:</p> Parameter Type <code>order_by</code> <code>str | null</code> <code>order</code> <code>asc</code> or <code>desc</code> <pre><code>GET /users?order_by=name&order=asc → ORDER BY users.name ASC\nGET /users?order_by=name&order=desc → ORDER BY users.name DESC\n</code></pre> <p>An unknown <code>order_by</code> value raises <code>InvalidOrderFieldError</code> (HTTP 422).</p> <p>You can also pass <code>order_fields</code> directly to <code>order_params()</code> to override the class-level defaults without modifying them:</p> <pre><code>UserOrderParams = UserCrud.order_params(order_fields=[User.name])\n</code></pre>","path":["Modules","CRUD"],"tags":[]},{"location":"module/crud/#relationship-loading","level":2,"title":"Relationship loading","text":"<p>Added in <code>v1.1</code></p> <p>By default, SQLAlchemy relationships are not loaded unless explicitly requested. Instead of using <code>lazy=\"selectin\"</code> on model definitions (which is implicit and applies globally), define a <code>default_load_options</code> on the CRUD class to control loading strategy explicitly.</p> <p>Warning</p> <p>Avoid using <code>lazy=\"selectin\"</code> on model relationships. It fires silently on every query, cannot be disabled per-call, and can cause unexpected cascading loads through deep relationship chains. Use <code>default_load_options</code> instead.</p> <pre><code>from sqlalchemy.orm import selectinload\n\nArticleCrud = CrudFactory(\n model=Article,\n default_load_options=[\n selectinload(Article.category),\n selectinload(Article.tags),\n ],\n)\n</code></pre> <p><code>default_load_options</code> applies automatically to all read operations (<code>get</code>, <code>first</code>, <code>get_multi</code>, <code>offset_paginate</code>, <code>cursor_paginate</code>). When <code>load_options</code> is passed at call-site, it fully replaces <code>default_load_options</code> for that query — giving you precise per-call control:</p> <pre><code># Only loads category, tags are not loaded\narticle = await ArticleCrud.get(\n session=session,\n filters=[Article.id == article_id],\n load_options=[selectinload(Article.category)],\n)\n\n# Loads nothing — useful for write-then-refresh flows or lightweight checks\narticles = await ArticleCrud.get_multi(session=session, load_options=[])\n</code></pre>","path":["Modules","CRUD"],"tags":[]},{"location":"module/crud/#many-to-many-relationships","level":2,"title":"Many-to-many relationships","text":"<p>Use <code>m2m_fields</code> to map schema fields containing lists of IDs to SQLAlchemy relationships. The CRUD class resolves and validates all IDs before persisting:</p> <pre><code>PostCrud = CrudFactory(\n model=Post,\n m2m_fields={\"tag_ids\": Post.tags},\n)\n\npost = await PostCrud.create(session=session, obj=PostCreateSchema(title=\"Hello\", tag_ids=[1, 2, 3]))\n</code></pre>","path":["Modules","CRUD"],"tags":[]},{"location":"module/crud/#upsert","level":2,"title":"Upsert","text":"<p>Atomic <code>INSERT ... ON CONFLICT DO UPDATE</code> using PostgreSQL:</p> <pre><code>await UserCrud.upsert(\n session=session,\n obj=UserCreateSchema(email=\"alice@example.com\", username=\"alice\"),\n index_elements=[User.email],\n set_={\"username\"},\n)\n</code></pre>","path":["Modules","CRUD"],"tags":[]},{"location":"module/crud/#response-serialization","level":2,"title":"Response serialization","text":"<p>Added in <code>v1.1</code></p> <p>Pass a Pydantic schema class to <code>create</code>, <code>get</code>, <code>update</code>, or <code>offset_paginate</code> to serialize the result directly into that schema and wrap it in a <code>Response[schema]</code> or <code>PaginatedResponse[schema]</code>:</p> <pre><code>class UserRead(PydanticBase):\n id: UUID\n username: str\n\n@router.get(\n \"/{uuid}\",\n responses=generate_error_responses(NotFoundError),\n)\nasync def get_user(session: SessionDep, uuid: UUID) -> Response[UserRead]:\n return await crud.UserCrud.get(\n session=session,\n filters=[User.id == uuid],\n schema=UserRead,\n )\n\n@router.get(\"\")\nasync def list_users(session: SessionDep, page: int = 1) -> PaginatedResponse[UserRead]:\n return await crud.UserCrud.offset_paginate(\n session=session,\n page=page,\n schema=UserRead,\n )\n</code></pre> <p>The schema must have <code>from_attributes=True</code> (or inherit from <code>PydanticBase</code>) so it can be built from SQLAlchemy model instances.</p> <p> API Reference</p>","path":["Modules","CRUD"],"tags":[]},{"location":"module/db/","level":1,"title":"DB","text":"<p>SQLAlchemy async session management with transactions, table locking, and row-change polling.</p> <p>Info</p> <p>This module has been coded and tested to be compatible with PostgreSQL only.</p>","path":["Modules","DB"],"tags":[]},{"location":"module/db/#overview","level":2,"title":"Overview","text":"<p>The <code>db</code> module provides helpers to create FastAPI dependencies and context managers for <code>AsyncSession</code>, along with utilities for nested transactions, table lock and polling for row changes.</p>","path":["Modules","DB"],"tags":[]},{"location":"module/db/#session-dependency","level":2,"title":"Session dependency","text":"<p>Use <code>create_db_dependency</code> to create a FastAPI dependency that yields a session and auto-commits on success:</p> <pre><code>from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker\nfrom fastapi_toolsets.db import create_db_dependency\n\nengine = create_async_engine(url=\"postgresql+asyncpg://...\", future=True)\nsession_maker = async_sessionmaker(bind=engine, expire_on_commit=False)\n\nget_db = create_db_dependency(session_maker=session_maker)\n\n@router.get(\"/users\")\nasync def list_users(session: AsyncSession = Depends(get_db)):\n ...\n</code></pre>","path":["Modules","DB"],"tags":[]},{"location":"module/db/#session-context-manager","level":2,"title":"Session context manager","text":"<p>Use <code>create_db_context</code> for sessions outside request handlers (e.g. background tasks, CLI commands):</p> <pre><code>from fastapi_toolsets.db import create_db_context\n\ndb_context = create_db_context(session_maker=session_maker)\n\nasync def seed():\n async with db_context() as session:\n ...\n</code></pre>","path":["Modules","DB"],"tags":[]},{"location":"module/db/#nested-transactions","level":2,"title":"Nested transactions","text":"<p><code>get_transaction</code> handles savepoints automatically, allowing safe nesting:</p> <pre><code>from fastapi_toolsets.db import get_transaction\n\nasync def create_user_with_role(session=session):\n async with get_transaction(session=session):\n ...\n async with get_transaction(session=session): # uses savepoint\n ...\n</code></pre>","path":["Modules","DB"],"tags":[]},{"location":"module/db/#table-locking","level":2,"title":"Table locking","text":"<p><code>lock_tables</code> acquires PostgreSQL table-level locks before executing critical sections:</p> <pre><code>from fastapi_toolsets.db import lock_tables\n\nasync with lock_tables(session=session, tables=[User], mode=\"EXCLUSIVE\"):\n # No other transaction can modify User until this block exits\n ...\n</code></pre> <p>Available lock modes are defined in <code>LockMode</code>: <code>ACCESS_SHARE</code>, <code>ROW_SHARE</code>, <code>ROW_EXCLUSIVE</code>, <code>SHARE_UPDATE_EXCLUSIVE</code>, <code>SHARE</code>, <code>SHARE_ROW_EXCLUSIVE</code>, <code>EXCLUSIVE</code>, <code>ACCESS_EXCLUSIVE</code>.</p>","path":["Modules","DB"],"tags":[]},{"location":"module/db/#row-change-polling","level":2,"title":"Row-change polling","text":"<p><code>wait_for_row_change</code> polls a row until a specific column changes value, useful for waiting on async side effects:</p> <pre><code>from fastapi_toolsets.db import wait_for_row_change\n\n# Wait up to 30s for order.status to change\nawait wait_for_row_change(\n session=session,\n model=Order,\n pk_value=order_id,\n columns=[Order.status],\n interval=1.0,\n timeout=30.0,\n)\n</code></pre>","path":["Modules","DB"],"tags":[]},{"location":"module/db/#creating-a-database","level":2,"title":"Creating a database","text":"<p>Added in <code>v2.1</code></p> <p><code>create_database</code> creates a database at a given URL. It connects to server_url and issues a <code>CREATE DATABASE</code> statement:</p> <pre><code>from fastapi_toolsets.db import create_database\n\nSERVER_URL = \"postgresql+asyncpg://postgres:postgres@localhost/postgres\"\n\nawait create_database(db_name=\"myapp_test\", server_url=SERVER_URL)\n</code></pre> <p>For test isolation with automatic cleanup, use <code>create_worker_database</code> from the <code>pytest</code> module instead — it handles drop-before, create, and drop-after automatically.</p>","path":["Modules","DB"],"tags":[]},{"location":"module/db/#cleaning-up-tables","level":2,"title":"Cleaning up tables","text":"<p>Added in <code>v2.1</code></p> <p><code>cleanup_tables</code> truncates all tables:</p> <pre><code>from fastapi_toolsets.db import cleanup_tables\n\n@pytest.fixture(autouse=True)\nasync def clean(db_session):\n yield\n await cleanup_tables(session=db_session, base=Base)\n</code></pre> <p> API Reference</p>","path":["Modules","DB"],"tags":[]},{"location":"module/dependencies/","level":1,"title":"Dependencies","text":"<p>FastAPI dependency factories for automatic model resolution from path and body parameters.</p>","path":["Modules","Dependencies"],"tags":[]},{"location":"module/dependencies/#overview","level":2,"title":"Overview","text":"<p>The <code>dependencies</code> module provides two factory functions that create FastAPI dependencies to fetch a model instance from the database automatically — either from a path parameter or from a request body field — and inject it directly into your route handler.</p>","path":["Modules","Dependencies"],"tags":[]},{"location":"module/dependencies/#pathdependency","level":2,"title":"<code>PathDependency</code>","text":"<p><code>PathDependency</code> resolves a model from a URL path parameter and injects it into the route handler. Raises <code>NotFoundError</code> automatically if the record does not exist.</p> <pre><code>from fastapi_toolsets.dependencies import PathDependency\n\nUserDep = PathDependency(model=User, field=User.id, session_dep=get_db)\n\n@router.get(\"/users/{user_id}\")\nasync def get_user(user: User = UserDep):\n return user\n</code></pre> <p>By default the parameter name is inferred from the field (<code>user_id</code> for <code>User.id</code>). You can override it:</p> <pre><code>UserDep = PathDependency(model=User, field=User.id, session_dep=get_db, param_name=\"id\")\n\n@router.get(\"/users/{id}\")\nasync def get_user(user: User = UserDep):\n return user\n</code></pre>","path":["Modules","Dependencies"],"tags":[]},{"location":"module/dependencies/#bodydependency","level":2,"title":"<code>BodyDependency</code>","text":"<p><code>BodyDependency</code> resolves a model from a field in the request body. Useful when a body contains a foreign key and you want the full object injected:</p> <pre><code>from fastapi_toolsets.dependencies import BodyDependency\n\nRoleDep = BodyDependency(model=Role, field=Role.id, session_dep=get_db, body_field=\"role_id\")\n\n@router.post(\"/users\")\nasync def create_user(body: UserCreateSchema, role: Role = RoleDep):\n user = User(username=body.username, role=role)\n ...\n</code></pre> <p> API Reference</p>","path":["Modules","Dependencies"],"tags":[]},{"location":"module/exceptions/","level":1,"title":"Exceptions","text":"<p>Structured API exceptions with consistent error responses and automatic OpenAPI documentation.</p>","path":["Modules","Exceptions"],"tags":[]},{"location":"module/exceptions/#overview","level":2,"title":"Overview","text":"<p>The <code>exceptions</code> module provides a set of pre-built HTTP exceptions and a FastAPI exception handler that formats all errors — including validation errors — into a uniform <code>ErrorResponse</code>.</p>","path":["Modules","Exceptions"],"tags":[]},{"location":"module/exceptions/#setup","level":2,"title":"Setup","text":"<p>Register the exception handlers on your FastAPI app at startup:</p> <pre><code>from fastapi import FastAPI\nfrom fastapi_toolsets.exceptions import init_exceptions_handlers\n\napp = FastAPI()\ninit_exceptions_handlers(app=app)\n</code></pre> <p>This registers handlers for:</p> <ul> <li><code>ApiException</code> — all custom exceptions below</li> <li><code>HTTPException</code> — Starlette/FastAPI HTTP errors</li> <li><code>RequestValidationError</code> — Pydantic request validation (422)</li> <li><code>ResponseValidationError</code> — Pydantic response validation (422)</li> <li><code>Exception</code> — unhandled errors (500)</li> </ul> <p>It also patches <code>app.openapi()</code> to replace the default Pydantic 422 schema with a structured example matching the <code>ErrorResponse</code> format.</p>","path":["Modules","Exceptions"],"tags":[]},{"location":"module/exceptions/#built-in-exceptions","level":2,"title":"Built-in exceptions","text":"Exception Status Default message <code>UnauthorizedError</code> 401 Unauthorized <code>ForbiddenError</code> 403 Forbidden <code>NotFoundError</code> 404 Not Found <code>ConflictError</code> 409 Conflict <code>NoSearchableFieldsError</code> 400 No Searchable Fields <code>InvalidFacetFilterError</code> 400 Invalid Facet Filter <code>InvalidOrderFieldError</code> 422 Invalid Order Field","path":["Modules","Exceptions"],"tags":[]},{"location":"module/exceptions/#per-instance-overrides","level":3,"title":"Per-instance overrides","text":"<p>All built-in exceptions accept optional keyword arguments to customise the response for a specific raise site without changing the class defaults:</p> Argument Effect <code>detail</code> Overrides both <code>str(exc)</code> (log output) and the <code>message</code> field in the response body <code>desc</code> Overrides the <code>description</code> field <code>data</code> Overrides the <code>data</code> field <pre><code>raise NotFoundError(detail=\"User 42 not found\", desc=\"No user with that ID exists in the database.\")\n</code></pre>","path":["Modules","Exceptions"],"tags":[]},{"location":"module/exceptions/#custom-exceptions","level":2,"title":"Custom exceptions","text":"<p>Subclass <code>ApiException</code> and define an <code>api_error</code> class variable:</p> <pre><code>from fastapi_toolsets.exceptions import ApiException\nfrom fastapi_toolsets.schemas import ApiError\n\nclass PaymentRequiredError(ApiException):\n api_error = ApiError(\n code=402,\n msg=\"Payment Required\",\n desc=\"Your subscription has expired.\",\n err_code=\"BILLING-402\",\n )\n</code></pre> <p>Warning</p> <p>Subclasses that do not define <code>api_error</code> raise a <code>TypeError</code> at class creation time, not at raise time.</p>","path":["Modules","Exceptions"],"tags":[]},{"location":"module/exceptions/#custom-__init__","level":3,"title":"Custom <code>__init__</code>","text":"<p>Override <code>__init__</code> to compute <code>detail</code>, <code>desc</code>, or <code>data</code> dynamically, then delegate to <code>super().__init__()</code>:</p> <pre><code>class OrderValidationError(ApiException):\n api_error = ApiError(\n code=422,\n msg=\"Order Validation Failed\",\n desc=\"One or more order fields are invalid.\",\n err_code=\"ORDER-422\",\n )\n\n def __init__(self, *field_errors: str) -> None:\n super().__init__(\n f\"{len(field_errors)} validation error(s)\",\n desc=\", \".join(field_errors),\n data={\"errors\": [{\"message\": e} for e in field_errors]},\n )\n</code></pre>","path":["Modules","Exceptions"],"tags":[]},{"location":"module/exceptions/#intermediate-base-classes","level":3,"title":"Intermediate base classes","text":"<p>Use <code>abstract=True</code> when creating a shared base that is not meant to be raised directly:</p> <pre><code>class BillingError(ApiException, abstract=True):\n \"\"\"Base for all billing-related errors.\"\"\"\n\nclass PaymentRequiredError(BillingError):\n api_error = ApiError(code=402, msg=\"Payment Required\", desc=\"...\", err_code=\"BILLING-402\")\n\nclass SubscriptionExpiredError(BillingError):\n api_error = ApiError(code=402, msg=\"Subscription Expired\", desc=\"...\", err_code=\"BILLING-402-EXP\")\n</code></pre>","path":["Modules","Exceptions"],"tags":[]},{"location":"module/exceptions/#openapi-response-documentation","level":2,"title":"OpenAPI response documentation","text":"<p>Use <code>generate_error_responses</code> to add error schemas to your endpoint's OpenAPI spec:</p> <pre><code>from fastapi_toolsets.exceptions import generate_error_responses, NotFoundError, ForbiddenError\n\n@router.get(\n \"/users/{id}\",\n responses=generate_error_responses(NotFoundError, ForbiddenError),\n)\nasync def get_user(...): ...\n</code></pre> <p>Multiple exceptions sharing the same HTTP status code are grouped under one entry, each appearing as a named example keyed by its <code>err_code</code>. This keeps the OpenAPI UI readable when several error variants map to the same status.</p> <p> API Reference</p>","path":["Modules","Exceptions"],"tags":[]},{"location":"module/fixtures/","level":1,"title":"Fixtures","text":"<p>Dependency-aware database seeding with context-based loading strategies.</p>","path":["Modules","Fixtures"],"tags":[]},{"location":"module/fixtures/#overview","level":2,"title":"Overview","text":"<p>The <code>fixtures</code> module lets you define named fixtures with dependencies between them, then load them into the database in the correct order. Fixtures can be scoped to contexts (e.g. base data, testing data) so that only the relevant ones are loaded for each environment.</p>","path":["Modules","Fixtures"],"tags":[]},{"location":"module/fixtures/#defining-fixtures","level":2,"title":"Defining fixtures","text":"<pre><code>from fastapi_toolsets.fixtures import FixtureRegistry, Context\n\nfixtures = FixtureRegistry()\n\n@fixtures.register\ndef roles():\n return [\n Role(id=1, name=\"admin\"),\n Role(id=2, name=\"user\"),\n ]\n\n@fixtures.register(depends_on=[\"roles\"], contexts=[Context.TESTING])\ndef test_users():\n return [\n User(id=1, username=\"alice\", role_id=1),\n User(id=2, username=\"bob\", role_id=2),\n ]\n</code></pre> <p>Dependencies declared via <code>depends_on</code> are resolved topologically — <code>roles</code> will always be loaded before <code>test_users</code>.</p>","path":["Modules","Fixtures"],"tags":[]},{"location":"module/fixtures/#loading-fixtures","level":2,"title":"Loading fixtures","text":"<p>By context with <code>load_fixtures_by_context</code>:</p> <pre><code>from fastapi_toolsets.fixtures import load_fixtures_by_context\n\nasync with db_context() as session:\n await load_fixtures_by_context(session=session, registry=fixtures, context=Context.TESTING)\n</code></pre> <p>Directly with <code>load_fixtures</code>:</p> <pre><code>from fastapi_toolsets.fixtures import load_fixtures\n\nasync with db_context() as session:\n await load_fixtures(session=session, registry=fixtures)\n</code></pre>","path":["Modules","Fixtures"],"tags":[]},{"location":"module/fixtures/#contexts","level":2,"title":"Contexts","text":"<p><code>Context</code> is an enum with predefined values:</p> Context Description <code>Context.BASE</code> Core data required in all environments <code>Context.TESTING</code> Data only loaded during tests <code>Context.PRODUCTION</code> Data only loaded in production <p>A fixture with no <code>contexts</code> defined takes <code>Context.BASE</code> by default.</p>","path":["Modules","Fixtures"],"tags":[]},{"location":"module/fixtures/#load-strategies","level":2,"title":"Load strategies","text":"<p><code>LoadStrategy</code> controls how the fixture loader handles rows that already exist:</p> Strategy Description <code>LoadStrategy.INSERT</code> Insert only, fail on duplicates <code>LoadStrategy.UPSERT</code> Insert or update on conflict <code>LoadStrategy.SKIP</code> Skip rows that already exist","path":["Modules","Fixtures"],"tags":[]},{"location":"module/fixtures/#merging-registries","level":2,"title":"Merging registries","text":"<p>Split fixtures definitions across modules and merge them:</p> <pre><code>from myapp.fixtures.dev import dev_fixtures\nfrom myapp.fixtures.prod import prod_fixtures\n\nfixtures = fixturesRegistry()\nfixtures.include_registry(registry=dev_fixtures)\nfixtures.include_registry(registry=prod_fixtures)\n\n## Pytest integration\n\nUse [`register_fixtures`](../reference/pytest.md#fastapi_toolsets.pytest.plugin.register_fixtures) to expose each fixture in your registry as an injectable pytest fixture named `fixture_{name}` by default:\n\n```python\n# conftest.py\nimport pytest\nfrom fastapi_toolsets.pytest import create_db_session, register_fixtures\nfrom app.fixtures import registry\nfrom app.models import Base\n\nDATABASE_URL = \"postgresql+asyncpg://user:pass@localhost/test_db\"\n\n@pytest.fixture\nasync def db_session():\n async with create_db_session(database_url=DATABASE_URL, base=Base, cleanup=True) as session:\n yield session\n\nregister_fixtures(registry=registry, namespace=globals())\n</code></pre> <pre><code># test_users.py\nasync def test_user_can_login(fixture_users: list[User], fixture_roles: list[Role]):\n ...\n</code></pre> <p>The load order is resolved automatically from the <code>depends_on</code> declarations in your registry. Each generated fixture receives <code>db_session</code> as a dependency and returns the list of loaded model instances.</p>","path":["Modules","Fixtures"],"tags":[]},{"location":"module/fixtures/#cli-integration","level":2,"title":"CLI integration","text":"<p>Fixtures can be triggered from the CLI. See the CLI module for setup instructions.</p> <p> API Reference</p>","path":["Modules","Fixtures"],"tags":[]},{"location":"module/logger/","level":1,"title":"Logger","text":"<p>Lightweight logging utilities with consistent formatting and uvicorn integration.</p>","path":["Modules","Logger"],"tags":[]},{"location":"module/logger/#overview","level":2,"title":"Overview","text":"<p>The <code>logger</code> module provides two helpers: one to configure the root logger (and uvicorn loggers) at startup, and one to retrieve a named logger anywhere in your codebase.</p>","path":["Modules","Logger"],"tags":[]},{"location":"module/logger/#setup","level":2,"title":"Setup","text":"<p>Call <code>configure_logging</code> once at application startup:</p> <pre><code>from fastapi_toolsets.logger import configure_logging\n\nconfigure_logging(level=\"INFO\")\n</code></pre> <p>This sets up a stdout handler with a consistent format and also configures uvicorn's access and error loggers so all log output shares the same style.</p>","path":["Modules","Logger"],"tags":[]},{"location":"module/logger/#getting-a-logger","level":2,"title":"Getting a logger","text":"<pre><code>from fastapi_toolsets.logger import get_logger\n\nlogger = get_logger(name=__name__)\nlogger.info(\"User created\")\n</code></pre> <p>When called without arguments, <code>get_logger</code> auto-detects the caller's module name via frame inspection:</p> <pre><code># Equivalent to get_logger(name=__name__)\nlogger = get_logger()\n</code></pre> <p> API Reference</p>","path":["Modules","Logger"],"tags":[]},{"location":"module/metrics/","level":1,"title":"Metrics","text":"<p>Prometheus metrics integration with a decorator-based registry and multi-process support.</p>","path":["Modules","Metrics"],"tags":[]},{"location":"module/metrics/#installation","level":2,"title":"Installation","text":"uvpip <pre><code>uv add \"fastapi-toolsets[metrics]\"\n</code></pre> <pre><code>pip install \"fastapi-toolsets[metrics]\"\n</code></pre>","path":["Modules","Metrics"],"tags":[]},{"location":"module/metrics/#overview","level":2,"title":"Overview","text":"<p>The <code>metrics</code> module provides a <code>MetricsRegistry</code> to declare Prometheus metrics with decorators, and an <code>init_metrics</code> function to mount a <code>/metrics</code> endpoint on your FastAPI app.</p>","path":["Modules","Metrics"],"tags":[]},{"location":"module/metrics/#setup","level":2,"title":"Setup","text":"<pre><code>from fastapi import FastAPI\nfrom fastapi_toolsets.metrics import MetricsRegistry, init_metrics\n\napp = FastAPI()\nmetrics = MetricsRegistry()\n\ninit_metrics(app=app, registry=metrics)\n</code></pre> <p>This mounts the <code>/metrics</code> endpoint that Prometheus can scrape.</p>","path":["Modules","Metrics"],"tags":[]},{"location":"module/metrics/#declaring-metrics","level":2,"title":"Declaring metrics","text":"","path":["Modules","Metrics"],"tags":[]},{"location":"module/metrics/#providers","level":3,"title":"Providers","text":"<p>Providers are called once at startup by <code>init_metrics</code>. The return value (the Prometheus metric object) is stored in the registry and can be retrieved later with <code>registry.get(name)</code>.</p> <p>Use providers when you want deferred initialization: the Prometheus metric is not registered with the global <code>CollectorRegistry</code> until <code>init_metrics</code> runs, not at import time. This is particularly useful for testing — importing the module in a test suite without calling <code>init_metrics</code> leaves no metrics registered, avoiding cross-test pollution.</p> <p>It is also useful when metrics are defined across multiple modules and merged with <code>include_registry</code>: any code that needs a metric can call <code>metrics.get()</code> on the shared registry instead of importing the metric directly from its origin module.</p> <p>If neither of these applies to you, declaring metrics at module level (e.g. <code>HTTP_REQUESTS = Counter(...)</code>) is simpler and equally valid.</p> <pre><code>from prometheus_client import Counter, Histogram\n\n@metrics.register\ndef http_requests():\n return Counter(\"http_requests_total\", \"Total HTTP requests\", [\"method\", \"status\"])\n\n@metrics.register\ndef request_duration():\n return Histogram(\"request_duration_seconds\", \"Request duration\")\n</code></pre> <p>To use a provider's metric elsewhere (e.g. in a middleware), call <code>metrics.get()</code> inside the handler — not at module level, as providers are only initialized when <code>init_metrics</code> runs:</p> <pre><code>async def metrics_middleware(request: Request, call_next):\n response = await call_next(request)\n metrics.get(\"http_requests\").labels(\n method=request.method, status=response.status_code\n ).inc()\n return response\n</code></pre>","path":["Modules","Metrics"],"tags":[]},{"location":"module/metrics/#collectors","level":3,"title":"Collectors","text":"<p>Collectors are called on every scrape. Use them for metrics that reflect current state (e.g. gauges).</p> <p>Declare the metric at module level</p> <p>Do not instantiate the Prometheus metric inside the collector function. Doing so recreates it on every scrape, raising <code>ValueError: Duplicated timeseries in CollectorRegistry</code>. Declare it once at module level instead:</p> <pre><code>from prometheus_client import Gauge\n\n_queue_depth = Gauge(\"queue_depth\", \"Current queue depth\")\n\n@metrics.register(collect=True)\ndef collect_queue_depth():\n _queue_depth.set(get_current_queue_depth())\n</code></pre>","path":["Modules","Metrics"],"tags":[]},{"location":"module/metrics/#merging-registries","level":2,"title":"Merging registries","text":"<p>Split metrics definitions across modules and merge them:</p> <pre><code>from myapp.metrics.http import http_metrics\nfrom myapp.metrics.db import db_metrics\n\nmetrics = MetricsRegistry()\nmetrics.include_registry(registry=http_metrics)\nmetrics.include_registry(registry=db_metrics)\n</code></pre>","path":["Modules","Metrics"],"tags":[]},{"location":"module/metrics/#multi-process-mode","level":2,"title":"Multi-process mode","text":"<p>Multi-process support is enabled automatically when the <code>PROMETHEUS_MULTIPROC_DIR</code> environment variable is set. No code changes are required.</p> <p>Environment variable name</p> <p>The correct variable is <code>PROMETHEUS_MULTIPROC_DIR</code> (not <code>PROMETHEUS_MULTIPROCESS_DIR</code>).</p> <p> API Reference</p>","path":["Modules","Metrics"],"tags":[]},{"location":"module/models/","level":1,"title":"Models","text":"<p>Added in <code>v2.0</code></p> <p>Reusable SQLAlchemy 2.0 mixins for common column patterns, designed to be composed freely on any <code>DeclarativeBase</code> model.</p>","path":["Modules","Models"],"tags":[]},{"location":"module/models/#overview","level":2,"title":"Overview","text":"<p>The <code>models</code> module provides mixins that each add a single, well-defined column behaviour. They work with standard SQLAlchemy 2.0 declarative syntax and are fully compatible with <code>AsyncSession</code>.</p> <pre><code>from fastapi_toolsets.models import UUIDMixin, TimestampMixin\n\nclass Article(Base, UUIDMixin, TimestampMixin):\n __tablename__ = \"articles\"\n\n title: Mapped[str]\n content: Mapped[str]\n</code></pre> <p>All timestamp columns are timezone-aware (<code>TIMESTAMPTZ</code>). All defaults are server-side, so they are also applied when inserting rows via raw SQL outside the ORM.</p>","path":["Modules","Models"],"tags":[]},{"location":"module/models/#mixins","level":2,"title":"Mixins","text":"","path":["Modules","Models"],"tags":[]},{"location":"module/models/#uuidmixin","level":3,"title":"<code>UUIDMixin</code>","text":"<p>Adds a <code>id: UUID</code> primary key generated server-side by PostgreSQL using <code>gen_random_uuid()</code> (requires PostgreSQL 13+). The value is retrieved via <code>RETURNING</code> after insert, so it is available on the Python object immediately after <code>flush()</code>.</p> <pre><code>from fastapi_toolsets.models import UUIDMixin\n\nclass User(Base, UUIDMixin):\n __tablename__ = \"users\"\n\n username: Mapped[str]\n\n# id is None before flush\nuser = User(username=\"alice\")\nawait session.flush()\nprint(user.id) # UUID('...')\n</code></pre>","path":["Modules","Models"],"tags":[]},{"location":"module/models/#createdatmixin","level":3,"title":"<code>CreatedAtMixin</code>","text":"<p>Adds a <code>created_at: datetime</code> column set to <code>NOW()</code> on insert. The column has no <code>onupdate</code> hook — it is intentionally immutable after the row is created.</p> <pre><code>from fastapi_toolsets.models import UUIDMixin, CreatedAtMixin\n\nclass Order(Base, UUIDMixin, CreatedAtMixin):\n __tablename__ = \"orders\"\n\n total: Mapped[float]\n</code></pre>","path":["Modules","Models"],"tags":[]},{"location":"module/models/#updatedatmixin","level":3,"title":"<code>UpdatedAtMixin</code>","text":"<p>Adds an <code>updated_at: datetime</code> column set to <code>NOW()</code> on insert and automatically updated to <code>NOW()</code> on every ORM-level update (via SQLAlchemy's <code>onupdate</code> hook).</p> <pre><code>from fastapi_toolsets.models import UUIDMixin, UpdatedAtMixin\n\nclass Post(Base, UUIDMixin, UpdatedAtMixin):\n __tablename__ = \"posts\"\n\n title: Mapped[str]\n\npost = Post(title=\"Hello\")\nawait session.flush()\nawait session.refresh(post)\n\npost.title = \"Hello World\"\nawait session.flush()\nawait session.refresh(post)\nprint(post.updated_at)\n</code></pre> <p>Note</p> <p><code>updated_at</code> is updated by SQLAlchemy at ORM flush time. If you update rows via raw SQL (e.g. <code>UPDATE posts SET ...</code>), the column will not be updated automatically — use a database trigger if you need that guarantee.</p>","path":["Modules","Models"],"tags":[]},{"location":"module/models/#timestampmixin","level":3,"title":"<code>TimestampMixin</code>","text":"<p>Convenience mixin that combines <code>CreatedAtMixin</code> and <code>UpdatedAtMixin</code>. Equivalent to inheriting both.</p> <pre><code>from fastapi_toolsets.models import UUIDMixin, TimestampMixin\n\nclass Article(Base, UUIDMixin, TimestampMixin):\n __tablename__ = \"articles\"\n\n title: Mapped[str]\n</code></pre>","path":["Modules","Models"],"tags":[]},{"location":"module/models/#composing-mixins","level":2,"title":"Composing mixins","text":"<p>All mixins can be combined in any order. The only constraint is that exactly one primary key must be defined — either via <code>UUIDMixin</code> or directly on the model.</p> <pre><code>from fastapi_toolsets.models import UUIDMixin, TimestampMixin\n\nclass Event(Base, UUIDMixin, TimestampMixin):\n __tablename__ = \"events\"\n name: Mapped[str]\n\nclass Counter(Base, UpdatedAtMixin):\n __tablename__ = \"counters\"\n id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)\n value: Mapped[int]\n</code></pre> <p> API Reference</p>","path":["Modules","Models"],"tags":[]},{"location":"module/pytest/","level":1,"title":"Pytest","text":"<p>Testing helpers for FastAPI applications with async client, database sessions, and parallel worker support.</p>","path":["Modules","Pytest"],"tags":[]},{"location":"module/pytest/#installation","level":2,"title":"Installation","text":"uvpip <pre><code>uv add \"fastapi-toolsets[pytest]\"\n</code></pre> <pre><code>pip install \"fastapi-toolsets[pytest]\"\n</code></pre>","path":["Modules","Pytest"],"tags":[]},{"location":"module/pytest/#overview","level":2,"title":"Overview","text":"<p>The <code>pytest</code> module provides utilities for setting up async test clients, managing test database sessions, and supporting parallel test execution with <code>pytest-xdist</code>.</p>","path":["Modules","Pytest"],"tags":[]},{"location":"module/pytest/#creating-an-async-client","level":2,"title":"Creating an async client","text":"<p>Use <code>create_async_client</code> to get an <code>httpx.AsyncClient</code> configured for your FastAPI app:</p> <pre><code>from fastapi_toolsets.pytest import create_async_client\n\n@pytest.fixture\nasync def http_client(db_session):\n async def _override_get_db():\n yield db_session\n\n async with create_async_client(\n app=app,\n base_url=\"http://127.0.0.1/api/v1\",\n dependency_overrides={get_db: _override_get_db},\n ) as c:\n yield c\n</code></pre>","path":["Modules","Pytest"],"tags":[]},{"location":"module/pytest/#database-sessions-in-tests","level":2,"title":"Database sessions in tests","text":"<p>Use <code>create_db_session</code> to create an isolated <code>AsyncSession</code> for a test, combined with <code>create_worker_database</code> to set up a per-worker database:</p> <pre><code>from fastapi_toolsets.pytest import create_worker_database, create_db_session\n\n@pytest.fixture(scope=\"session\")\nasync def worker_db_url():\n async with create_worker_database(\n database_url=str(settings.SQLALCHEMY_DATABASE_URI)\n ) as url:\n yield url\n\n\n@pytest.fixture\nasync def db_session(worker_db_url):\n async with create_db_session(\n database_url=worker_db_url, base=Base, cleanup=True\n ) as session:\n yield session\n</code></pre> <p>Info</p> <p>In this example, the database is reset between each test using the argument <code>cleanup=True</code>.</p> <p>Use <code>worker_database_url</code> to derive the per-worker URL manually if needed:</p> <pre><code>from fastapi_toolsets.pytest import worker_database_url\n\nurl = worker_database_url(\"postgresql+asyncpg://user:pass@localhost/test_db\", default_test_db=\"test\")\n# e.g. \"postgresql+asyncpg://user:pass@localhost/test_db_gw0\" under xdist\n</code></pre>","path":["Modules","Pytest"],"tags":[]},{"location":"module/pytest/#parallel-testing-with-pytest-xdist","level":2,"title":"Parallel testing with pytest-xdist","text":"<p>The examples above are already compatible with parallel test execution with <code>pytest-xdist</code>.</p>","path":["Modules","Pytest"],"tags":[]},{"location":"module/pytest/#cleaning-up-tables","level":2,"title":"Cleaning up tables","text":"<p>Warning</p> <p>Since <code>V2.1.0</code> <code>cleanup_tables</code> now live in <code>fastapi_toolsets.db</code>. For backward compatibility the function is still available in <code>fastapi_toolsets.pytest</code>, but this will be remove in <code>V3.0.0</code>.</p> <p>If you want to manually clean up a database you can use <code>cleanup_tables</code>, this will truncate all tables between tests for fast isolation:</p> <pre><code>from fastapi_toolsets.db import cleanup_tables\n\n@pytest.fixture(autouse=True)\nasync def clean(db_session):\n yield\n await cleanup_tables(session=db_session, base=Base)\n</code></pre> <p> API Reference</p>","path":["Modules","Pytest"],"tags":[]},{"location":"module/schemas/","level":1,"title":"Schemas","text":"<p>Standardized Pydantic response models for consistent API responses across your FastAPI application.</p>","path":["Modules","Schemas"],"tags":[]},{"location":"module/schemas/#overview","level":2,"title":"Overview","text":"<p>The <code>schemas</code> module provides generic response wrappers that enforce a uniform response structure. All models use <code>from_attributes=True</code> for ORM compatibility and <code>validate_assignment=True</code> for runtime type safety.</p>","path":["Modules","Schemas"],"tags":[]},{"location":"module/schemas/#response-models","level":2,"title":"Response models","text":"","path":["Modules","Schemas"],"tags":[]},{"location":"module/schemas/#responset","level":3,"title":"<code>Response[T]</code>","text":"<p>The most common wrapper for a single resource response.</p> <pre><code>from fastapi_toolsets.schemas import Response\n\n@router.get(\"/users/{id}\")\nasync def get_user(user: User = UserDep) -> Response[UserSchema]:\n return Response(data=user, message=\"User retrieved\")\n</code></pre>","path":["Modules","Schemas"],"tags":[]},{"location":"module/schemas/#paginatedresponset","level":3,"title":"<code>PaginatedResponse[T]</code>","text":"<p>Wraps a list of items with pagination metadata and optional facet values. The <code>pagination</code> field accepts either <code>OffsetPagination</code> or <code>CursorPagination</code> depending on the strategy used.</p>","path":["Modules","Schemas"],"tags":[]},{"location":"module/schemas/#offsetpagination","level":4,"title":"<code>OffsetPagination</code>","text":"<p>Page-number based. Requires <code>total_count</code> so clients can compute the total number of pages.</p> <pre><code>from fastapi_toolsets.schemas import PaginatedResponse, OffsetPagination\n\n@router.get(\"/users\")\nasync def list_users() -> PaginatedResponse[UserSchema]:\n return PaginatedResponse(\n data=users,\n pagination=OffsetPagination(\n total_count=100,\n items_per_page=10,\n page=1,\n has_more=True,\n ),\n )\n</code></pre>","path":["Modules","Schemas"],"tags":[]},{"location":"module/schemas/#cursorpagination","level":4,"title":"<code>CursorPagination</code>","text":"<p>Cursor based. Efficient for large or frequently updated datasets where offset pagination is impractical. Provides opaque <code>next_cursor</code> / <code>prev_cursor</code> tokens; no total count is exposed.</p> <pre><code>from fastapi_toolsets.schemas import PaginatedResponse, CursorPagination\n\n@router.get(\"/events\")\nasync def list_events() -> PaginatedResponse[EventSchema]:\n return PaginatedResponse(\n data=events,\n pagination=CursorPagination(\n next_cursor=\"eyJpZCI6IDQyfQ==\",\n prev_cursor=None,\n items_per_page=20,\n has_more=True,\n ),\n )\n</code></pre> <p>The optional <code>filter_attributes</code> field is populated when <code>facet_fields</code> are configured on the CRUD class (see Filter attributes). It is <code>None</code> by default and can be hidden from API responses with <code>response_model_exclude_none=True</code>.</p>","path":["Modules","Schemas"],"tags":[]},{"location":"module/schemas/#errorresponse","level":3,"title":"<code>ErrorResponse</code>","text":"<p>Returned automatically by the exceptions handler.</p> <p> API Reference</p>","path":["Modules","Schemas"],"tags":[]},{"location":"reference/cli/","level":1,"title":"<code>cli</code>","text":"<p>Here's the reference for the CLI configuration helpers used to load settings from <code>pyproject.toml</code>.</p> <p>You can import them directly from <code>fastapi_toolsets.cli.config</code>:</p> <pre><code>from fastapi_toolsets.cli.config import (\n import_from_string,\n get_config_value,\n get_fixtures_registry,\n get_db_context,\n get_custom_cli,\n)\n</code></pre>","path":["Reference","cli"],"tags":[]},{"location":"reference/cli/#fastapi_toolsets.cli.config.import_from_string","level":2,"title":"<code>fastapi_toolsets.cli.config.import_from_string(import_path)</code>","text":"<p>Import an object from a dotted string path.</p> <p>Parameters:</p> Name Type Description Default <code>import_path</code> <code>str</code> <p>Import path in <code>\"module.submodule:attribute\"</code> format</p> required <p>Returns:</p> Type Description <code>Any</code> <p>The imported attribute</p> <p>Raises:</p> Type Description <code>BadParameter</code> <p>If the import path is invalid or import fails</p>","path":["Reference","cli"],"tags":[]},{"location":"reference/cli/#fastapi_toolsets.cli.config.get_config_value","level":2,"title":"<code>fastapi_toolsets.cli.config.get_config_value(key, required=False)</code>","text":"<pre><code>get_config_value(key: str, required: Literal[True]) -> Any\n</code></pre><pre><code>get_config_value(\n key: str, required: bool = False\n) -> Any | None\n</code></pre> <p>Get a configuration value from pyproject.toml.</p> <p>Parameters:</p> Name Type Description Default <code>key</code> <code>str</code> <p>The configuration key in [tool.fastapi-toolsets].</p> required <code>required</code> <code>bool</code> <p>If True, raises an error when the key is missing.</p> <code>False</code> <p>Returns:</p> Type Description <code>Any | None</code> <p>The configuration value, or None if not found and not required.</p> <p>Raises:</p> Type Description <code>BadParameter</code> <p>If required=True and the key is missing.</p>","path":["Reference","cli"],"tags":[]},{"location":"reference/cli/#fastapi_toolsets.cli.config.get_fixtures_registry","level":2,"title":"<code>fastapi_toolsets.cli.config.get_fixtures_registry()</code>","text":"<p>Import and return the fixtures registry from config.</p>","path":["Reference","cli"],"tags":[]},{"location":"reference/cli/#fastapi_toolsets.cli.config.get_db_context","level":2,"title":"<code>fastapi_toolsets.cli.config.get_db_context()</code>","text":"<p>Import and return the db_context function from config.</p>","path":["Reference","cli"],"tags":[]},{"location":"reference/cli/#fastapi_toolsets.cli.config.get_custom_cli","level":2,"title":"<code>fastapi_toolsets.cli.config.get_custom_cli()</code>","text":"<p>Import and return the custom CLI Typer instance from config.</p>","path":["Reference","cli"],"tags":[]},{"location":"reference/cli/#fastapi_toolsets.cli.utils.async_command","level":2,"title":"<code>fastapi_toolsets.cli.utils.async_command(func)</code>","text":"<p>Decorator to run an async function as a sync CLI command.</p> Example <pre><code>@fixture_cli.command(\"load\")\n@async_command\nasync def load(ctx: typer.Context) -> None:\n async with get_db_context() as session:\n await load_fixtures(session, registry)\n</code></pre>","path":["Reference","cli"],"tags":[]},{"location":"reference/crud/","level":1,"title":"<code>crud</code>","text":"<p>Here's the reference for the CRUD classes, factory, and search utilities.</p> <p>You can import the main symbols from <code>fastapi_toolsets.crud</code>:</p> <pre><code>from fastapi_toolsets.crud import CrudFactory, AsyncCrud\nfrom fastapi_toolsets.crud.search import SearchConfig, get_searchable_fields, build_search_filters\n</code></pre>","path":["Reference","crud"],"tags":[]},{"location":"reference/crud/#fastapi_toolsets.crud.factory.AsyncCrud","level":2,"title":"<code>fastapi_toolsets.crud.factory.AsyncCrud</code>","text":"<p> Bases: <code>Generic[ModelType]</code></p> <p>Generic async CRUD operations for SQLAlchemy models.</p> <p>Subclass this and set the <code>model</code> class variable, or use <code>CrudFactory</code>.</p>","path":["Reference","crud"],"tags":[]},{"location":"reference/crud/#fastapi_toolsets.crud.factory.AsyncCrud.count","level":3,"title":"<code>count(session, filters=None, *, joins=None, outer_join=False)</code> <code>async</code> <code>classmethod</code>","text":"<p>Count records matching the filters.</p> <p>Parameters:</p> Name Type Description Default <code>session</code> <code>AsyncSession</code> <p>DB async session</p> required <code>filters</code> <code>list[Any] | None</code> <p>List of SQLAlchemy filter conditions</p> <code>None</code> <code>joins</code> <code>JoinType | None</code> <p>List of (model, condition) tuples for joining related tables</p> <code>None</code> <code>outer_join</code> <code>bool</code> <p>Use LEFT OUTER JOIN instead of INNER JOIN</p> <code>False</code> <p>Returns:</p> Type Description <code>int</code> <p>Number of matching records</p>","path":["Reference","crud"],"tags":[]},{"location":"reference/crud/#fastapi_toolsets.crud.factory.AsyncCrud.create","level":3,"title":"<code>create(session, obj, *, schema=None)</code> <code>async</code> <code>classmethod</code>","text":"<pre><code>create(\n session: AsyncSession,\n obj: BaseModel,\n *,\n schema: type[SchemaType],\n) -> Response[SchemaType]\n</code></pre><pre><code>create(\n session: AsyncSession,\n obj: BaseModel,\n *,\n schema: None = ...,\n) -> ModelType\n</code></pre> <p>Create a new record in the database.</p> <p>Parameters:</p> Name Type Description Default <code>session</code> <code>AsyncSession</code> <p>DB async session</p> required <code>obj</code> <code>BaseModel</code> <p>Pydantic model with data to create</p> required <code>schema</code> <code>type[BaseModel] | None</code> <p>Pydantic schema to serialize the result into. When provided, the result is automatically wrapped in a <code>Response[schema]</code>.</p> <code>None</code> <p>Returns:</p> Type Description <code>ModelType | Response[Any]</code> <p>Created model instance, or <code>Response[schema]</code> when <code>schema</code> is given.</p>","path":["Reference","crud"],"tags":[]},{"location":"reference/crud/#fastapi_toolsets.crud.factory.AsyncCrud.cursor_paginate","level":3,"title":"<code>cursor_paginate(session, *, cursor=None, filters=None, joins=None, outer_join=False, load_options=None, order_by=None, items_per_page=20, search=None, search_fields=None, facet_fields=None, filter_by=None, schema)</code> <code>async</code> <code>classmethod</code>","text":"<p>Get paginated results using cursor-based pagination.</p> <p>Parameters:</p> Name Type Description Default <code>session</code> <code>AsyncSession</code> <p>DB async session.</p> required <code>cursor</code> <code>str | None</code> <p>Cursor string from a previous <code>CursorPagination</code>. Omit (or pass <code>None</code>) to start from the beginning.</p> <code>None</code> <code>filters</code> <code>list[Any] | None</code> <p>List of SQLAlchemy filter conditions.</p> <code>None</code> <code>joins</code> <code>JoinType | None</code> <p>List of <code>(model, condition)</code> tuples for joining related tables.</p> <code>None</code> <code>outer_join</code> <code>bool</code> <p>Use LEFT OUTER JOIN instead of INNER JOIN.</p> <code>False</code> <code>load_options</code> <code>list[ExecutableOption] | None</code> <p>SQLAlchemy loader options. Falls back to <code>default_load_options</code> when not provided.</p> <code>None</code> <code>order_by</code> <code>OrderByClause | None</code> <p>Additional ordering applied after the cursor column.</p> <code>None</code> <code>items_per_page</code> <code>int</code> <p>Number of items per page (default 20).</p> <code>20</code> <code>search</code> <code>str | SearchConfig | None</code> <p>Search query string or SearchConfig object.</p> <code>None</code> <code>search_fields</code> <code>Sequence[SearchFieldType] | None</code> <p>Fields to search in (overrides class default).</p> <code>None</code> <code>facet_fields</code> <code>Sequence[FacetFieldType] | None</code> <p>Columns to compute distinct values for (overrides class default).</p> <code>None</code> <code>filter_by</code> <code>dict[str, Any] | BaseModel | None</code> <p>Dict of {column_key: value} to filter by declared facet fields. Keys must match the column.key of a facet field. Scalar → equality, list → IN clause. Raises InvalidFacetFilterError for unknown keys.</p> <code>None</code> <code>schema</code> <code>type[BaseModel]</code> <p>Optional Pydantic schema to serialize each item into.</p> required <p>Returns:</p> Type Description <code>PaginatedResponse[Any]</code> <p>PaginatedResponse with CursorPagination metadata</p>","path":["Reference","crud"],"tags":[]},{"location":"reference/crud/#fastapi_toolsets.crud.factory.AsyncCrud.delete","level":3,"title":"<code>delete(session, filters, *, return_response=False)</code> <code>async</code> <code>classmethod</code>","text":"<pre><code>delete(\n session: AsyncSession,\n filters: list[Any],\n *,\n return_response: Literal[True],\n) -> Response[None]\n</code></pre><pre><code>delete(\n session: AsyncSession,\n filters: list[Any],\n *,\n return_response: Literal[False] = ...,\n) -> None\n</code></pre> <p>Delete records from the database.</p> <p>Parameters:</p> Name Type Description Default <code>session</code> <code>AsyncSession</code> <p>DB async session</p> required <code>filters</code> <code>list[Any]</code> <p>List of SQLAlchemy filter conditions</p> required <code>return_response</code> <code>bool</code> <p>When <code>True</code>, returns <code>Response[None]</code> instead of <code>None</code>. Useful for API endpoints that expect a consistent response envelope.</p> <code>False</code> <p>Returns:</p> Type Description <code>None | Response[None]</code> <p><code>None</code>, or <code>Response[None]</code> when <code>return_response=True</code>.</p>","path":["Reference","crud"],"tags":[]},{"location":"reference/crud/#fastapi_toolsets.crud.factory.AsyncCrud.exists","level":3,"title":"<code>exists(session, filters, *, joins=None, outer_join=False)</code> <code>async</code> <code>classmethod</code>","text":"<p>Check if a record exists.</p> <p>Parameters:</p> Name Type Description Default <code>session</code> <code>AsyncSession</code> <p>DB async session</p> required <code>filters</code> <code>list[Any]</code> <p>List of SQLAlchemy filter conditions</p> required <code>joins</code> <code>JoinType | None</code> <p>List of (model, condition) tuples for joining related tables</p> <code>None</code> <code>outer_join</code> <code>bool</code> <p>Use LEFT OUTER JOIN instead of INNER JOIN</p> <code>False</code> <p>Returns:</p> Type Description <code>bool</code> <p>True if at least one record matches</p>","path":["Reference","crud"],"tags":[]},{"location":"reference/crud/#fastapi_toolsets.crud.factory.AsyncCrud.filter_params","level":3,"title":"<code>filter_params(*, facet_fields=None)</code> <code>classmethod</code>","text":"<p>Return a FastAPI dependency that collects facet filter values from query parameters. Args: facet_fields: Override the facet fields for this dependency. Falls back to the class-level <code>facet_fields</code> if not provided.</p> <p>Returns:</p> Type Description <code>Callable[..., Awaitable[dict[str, list[str]]]]</code> <p>An async dependency function named <code>{Model}FilterParams</code> that resolves to a</p> <code>Callable[..., Awaitable[dict[str, list[str]]]]</code> <p><code>dict[str, list[str]]</code> containing only the keys that were supplied in the</p> <code>Callable[..., Awaitable[dict[str, list[str]]]]</code> <p>request (absent/<code>None</code> parameters are excluded).</p> <p>Raises:</p> Type Description <code>ValueError</code> <p>If no facet fields are configured on this CRUD class and none are provided via <code>facet_fields</code>.</p>","path":["Reference","crud"],"tags":[]},{"location":"reference/crud/#fastapi_toolsets.crud.factory.AsyncCrud.first","level":3,"title":"<code>first(session, filters=None, *, joins=None, outer_join=False, load_options=None)</code> <code>async</code> <code>classmethod</code>","text":"<p>Get the first matching record, or None.</p> <p>Parameters:</p> Name Type Description Default <code>session</code> <code>AsyncSession</code> <p>DB async session</p> required <code>filters</code> <code>list[Any] | None</code> <p>List of SQLAlchemy filter conditions</p> <code>None</code> <code>joins</code> <code>JoinType | None</code> <p>List of (model, condition) tuples for joining related tables</p> <code>None</code> <code>outer_join</code> <code>bool</code> <p>Use LEFT OUTER JOIN instead of INNER JOIN</p> <code>False</code> <code>load_options</code> <code>list[ExecutableOption] | None</code> <p>SQLAlchemy loader options</p> <code>None</code> <p>Returns:</p> Type Description <code>ModelType | None</code> <p>Model instance or None</p>","path":["Reference","crud"],"tags":[]},{"location":"reference/crud/#fastapi_toolsets.crud.factory.AsyncCrud.get","level":3,"title":"<code>get(session, filters, *, joins=None, outer_join=False, with_for_update=False, load_options=None, schema=None)</code> <code>async</code> <code>classmethod</code>","text":"<pre><code>get(\n session: AsyncSession,\n filters: list[Any],\n *,\n joins: JoinType | None = None,\n outer_join: bool = False,\n with_for_update: bool = False,\n load_options: list[ExecutableOption] | None = None,\n schema: type[SchemaType],\n) -> Response[SchemaType]\n</code></pre><pre><code>get(\n session: AsyncSession,\n filters: list[Any],\n *,\n joins: JoinType | None = None,\n outer_join: bool = False,\n with_for_update: bool = False,\n load_options: list[ExecutableOption] | None = None,\n schema: None = ...,\n) -> ModelType\n</code></pre> <p>Get exactly one record. Raises NotFoundError if not found.</p> <p>Parameters:</p> Name Type Description Default <code>session</code> <code>AsyncSession</code> <p>DB async session</p> required <code>filters</code> <code>list[Any]</code> <p>List of SQLAlchemy filter conditions</p> required <code>joins</code> <code>JoinType | None</code> <p>List of (model, condition) tuples for joining related tables</p> <code>None</code> <code>outer_join</code> <code>bool</code> <p>Use LEFT OUTER JOIN instead of INNER JOIN</p> <code>False</code> <code>with_for_update</code> <code>bool</code> <p>Lock the row for update</p> <code>False</code> <code>load_options</code> <code>list[ExecutableOption] | None</code> <p>SQLAlchemy loader options (e.g., selectinload)</p> <code>None</code> <code>schema</code> <code>type[BaseModel] | None</code> <p>Pydantic schema to serialize the result into. When provided, the result is automatically wrapped in a <code>Response[schema]</code>.</p> <code>None</code> <p>Returns:</p> Type Description <code>ModelType | Response[Any]</code> <p>Model instance, or <code>Response[schema]</code> when <code>schema</code> is given.</p> <p>Raises:</p> Type Description <code>NotFoundError</code> <p>If no record found</p> <code>MultipleResultsFound</code> <p>If more than one record found</p>","path":["Reference","crud"],"tags":[]},{"location":"reference/crud/#fastapi_toolsets.crud.factory.AsyncCrud.get_multi","level":3,"title":"<code>get_multi(session, *, filters=None, joins=None, outer_join=False, load_options=None, order_by=None, limit=None, offset=None)</code> <code>async</code> <code>classmethod</code>","text":"<p>Get multiple records from the database.</p> <p>Parameters:</p> Name Type Description Default <code>session</code> <code>AsyncSession</code> <p>DB async session</p> required <code>filters</code> <code>list[Any] | None</code> <p>List of SQLAlchemy filter conditions</p> <code>None</code> <code>joins</code> <code>JoinType | None</code> <p>List of (model, condition) tuples for joining related tables</p> <code>None</code> <code>outer_join</code> <code>bool</code> <p>Use LEFT OUTER JOIN instead of INNER JOIN</p> <code>False</code> <code>load_options</code> <code>list[ExecutableOption] | None</code> <p>SQLAlchemy loader options</p> <code>None</code> <code>order_by</code> <code>OrderByClause | None</code> <p>Column or list of columns to order by</p> <code>None</code> <code>limit</code> <code>int | None</code> <p>Max number of rows to return</p> <code>None</code> <code>offset</code> <code>int | None</code> <p>Rows to skip</p> <code>None</code> <p>Returns:</p> Type Description <code>Sequence[ModelType]</code> <p>List of model instances</p>","path":["Reference","crud"],"tags":[]},{"location":"reference/crud/#fastapi_toolsets.crud.factory.AsyncCrud.offset_paginate","level":3,"title":"<code>offset_paginate(session, *, filters=None, joins=None, outer_join=False, load_options=None, order_by=None, page=1, items_per_page=20, search=None, search_fields=None, facet_fields=None, filter_by=None, schema)</code> <code>async</code> <code>classmethod</code>","text":"<p>Get paginated results using offset-based pagination.</p> <p>Parameters:</p> Name Type Description Default <code>session</code> <code>AsyncSession</code> <p>DB async session</p> required <code>filters</code> <code>list[Any] | None</code> <p>List of SQLAlchemy filter conditions</p> <code>None</code> <code>joins</code> <code>JoinType | None</code> <p>List of (model, condition) tuples for joining related tables</p> <code>None</code> <code>outer_join</code> <code>bool</code> <p>Use LEFT OUTER JOIN instead of INNER JOIN</p> <code>False</code> <code>load_options</code> <code>list[ExecutableOption] | None</code> <p>SQLAlchemy loader options</p> <code>None</code> <code>order_by</code> <code>OrderByClause | None</code> <p>Column or list of columns to order by</p> <code>None</code> <code>page</code> <code>int</code> <p>Page number (1-indexed)</p> <code>1</code> <code>items_per_page</code> <code>int</code> <p>Number of items per page</p> <code>20</code> <code>search</code> <code>str | SearchConfig | None</code> <p>Search query string or SearchConfig object</p> <code>None</code> <code>search_fields</code> <code>Sequence[SearchFieldType] | None</code> <p>Fields to search in (overrides class default)</p> <code>None</code> <code>facet_fields</code> <code>Sequence[FacetFieldType] | None</code> <p>Columns to compute distinct values for (overrides class default)</p> <code>None</code> <code>filter_by</code> <code>dict[str, Any] | BaseModel | None</code> <p>Dict of {column_key: value} to filter by declared facet fields. Keys must match the column.key of a facet field. Scalar → equality, list → IN clause. Raises InvalidFacetFilterError for unknown keys.</p> <code>None</code> <code>schema</code> <code>type[BaseModel]</code> <p>Pydantic schema to serialize each item into.</p> required <p>Returns:</p> Type Description <code>PaginatedResponse[Any]</code> <p>PaginatedResponse with OffsetPagination metadata</p>","path":["Reference","crud"],"tags":[]},{"location":"reference/crud/#fastapi_toolsets.crud.factory.AsyncCrud.order_params","level":3,"title":"<code>order_params(*, order_fields=None, default_field=None, default_order='asc')</code> <code>classmethod</code>","text":"<p>Return a FastAPI dependency that resolves order query params into an order_by clause.</p> <p>Parameters:</p> Name Type Description Default <code>order_fields</code> <code>Sequence[QueryableAttribute[Any]] | None</code> <p>Override the allowed order fields. Falls back to the class-level <code>order_fields</code> if not provided.</p> <code>None</code> <code>default_field</code> <code>QueryableAttribute[Any] | None</code> <p>Field to order by when <code>order_by</code> query param is absent. If <code>None</code> and no <code>order_by</code> is provided, no ordering is applied.</p> <code>None</code> <code>default_order</code> <code>Literal['asc', 'desc']</code> <p>Default order direction when <code>order</code> is absent (<code>\"asc\"</code> or <code>\"desc\"</code>).</p> <code>'asc'</code> <p>Returns:</p> Type Description <code>Callable[..., Awaitable[OrderByClause | None]]</code> <p>An async dependency function named <code>{Model}OrderParams</code> that resolves to an</p> <code>Callable[..., Awaitable[OrderByClause | None]]</code> <p><code>OrderByClause</code> (or <code>None</code>). Pass it to <code>Depends()</code> in your route.</p> <p>Raises:</p> Type Description <code>ValueError</code> <p>If no order fields are configured on this CRUD class and none are provided via <code>order_fields</code>.</p> <code>InvalidOrderFieldError</code> <p>When the request provides an unknown <code>order_by</code> value.</p>","path":["Reference","crud"],"tags":[]},{"location":"reference/crud/#fastapi_toolsets.crud.factory.AsyncCrud.update","level":3,"title":"<code>update(session, obj, filters, *, exclude_unset=True, exclude_none=False, schema=None)</code> <code>async</code> <code>classmethod</code>","text":"<pre><code>update(\n session: AsyncSession,\n obj: BaseModel,\n filters: list[Any],\n *,\n exclude_unset: bool = True,\n exclude_none: bool = False,\n schema: type[SchemaType],\n) -> Response[SchemaType]\n</code></pre><pre><code>update(\n session: AsyncSession,\n obj: BaseModel,\n filters: list[Any],\n *,\n exclude_unset: bool = True,\n exclude_none: bool = False,\n schema: None = ...,\n) -> ModelType\n</code></pre> <p>Update a record in the database.</p> <p>Parameters:</p> Name Type Description Default <code>session</code> <code>AsyncSession</code> <p>DB async session</p> required <code>obj</code> <code>BaseModel</code> <p>Pydantic model with update data</p> required <code>filters</code> <code>list[Any]</code> <p>List of SQLAlchemy filter conditions</p> required <code>exclude_unset</code> <code>bool</code> <p>Exclude fields not explicitly set in the schema</p> <code>True</code> <code>exclude_none</code> <code>bool</code> <p>Exclude fields with None value</p> <code>False</code> <code>schema</code> <code>type[BaseModel] | None</code> <p>Pydantic schema to serialize the result into. When provided, the result is automatically wrapped in a <code>Response[schema]</code>.</p> <code>None</code> <p>Returns:</p> Type Description <code>ModelType | Response[Any]</code> <p>Updated model instance, or <code>Response[schema]</code> when <code>schema</code> is given.</p> <p>Raises:</p> Type Description <code>NotFoundError</code> <p>If no record found</p>","path":["Reference","crud"],"tags":[]},{"location":"reference/crud/#fastapi_toolsets.crud.factory.AsyncCrud.upsert","level":3,"title":"<code>upsert(session, obj, index_elements, *, set_=None, where=None)</code> <code>async</code> <code>classmethod</code>","text":"<p>Create or update a record (PostgreSQL only).</p> <p>Uses INSERT ... ON CONFLICT for atomic upsert.</p> <p>Parameters:</p> Name Type Description Default <code>session</code> <code>AsyncSession</code> <p>DB async session</p> required <code>obj</code> <code>BaseModel</code> <p>Pydantic model with data</p> required <code>index_elements</code> <code>list[str]</code> <p>Columns for ON CONFLICT (unique constraint)</p> required <code>set_</code> <code>BaseModel | None</code> <p>Pydantic model for ON CONFLICT DO UPDATE SET</p> <code>None</code> <code>where</code> <code>WhereHavingRole | None</code> <p>WHERE clause for ON CONFLICT DO UPDATE</p> <code>None</code> <p>Returns:</p> Type Description <code>ModelType | None</code> <p>Model instance</p>","path":["Reference","crud"],"tags":[]},{"location":"reference/crud/#fastapi_toolsets.crud.factory.CrudFactory","level":2,"title":"<code>fastapi_toolsets.crud.factory.CrudFactory(model, *, searchable_fields=None, facet_fields=None, order_fields=None, m2m_fields=None, default_load_options=None, cursor_column=None)</code>","text":"<p>Create a CRUD class for a specific model.</p> <p>Parameters:</p> Name Type Description Default <code>model</code> <code>type[ModelType]</code> <p>SQLAlchemy model class</p> required <code>searchable_fields</code> <code>Sequence[SearchFieldType] | None</code> <p>Optional list of searchable fields</p> <code>None</code> <code>facet_fields</code> <code>Sequence[FacetFieldType] | None</code> <p>Optional list of columns to compute distinct values for in paginated responses. Supports direct columns (<code>User.status</code>) and relationship tuples (<code>(User.role, Role.name)</code>). Can be overridden per call.</p> <code>None</code> <code>order_fields</code> <code>Sequence[QueryableAttribute[Any]] | None</code> <p>Optional list of model attributes that callers are allowed to order by via <code>order_params()</code>. Can be overridden per call.</p> <code>None</code> <code>m2m_fields</code> <code>M2MFieldType | None</code> <p>Optional mapping for many-to-many relationships. Maps schema field names (containing lists of IDs) to SQLAlchemy relationship attributes.</p> <code>None</code> <code>default_load_options</code> <code>list[ExecutableOption] | None</code> <p>Default SQLAlchemy loader options applied to all read queries when no explicit <code>load_options</code> are passed. Use this instead of <code>lazy=\"selectin\"</code> on the model so that loading strategy is explicit and per-CRUD. Overridden entirely (not merged) when <code>load_options</code> is provided at call-site.</p> <code>None</code> <code>cursor_column</code> <code>Any | None</code> <p>Required to call <code>cursor_paginate</code>. Must be monotonically ordered (e.g. integer PK, UUID v7, timestamp). See the cursor pagination docs for supported column types.</p> <code>None</code> <p>Returns:</p> Type Description <code>type[AsyncCrud[ModelType]]</code> <p>AsyncCrud subclass bound to the model</p> Example <pre><code>from fastapi_toolsets.crud import CrudFactory\nfrom myapp.models import User, Post\n\nUserCrud = CrudFactory(User)\nPostCrud = CrudFactory(Post)\n\n# With searchable fields:\nUserCrud = CrudFactory(\n User,\n searchable_fields=[User.username, User.email, (User.role, Role.name)]\n)\n\n# With many-to-many fields:\n# Schema has `tag_ids: list[UUID]`, model has `tags` relationship to Tag\nPostCrud = CrudFactory(\n Post,\n m2m_fields={\"tag_ids\": Post.tags},\n)\n\n# With facet fields for filter dropdowns / faceted search:\nUserCrud = CrudFactory(\n User,\n facet_fields=[User.status, User.country, (User.role, Role.name)],\n)\n\n# With a fixed cursor column for cursor_paginate:\nPostCrud = CrudFactory(\n Post,\n cursor_column=Post.created_at,\n)\n\n# With default load strategy (replaces lazy=\"selectin\" on the model):\nArticleCrud = CrudFactory(\n Article,\n default_load_options=[selectinload(Article.category), selectinload(Article.tags)],\n)\n\n# Override default_load_options for a specific call:\narticle = await ArticleCrud.get(\n session,\n [Article.id == 1],\n load_options=[selectinload(Article.category)], # tags won't load\n)\n\n# Usage\nuser = await UserCrud.get(session, [User.id == 1])\nposts = await PostCrud.get_multi(session, filters=[Post.user_id == user.id])\n\n# Create with M2M - tag_ids are automatically resolved\npost = await PostCrud.create(session, PostCreate(title=\"Hello\", tag_ids=[id1, id2]))\n\n# With search\nresult = await UserCrud.offset_paginate(session, search=\"john\")\n\n# With joins (inner join by default):\nusers = await UserCrud.get_multi(\n session,\n joins=[(Post, Post.user_id == User.id)],\n filters=[Post.published == True],\n)\n\n# With outer join:\nusers = await UserCrud.get_multi(\n session,\n joins=[(Post, Post.user_id == User.id)],\n outer_join=True,\n)\n</code></pre>","path":["Reference","crud"],"tags":[]},{"location":"reference/crud/#fastapi_toolsets.crud.search.SearchConfig","level":2,"title":"<code>fastapi_toolsets.crud.search.SearchConfig</code> <code>dataclass</code>","text":"<p>Advanced search configuration.</p> <p>Attributes:</p> Name Type Description <code>query</code> <code>str</code> <p>The search string</p> <code>fields</code> <code>Sequence[SearchFieldType] | None</code> <p>Fields to search (columns or tuples for relationships)</p> <code>case_sensitive</code> <code>bool</code> <p>Case-sensitive search (default: False)</p> <code>match_mode</code> <code>Literal['any', 'all']</code> <p>\"any\" (OR) or \"all\" (AND) to combine fields</p>","path":["Reference","crud"],"tags":[]},{"location":"reference/crud/#fastapi_toolsets.crud.search.get_searchable_fields","level":2,"title":"<code>fastapi_toolsets.crud.search.get_searchable_fields(model, *, include_relationships=True, max_depth=1)</code> <code>cached</code>","text":"<p>Auto-detect String fields on a model and its relationships.</p> <p>Parameters:</p> Name Type Description Default <code>model</code> <code>type[DeclarativeBase]</code> <p>SQLAlchemy model class</p> required <code>include_relationships</code> <code>bool</code> <p>Include fields from many-to-one/one-to-one relationships</p> <code>True</code> <code>max_depth</code> <code>int</code> <p>Max depth for relationship traversal (default: 1)</p> <code>1</code> <p>Returns:</p> Type Description <code>list[SearchFieldType]</code> <p>List of columns and tuples (relationship, column)</p>","path":["Reference","crud"],"tags":[]},{"location":"reference/crud/#fastapi_toolsets.crud.search.build_search_filters","level":2,"title":"<code>fastapi_toolsets.crud.search.build_search_filters(model, search, search_fields=None, default_fields=None)</code>","text":"<p>Build SQLAlchemy filter conditions for search.</p> <p>Parameters:</p> Name Type Description Default <code>model</code> <code>type[DeclarativeBase]</code> <p>SQLAlchemy model class</p> required <code>search</code> <code>str | SearchConfig</code> <p>Search string or SearchConfig</p> required <code>search_fields</code> <code>Sequence[SearchFieldType] | None</code> <p>Fields specified per-call (takes priority)</p> <code>None</code> <code>default_fields</code> <code>Sequence[SearchFieldType] | None</code> <p>Default fields (from ClassVar)</p> <code>None</code> <p>Returns:</p> Type Description <code>tuple[list[ColumnElement[bool]], list[InstrumentedAttribute[Any]]]</code> <p>Tuple of (filter_conditions, joins_needed)</p> <p>Raises:</p> Type Description <code>NoSearchableFieldsError</code> <p>If no searchable field has been configured</p>","path":["Reference","crud"],"tags":[]},{"location":"reference/db/","level":1,"title":"<code>db</code>","text":"<p>Here's the reference for all database session utilities, transaction helpers, and locking functions.</p> <p>You can import them directly from <code>fastapi_toolsets.db</code>:</p> <pre><code>from fastapi_toolsets.db import (\n LockMode,\n cleanup_tables,\n create_database,\n create_db_dependency,\n create_db_context,\n get_transaction,\n lock_tables,\n wait_for_row_change,\n)\n</code></pre>","path":["Reference","db"],"tags":[]},{"location":"reference/db/#fastapi_toolsets.db.LockMode","level":2,"title":"<code>fastapi_toolsets.db.LockMode</code>","text":"<p> Bases: <code>str</code>, <code>Enum</code></p> <p>PostgreSQL table lock modes.</p> <p>See: https://www.postgresql.org/docs/current/explicit-locking.html</p>","path":["Reference","db"],"tags":[]},{"location":"reference/db/#fastapi_toolsets.db.create_db_dependency","level":2,"title":"<code>fastapi_toolsets.db.create_db_dependency(session_maker)</code>","text":"<p>Create a FastAPI dependency for database sessions.</p> <p>Creates a dependency function that yields a session and auto-commits if a transaction is active when the request completes.</p> <p>Parameters:</p> Name Type Description Default <code>session_maker</code> <code>async_sessionmaker[AsyncSession]</code> <p>Async session factory from create_session_factory()</p> required <p>Returns:</p> Type Description <code>Callable[[], AsyncGenerator[AsyncSession, None]]</code> <p>An async generator function usable with FastAPI's Depends()</p> Example <pre><code>from fastapi import Depends\nfrom sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker\nfrom fastapi_toolsets.db import create_db_dependency\n\nengine = create_async_engine(\"postgresql+asyncpg://...\")\nSessionLocal = async_sessionmaker(engine, expire_on_commit=False)\nget_db = create_db_dependency(SessionLocal)\n\n@app.get(\"/users\")\nasync def list_users(session: AsyncSession = Depends(get_db)):\n ...\n</code></pre>","path":["Reference","db"],"tags":[]},{"location":"reference/db/#fastapi_toolsets.db.create_db_context","level":2,"title":"<code>fastapi_toolsets.db.create_db_context(session_maker)</code>","text":"<p>Create a context manager for database sessions.</p> <p>Creates a context manager for use outside of FastAPI request handlers, such as in background tasks, CLI commands, or tests.</p> <p>Parameters:</p> Name Type Description Default <code>session_maker</code> <code>async_sessionmaker[AsyncSession]</code> <p>Async session factory from create_session_factory()</p> required <p>Returns:</p> Type Description <code>Callable[[], AbstractAsyncContextManager[AsyncSession]]</code> <p>An async context manager function</p> Example <pre><code>from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker\nfrom fastapi_toolsets.db import create_db_context\n\nengine = create_async_engine(\"postgresql+asyncpg://...\")\nSessionLocal = async_sessionmaker(engine, expire_on_commit=False)\nget_db_context = create_db_context(SessionLocal)\n\nasync def background_task():\n async with get_db_context() as session:\n user = await UserCrud.get(session, [User.id == 1])\n ...\n</code></pre>","path":["Reference","db"],"tags":[]},{"location":"reference/db/#fastapi_toolsets.db.get_transaction","level":2,"title":"<code>fastapi_toolsets.db.get_transaction(session)</code> <code>async</code>","text":"<p>Get a transaction context, handling nested transactions.</p> <p>If already in a transaction, creates a savepoint (nested transaction). Otherwise, starts a new transaction.</p> <p>Parameters:</p> Name Type Description Default <code>session</code> <code>AsyncSession</code> <p>AsyncSession instance</p> required <p>Yields:</p> Type Description <code>AsyncGenerator[AsyncSession, None]</code> <p>The session within the transaction context</p> Example <pre><code>async with get_transaction(session):\n session.add(model)\n # Auto-commits on exit, rolls back on exception\n</code></pre>","path":["Reference","db"],"tags":[]},{"location":"reference/db/#fastapi_toolsets.db.lock_tables","level":2,"title":"<code>fastapi_toolsets.db.lock_tables(session, tables, *, mode=LockMode.SHARE_UPDATE_EXCLUSIVE, timeout='5s')</code> <code>async</code>","text":"<p>Lock PostgreSQL tables for the duration of a transaction.</p> <p>Acquires table-level locks that are held until the transaction ends. Useful for preventing concurrent modifications during critical operations.</p> <p>Parameters:</p> Name Type Description Default <code>session</code> <code>AsyncSession</code> <p>AsyncSession instance</p> required <code>tables</code> <code>list[type[DeclarativeBase]]</code> <p>List of SQLAlchemy model classes to lock</p> required <code>mode</code> <code>LockMode</code> <p>Lock mode (default: SHARE UPDATE EXCLUSIVE)</p> <code>SHARE_UPDATE_EXCLUSIVE</code> <code>timeout</code> <code>str</code> <p>Lock timeout (default: \"5s\")</p> <code>'5s'</code> <p>Yields:</p> Type Description <code>AsyncGenerator[AsyncSession, None]</code> <p>The session with locked tables</p> <p>Raises:</p> Type Description <code>SQLAlchemyError</code> <p>If lock cannot be acquired within timeout</p> Example <pre><code>from fastapi_toolsets.db import lock_tables, LockMode\n\nasync with lock_tables(session, [User, Account]):\n # Tables are locked with SHARE UPDATE EXCLUSIVE mode\n user = await UserCrud.get(session, [User.id == 1])\n user.balance += 100\n\n# With custom lock mode\nasync with lock_tables(session, [Order], mode=LockMode.EXCLUSIVE):\n # Exclusive lock - no other transactions can access\n await process_order(session, order_id)\n</code></pre>","path":["Reference","db"],"tags":[]},{"location":"reference/db/#fastapi_toolsets.db.wait_for_row_change","level":2,"title":"<code>fastapi_toolsets.db.wait_for_row_change(session, model, pk_value, *, columns=None, interval=0.5, timeout=None)</code> <code>async</code>","text":"<p>Poll a database row until a change is detected.</p> <p>Queries the row every <code>interval</code> seconds and returns the model instance once a change is detected in any column (or only the specified <code>columns</code>).</p> <p>Parameters:</p> Name Type Description Default <code>session</code> <code>AsyncSession</code> <p>AsyncSession instance</p> required <code>model</code> <code>type[_M]</code> <p>SQLAlchemy model class</p> required <code>pk_value</code> <code>Any</code> <p>Primary key value of the row to watch</p> required <code>columns</code> <code>list[str] | None</code> <p>Optional list of column names to watch. If None, all columns are watched.</p> <code>None</code> <code>interval</code> <code>float</code> <p>Polling interval in seconds (default: 0.5)</p> <code>0.5</code> <code>timeout</code> <code>float | None</code> <p>Maximum time to wait in seconds. None means wait forever.</p> <code>None</code> <p>Returns:</p> Type Description <code>_M</code> <p>The refreshed model instance with updated values</p> <p>Raises:</p> Type Description <code>NotFoundError</code> <p>If the row does not exist or is deleted during polling</p> <code>TimeoutError</code> <p>If timeout expires before a change is detected</p> Example <pre><code>from fastapi_toolsets.db import wait_for_row_change\n\n# Wait for any column to change\nupdated = await wait_for_row_change(session, User, user_id)\n\n# Watch specific columns with a timeout\nupdated = await wait_for_row_change(\n session, User, user_id,\n columns=[\"status\", \"email\"],\n interval=1.0,\n timeout=30.0,\n)\n</code></pre>","path":["Reference","db"],"tags":[]},{"location":"reference/db/#fastapi_toolsets.db.create_database","level":2,"title":"<code>fastapi_toolsets.db.create_database(db_name, *, server_url)</code> <code>async</code>","text":"<p>Create a database.</p> <p>Connects to server_url using <code>AUTOCOMMIT</code> isolation and issues a <code>CREATE DATABASE</code> statement for db_name.</p> <p>Parameters:</p> Name Type Description Default <code>db_name</code> <code>str</code> <p>Name of the database to create.</p> required <code>server_url</code> <code>str</code> <p>URL used for server-level DDL (must point to an existing database on the same server).</p> required Example <pre><code>from fastapi_toolsets.db import create_database\n\nSERVER_URL = \"postgresql+asyncpg://postgres:postgres@localhost/postgres\"\nawait create_database(\"myapp_test\", server_url=SERVER_URL)\n</code></pre>","path":["Reference","db"],"tags":[]},{"location":"reference/db/#fastapi_toolsets.db.cleanup_tables","level":2,"title":"<code>fastapi_toolsets.db.cleanup_tables(session, base)</code> <code>async</code>","text":"<p>Truncate all tables for fast between-test cleanup.</p> <p>Executes a single <code>TRUNCATE … RESTART IDENTITY CASCADE</code> statement across every table in base's metadata, which is significantly faster than dropping and re-creating tables between tests.</p> <p>This is a no-op when the metadata contains no tables.</p> <p>Parameters:</p> Name Type Description Default <code>session</code> <code>AsyncSession</code> <p>An active async database session.</p> required <code>base</code> <code>type[DeclarativeBase]</code> <p>SQLAlchemy DeclarativeBase class containing model metadata.</p> required Example <pre><code>@pytest.fixture\nasync def db_session(worker_db_url):\n async with create_db_session(worker_db_url, Base) as session:\n yield session\n await cleanup_tables(session, Base)\n</code></pre>","path":["Reference","db"],"tags":[]},{"location":"reference/dependencies/","level":1,"title":"<code>dependencies</code>","text":"<p>Here's the reference for the FastAPI dependency factory functions.</p> <p>You can import them directly from <code>fastapi_toolsets.dependencies</code>:</p> <pre><code>from fastapi_toolsets.dependencies import PathDependency, BodyDependency\n</code></pre>","path":["Reference","dependencies"],"tags":[]},{"location":"reference/dependencies/#fastapi_toolsets.dependencies.PathDependency","level":2,"title":"<code>fastapi_toolsets.dependencies.PathDependency(model, field, *, session_dep, param_name=None)</code>","text":"<p>Create a dependency that fetches a DB object from a path parameter.</p> <p>Parameters:</p> Name Type Description Default <code>model</code> <code>type[ModelType]</code> <p>SQLAlchemy model class</p> required <code>field</code> <code>Any</code> <p>Model field to filter by (e.g., User.id)</p> required <code>session_dep</code> <code>SessionDependency</code> <p>Session dependency function (e.g., get_db)</p> required <code>param_name</code> <code>str | None</code> <p>Path parameter name (defaults to model_field, e.g., user_id)</p> <code>None</code> <p>Returns:</p> Type Description <code>ModelType</code> <p>A Depends() instance that resolves to the model instance</p> <p>Raises:</p> Type Description <code>NotFoundError</code> <p>If no matching record is found</p> Example <pre><code>UserDep = PathDependency(User, User.id, session_dep=get_db)\n\n@router.get(\"/user/{id}\")\nasync def get(\n user: User = UserDep,\n): ...\n</code></pre>","path":["Reference","dependencies"],"tags":[]},{"location":"reference/dependencies/#fastapi_toolsets.dependencies.BodyDependency","level":2,"title":"<code>fastapi_toolsets.dependencies.BodyDependency(model, field, *, session_dep, body_field)</code>","text":"<p>Create a dependency that fetches a DB object from a body field.</p> <p>Parameters:</p> Name Type Description Default <code>model</code> <code>type[ModelType]</code> <p>SQLAlchemy model class</p> required <code>field</code> <code>Any</code> <p>Model field to filter by (e.g., User.id)</p> required <code>session_dep</code> <code>SessionDependency</code> <p>Session dependency function (e.g., get_db)</p> required <code>body_field</code> <code>str</code> <p>Name of the field in the request body</p> required <p>Returns:</p> Type Description <code>ModelType</code> <p>A Depends() instance that resolves to the model instance</p> <p>Raises:</p> Type Description <code>NotFoundError</code> <p>If no matching record is found</p> Example <pre><code>UserDep = BodyDependency(\n User, User.ctfd_id, session_dep=get_db, body_field=\"user_id\"\n)\n\n@router.post(\"/assign\")\nasync def assign(\n user: User = UserDep,\n): ...\n</code></pre>","path":["Reference","dependencies"],"tags":[]},{"location":"reference/exceptions/","level":1,"title":"<code>exceptions</code>","text":"<p>Here's the reference for all exception classes and handler utilities.</p> <p>You can import them directly from <code>fastapi_toolsets.exceptions</code>:</p> <pre><code>from fastapi_toolsets.exceptions import (\n ApiException,\n UnauthorizedError,\n ForbiddenError,\n NotFoundError,\n ConflictError,\n NoSearchableFieldsError,\n InvalidFacetFilterError,\n InvalidOrderFieldError,\n generate_error_responses,\n init_exceptions_handlers,\n)\n</code></pre>","path":["Reference","exceptions"],"tags":[]},{"location":"reference/exceptions/#fastapi_toolsets.exceptions.exceptions.ApiException","level":2,"title":"<code>fastapi_toolsets.exceptions.exceptions.ApiException</code>","text":"<p> Bases: <code>Exception</code></p> <p>Base exception for API errors with structured response.</p>","path":["Reference","exceptions"],"tags":[]},{"location":"reference/exceptions/#fastapi_toolsets.exceptions.exceptions.ApiException.__init__","level":3,"title":"<code>__init__(detail=None, *, desc=None, data=None)</code>","text":"<p>Initialize the exception.</p> <p>Parameters:</p> Name Type Description Default <code>detail</code> <code>str | None</code> <p>Optional human-readable message</p> <code>None</code> <code>desc</code> <code>str | None</code> <p>Optional per-instance override for the <code>description</code> field in the HTTP response body.</p> <code>None</code> <code>data</code> <code>Any</code> <p>Optional per-instance override for the <code>data</code> field in the HTTP response body.</p> <code>None</code>","path":["Reference","exceptions"],"tags":[]},{"location":"reference/exceptions/#fastapi_toolsets.exceptions.exceptions.UnauthorizedError","level":2,"title":"<code>fastapi_toolsets.exceptions.exceptions.UnauthorizedError</code>","text":"<p> Bases: <code>ApiException</code></p> <p>HTTP 401 - User is not authenticated.</p>","path":["Reference","exceptions"],"tags":[]},{"location":"reference/exceptions/#fastapi_toolsets.exceptions.exceptions.UnauthorizedError.__init__","level":3,"title":"<code>__init__(detail=None, *, desc=None, data=None)</code>","text":"<p>Initialize the exception.</p> <p>Parameters:</p> Name Type Description Default <code>detail</code> <code>str | None</code> <p>Optional human-readable message</p> <code>None</code> <code>desc</code> <code>str | None</code> <p>Optional per-instance override for the <code>description</code> field in the HTTP response body.</p> <code>None</code> <code>data</code> <code>Any</code> <p>Optional per-instance override for the <code>data</code> field in the HTTP response body.</p> <code>None</code>","path":["Reference","exceptions"],"tags":[]},{"location":"reference/exceptions/#fastapi_toolsets.exceptions.exceptions.ForbiddenError","level":2,"title":"<code>fastapi_toolsets.exceptions.exceptions.ForbiddenError</code>","text":"<p> Bases: <code>ApiException</code></p> <p>HTTP 403 - User lacks required permissions.</p>","path":["Reference","exceptions"],"tags":[]},{"location":"reference/exceptions/#fastapi_toolsets.exceptions.exceptions.ForbiddenError.__init__","level":3,"title":"<code>__init__(detail=None, *, desc=None, data=None)</code>","text":"<p>Initialize the exception.</p> <p>Parameters:</p> Name Type Description Default <code>detail</code> <code>str | None</code> <p>Optional human-readable message</p> <code>None</code> <code>desc</code> <code>str | None</code> <p>Optional per-instance override for the <code>description</code> field in the HTTP response body.</p> <code>None</code> <code>data</code> <code>Any</code> <p>Optional per-instance override for the <code>data</code> field in the HTTP response body.</p> <code>None</code>","path":["Reference","exceptions"],"tags":[]},{"location":"reference/exceptions/#fastapi_toolsets.exceptions.exceptions.NotFoundError","level":2,"title":"<code>fastapi_toolsets.exceptions.exceptions.NotFoundError</code>","text":"<p> Bases: <code>ApiException</code></p> <p>HTTP 404 - Resource not found.</p>","path":["Reference","exceptions"],"tags":[]},{"location":"reference/exceptions/#fastapi_toolsets.exceptions.exceptions.NotFoundError.__init__","level":3,"title":"<code>__init__(detail=None, *, desc=None, data=None)</code>","text":"<p>Initialize the exception.</p> <p>Parameters:</p> Name Type Description Default <code>detail</code> <code>str | None</code> <p>Optional human-readable message</p> <code>None</code> <code>desc</code> <code>str | None</code> <p>Optional per-instance override for the <code>description</code> field in the HTTP response body.</p> <code>None</code> <code>data</code> <code>Any</code> <p>Optional per-instance override for the <code>data</code> field in the HTTP response body.</p> <code>None</code>","path":["Reference","exceptions"],"tags":[]},{"location":"reference/exceptions/#fastapi_toolsets.exceptions.exceptions.ConflictError","level":2,"title":"<code>fastapi_toolsets.exceptions.exceptions.ConflictError</code>","text":"<p> Bases: <code>ApiException</code></p> <p>HTTP 409 - Resource conflict.</p>","path":["Reference","exceptions"],"tags":[]},{"location":"reference/exceptions/#fastapi_toolsets.exceptions.exceptions.ConflictError.__init__","level":3,"title":"<code>__init__(detail=None, *, desc=None, data=None)</code>","text":"<p>Initialize the exception.</p> <p>Parameters:</p> Name Type Description Default <code>detail</code> <code>str | None</code> <p>Optional human-readable message</p> <code>None</code> <code>desc</code> <code>str | None</code> <p>Optional per-instance override for the <code>description</code> field in the HTTP response body.</p> <code>None</code> <code>data</code> <code>Any</code> <p>Optional per-instance override for the <code>data</code> field in the HTTP response body.</p> <code>None</code>","path":["Reference","exceptions"],"tags":[]},{"location":"reference/exceptions/#fastapi_toolsets.exceptions.exceptions.NoSearchableFieldsError","level":2,"title":"<code>fastapi_toolsets.exceptions.exceptions.NoSearchableFieldsError</code>","text":"<p> Bases: <code>ApiException</code></p> <p>Raised when search is requested but no searchable fields are available.</p>","path":["Reference","exceptions"],"tags":[]},{"location":"reference/exceptions/#fastapi_toolsets.exceptions.exceptions.NoSearchableFieldsError.__init__","level":3,"title":"<code>__init__(model)</code>","text":"<p>Initialize the exception.</p> <p>Parameters:</p> Name Type Description Default <code>model</code> <code>type</code> <p>The model class that has no searchable fields configured.</p> required","path":["Reference","exceptions"],"tags":[]},{"location":"reference/exceptions/#fastapi_toolsets.exceptions.exceptions.InvalidFacetFilterError","level":2,"title":"<code>fastapi_toolsets.exceptions.exceptions.InvalidFacetFilterError</code>","text":"<p> Bases: <code>ApiException</code></p> <p>Raised when filter_by contains a key not declared in facet_fields.</p>","path":["Reference","exceptions"],"tags":[]},{"location":"reference/exceptions/#fastapi_toolsets.exceptions.exceptions.InvalidFacetFilterError.__init__","level":3,"title":"<code>__init__(key, valid_keys)</code>","text":"<p>Initialize the exception.</p> <p>Parameters:</p> Name Type Description Default <code>key</code> <code>str</code> <p>The unknown filter key provided by the caller.</p> required <code>valid_keys</code> <code>set[str]</code> <p>Set of valid keys derived from the declared facet_fields.</p> required","path":["Reference","exceptions"],"tags":[]},{"location":"reference/exceptions/#fastapi_toolsets.exceptions.exceptions.InvalidOrderFieldError","level":2,"title":"<code>fastapi_toolsets.exceptions.exceptions.InvalidOrderFieldError</code>","text":"<p> Bases: <code>ApiException</code></p> <p>Raised when order_by contains a field not in the allowed order fields.</p>","path":["Reference","exceptions"],"tags":[]},{"location":"reference/exceptions/#fastapi_toolsets.exceptions.exceptions.InvalidOrderFieldError.__init__","level":3,"title":"<code>__init__(field, valid_fields)</code>","text":"<p>Initialize the exception.</p> <p>Parameters:</p> Name Type Description Default <code>field</code> <code>str</code> <p>The unknown order field provided by the caller.</p> required <code>valid_fields</code> <code>list[str]</code> <p>List of valid field names.</p> required","path":["Reference","exceptions"],"tags":[]},{"location":"reference/exceptions/#fastapi_toolsets.exceptions.exceptions.generate_error_responses","level":2,"title":"<code>fastapi_toolsets.exceptions.exceptions.generate_error_responses(*errors)</code>","text":"<p>Generate OpenAPI response documentation for exceptions.</p> <p>Parameters:</p> Name Type Description Default <code>*errors</code> <code>type[ApiException]</code> <p>Exception classes that inherit from ApiException.</p> <code>()</code> <p>Returns:</p> Type Description <code>dict[int | str, dict[str, Any]]</code> <p>Dict suitable for FastAPI's <code>responses</code> parameter.</p>","path":["Reference","exceptions"],"tags":[]},{"location":"reference/exceptions/#fastapi_toolsets.exceptions.handler.init_exceptions_handlers","level":2,"title":"<code>fastapi_toolsets.exceptions.handler.init_exceptions_handlers(app)</code>","text":"<p>Register exception handlers and custom OpenAPI schema on a FastAPI app.</p> <p>Parameters:</p> Name Type Description Default <code>app</code> <code>FastAPI</code> <p>FastAPI application instance.</p> required <p>Returns:</p> Type Description <code>FastAPI</code> <p>The same FastAPI instance (for chaining).</p>","path":["Reference","exceptions"],"tags":[]},{"location":"reference/fixtures/","level":1,"title":"<code>fixtures</code>","text":"<p>Here's the reference for the fixture registry, enums, and loading utilities.</p> <p>You can import them directly from <code>fastapi_toolsets.fixtures</code>:</p> <pre><code>from fastapi_toolsets.fixtures import (\n Context,\n LoadStrategy,\n Fixture,\n FixtureRegistry,\n load_fixtures,\n load_fixtures_by_context,\n get_obj_by_attr,\n)\n</code></pre>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.enum.Context","level":2,"title":"<code>fastapi_toolsets.fixtures.enum.Context</code>","text":"<p> Bases: <code>str</code>, <code>Enum</code></p> <p>Predefined fixture contexts.</p>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.enum.Context.BASE","level":3,"title":"<code>BASE = 'base'</code> <code>class-attribute</code> <code>instance-attribute</code>","text":"<p>Base fixtures loaded in all environments.</p>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.enum.Context.DEVELOPMENT","level":3,"title":"<code>DEVELOPMENT = 'development'</code> <code>class-attribute</code> <code>instance-attribute</code>","text":"<p>Development fixtures.</p>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.enum.Context.PRODUCTION","level":3,"title":"<code>PRODUCTION = 'production'</code> <code>class-attribute</code> <code>instance-attribute</code>","text":"<p>Production-only fixtures.</p>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.enum.Context.TESTING","level":3,"title":"<code>TESTING = 'testing'</code> <code>class-attribute</code> <code>instance-attribute</code>","text":"<p>Test fixtures.</p>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.enum.LoadStrategy","level":2,"title":"<code>fastapi_toolsets.fixtures.enum.LoadStrategy</code>","text":"<p> Bases: <code>str</code>, <code>Enum</code></p> <p>Strategy for loading fixtures into the database.</p>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.enum.LoadStrategy.INSERT","level":3,"title":"<code>INSERT = 'insert'</code> <code>class-attribute</code> <code>instance-attribute</code>","text":"<p>Insert new records. Fails if record already exists.</p>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.enum.LoadStrategy.MERGE","level":3,"title":"<code>MERGE = 'merge'</code> <code>class-attribute</code> <code>instance-attribute</code>","text":"<p>Insert or update based on primary key (SQLAlchemy merge).</p>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.enum.LoadStrategy.SKIP_EXISTING","level":3,"title":"<code>SKIP_EXISTING = 'skip_existing'</code> <code>class-attribute</code> <code>instance-attribute</code>","text":"<p>Insert only if record doesn't exist (based on primary key).</p>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.registry.Fixture","level":2,"title":"<code>fastapi_toolsets.fixtures.registry.Fixture</code> <code>dataclass</code>","text":"<p>A fixture definition with metadata.</p>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.registry.FixtureRegistry","level":2,"title":"<code>fastapi_toolsets.fixtures.registry.FixtureRegistry</code>","text":"<p>Registry for managing fixtures with dependencies.</p> Example <pre><code>from fastapi_toolsets.fixtures import FixtureRegistry, Context\n\nfixtures = FixtureRegistry()\n\n@fixtures.register\ndef roles():\n return [\n Role(id=1, name=\"admin\"),\n Role(id=2, name=\"user\"),\n ]\n\n@fixtures.register(depends_on=[\"roles\"])\ndef users():\n return [\n User(id=1, username=\"admin\", role_id=1),\n ]\n\n@fixtures.register(depends_on=[\"users\"], contexts=[Context.TESTING])\ndef test_data():\n return [\n Post(id=1, title=\"Test\", user_id=1),\n ]\n</code></pre>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.registry.FixtureRegistry.get","level":3,"title":"<code>get(name)</code>","text":"<p>Get a fixture by name.</p>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.registry.FixtureRegistry.get_all","level":3,"title":"<code>get_all()</code>","text":"<p>Get all registered fixtures.</p>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.registry.FixtureRegistry.get_by_context","level":3,"title":"<code>get_by_context(*contexts)</code>","text":"<p>Get fixtures for specific contexts.</p>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.registry.FixtureRegistry.include_registry","level":3,"title":"<code>include_registry(registry)</code>","text":"<p>Include another <code>FixtureRegistry</code> in the same current <code>FixtureRegistry</code>.</p> <p>Parameters:</p> Name Type Description Default <code>registry</code> <code>FixtureRegistry</code> <p>The <code>FixtureRegistry</code> to include</p> required <p>Raises:</p> Type Description <code>ValueError</code> <p>If a fixture name already exists in the current registry</p> Example <pre><code>registry = FixtureRegistry()\ndev_registry = FixtureRegistry()\n\n@dev_registry.register\ndef dev_data():\n return [...]\n\nregistry.include_registry(registry=dev_registry)\n</code></pre>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.registry.FixtureRegistry.register","level":3,"title":"<code>register(func=None, *, name=None, depends_on=None, contexts=None)</code>","text":"<p>Register a fixture function.</p> <p>Can be used as a decorator with or without arguments.</p> <p>Parameters:</p> Name Type Description Default <code>func</code> <code>Callable[[], Sequence[DeclarativeBase]] | None</code> <p>Fixture function returning list of model instances</p> <code>None</code> <code>name</code> <code>str | None</code> <p>Fixture name (defaults to function name)</p> <code>None</code> <code>depends_on</code> <code>list[str] | None</code> <p>List of fixture names this depends on</p> <code>None</code> <code>contexts</code> <code>list[str | Context] | None</code> <p>List of contexts this fixture belongs to</p> <code>None</code> Example <pre><code>@fixtures.register\ndef roles():\n return [Role(id=1, name=\"admin\")]\n\n@fixtures.register(depends_on=[\"roles\"], contexts=[Context.TESTING])\ndef test_users():\n return [User(id=1, username=\"test\", role_id=1)]\n</code></pre>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.registry.FixtureRegistry.resolve_context_dependencies","level":3,"title":"<code>resolve_context_dependencies(*contexts)</code>","text":"<p>Resolve all fixtures for contexts with dependencies.</p> <p>Parameters:</p> Name Type Description Default <code>*contexts</code> <code>str | Context</code> <p>Contexts to load</p> <code>()</code> <p>Returns:</p> Type Description <code>list[str]</code> <p>List of fixture names in load order</p>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.registry.FixtureRegistry.resolve_dependencies","level":3,"title":"<code>resolve_dependencies(*names)</code>","text":"<p>Resolve fixture dependencies in topological order.</p> <p>Parameters:</p> Name Type Description Default <code>*names</code> <code>str</code> <p>Fixture names to resolve</p> <code>()</code> <p>Returns:</p> Type Description <code>list[str]</code> <p>List of fixture names in load order (dependencies first)</p> <p>Raises:</p> Type Description <code>KeyError</code> <p>If a fixture is not found</p> <code>ValueError</code> <p>If circular dependency detected</p>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.utils.load_fixtures","level":2,"title":"<code>fastapi_toolsets.fixtures.utils.load_fixtures(session, registry, *names, strategy=LoadStrategy.MERGE)</code> <code>async</code>","text":"<p>Load specific fixtures by name with dependencies.</p> <p>Parameters:</p> Name Type Description Default <code>session</code> <code>AsyncSession</code> <p>Database session</p> required <code>registry</code> <code>FixtureRegistry</code> <p>Fixture registry</p> required <code>*names</code> <code>str</code> <p>Fixture names to load (dependencies auto-resolved)</p> <code>()</code> <code>strategy</code> <code>LoadStrategy</code> <p>How to handle existing records</p> <code>MERGE</code> <p>Returns:</p> Type Description <code>dict[str, list[DeclarativeBase]]</code> <p>Dict mapping fixture names to loaded instances</p>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.utils.load_fixtures_by_context","level":2,"title":"<code>fastapi_toolsets.fixtures.utils.load_fixtures_by_context(session, registry, *contexts, strategy=LoadStrategy.MERGE)</code> <code>async</code>","text":"<p>Load all fixtures for specific contexts.</p> <p>Parameters:</p> Name Type Description Default <code>session</code> <code>AsyncSession</code> <p>Database session</p> required <code>registry</code> <code>FixtureRegistry</code> <p>Fixture registry</p> required <code>*contexts</code> <code>str | Context</code> <p>Contexts to load (e.g., Context.BASE, Context.TESTING)</p> <code>()</code> <code>strategy</code> <code>LoadStrategy</code> <p>How to handle existing records</p> <code>MERGE</code> <p>Returns:</p> Type Description <code>dict[str, list[DeclarativeBase]]</code> <p>Dict mapping fixture names to loaded instances</p>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/fixtures/#fastapi_toolsets.fixtures.utils.get_obj_by_attr","level":2,"title":"<code>fastapi_toolsets.fixtures.utils.get_obj_by_attr(fixtures, attr_name, value)</code>","text":"<p>Get a SQLAlchemy model instance by matching an attribute value.</p> <p>Parameters:</p> Name Type Description Default <code>fixtures</code> <code>Callable[[], Sequence[ModelType]]</code> <p>A fixture function registered via <code>@registry.register</code> that returns a sequence of SQLAlchemy model instances.</p> required <code>attr_name</code> <code>str</code> <p>Name of the attribute to match against.</p> required <code>value</code> <code>Any</code> <p>Value to match.</p> required <p>Returns:</p> Type Description <code>ModelType</code> <p>The first model instance where the attribute matches the given value.</p> <p>Raises:</p> Type Description <code>StopIteration</code> <p>If no matching object is found in the fixture group.</p>","path":["Reference","fixtures"],"tags":[]},{"location":"reference/logger/","level":1,"title":"<code>logger</code>","text":"<p>Here's the reference for the logging utilities.</p> <p>You can import them directly from <code>fastapi_toolsets.logger</code>:</p> <pre><code>from fastapi_toolsets.logger import configure_logging, get_logger\n</code></pre>","path":["Reference","logger"],"tags":[]},{"location":"reference/logger/#fastapi_toolsets.logger.configure_logging","level":2,"title":"<code>fastapi_toolsets.logger.configure_logging(level='INFO', fmt=DEFAULT_FORMAT, logger_name=None)</code>","text":"<p>Configure logging with a stdout handler and consistent format.</p> <p>Sets up a :class:<code>~logging.StreamHandler</code> writing to stdout with the given format and level. Also configures the uvicorn loggers so that FastAPI access logs use the same format.</p> <p>Calling this function multiple times is safe -- existing handlers are replaced rather than duplicated.</p> <p>Parameters:</p> Name Type Description Default <code>level</code> <code>LogLevel | int</code> <p>Log level (e.g. <code>\"DEBUG\"</code>, <code>\"INFO\"</code>, or <code>logging.DEBUG</code>).</p> <code>'INFO'</code> <code>fmt</code> <code>str</code> <p>Log format string. Defaults to <code>\"%(asctime)s - %(name)s - %(levelname)s - %(message)s\"</code>.</p> <code>DEFAULT_FORMAT</code> <code>logger_name</code> <code>str | None</code> <p>Logger name to configure. <code>None</code> (the default) configures the root logger so all loggers inherit the settings.</p> <code>None</code> <p>Returns:</p> Type Description <code>Logger</code> <p>The configured Logger instance.</p> Example <pre><code>from fastapi_toolsets.logger import configure_logging\n\nlogger = configure_logging(\"DEBUG\")\nlogger.info(\"Application started\")\n</code></pre>","path":["Reference","logger"],"tags":[]},{"location":"reference/logger/#fastapi_toolsets.logger.get_logger","level":2,"title":"<code>fastapi_toolsets.logger.get_logger(name=_SENTINEL)</code>","text":"<p>Return a logger with the given name.</p> <p>A thin convenience wrapper around :func:<code>logging.getLogger</code> that keeps logging imports consistent across the codebase.</p> <p>When called without arguments, the caller's <code>__name__</code> is used automatically, so <code>get_logger()</code> in a module is equivalent to <code>logging.getLogger(__name__)</code>. Pass <code>None</code> explicitly to get the root logger.</p> <p>Parameters:</p> Name Type Description Default <code>name</code> <code>str | None</code> <p>Logger name. Defaults to the caller's <code>__name__</code>. Pass <code>None</code> to get the root logger.</p> <code>_SENTINEL</code> <p>Returns:</p> Type Description <code>Logger</code> <p>A Logger instance.</p> Example <pre><code>from fastapi_toolsets.logger import get_logger\n\nlogger = get_logger() # uses caller's __name__\nlogger = get_logger(\"myapp\") # explicit name\nlogger = get_logger(None) # root logger\n</code></pre>","path":["Reference","logger"],"tags":[]},{"location":"reference/metrics/","level":1,"title":"<code>metrics</code>","text":"<p>Here's the reference for the Prometheus metrics registry and endpoint handler.</p> <p>You can import them directly from <code>fastapi_toolsets.metrics</code>:</p> <pre><code>from fastapi_toolsets.metrics import Metric, MetricsRegistry, init_metrics\n</code></pre>","path":["Reference","metrics"],"tags":[]},{"location":"reference/metrics/#fastapi_toolsets.metrics.registry.Metric","level":2,"title":"<code>fastapi_toolsets.metrics.registry.Metric</code> <code>dataclass</code>","text":"<p>A metric definition with metadata.</p>","path":["Reference","metrics"],"tags":[]},{"location":"reference/metrics/#fastapi_toolsets.metrics.registry.MetricsRegistry","level":2,"title":"<code>fastapi_toolsets.metrics.registry.MetricsRegistry</code>","text":"<p>Registry for managing Prometheus metric providers and collectors.</p>","path":["Reference","metrics"],"tags":[]},{"location":"reference/metrics/#fastapi_toolsets.metrics.registry.MetricsRegistry.get","level":3,"title":"<code>get(name)</code>","text":"<p>Return the metric instance created by a provider.</p> <p>Parameters:</p> Name Type Description Default <code>name</code> <code>str</code> <p>The metric name (defaults to the provider function name).</p> required <p>Raises:</p> Type Description <code>KeyError</code> <p>If the metric name is unknown or <code>init_metrics</code> has not been called yet.</p>","path":["Reference","metrics"],"tags":[]},{"location":"reference/metrics/#fastapi_toolsets.metrics.registry.MetricsRegistry.get_all","level":3,"title":"<code>get_all()</code>","text":"<p>Get all registered metric definitions.</p>","path":["Reference","metrics"],"tags":[]},{"location":"reference/metrics/#fastapi_toolsets.metrics.registry.MetricsRegistry.get_collectors","level":3,"title":"<code>get_collectors()</code>","text":"<p>Get collectors (called on each scrape).</p>","path":["Reference","metrics"],"tags":[]},{"location":"reference/metrics/#fastapi_toolsets.metrics.registry.MetricsRegistry.get_providers","level":3,"title":"<code>get_providers()</code>","text":"<p>Get metric providers (called once at init).</p>","path":["Reference","metrics"],"tags":[]},{"location":"reference/metrics/#fastapi_toolsets.metrics.registry.MetricsRegistry.include_registry","level":3,"title":"<code>include_registry(registry)</code>","text":"<p>Include another :class:<code>MetricsRegistry</code> into this one.</p> <p>Parameters:</p> Name Type Description Default <code>registry</code> <code>MetricsRegistry</code> <p>The registry to merge in.</p> required <p>Raises:</p> Type Description <code>ValueError</code> <p>If a metric name already exists in the current registry.</p>","path":["Reference","metrics"],"tags":[]},{"location":"reference/metrics/#fastapi_toolsets.metrics.registry.MetricsRegistry.register","level":3,"title":"<code>register(func=None, *, name=None, collect=False)</code>","text":"<p>Register a metric provider or collector function.</p> <p>Can be used as a decorator with or without arguments.</p> <p>Parameters:</p> Name Type Description Default <code>func</code> <code>Callable[..., Any] | None</code> <p>The metric function to register.</p> <code>None</code> <code>name</code> <code>str | None</code> <p>Metric name (defaults to function name).</p> <code>None</code> <code>collect</code> <code>bool</code> <p>If <code>True</code>, the function is called on every scrape. If <code>False</code> (default), called once at init time.</p> <code>False</code>","path":["Reference","metrics"],"tags":[]},{"location":"reference/metrics/#fastapi_toolsets.metrics.handler.init_metrics","level":2,"title":"<code>fastapi_toolsets.metrics.handler.init_metrics(app, registry, *, path='/metrics')</code>","text":"<p>Register a Prometheus <code>/metrics</code> endpoint on a FastAPI app.</p> <p>Parameters:</p> Name Type Description Default <code>app</code> <code>FastAPI</code> <p>FastAPI application instance.</p> required <code>registry</code> <code>MetricsRegistry</code> <p>A :class:<code>MetricsRegistry</code> containing providers and collectors.</p> required <code>path</code> <code>str</code> <p>URL path for the metrics endpoint (default <code>/metrics</code>).</p> <code>'/metrics'</code> <p>Returns:</p> Type Description <code>FastAPI</code> <p>The same FastAPI instance (for chaining).</p> Example <pre><code>from fastapi import FastAPI\nfrom fastapi_toolsets.metrics import MetricsRegistry, init_metrics\n\nmetrics = MetricsRegistry()\napp = FastAPI()\ninit_metrics(app, registry=metrics)\n</code></pre>","path":["Reference","metrics"],"tags":[]},{"location":"reference/models/","level":1,"title":"<code>models</code>","text":"<p>Here's the reference for the SQLAlchemy model mixins provided by the <code>models</code> module.</p> <p>You can import them directly from <code>fastapi_toolsets.models</code>:</p> <pre><code>from fastapi_toolsets.models import (\n UUIDMixin,\n CreatedAtMixin,\n UpdatedAtMixin,\n TimestampMixin,\n)\n</code></pre>","path":["Reference","models"],"tags":[]},{"location":"reference/models/#fastapi_toolsets.models.UUIDMixin","level":2,"title":"<code>fastapi_toolsets.models.UUIDMixin</code>","text":"<p>Mixin that adds a UUID primary key auto-generated by the database.</p>","path":["Reference","models"],"tags":[]},{"location":"reference/models/#fastapi_toolsets.models.CreatedAtMixin","level":2,"title":"<code>fastapi_toolsets.models.CreatedAtMixin</code>","text":"<p>Mixin that adds a <code>created_at</code> timestamp column.</p>","path":["Reference","models"],"tags":[]},{"location":"reference/models/#fastapi_toolsets.models.UpdatedAtMixin","level":2,"title":"<code>fastapi_toolsets.models.UpdatedAtMixin</code>","text":"<p>Mixin that adds an <code>updated_at</code> timestamp column.</p>","path":["Reference","models"],"tags":[]},{"location":"reference/models/#fastapi_toolsets.models.TimestampMixin","level":2,"title":"<code>fastapi_toolsets.models.TimestampMixin</code>","text":"<p> Bases: <code>CreatedAtMixin</code>, <code>UpdatedAtMixin</code></p> <p>Mixin that combines <code>created_at</code> and <code>updated_at</code> timestamp columns.</p>","path":["Reference","models"],"tags":[]},{"location":"reference/pytest/","level":1,"title":"<code>pytest</code>","text":"<p>Here's the reference for all testing utilities and pytest fixtures.</p> <p>You can import them directly from <code>fastapi_toolsets.pytest</code>:</p> <pre><code>from fastapi_toolsets.pytest import (\n register_fixtures,\n create_async_client,\n create_db_session,\n worker_database_url,\n create_worker_database,\n cleanup_tables,\n)\n</code></pre>","path":["Reference","pytest"],"tags":[]},{"location":"reference/pytest/#fastapi_toolsets.pytest.plugin.register_fixtures","level":2,"title":"<code>fastapi_toolsets.pytest.plugin.register_fixtures(registry, namespace, *, prefix='fixture_', session_fixture='db_session', strategy=LoadStrategy.MERGE)</code>","text":"<p>Register pytest fixtures from a FixtureRegistry.</p> <p>Automatically creates pytest fixtures for each fixture in the registry. Dependencies are resolved via pytest fixture dependencies.</p> <p>Parameters:</p> Name Type Description Default <code>registry</code> <code>FixtureRegistry</code> <p>The FixtureRegistry containing fixtures</p> required <code>namespace</code> <code>dict[str, Any]</code> <p>The module's globals() dict to add fixtures to</p> required <code>prefix</code> <code>str</code> <p>Prefix for generated fixture names (default: \"fixture_\")</p> <code>'fixture_'</code> <code>session_fixture</code> <code>str</code> <p>Name of the db session fixture (default: \"db_session\")</p> <code>'db_session'</code> <code>strategy</code> <code>LoadStrategy</code> <p>Loading strategy for fixtures (default: MERGE)</p> <code>MERGE</code> <p>Returns:</p> Type Description <code>list[str]</code> <p>List of created fixture names</p> Example <pre><code># conftest.py\nfrom app.fixtures import fixtures\nfrom fastapi_toolsets.pytest_plugin import register_fixtures\n\nregister_fixtures(fixtures, globals())\n\n# Creates fixtures like:\n# - fixture_roles\n# - fixture_users (depends on fixture_roles if users depends on roles)\n# - fixture_posts (depends on fixture_users if posts depends on users)\n</code></pre>","path":["Reference","pytest"],"tags":[]},{"location":"reference/pytest/#fastapi_toolsets.pytest.utils.create_async_client","level":2,"title":"<code>fastapi_toolsets.pytest.utils.create_async_client(app, base_url='http://test', dependency_overrides=None)</code> <code>async</code>","text":"<p>Create an async httpx client for testing FastAPI applications.</p> <p>Parameters:</p> Name Type Description Default <code>app</code> <code>Any</code> <p>FastAPI application instance.</p> required <code>base_url</code> <code>str</code> <p>Base URL for requests. Defaults to \"http://test\".</p> <code>'http://test'</code> <code>dependency_overrides</code> <code>dict[Callable[..., Any], Callable[..., Any]] | None</code> <p>Optional mapping of original dependencies to their test replacements. Applied via <code>app.dependency_overrides</code> before yielding and cleaned up after.</p> <code>None</code> <p>Yields:</p> Type Description <code>AsyncGenerator[AsyncClient, None]</code> <p>An AsyncClient configured for the app.</p> Example <pre><code>from fastapi import FastAPI\nfrom fastapi_toolsets.pytest import create_async_client\n\napp = FastAPI()\n\n@pytest.fixture\nasync def client():\n async with create_async_client(app) as c:\n yield c\n\nasync def test_endpoint(client: AsyncClient):\n response = await client.get(\"/health\")\n assert response.status_code == 200\n</code></pre> Example with dependency overrides <pre><code>from fastapi_toolsets.pytest import create_async_client, create_db_session\nfrom app.db import get_db\n\n@pytest.fixture\nasync def db_session():\n async with create_db_session(DATABASE_URL, Base, cleanup=True) as session:\n yield session\n\n@pytest.fixture\nasync def client(db_session):\n async def override():\n yield db_session\n\n async with create_async_client(\n app, dependency_overrides={get_db: override}\n ) as c:\n yield c\n</code></pre>","path":["Reference","pytest"],"tags":[]},{"location":"reference/pytest/#fastapi_toolsets.pytest.utils.create_db_session","level":2,"title":"<code>fastapi_toolsets.pytest.utils.create_db_session(database_url, base, *, echo=False, expire_on_commit=False, drop_tables=True, cleanup=False)</code> <code>async</code>","text":"<p>Create a database session for testing.</p> <p>Creates tables before yielding the session and optionally drops them after. Each call creates a fresh engine and session for test isolation.</p> <p>Parameters:</p> Name Type Description Default <code>database_url</code> <code>str</code> <p>Database connection URL (e.g., \"postgresql+asyncpg://...\").</p> required <code>base</code> <code>type[DeclarativeBase]</code> <p>SQLAlchemy DeclarativeBase class containing model metadata.</p> required <code>echo</code> <code>bool</code> <p>Enable SQLAlchemy query logging. Defaults to False.</p> <code>False</code> <code>expire_on_commit</code> <code>bool</code> <p>Expire objects after commit. Defaults to False.</p> <code>False</code> <code>drop_tables</code> <code>bool</code> <p>Drop tables after test. Defaults to True.</p> <code>True</code> <code>cleanup</code> <code>bool</code> <p>Truncate all tables after test using :func:<code>cleanup_tables</code>. Defaults to False.</p> <code>False</code> <p>Yields:</p> Type Description <code>AsyncGenerator[AsyncSession, None]</code> <p>An AsyncSession ready for database operations.</p> Example <pre><code>from fastapi_toolsets.pytest import create_db_session\nfrom app.models import Base\n\nDATABASE_URL = \"postgresql+asyncpg://user:pass@localhost/test_db\"\n\n@pytest.fixture\nasync def db_session():\n async with create_db_session(\n DATABASE_URL, Base, cleanup=True\n ) as session:\n yield session\n\nasync def test_create_user(db_session: AsyncSession):\n user = User(name=\"test\")\n db_session.add(user)\n await db_session.commit()\n</code></pre>","path":["Reference","pytest"],"tags":[]},{"location":"reference/pytest/#fastapi_toolsets.pytest.utils.worker_database_url","level":2,"title":"<code>fastapi_toolsets.pytest.utils.worker_database_url(database_url, default_test_db)</code>","text":"<p>Derive a per-worker database URL for pytest-xdist parallel runs.</p> <p>Appends <code>_{worker_name}</code> to the database name so each xdist worker operates on its own database. When not running under xdist, <code>_{default_test_db}</code> is appended instead.</p> <p>The worker name is read from the <code>PYTEST_XDIST_WORKER</code> environment variable (set automatically by xdist in each worker process).</p> <p>Parameters:</p> Name Type Description Default <code>database_url</code> <code>str</code> <p>Original database connection URL.</p> required <code>default_test_db</code> <code>str</code> <p>Suffix appended to the database name when <code>PYTEST_XDIST_WORKER</code> is not set.</p> required <p>Returns:</p> Type Description <code>str</code> <p>A database URL with a worker- or default-specific database name.</p>","path":["Reference","pytest"],"tags":[]},{"location":"reference/pytest/#fastapi_toolsets.pytest.utils.create_worker_database","level":2,"title":"<code>fastapi_toolsets.pytest.utils.create_worker_database(database_url, default_test_db='test_db')</code> <code>async</code>","text":"<p>Create and drop a per-worker database for pytest-xdist isolation.</p> <p>Derives a worker-specific database URL using :func:<code>worker_database_url</code>, then delegates to :func:<code>~fastapi_toolsets.db.create_database</code> to create and drop it. Intended for use as a session-scoped fixture.</p> <p>When running under xdist the database name is suffixed with the worker name (e.g. <code>_gw0</code>). Otherwise it is suffixed with default_test_db.</p> <p>Parameters:</p> Name Type Description Default <code>database_url</code> <code>str</code> <p>Original database connection URL (used as the server connection and as the base for the worker database name).</p> required <code>default_test_db</code> <code>str</code> <p>Suffix appended to the database name when <code>PYTEST_XDIST_WORKER</code> is not set. Defaults to <code>\"test_db\"</code>.</p> <code>'test_db'</code> <p>Yields:</p> Type Description <code>AsyncGenerator[str, None]</code> <p>The worker-specific database URL.</p> Example <pre><code>from fastapi_toolsets.pytest import create_worker_database, create_db_session\n\nDATABASE_URL = \"postgresql+asyncpg://postgres:postgres@localhost/test_db\"\n\n@pytest.fixture(scope=\"session\")\nasync def worker_db_url():\n async with create_worker_database(DATABASE_URL) as url:\n yield url\n\n@pytest.fixture\nasync def db_session(worker_db_url):\n async with create_db_session(\n worker_db_url, Base, cleanup=True\n ) as session:\n yield session\n</code></pre>","path":["Reference","pytest"],"tags":[]},{"location":"reference/schemas/","level":1,"title":"<code>schemas</code>","text":"<p>Here's the reference for all response models and types provided by the <code>schemas</code> module.</p> <p>You can import them directly from <code>fastapi_toolsets.schemas</code>:</p> <pre><code>from fastapi_toolsets.schemas import (\n PydanticBase,\n ResponseStatus,\n ApiError,\n BaseResponse,\n Response,\n ErrorResponse,\n OffsetPagination,\n CursorPagination,\n PaginatedResponse,\n)\n</code></pre>","path":["Reference","schemas"],"tags":[]},{"location":"reference/schemas/#fastapi_toolsets.schemas.PydanticBase","level":2,"title":"<code>fastapi_toolsets.schemas.PydanticBase</code>","text":"<p> Bases: <code>BaseModel</code></p> <p>Base class for all Pydantic models with common configuration.</p>","path":["Reference","schemas"],"tags":[]},{"location":"reference/schemas/#fastapi_toolsets.schemas.ResponseStatus","level":2,"title":"<code>fastapi_toolsets.schemas.ResponseStatus</code>","text":"<p> Bases: <code>str</code>, <code>Enum</code></p> <p>Standard API response status.</p>","path":["Reference","schemas"],"tags":[]},{"location":"reference/schemas/#fastapi_toolsets.schemas.ApiError","level":2,"title":"<code>fastapi_toolsets.schemas.ApiError</code>","text":"<p> Bases: <code>PydanticBase</code></p> <p>Structured API error definition.</p> <p>Used to define standard error responses with consistent format.</p> <p>Attributes:</p> Name Type Description <code>code</code> <code>int</code> <p>HTTP status code</p> <code>msg</code> <code>str</code> <p>Short error message</p> <code>desc</code> <code>str</code> <p>Detailed error description</p> <code>err_code</code> <code>str</code> <p>Application-specific error code (e.g., \"AUTH-401\")</p>","path":["Reference","schemas"],"tags":[]},{"location":"reference/schemas/#fastapi_toolsets.schemas.BaseResponse","level":2,"title":"<code>fastapi_toolsets.schemas.BaseResponse</code>","text":"<p> Bases: <code>PydanticBase</code></p> <p>Base response structure for all API responses.</p> <p>Attributes:</p> Name Type Description <code>status</code> <code>ResponseStatus</code> <p>SUCCESS or FAIL</p> <code>message</code> <code>str</code> <p>Human-readable message</p> <code>error_code</code> <code>str | None</code> <p>Error code if status is FAIL, None otherwise</p>","path":["Reference","schemas"],"tags":[]},{"location":"reference/schemas/#fastapi_toolsets.schemas.Response","level":2,"title":"<code>fastapi_toolsets.schemas.Response</code>","text":"<p> Bases: <code>BaseResponse</code>, <code>Generic[DataT]</code></p> <p>Generic API response with data payload.</p> Example <pre><code>Response[UserRead](data=user, message=\"User retrieved\")\n</code></pre>","path":["Reference","schemas"],"tags":[]},{"location":"reference/schemas/#fastapi_toolsets.schemas.ErrorResponse","level":2,"title":"<code>fastapi_toolsets.schemas.ErrorResponse</code>","text":"<p> Bases: <code>BaseResponse</code></p> <p>Error response with additional description field.</p> <p>Used for error responses that need more context.</p>","path":["Reference","schemas"],"tags":[]},{"location":"reference/schemas/#fastapi_toolsets.schemas.OffsetPagination","level":2,"title":"<code>fastapi_toolsets.schemas.OffsetPagination</code>","text":"<p> Bases: <code>PydanticBase</code></p> <p>Pagination metadata for offset-based list responses.</p> <p>Attributes:</p> Name Type Description <code>total_count</code> <code>int</code> <p>Total number of items across all pages</p> <code>items_per_page</code> <code>int</code> <p>Number of items per page</p> <code>page</code> <code>int</code> <p>Current page number (1-indexed)</p> <code>has_more</code> <code>bool</code> <p>Whether there are more pages</p>","path":["Reference","schemas"],"tags":[]},{"location":"reference/schemas/#fastapi_toolsets.schemas.CursorPagination","level":2,"title":"<code>fastapi_toolsets.schemas.CursorPagination</code>","text":"<p> Bases: <code>PydanticBase</code></p> <p>Pagination metadata for cursor-based list responses.</p> <p>Attributes:</p> Name Type Description <code>next_cursor</code> <code>str | None</code> <p>Encoded cursor for the next page, or None on the last page.</p> <code>prev_cursor</code> <code>str | None</code> <p>Encoded cursor for the previous page, or None on the first page.</p> <code>items_per_page</code> <code>int</code> <p>Number of items requested per page.</p> <code>has_more</code> <code>bool</code> <p>Whether there is at least one more page after this one.</p>","path":["Reference","schemas"],"tags":[]},{"location":"reference/schemas/#fastapi_toolsets.schemas.PaginatedResponse","level":2,"title":"<code>fastapi_toolsets.schemas.PaginatedResponse</code>","text":"<p> Bases: <code>BaseResponse</code>, <code>Generic[DataT]</code></p> <p>Paginated API response for list endpoints.</p>","path":["Reference","schemas"],"tags":[]}]} |