doc: add missing docstring + add missing feature to README (#57)

This commit is contained in:
d3vyce
2026-02-12 18:09:39 +01:00
committed by GitHub
parent c8c263ca8f
commit 8825c772ce
13 changed files with 88 additions and 15 deletions

View File

@@ -18,10 +18,16 @@ def _ensure_project_in_path():
def import_from_string(import_path: str):
"""Import an object from a string path like 'module.submodule:attribute'.
"""Import an object from a dotted string path.
Args:
import_path: Import path in ``"module.submodule:attribute"`` format
Returns:
The imported attribute
Raises:
typer.BadParameter: If the import path is invalid or import fails.
typer.BadParameter: If the import path is invalid or import fails
"""
if ":" not in import_path:
raise typer.BadParameter(

View File

@@ -10,6 +10,12 @@ def find_pyproject(start_path: Path | None = None) -> Path | None:
"""Find pyproject.toml by walking up the directory tree.
Similar to how pytest, black, and ruff discover their config files.
Args:
start_path: Directory to start searching from. Defaults to cwd.
Returns:
Path to pyproject.toml, or None if not found.
"""
path = (start_path or Path.cwd()).resolve()

View File

@@ -210,6 +210,20 @@ async def wait_for_row_change(
Raises:
LookupError: If the row does not exist or is deleted during polling
TimeoutError: If timeout expires before a change is detected
Example:
from fastapi_toolsets.db import wait_for_row_change
# Wait for any column to change
updated = await wait_for_row_change(session, User, user_id)
# Watch specific columns with a timeout
updated = await wait_for_row_change(
session, User, user_id,
columns=["status", "email"],
interval=1.0,
timeout=30.0,
)
"""
instance = await session.get(model, pk_value)
if instance is None:

View File

@@ -1,3 +1,5 @@
"""Standardized API exceptions and error response handlers."""
from .exceptions import (
ApiError,
ApiException,

View File

@@ -87,6 +87,12 @@ class InsufficientRolesError(ForbiddenError):
)
def __init__(self, required_roles: list[str], user_roles: set[str] | None = None):
"""Initialize the exception.
Args:
required_roles: Roles needed to access the resource
user_roles: Roles the current user has, if known
"""
self.required_roles = required_roles
self.user_roles = user_roles
@@ -130,6 +136,11 @@ class NoSearchableFieldsError(ApiException):
)
def __init__(self, model: type) -> None:
"""Initialize the exception.
Args:
model: The SQLAlchemy model class that has no searchable fields
"""
self.model = model
detail = (
f"No searchable fields found for model '{model.__name__}'. "

View File

@@ -12,6 +12,25 @@ from .exceptions import ApiException
def init_exceptions_handlers(app: FastAPI) -> FastAPI:
"""Register exception handlers and custom OpenAPI schema on a FastAPI app.
Installs handlers for :class:`ApiException`, validation errors, and
unhandled exceptions, and replaces the default 422 schema with a
consistent error format.
Args:
app: FastAPI application instance
Returns:
The same FastAPI instance (for chaining)
Example:
from fastapi import FastAPI
from fastapi_toolsets.exceptions import init_exceptions_handlers
app = FastAPI()
init_exceptions_handlers(app)
"""
_register_exception_handlers(app)
app.openapi = lambda: _custom_openapi(app) # type: ignore[method-assign]
return app

View File

@@ -1,3 +1,5 @@
"""Fixture system for seeding databases with dependency resolution."""
from .enum import LoadStrategy
from .registry import Context, FixtureRegistry
from .utils import get_obj_by_attr, load_fixtures, load_fixtures_by_context

View File

@@ -1,3 +1,5 @@
"""Enums for fixture loading strategies and contexts."""
from enum import Enum

View File

@@ -1,3 +1,5 @@
"""Fixture loading utilities for database seeding."""
from collections.abc import Callable, Sequence
from typing import Any, TypeVar

View File

@@ -35,6 +35,12 @@ def configure_logging(
Returns:
The configured Logger instance.
Example:
from fastapi_toolsets.logger import configure_logging
logger = configure_logging("DEBUG")
logger.info("Application started")
"""
formatter = logging.Formatter(fmt)
@@ -75,6 +81,13 @@ def get_logger(name: str | None = _SENTINEL) -> logging.Logger: # type: ignore[
Returns:
A Logger instance.
Example:
from fastapi_toolsets.logger import get_logger
logger = get_logger() # uses caller's __name__
logger = get_logger("myapp") # explicit name
logger = get_logger(None) # root logger
"""
if name is _SENTINEL:
name = sys._getframe(1).f_globals.get("__name__")

View File

@@ -1,3 +1,5 @@
"""Pytest helpers for FastAPI testing: sessions, clients, and fixtures."""
from .plugin import register_fixtures
from .utils import (
cleanup_tables,

View File

@@ -33,7 +33,6 @@ async def create_async_client(
An AsyncClient configured for the app.
Example:
```python
from fastapi import FastAPI
from fastapi_toolsets.pytest import create_async_client
@@ -47,7 +46,6 @@ async def create_async_client(
async def test_endpoint(client: AsyncClient):
response = await client.get("/health")
assert response.status_code == 200
```
"""
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url=base_url) as client:
@@ -79,7 +77,6 @@ async def create_db_session(
An AsyncSession ready for database operations.
Example:
```python
from fastapi_toolsets.pytest import create_db_session
from app.models import Base
@@ -94,7 +91,6 @@ async def create_db_session(
user = User(name="test")
db_session.add(user)
await db_session.commit()
```
"""
engine = create_async_engine(database_url, echo=echo)
@@ -151,7 +147,6 @@ def worker_database_url(database_url: str, default_test_db: str) -> str:
A database URL with a worker- or default-specific database name.
Example:
```python
# With PYTEST_XDIST_WORKER="gw0":
url = worker_database_url(
"postgresql+asyncpg://user:pass@localhost/test_db",
@@ -165,7 +160,6 @@ def worker_database_url(database_url: str, default_test_db: str) -> str:
default_test_db="test",
)
# "postgresql+asyncpg://user:pass@localhost/test_db_test"
```
"""
worker = _get_xdist_worker(default_test_db=default_test_db)
@@ -198,7 +192,6 @@ async def create_worker_database(
The worker-specific database URL.
Example:
```python
from fastapi_toolsets.pytest import (
create_worker_database, create_db_session, cleanup_tables
)
@@ -215,7 +208,6 @@ async def create_worker_database(
async with create_db_session(worker_db_url, Base) as session:
yield session
await cleanup_tables(session, Base)
```
"""
worker_url = worker_database_url(
database_url=database_url, default_test_db=default_test_db
@@ -256,13 +248,11 @@ async def cleanup_tables(
base: SQLAlchemy DeclarativeBase class containing model metadata.
Example:
```python
@pytest.fixture
async def db_session(worker_db_url):
async with create_db_session(worker_db_url, Base) as session:
yield session
await cleanup_tables(session, Base)
```
"""
tables = base.metadata.sorted_tables
if not tables: