Skip to content

fastapi_tools

fastapi_tools package.

Modules:

  • auth

    Authentication service modules.

  • config

    Configuration module for the fastapi_tools package.

  • data_models

    fastapi_tools package.

  • dependencies

    FastAPI dependency injection functions.

  • exceptions

    Custom HTTP exceptions for the webapp.

  • factory

    FastAPI application factory.

  • metaclasses

    Configuration module for the fastapi_tools package.

  • middleware

    Custom middleware for the webapp.

  • params

    fastapi_tools package.

  • routers

    Router modules for fastapi_tools.

  • schemas

    Schema modules for fastapi_tools.

  • security

    Security utilities for the webapp.

  • templating

    Jinja2 template engine configuration.

  • utils

    Utility modules for fastapi_tools.

Classes:

Functions:

  • create_app

    Create and configure a FastAPI application.

  • get_current_user

    Require an authenticated user; raises NotAuthenticatedException otherwise.

  • get_optional_user

    Get the current user if authenticated, or None.

GoogleOAuthConfig

Bases: BaseModelKwargs

Google OAuth 2.0 configuration.

NotAuthenticatedException

NotAuthenticatedException(
    detail: str = "Not authenticated",
)

Bases: HTTPException

Raised when a request requires authentication but none was provided.

Source code in src/fastapi_tools/exceptions.py
def __init__(self, detail: str = "Not authenticated") -> None:  # noqa: D107
    super().__init__(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail=detail,
        headers={"WWW-Authenticate": "Bearer"},
    )

NotAuthorizedException

NotAuthorizedException(detail: str = 'Not authorized')

Bases: HTTPException

Raised when the authenticated user lacks required permissions.

Source code in src/fastapi_tools/exceptions.py
def __init__(self, detail: str = "Not authorized") -> None:  # noqa: D107
    super().__init__(
        status_code=status.HTTP_403_FORBIDDEN,
        detail=detail,
    )

RateLimitExceededException

RateLimitExceededException(
    detail: str = "Rate limit exceeded",
    retry_after: int | None = None,
)

Bases: HTTPException

Raised when a client exceeds the configured rate limit.

Source code in src/fastapi_tools/exceptions.py
def __init__(  # noqa: D107
    self,
    detail: str = "Rate limit exceeded",
    retry_after: int | None = None,
) -> None:
    headers = {}
    if retry_after is not None:
        headers["Retry-After"] = str(retry_after)
    super().__init__(
        status_code=status.HTTP_429_TOO_MANY_REQUESTS,
        detail=detail,
        headers=headers or None,
    )

SessionConfig

Bases: BaseModelKwargs

Session management configuration.

WebappConfig

Bases: BaseModelKwargs

Main webapp configuration aggregating all sub-configs.

create_app

create_app(
    config: WebappConfig,
    *,
    extra_routers: list[APIRouter] | None = None,
    static_dir: Path | str | None = None,
    templates_dir: Path | str | None = None,
    lifespan: Callable | None = None,
) -> FastAPI

Create and configure a FastAPI application.

Parameters:

  • config (WebappConfig) –

    Webapp configuration.

  • extra_routers (list[APIRouter] | None, default: None ) –

    Additional routers to include (project-specific).

  • static_dir (Path | str | None, default: None ) –

    Path to static files directory. If None, /static not mounted.

  • templates_dir (Path | str | None, default: None ) –

    Path to templates directory. If None, templating not configured.

  • lifespan (Callable | None, default: None ) –

    Custom lifespan context manager. If None, a default is used that initialises SessionStore and GoogleAuthService only.

Returns:

  • FastAPI

    Configured FastAPI application instance.

Source code in src/fastapi_tools/factory.py
def create_app(
    config: WebappConfig,
    *,
    extra_routers: list[APIRouter] | None = None,
    static_dir: Path | str | None = None,
    templates_dir: Path | str | None = None,
    lifespan: Callable | None = None,
) -> FastAPI:
    """Create and configure a FastAPI application.

    Args:
        config: Webapp configuration.
        extra_routers: Additional routers to include (project-specific).
        static_dir: Path to static files directory. If None, /static not mounted.
        templates_dir: Path to templates directory. If None, templating not configured.
        lifespan: Custom lifespan context manager. If None, a default is used
                  that initialises SessionStore and GoogleAuthService only.

    Returns:
        Configured FastAPI application instance.
    """
    app = FastAPI(
        title=config.app_name,
        version=config.app_version,
        description=(
            "A FastAPI web application with Google OAuth authentication. "
            "Built with security best practices including rate limiting, "
            "CSRF protection, and secure session management."
        ),
        docs_url=None,
        redoc_url=None,
        openapi_url="/openapi.json" if config.debug else None,
        lifespan=lifespan or default_lifespan,
    )

    app.state.config = config

    # Mount bundled vendor assets (Bulma, HTMX, Swagger UI, ReDoc)
    app.mount(
        "/vendor",
        StaticFiles(directory=str(_VENDOR_STATIC)),
        name="vendor",
    )

    # Mount project-specific static assets
    if static_dir is not None:
        app.mount(
            "/static",
            StaticFiles(directory=str(static_dir)),
            name="static",
        )

    # Configure Jinja2 templates
    if templates_dir is not None:
        templates = make_templates(templates_dir)
        configure_templates(templates, config)
        app.state.templates = templates

    # Self-hosted API docs (Swagger UI + ReDoc) - no CDN dependencies
    if config.debug:
        _register_docs_routes(app)

    # CORS middleware
    app.add_middleware(
        CORSMiddleware,
        allow_origins=config.cors.allow_origins,
        allow_credentials=config.cors.allow_credentials,
        allow_methods=config.cors.allow_methods,
        allow_headers=config.cors.allow_headers,
    )

    # Custom middleware (RequestID, SecurityHeaders, Logging)
    setup_middleware(app, config)

    # Host header injection protection
    app.add_middleware(
        TrustedHostMiddleware,
        allowed_hosts=["*"] if config.debug else config.trusted_hosts,
    )

    # Trust proxy headers only from local reverse proxy
    app.add_middleware(
        ProxyHeadersMiddleware,
        trusted_hosts=["127.0.0.1", "::1"],
    )

    # Exception handlers
    register_exception_handlers(app)

    # Built-in routers
    app.include_router(health_router)
    app.include_router(auth_router)

    # Project-specific routers
    if extra_routers:
        for extra_router in extra_routers:
            app.include_router(extra_router)

    lg.info(f"Created FastAPI app: {config.app_name} v{config.app_version}")

    return app

get_current_user async

get_current_user(
    session: Annotated[
        SessionData | None, Depends(get_current_session)
    ],
) -> SessionData

Require an authenticated user; raises NotAuthenticatedException otherwise.

Source code in src/fastapi_tools/dependencies.py
async def get_current_user(
    session: Annotated[SessionData | None, Depends(get_current_session)],
) -> SessionData:
    """Require an authenticated user; raises NotAuthenticatedException otherwise."""
    if session is None:
        raise NotAuthenticatedException
    return session

get_optional_user async

get_optional_user(
    session: Annotated[
        SessionData | None, Depends(get_current_session)
    ],
) -> SessionData | None

Get the current user if authenticated, or None.

Source code in src/fastapi_tools/dependencies.py
async def get_optional_user(
    session: Annotated[SessionData | None, Depends(get_current_session)],
) -> SessionData | None:
    """Get the current user if authenticated, or None."""
    return session