# Starlette-Templates Serve Starlette templates with file-based routing, Pydantic forms, and reusable components # Getting Started # Starlette-Templates This package extends [Starlette](https://starlette.dev/) with support for template-driven routing, form handling, and reusable UI components, built on [Jinja2](https://jinja.palletsprojects.com/en/stable/) and [Pydantic](https://docs.pydantic.dev/latest/). **Why does this exist?** Starlette is a toolkit that offers building blocks for web apps. But common tasks like template routing, form validation, and UI reuse require significant boilerplate. This package streamlines those workflows by directly routing URLs to templates, validating form data with Pydantic, and enabling type-safe, reusable UI components built as Jinja templates. This makes applications easier to build, reason about, and scale. ## Features - Serve HTML templates with file-based routing - Pydantic forms with validation and rendering - Reusable UI components using Jinja2 and Pydantic with type and validation safety - Static files with gzip compression and multi-directory support - JSON:API compliant error responses with custom error pages - ETag and Last-Modified headers with 304 Not Modified support ## Installation ``` pip install starlette-templates ``` ## Quick Start ``` from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.routing import Mount from jinja2 import PackageLoader from starlette_templates.routing import TemplateRouter from starlette_templates.middleware import JinjaMiddleware app = Starlette( routes=[ # File-based routing for the "templates" package directory Mount("/", TemplateRouter()), ], middleware=[ # Configure Jinja2 environment for template autodiscovery Middleware( JinjaMiddleware, template_loaders=[PackageLoader("mypackage", "templates")], ) ] ) ``` Create `templates/index.html`: ``` My App

Welcome to {{ request.url.hostname }}

``` Visit `http://localhost:8000/` and your template will be rendered automatically. ## Next Steps - Learn about [Applications](https://starlette-templates.tycho.engineering/applications/index.md) for setup and configuration - Learn about [Template Files](https://starlette-templates.tycho.engineering/templates/index.md) for file-based routing - Explore [Static Files](https://starlette-templates.tycho.engineering/static-files/index.md) for serving assets with gzip compression - Explore [Forms](https://starlette-templates.tycho.engineering/forms/index.md) for type-safe form handling - Discover [Components](https://starlette-templates.tycho.engineering/components/index.md) for reusable UI elements - Learn about [Error Handling](https://starlette-templates.tycho.engineering/errors/index.md) for custom exceptions and error pages - Check the [API Reference](https://starlette-templates.tycho.engineering/api/index.md) for detailed documentation ## LLM context This documentation is available as an LLM-friendly markdown file: - [llms.txt](https://starlette-templates.tycho.engineering/llms.txt) - links to documentation pages - [llms-full.txt](https://starlette-templates.tycho.engineering/llms-full.txt) - concatenated full docs in one file # Core Concepts # Applications Use the JinjaMiddleware middleware and TemplateRouter ASGI app to add template support to Starlette applications. ## Project Structure A typical Starlette-Templates application is organized as a Python package: ``` myapp/ ├── __init__.py ├── app.py # Main application file ├── templates/ # Template files │ ├── base.html # Base layout │ ├── index.html # Homepage │ ├── about.html # About page │ ├── 404.html # Custom 404 page │ ├── 500.html # Custom 500 page │ ├── partials/ # Reusable fragments │ │ ├── navigation.html │ │ └── footer.html │ └── components/ # Component templates │ ├── alert.html │ └── card.html └── static/ # Static files ├── css/ ├── js/ └── images/ ``` Your `app.py` would typically look like: ``` from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.routing import Mount, Route from starlette.responses import JSONResponse from jinja2 import PackageLoader from importlib.resources import files from starlette_templates.routing import TemplateRouter from starlette_templates.staticfiles import StaticFiles from starlette_templates.middleware import JinjaMiddleware PKG_DIR = files("myapp") async def api(request): return JSONResponse({"message": "API Root"}) app = Starlette( routes=[ # Custom routes first Mount("/api", api, name="api"), # Static files Mount("/static", StaticFiles(directories=[PKG_DIR / "static"]), name="static"), # Template files for remaining routes Mount("/", TemplateRouter()), ], middleware=[ Middleware( JinjaMiddleware, template_loaders=[PackageLoader("myapp", "templates")], ) ] ) ``` ## Basic Setup A minimal Starlette Templates application requires two components: 1. JinjaMiddleware - Middleware that configures the Jinja2 environment, making it available on request state 1. TemplateRouter - An ASGI app that serves HTML templates ``` from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.routing import Mount from jinja2 import PackageLoader from starlette_templates.routing import TemplateRouter from starlette_templates.middleware import JinjaMiddleware app = Starlette( routes=[ Mount("/", TemplateRouter()), ], middleware=[ Middleware( JinjaMiddleware, template_loaders=[PackageLoader("mypackage", "templates")], ) ] ) ``` ## Static Files Starlette Templates provides enhanced static file handling with gzip compression and multi-directory support using StaticFiles. ``` from starlette.routing import Mount from starlette_templates.staticfiles import StaticFiles app = Starlette( routes=[ Mount("/static", StaticFiles(directories=["static"]), name="static"), Mount("/", TemplateRouter()), ] ) ``` For detailed information about static file handling, including pre-compressed files, multi-directory support, and HTTP caching, see the [Static Files](https://starlette-templates.tycho.engineering/static-files/index.md) guide. ## Custom Routes Override the default template routing by defining custom routes. Routes defined before the TemplateRouter mount take precedence: ``` from starlette.routing import Route from starlette.requests import Request from starlette_templates.responses import TemplateResponse async def homepage(request: Request) -> TemplateResponse: return TemplateResponse("home.html", context={"title": "Welcome"}) app = Starlette( routes=[ # Custom route for homepage Route("/", homepage, name="home"), # Template routing for all other routes Mount("/", TemplateRouter()) ], middleware=[ Middleware( JinjaMiddleware, template_loaders=[PackageLoader("myapp", "templates")] ) ] ) ``` ## Middleware Configuration ### JinjaMiddleware JinjaMiddleware configures and injects the Jinja2 environment into request state, making it available at `request.state.jinja_env`. ``` from jinja2 import PackageLoader, FileSystemLoader from starlette_templates.middleware import JinjaMiddleware app = Starlette( middleware=[ Middleware( JinjaMiddleware, template_loaders=[ PackageLoader("myapp", "templates"), FileSystemLoader("custom/templates"), ], include_default_loader=True, # Include built-in templates ) ] ) ``` The middleware provides: - Automatic template loader configuration - Built-in Jinja2 globals: `url_for`, `url`, `absurl`, `jsonify` - Request state injection for all templates ### Registering Components Register ComponentModel classes to make them available in all templates: ``` from starlette_templates.components.base import ComponentModel class Alert(ComponentModel): template: str = "components/alert.html" message: str variant: str = "info" app = Starlette( middleware=[ Middleware( JinjaMiddleware, template_loaders=[PackageLoader("myapp", "templates")], extra_components=[Alert], # Register component ) ] ) ``` Then use directly in templates: ``` {{ Alert(message="System error", variant="danger") }} ``` ## Context Processors Add custom context variables to all templates using context processors: ``` from starlette.requests import Request async def add_user_context(request: Request) -> dict: return {"user": await get_current_user(request)} async def add_site_context(request: Request) -> dict: return { "site_name": "My Site", "year": datetime.now().year, } templates = TemplateRouter( context_processors=[add_user_context, add_site_context] ) ``` Context processors are async functions that receive the [Request](https://starlette.dev/requests/) object and return a dictionary of context variables. ### Route-Specific Context Processors Context processors can also be applied to specific routes using Route objects with path patterns: ``` from starlette.requests import Request from starlette.routing import Route async def add_product_context(request: Request) -> dict: """Context processor that only runs for product pages.""" product_id = request.path_params.get("product_id") return {"product": await get_product(product_id)} async def add_user_context(request: Request) -> dict: """Global context processor that runs for all templates.""" return {"user": await get_current_user(request)} templates = TemplateRouter( context_processors=[ add_user_context, # Runs for all templates # Only runs when path matches /products/{product_id} Route("/products/{product_id}", add_product_context), ] ) ``` Route-specific context processors only execute when the URL path matches the route pattern, allowing you to add context data conditionally based on the request path. Path parameters from the route (like `{product_id}`) are available via `request.path_params`. ## Error Handling Starlette Templates provides error handling with custom exceptions, error pages, and JSON:API error responses. Create custom error pages by adding templates like `404.html`, `500.html`, or `error.html` to your template directory. The framework automatically serves these pages for browser requests and returns JSON:API compliant responses for API requests. For detailed information about error handling, custom exceptions, error pages, and JSON:API responses, see the [Error Handling](https://starlette-templates.tycho.engineering/errors/index.md) guide. ## HTTP Caching Configure HTTP caching with ETag and Last-Modified headers on TemplateRouter: ``` templates = TemplateRouter( cache_max_age=3600, # Cache for 1 hour gzip_min_size=1024, # Compress files > 1KB ) ``` TemplateRouter automatically: - Generates ETag headers based on file modification time - Sets Last-Modified headers - Returns 304 Not Modified responses when appropriate - Compresses responses over the threshold with gzip # Multi-Application Sites A multi-application setup allows you to have multiple independent Starlette applications that can share templates and middleware while maintaining separate routing and configuration. This is useful for large projects that need to be modular or have distinct sections (e.g., a blog, an e-commerce store, and an admin panel) where each section can be developed and maintained independently, even in separate packages, but still share common resources and served by a single main application. Multi-app architecture leverages Starlette's ability to mount sub-applications using the [Mount](https://starlette.dev/routing/) routing class, allowing each sub-application to have its own routes, middleware, and template loaders. This setup provides flexibility through shared templates and scalability through separation of concerns for complex web applications. ## When to use multi-app architecture Multi-app sites are useful when you want to: - Split different parts of your application into distinct modules (e.g., blog, e-commerce, admin) - Reuse common templates, components, and middleware across applications - Potentially deploy sub-applications separately while maintaining consistency - Allow different teams to work on separate applications with clear boundaries - Incrementally add new sections without restructuring existing code ## Project Structure A multi-app project structure that uses shared templates but has separate sub-applications might look like this: ``` myproject/ ├── __init__.py ├── app.py # Main application that mounts sub-apps ├── camping/ # Camping sub-application │ ├── __init__.py │ └── templates/ │ ├── base.html │ ├── index.html │ └── products.html ├── hiking/ # Hiking sub-application │ ├── __init__.py │ └── templates/ │ ├── base.html │ ├── index.html │ └── gear.html └── shared/ # Shared templates across all apps ├── __init__.py └── templates/ ├── nav.html ├── footer.html └── error.html ``` This shows a single package example, but you can also have each sub-application (shared, camping, hiking) in its own package with its own templates, dependencies, and codebase. ## Basic Multi-App Setup Here's a complete example of a multi-app setup: ``` from jinja2 import PackageLoader from starlette.routing import Mount from starlette.middleware import Middleware from starlette.applications import Starlette from starlette_templates.routing import TemplateRouter from starlette_templates.middleware import JinjaMiddleware # Create camping sub-application camping = Starlette( routes=[ # Use Mount (not Route) to match all sub-paths # Give a name for URL generation, {{ url('camping', '/') }} Mount("/", TemplateRouter(debug=True), name="camping") ], middleware=[ Middleware( JinjaMiddleware, template_loaders=[ PackageLoader("myproject.camping", "templates"), # Add shared templates PackageLoader("myproject.shared", "templates"), ] ) ], debug=True, ) # Create hiking sub-application hiking = Starlette( routes=[ # Use Mount (not Route) to match all sub-paths # Give a name for URL generation, {{ url('hiking', '/') }} Mount("/", TemplateRouter(debug=True), name="hiking") ], middleware=[ Middleware( JinjaMiddleware, template_loaders=[ PackageLoader("myproject.hiking", "templates"), # Add shared templates PackageLoader("myproject.shared", "templates"), ] ) ], debug=True, ) # Main application that mounts sub-applications app = Starlette( routes=[ # Mount sub-applications at desired paths, with names for URL generation Mount("/camping", camping, name="camping"), Mount("/hiking", hiking, name="hiking"), # Serve shared templates at root Mount("/", TemplateRouter(debug=True), name="shared") ], middleware=[ Middleware( JinjaMiddleware, template_loaders=[ # Shared templates for root app PackageLoader("myproject.shared", "templates"), ] ) ], debug=True, ) ``` ## URL Routing in Multi-App Sites When you mount sub-applications, the URL routing works hierarchically: - `/camping/` → `camping` app → `/` → `camping/templates/index.html` - `/camping/products` → `camping` app → `/products` → `camping/templates/products.html` - `/hiking/` → `hiking` app → `/` → `hiking/templates/index.html` - `/hiking/gear` → `hiking` app → `/gear` → `hiking/templates/gear.html` - `/` → `shared` app → `/` → `shared/templates/index.html` The mount path (`/camping`, `/hiking`, `/`) is stripped before passing to the sub-application, so the sub-app sees only the remaining path. ## Template Sharing Sub-applications can share templates in two ways: ### 1. Shared Template Package Add a shared templates package as a fallback loader: ``` Middleware( JinjaEnvMiddleware, template_loaders=[ PackageLoader("myproject.camping", "templates"), # App-specific first PackageLoader("myproject.shared", "templates"), # Shared as fallback ] ) ``` This allows: - App-specific templates override shared ones - Common templates (nav, footer) from shared package are reused - Each app can customize shared templates by creating local versions with the same name (e.g., `camping/templates/nav.html` overrides `shared/templates/nav.html`) ### 2. Template Inheritance Shared templates can define base layouts that sub-apps extend: **shared/templates/base.html:** ``` {% block title %}My Site{% endblock %} {% include 'nav.html' %} {% block content %}{% endblock %} {% include 'footer.html' %} ``` **camping/templates/index.html:** ``` {% extends 'base.html' %} {% block title %}Camping{% endblock %} {% block content %}

Welcome to Camping

View Products {% endblock %} ``` ## Debug mode configuration ### Setting debug mode To enable detailed error pages with full tracebacks, code context, and local variables, you need to set `debug=True` on TemplateRouter, not just the Starlette app: ``` routes=[ Mount("/", TemplateRouter(debug=True), name="camping") ] ``` Common Mistake Setting `debug=True` only on `Starlette(debug=True)` is snot enoughs. You must also set `debug=True` on each `TemplateRouter` instance to see detailed error pages. Both are needed because: `Starlette(debug=True)` enables Starlette's debug features `TemplateRouter(debug=True)` enables detailed error pages for template rendering errors TemplateRouter has its own internal error handler that catches exceptions before they reach the Starlette app level, so it needs its own `debug` flag. ### Debug error page features When `TemplateRouter(debug=True)` is set, error pages show: - Exception type and message with HTTP status code - Full traceback with expandable frames - Code context (5 lines before and after each frame) - Local variables for each frame - Request information (method, URL, headers, query params, cookies) - Validation errors with detailed field information (for Pydantic errors) ## Critical distinction Mount vs Route Important: Use Mount, Not Route When using TemplateRouter as a catch-all handler, you must use `Mount`, not `Route`. This is a common source of 404 errors. Using `Route("/", TemplateRouter(...))` only matches the **exact path** `/`: ``` # WRONG Only matches exact path "/" routes=[ Route("/", TemplateRouter(), name="camping") ] ``` This causes: - `/camping/` → Works (matches exact path) - `/camping/products` → 404 Not Found (doesn't match) - `/camping/gear` → 404 Not Found (doesn't match) Using `Mount("/", TemplateRouter(...))` matches `/` and all sub-paths: ``` # CORRECT Matches all paths routes=[ Mount("/", TemplateRouter(), name="camping") ] ``` This makes everything work: - `/camping/` → Works - `/camping/products` → Works - `/camping/gear` → Works - `/camping/anything/else` → Works - Use `Route` for specific endpoints with defined paths: ``` Route("/api/users", users_endpoint) Route("/health", health_check) ``` - Use `Mount` for catch-all handlers like TemplateRouter: ``` Mount("/", TemplateRouter()) Mount("/static", StaticFiles(directories=["static"])) ``` ## URL generation with `url(mount_name, path)` In multi-app sites, you can generate URLs using the `url(mount_name, path)` function in templates. The route names you define in `Mount()` become available: ``` # Route names defined here app = Starlette( routes=[ Mount("/camping", camping, name="camping"), Mount("/hiking", hiking, name="hiking"), ] ) ``` Use in templates: ``` Camping Camping Products Hiking Hiking Gear ``` The `url()` function (alias for `url_for()`) takes: 1. Route name (e.g., `"camping"`) 1. Path within that route (e.g., `"/products"`) ## Custom exception handlers For routes that don't use TemplateFiles, you can add custom exception handlers at the app level: ``` from starlette.exceptions import HTTPException from starlette.requests import Request from starlette.responses import Response from starlette_templates.errors import httpexception_handler, exception_handler async def handle_http_exception(request: Request, exc: HTTPException) -> Response: jinja_env = request.state.jinja_env return await httpexception_handler(request, exc, jinja_env, debug=True) async def handle_exception(request: Request, exc: Exception) -> Response: jinja_env = request.state.jinja_env return await exception_handler(request, exc, jinja_env, debug=True) app = Starlette( routes=[ Mount("/api", api_routes), # Non-TemplateRouter routes Mount("/", TemplateRouter(debug=True)), ], exception_handlers={ HTTPException: handle_http_exception, Exception: handle_exception, }, debug=True, ) ``` Note If all your routes use TemplateRouter, you don't need custom exception handlers. TemplateRouter has its own internal error handler that works with `TemplateRouter(debug=True)`. # Templates This guide covers template files, routing, and rendering in Starlette Templates. ## Template Router TemplateRouter is an ASGI app that serves HTML templates with automatic routing and file resolution. ``` from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.routing import Mount from jinja2 import PackageLoader from starlette_templates.routing import TemplateRouter from starlette_templates.middleware import JinjaMiddleware app = Starlette( routes=[ Mount("/", TemplateRouter()), ], middleware=[ # Required for template autodiscovery Middleware( JinjaMiddleware, template_loaders=[PackageLoader("myapp", "templates")], ) ] ) ``` ### Features - Automatically tries `.html`, `.htm`, `.jinja`, `.jinja2` extensions - Serves `index.html` or `index.htm` for directory requests - HTTP caching with ETag and Last-Modified headers with 304 responses - Automatic gzip compression for responses over 500 bytes - Custom error pages with `404.html`, `500.html`, `error.html` - Custom context variables with context processors ### File Resolution When a request is made, `TemplateRouter` resolves templates in this order: 1. Exact match: `/about` → `about.html` 1. With extensions: `/about` → tries `about.html`, `about.htm`, `about.jinja`, `about.jinja2` 1. Index files: `/blog/` → tries `blog/index.html`, `blog/index.htm` ### Configuration ``` from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.routing import Mount from starlette.requests import Request from jinja2 import PackageLoader from starlette_templates.routing import TemplateRouter from starlette_templates.middleware import JinjaMiddleware async def add_context(request: Request) -> dict: return {"user": await get_current_user(request)} app = Starlette( routes=[ Mount("/", TemplateRouter( context_processors=[add_context], # Add custom context cache_max_age=3600, # 1 hour cache gzip_min_size=500, # Compress files > 500 bytes )), ], middleware=[ # Required for template autodiscovery Middleware( JinjaMiddleware, template_loaders=[PackageLoader("myapp", "templates")], ) ] ) ``` ### Route-Specific Context Processors Add context processors that only run for specific URL patterns using Starlette's `Route`: ``` from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.routing import Mount, Route from starlette.requests import Request from jinja2 import PackageLoader from starlette_templates.routing import TemplateRouter from starlette_templates.middleware import JinjaMiddleware # Global context processor - runs for ALL templates async def add_user_context(request: Request) -> dict: return {"user": await get_current_user(request)} # Route-specific context processors async def add_country_context(request: Request) -> dict: """Only runs for /country/{code} paths.""" code = request.path_params["code"] return {"country": await get_country(code)} async def add_post_context(request: Request) -> dict: """Only runs for /blog/{post_id} paths.""" post_id = request.path_params["post_id"] return {"post": await get_post(post_id)} app = Starlette( routes=[ Mount("/", TemplateRouter( context_processors=[ add_user_context, # Global - runs for all templates Route('/country/{code}', add_country_context), # Only /country/* paths Route('/blog/{post_id}', add_post_context), # Only /blog/* paths ] )), ], middleware=[ Middleware( JinjaMiddleware, template_loaders=[PackageLoader("myapp", "templates")], ) ] ) ``` In your template at `/country/us`: ```

{{ country.name }}

User: {{ user.name }}

``` Route-specific processors: - Have access to path parameters via `request.path_params` - Only execute when their route pattern matches the current request - Can be mixed with global processors in the same list - Use Starlette's existing `Route` class - no new abstractions ## Template Response Use TemplateResponse to render templates in route handlers: ``` from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.routing import Route from starlette.requests import Request from jinja2 import PackageLoader from starlette_templates.responses import TemplateResponse from starlette_templates.middleware import JinjaEnvMiddleware async def homepage(request: Request) -> TemplateResponse: return TemplateResponse( "home.html", context={"title": "Welcome", "items": [1, 2, 3]} ) app = Starlette( routes=[ Route("/", homepage), ], middleware=[ # Required for template autodiscovery Middleware( JinjaMiddleware, template_loaders=[PackageLoader("myapp", "templates")], ) ] ) ``` ### Context Variables TemplateResponse automatically includes: - `request` - The Starlette `Request` object - All context variables from context processors - Any additional context passed to `TemplateResponse` ## Template Context All templates have access to: ### Request Object ```

Current path: {{ request.url.path }}

Host: {{ request.url.hostname }}

Method: {{ request.method }}

User agent: {{ request.headers.get('user-agent') }}

``` ### URL Helpers #### url_for Generate URLs for named routes: ``` Profile ``` #### url Generate URLs for mounted apps: ``` ``` #### absurl Generate absolute URLs: ``` ``` ### JSON Serialization Safely embed Python data in templates with `jsonify`: ``` ``` This properly escapes and serializes Python objects to JSON. ## Custom Filters and Globals Add custom Jinja2 filters and globals through JinjaMiddleware: ``` from starlette.applications import Starlette from starlette.middleware import Middleware from jinja2 import Environment, PackageLoader from starlette_templates.middleware import JinjaMiddleware def setup_jinja_env(env: Environment): # Add custom filter env.filters['uppercase'] = lambda x: x.upper() # Add custom global env.globals['site_name'] = "My Site" app = Starlette( middleware=[ Middleware( JinjaMiddleware, template_loaders=[PackageLoader("myapp", "templates")], # Custom setup function environment_setup=setup_jinja_env, ) ] ) ``` Then use in templates: ``` {{ "hello" | uppercase }} {{ site_name }} ``` ## Template Loaders Configure template loading with [Jinja2 loaders](https://jinja.palletsprojects.com/en/stable/api/#loaders): ### PackageLoader Load templates from a Python package: ``` from starlette.applications import Starlette from starlette.middleware import Middleware from jinja2 import PackageLoader from starlette_templates.middleware import JinjaMiddleware app = Starlette( middleware=[ Middleware( JinjaMiddleware, template_loaders=[ PackageLoader("myapp", "templates"), ] ) ] ) ``` ### FileSystemLoader Load templates from filesystem directories: ``` from starlette.applications import Starlette from starlette.middleware import Middleware from jinja2 import FileSystemLoader from starlette_templates.middleware import JinjaMiddleware app = Starlette( middleware=[ Middleware( JinjaMiddleware, template_loaders=[ FileSystemLoader("custom/templates"), ] ) ] ) ``` ### Multiple Loaders Use multiple loaders with fallback priority: ``` from starlette.applications import Starlette from starlette.middleware import Middleware from jinja2 import PackageLoader, FileSystemLoader from starlette_templates.middleware import JinjaMiddleware app = Starlette( middleware=[ Middleware( JinjaMiddleware, template_loaders=[ PackageLoader("myapp", "templates"), # Check here first FileSystemLoader("custom/templates"), # Then here ], include_default_loader=True, # Then built-in templates ) ] ) ``` ## Error Pages Create custom error pages by adding templates to your template directory: ### 404.html ``` 404 Not Found

{{ status_code }} - {{ error_title }}

{{ error_message }}

Go Home ``` ### 500.html ``` 500 Server Error

{{ status_code }} - {{ error_title }}

{{ error_message }}

{% if structured_errors %}

Details:

{% endif %} ``` ### error.html Generic fallback error page for any error without a specific template: ``` Error

{{ status_code }} - {{ error_title }}

{{ error_message }}

``` ## Template Inheritance Use Jinja2's template inheritance for consistent layouts: ### base.html ``` {% block title %}My Site{% endblock %}
{% block header %}

My Site

{% endblock %}
{% block content %}{% endblock %}
``` ### page.html ``` {% extends "base.html" %} {% block title %}About - {{ super() }}{% endblock %} {% block content %}

About Us

Welcome to our site!

{% endblock %} ``` ## Including Templates Include reusable template fragments: ``` {% include "partials/navigation.html" %} {% include "partials/alert.html" with context %} ``` ## Macros Define reusable template functions with macros: ``` {% macro render_field(field) %}
{% if field.errors %}
{{ field.errors }}
{% endif %}
{% endmacro %} {{ render_field(form.name) }} {{ render_field(form.email) }} ``` # Static Files Starlette-Templates provides static file handling with - Pre-compressed gzip file support - Automatic HTTP caching headers - Multi-directory fallback support - ETag and Last-Modified headers ## StaticFiles StaticFiles serves static files with support for pre-compressed `.gz` files that are automatically decompressed in browsers and multi-directory fallback support. ### Basic Usage ``` from starlette.applications import Starlette from starlette.routing import Mount from starlette_templates.staticfiles import StaticFiles app = Starlette( routes=[ Mount("/static", StaticFiles(directories=["static"]), name="static"), ] ) ``` ### Pre-compressed Files Create pre-compressed versions of your static files for better performance: ``` # Create pre-compressed CSS files gzip -k static/css/bootstrap.css gzip -k static/css/style.css # Create pre-compressed JavaScript files gzip -k static/js/app.js gzip -k static/js/vendor.js ``` This creates `.gz` versions alongside the original files: ``` static/ ├── css/ │ ├── bootstrap.css │ ├── bootstrap.css.gz │ ├── style.css │ └── style.css.gz └── js/ ├── app.js ├── app.js.gz ├── vendor.js └── vendor.js.gz ``` ### Using Pre-compressed Files Reference the `.gz` files in your templates: ``` ``` The browser automatically decompresses the files and applies proper caching headers. ### How It Works When serving a `.gz` file, StaticFiles: 1. Serves the pre-compressed content 1. Sets `Content-Encoding: gzip` header 1. Sets appropriate `Content-Type` based on the original file extension 1. Adds `ETag` and `Last-Modified` headers for caching 1. Returns `304 Not Modified` for cached requests ### Configuration ``` from pathlib import Path static_files = StaticFiles( directories=[Path("static")], packages=None, # Optional: list of package names html=False, # Don't serve HTML files check_dir=True, # Verify directory exists ) ``` ## Multi-Directory Support StaticFiles serves static files from multiple directories with fallback priority, useful for: - Theme overrides - Plugin systems - Framework + application static files - Development vs production assets ### Basic Usage ``` from pathlib import Path from starlette.applications import Starlette from starlette.routing import Mount from starlette_templates.staticfiles import StaticFiles static_files = StaticFiles( directories=[ Path("myapp/static"), # Check here first Path("framework/static"), # Fallback to here Path("vendor/static"), # Then here ] ) app = Starlette( routes=[ Mount("/static", static_files, name="static"), ] ) ``` ### Fallback Priority Files are resolved in order: 1. If `myapp/static/css/style.css` exists, serve it 1. Otherwise, if `framework/static/css/style.css` exists, serve it 1. Otherwise, if `vendor/static/css/style.css` exists, serve it 1. Otherwise, return 404 This allows you to override framework files with your application files. ### Example Directory Structure ``` myapp/ └── static/ └── css/ └── custom.css # Application-specific styles framework/ └── static/ ├── css/ │ ├── base.css # Framework base styles │ └── components.css # Framework components └── js/ └── framework.js # Framework JavaScript vendor/ └── static/ └── js/ ├── jquery.js # Third-party libraries └── bootstrap.js ``` ### Usage in Templates ``` ``` ### Combining Multi-Directory and Gzip Support StaticFiles automatically supports both multi-directory fallback and pre-compressed files: ``` from pathlib import Path from starlette_templates.staticfiles import StaticFiles # StaticFiles automatically supports .gz files and multiple directories static_files = StaticFiles( directories=[ Path("myapp/static"), Path("framework/static"), ] ) ``` Then create compressed versions in any directory: ``` gzip -k myapp/static/css/custom.css gzip -k framework/static/css/base.css ``` ## HTTP Caching StaticFiles supports HTTP caching: ### ETag Headers Automatically generated based on file modification time and size: ``` ETag: "abc123-1234567890" ``` ### Last-Modified Headers Set from file modification time: ``` Last-Modified: Wed, 20 Dec 2023 12:00:00 GMT ``` ### 304 Not Modified When client sends `If-None-Match` or `If-Modified-Since` headers that match: ``` HTTP/1.1 304 Not Modified ETag: "abc123-1234567890" ``` ### Cache-Control Headers Set appropriate cache headers for static files: ``` from starlette.responses import Response # In your static files configuration static_files = StaticFiles( directories=["static"], # Files are served with cache headers ) ``` Response includes: ``` Cache-Control: public, max-age=3600 ``` ## Best Practices ### Pre-compression Pre-compress large static files for production: ``` # Find and compress all CSS and JS files find static -type f \( -name "*.css" -o -name "*.js" \) -exec gzip -k {} \; # Or use a more selective approach gzip -k static/css/bootstrap.css gzip -k static/js/app.bundle.js ``` ### Build Process Integration Integrate compression into your build process: ``` # build.py import gzip import shutil from pathlib import Path def compress_static_files(): static_dir = Path("static") for file in static_dir.rglob("*.css"): with open(file, 'rb') as f_in: with gzip.open(f"{file}.gz", 'wb') as f_out: shutil.copyfileobj(f_in, f_out) for file in static_dir.rglob("*.js"): with open(file, 'rb') as f_in: with gzip.open(f"{file}.gz", 'wb') as f_out: shutil.copyfileobj(f_in, f_out) if __name__ == "__main__": compress_static_files() ``` ### Directory Organization Organize static files by type and priority: ``` static/ ├── css/ │ ├── vendor/ # Third-party CSS │ ├── base/ # Base styles │ └── components/ # Component styles ├── js/ │ ├── vendor/ # Third-party JS │ └── app/ # Application JS ├── img/ │ ├── icons/ │ └── photos/ └── fonts/ ``` ### Development vs Production Use different configurations for development and production: ``` import os from pathlib import Path from starlette_templates.staticfiles import StaticFiles # StaticFiles works for both development and production static_files = StaticFiles(directories=["static"]) # In production, ensure you have .gz files created for better performance ``` ### CDN Integration For production, consider using a CDN with fallback: ``` # In your context processor async def add_static_context(request: Request) -> dict: cdn_url = os.getenv("CDN_URL", "") return { "static_url": cdn_url if cdn_url else request.url_for("static", path="/"), } ``` In templates: ``` ``` ## Troubleshooting ### File Not Found If static files aren't loading: 1. Verify the directory path is correct 1. Check file permissions 1. Ensure the mount path is correct 1. Verify the static files route comes before catch-all routes ``` app = Starlette( routes=[ # Static files BEFORE template files Mount("/static", StaticFiles(directories=["static"]), name="static"), # Template files last Mount("/", TemplateRouter()), ] ) ``` ### Gzip Not Working If pre-compressed files aren't being served: 1. Verify `.gz` files exist 1. Check file naming (must end in `.gz`) 1. Ensure you're using starlette-templates `StaticFiles` (not Starlette's built-in `StaticFiles`) 1. Check browser accepts gzip encoding ### Caching Issues If files aren't being cached: 1. Check browser caching is enabled 1. Verify ETag headers are being sent 1. Clear browser cache for testing 1. Check proxy/CDN configuration doesn't strip cache headers # Forms Starlette-Templates provides type-safe forms with automatic validation and rendering using Pydantic models. The FormModel base class extends Pydantic's `BaseModel` with form-specific functionality. ## Creating forms Define forms using the FormModel base class and field types: ``` from starlette_templates.forms import ( FormModel, TextField, SelectField, SubmitButtonField ) class ContactForm(FormModel): name: str = TextField( label="Your Name", placeholder="Enter your name", required=True, min_length=2, ) category: str = SelectField( default="general", choices={ "general": "General Inquiry", "support": "Technical Support", "sales": "Sales", }, label="Category", ) submit: str = SubmitButtonField(text="Send Message") ``` FormModel supports various field types (see [Available Form Fields](#available-form-fields) below) and validation using Pydantic's validators. The fields, like TextField and SelectField, are [Pydantic model fields](https://docs.pydantic.dev/latest/concepts/fields/) with additional metadata for rendering and validation. These form fields define template components, labels, placeholders, and validation rules, in-addition to the standard Pydantic field behavior. ## Using forms in routes ### Basic usage ``` from starlette.routing import Route from starlette.requests import Request from starlette_templates.responses import TemplateResponse async def contact_page(request: Request) -> TemplateResponse: # Parse form data and validate without raising on error form = await ContactForm.from_request(request, raise_on_error=False) # Check if form is valid and was submitted if form.is_valid(request): # Send email, save to database, etc. await send_contact_email(form.name, form.email, form.category) return TemplateResponse("success.html", context={"form": form}) # Show form (with errors if validation failed) return TemplateResponse("contact.html", context={"form": form}) app = Starlette( routes=[Route("/contact", contact_page, methods=["GET", "POST"])] ) ``` ### Error handling ``` from pydantic import ValidationError async def signup_page(request: Request) -> TemplateResponse: try: # Raise ValidationError on invalid data form = await SignupForm.from_request(request, raise_on_error=True) if form.is_valid(request): # Process valid form user = await create_user(form) return TemplateResponse("welcome.html", context={"user": user}) except ValidationError as e: # Handle validation errors return TemplateResponse( "signup.html", context={"errors": e.errors()}, status_code=400 ) # Show empty form for GET requests form = SignupForm() return TemplateResponse("signup.html", context={"form": form}) ``` ## Rendering forms ### Complete form A FormModel instance can be rendered in templates using the `form()` function or by rendering individual fields. Render the entire form with a single call: ``` {{ form() }} ``` You can pass form attributes like `method` `action`, and `enctype`: ``` {{ form( method="POST", action="/submit", enctype="multipart/form-data") }} ``` ### Individual fields Render individual fields with full control: ```
{{ form.render('name') }} {{ form.render('email') }} {{ form.render('category') }} {{ form.render('subscribe') }} {{ form.render('submit') }}
``` ### Accessing field values Access validated field values directly: ``` {% if form.is_valid(request) %}

Thank you, {{ form.name }}!

We'll contact you at {{ form.email }}

{% endif %} ``` ### Validation errors Validation errors are automatically displayed when you render fields. The FormModel injects validation state and error messages into rendered components. ```
{{ form.render('email') }} {{ form.render('password') }} {{ form.render('submit') }}
``` Check if the form has validation errors: ``` {% if form.has_errors() %}

{{ form.get_error_banner_text() }}

{% endif %}
{{ form.render('email') }} {{ form.render('password') }} {{ form.render('submit') }}
``` The `has_errors()` method returns `True` if there are any validation errors, and `get_error_banner_text()` returns the configured error banner message (default: "Please correct the errors below"). ### How error handling works When FormModel.from_request() is called with `raise_on_error=False`: 1. The form attempts to validate the request data 1. If validation fails, errors are captured internally in `_field_errors` 1. When you call `form.render('field_name')`, the error is automatically injected into the component 1. The component displays the error message with appropriate styling (e.g., Bootstrap's `is-invalid` class) This means you don't need to manually access or display errors - they're handled automatically by the rendering system. ## Available form fields ### TextField TextField - Single-line text input: ``` name: str = TextField( label="Name", placeholder="Enter your name", required=True, min_length=2, max_length=100, ) ``` ### TextAreaField TextAreaField - Multi-line text input: ``` message: str = TextAreaField( label="Message", placeholder="Enter your message", rows=5, required=True, min_length=10, ) ``` ### EmailField EmailField - Email input with validation: ``` email: str = EmailField( label="Email", placeholder="you@example.com", required=True, ) ``` ### IntegerField IntegerField - Numeric input for integers: ``` age: int = IntegerField( label="Age", min_value=18, max_value=120, required=True, ) ``` ### FloatField FloatField - Numeric input for floats: ``` price: float = FloatField( label="Price", min_value=0.0, step=0.01, required=True, ) ``` ### CheckboxField CheckboxField - Boolean checkbox: ``` agree: bool = CheckboxField( default=False, label="I agree to the terms", required=True, ) ``` ### SelectField SelectField - Dropdown select (single or multiple): ``` # Single select country: str = SelectField( label="Country", choices={ "us": "United States", "uk": "United Kingdom", "ca": "Canada", }, default="us", ) # Multiple select interests: list[str] = SelectField( label="Interests", choices={ "tech": "Technology", "music": "Music", "sports": "Sports", }, multiple=True, ) ``` ### DateField DateField - Date picker with Flatpickr: ``` from datetime import date birth_date: date = DateField( label="Birth Date", required=True, ) ``` ### HiddenField HiddenField - Hidden input: ``` user_id: int = HiddenField(default=0) ``` ### SubmitButtonField SubmitButtonField - Submit button: ``` submit: str = SubmitButtonField(text="Submit") ``` ### TagField TagField - Tag input with comma separation: ``` tags: list[str] = TagField( label="Tags", placeholder="Enter tags separated by commas", ) ``` ## Form validation ### Pydantic validators Use [Pydantic validators](https://docs.pydantic.dev/latest/concepts/validators/) for custom validation logic: ``` from pydantic import field_validator, model_validator class RegistrationForm(FormModel): username: str = TextField(label="Username", required=True) password: str = TextField(label="Password", type="password", required=True) confirm_password: str = TextField(label="Confirm Password", type="password", required=True) @field_validator('username') @classmethod def validate_username(cls, v): if len(v) < 3: raise ValueError('Username must be at least 3 characters') if not v.isalnum(): raise ValueError('Username must be alphanumeric') return v @model_validator(mode='after') def validate_passwords(self): if self.password != self.confirm_password: raise ValueError('Passwords do not match') return self ``` ### Custom validation Add custom validation methods: ``` class PaymentForm(FormModel): amount: float = FloatField(label="Amount", required=True) currency: str = SelectField( label="Currency", choices={"usd": "USD", "eur": "EUR", "gbp": "GBP"} ) @field_validator('amount') @classmethod def validate_amount(cls, v): if v <= 0: raise ValueError('Amount must be positive') if v > 10000: raise ValueError('Amount cannot exceed 10,000') return v ``` ## Parsing request data The model_from_request helper combines path parameters, query parameters, and body data: ``` from starlette_templates.forms import model_from_request from pydantic import BaseModel class UserData(BaseModel): name: str email: str age: int async def create_user(request: Request): # Combines path params, query params, and body data # data is a validated UserData instance data = await model_from_request(request, UserData) ``` If path, query, and body parameters overlap, body data takes precedence over query parameters, which take precedence over path parameters. If you are using a FormModel, use FormModel.from_request() instead to get form-specific behavior and validation. ``` from starlette_templates.forms import FormModel class ProfileForm(FormModel): username: str bio: str async def edit_profile(request: Request): # Combines path params, query params, and body data # form is a validated ProfileForm instance form = await ProfileForm.from_request(request) ``` # Components Components in Starlette-Templates are reusable UI elements built with Jinja2 templates and Pydantic models. They provide: - Type safety with Pydantic validation - Reusable, testable UI elements - Template-based rendering - Integration with the Jinja2 environment ## Creating Components Define components using the ComponentModel base class: ``` from starlette_templates.components.base import ComponentModel from pydantic import Field class Alert(ComponentModel): # Path to component template template: str = "components/alert.html" # Component properties with validation message: str = Field(..., description="Alert message") variant: str = Field(default="info", description="Alert variant") dismissible: bool = Field(default=False, description="Show close button") ``` ### Component Template Create the template file (`components/alert.html`): ``` ``` ## Using Components ### In Routes Create component instances and pass them to templates: ``` from starlette.requests import Request from starlette_templates.responses import TemplateResponse async def dashboard(request: Request) -> TemplateResponse: alert = Alert( message="Welcome back!", variant="success", dismissible=True ) return TemplateResponse( "dashboard.html", context={"alert": alert} ) ``` ### In Templates Render components by calling them: ``` {{ alert }} ``` ### Global Registration Register components globally to use them in any template: ``` from starlette.applications import Starlette from starlette.middleware import Middleware from starlette_templates.middleware import JinjaEnvMiddleware app = Starlette( middleware=[ Middleware( JinjaEnvMiddleware, template_loaders=[PackageLoader("myapp", "templates")], extra_components=[Alert], # Register component ) ] ) ``` Then use directly in templates without passing from routes: ``` {{ Alert(message="System error", variant="danger", dismissible=True) }} ``` ## Built-in Form Components Starlette Templates includes Bootstrap-compatible form components: ### Input Input - Text, email, password, number inputs: ``` from starlette_templates.components.forms import Input # In route input_field = Input( name="username", label="Username", type="text", placeholder="Enter username", required=True, ) # In template {{ Input(name="email", label="Email", type="email", required=True) }} ``` ### Textarea Textarea - Multi-line text input: ``` from starlette_templates.components.forms import Textarea textarea = Textarea( name="message", label="Message", rows=5, placeholder="Enter your message", ) ``` ### Select Select - Native select dropdown: ``` from starlette_templates.components.forms import Select select = Select( name="country", label="Country", choices={ "us": "United States", "uk": "United Kingdom", "ca": "Canada", }, selected="us", ) ``` ### Checkbox Checkbox - Checkbox input: ``` from starlette_templates.components.forms import Checkbox checkbox = Checkbox( name="agree", label="I agree to the terms", checked=False, ) ``` ### Radio Radio - Radio button: ``` from starlette_templates.components.forms import Radio # In template {{ Radio(name="plan", label="Basic Plan", value="basic", checked=True) }} {{ Radio(name="plan", label="Pro Plan", value="pro") }} ``` ### Switch Switch - Toggle switch: ``` from starlette_templates.components.forms import Switch switch = Switch( name="notifications", label="Enable notifications", checked=True, ) ``` ### FileInput FileInput - File upload: ``` from starlette_templates.components.forms import FileInput file_input = FileInput( name="avatar", label="Profile Picture", accept="image/*", ) ``` ### Range Range - Range slider: ``` from starlette_templates.components.forms import Range range_slider = Range( name="volume", label="Volume", min_value=0, max_value=100, step=1, value=50, ) ``` ### ChoicesSelect ChoicesSelect - Enhanced select with Choices.js: ``` from starlette_templates.components.forms import ChoicesSelect choices_select = ChoicesSelect( name="tags", label="Tags", choices={ "python": "Python", "javascript": "JavaScript", "go": "Go", }, multiple=True, ) ``` ### DatePicker DatePicker - Date picker with Flatpickr: ``` from starlette_templates.components.forms import DatePicker date_picker = DatePicker( name="birth_date", label="Birth Date", placeholder="Select date", ) ``` ### SubmitButton SubmitButton - Form submit button: ``` from starlette_templates.components.forms import SubmitButton submit_btn = SubmitButton( text="Submit Form", variant="primary", ) ``` ## Component Examples ### Card Component ``` class Card(ComponentModel): template: str = "components/card.html" title: str = Field(..., description="Card title") body: str = Field(..., description="Card body content") footer: str | None = Field(None, description="Card footer") variant: str = Field(default="default", description="Card style variant") ``` Template (`components/card.html`): ```
{{ title }}
{{ body }}
{% if footer %} {% endif %}
``` Usage: ``` {{ Card( title="Welcome", body="This is a card component", footer="Card footer", variant="primary" ) }} ``` ### Badge Component ``` class Badge(ComponentModel): template: str = "components/badge.html" text: str = Field(..., description="Badge text") variant: str = Field(default="primary", description="Badge color") pill: bool = Field(default=False, description="Use pill style") ``` Template (`components/badge.html`): ``` {{ text }} ``` Usage: ``` {{ Badge(text="New", variant="success", pill=True) }} {{ Badge(text="Beta", variant="warning") }} ``` ### Button Component ``` class Button(ComponentModel): template: str = "components/button.html" text: str = Field(..., description="Button text") variant: str = Field(default="primary", description="Button style") size: str = Field(default="md", description="Button size") disabled: bool = Field(default=False, description="Disabled state") type: str = Field(default="button", description="Button type") ``` Template (`components/button.html`): ``` ``` Usage: ``` {{ Button(text="Click Me", variant="primary") }} {{ Button(text="Submit", variant="success", type="submit") }} {{ Button(text="Disabled", disabled=True) }} ``` ### Modal Component ``` class Modal(ComponentModel): template: str = "components/modal.html" id: str = Field(..., description="Modal ID") title: str = Field(..., description="Modal title") body: str = Field(..., description="Modal body content") footer: str | None = Field(None, description="Modal footer") size: str = Field(default="md", description="Modal size (sm, md, lg, xl)") ``` Template (`components/modal.html`): ``` ``` ## Component Validation Components use Pydantic validation to ensure correct usage: ``` class Image(ComponentModel): template: str = "components/image.html" src: str = Field(..., description="Image source URL") alt: str = Field(..., description="Alt text") width: int | None = Field(None, ge=1, description="Image width") height: int | None = Field(None, ge=1, description="Image height") @field_validator('src') @classmethod def validate_src(cls, v): if not v.startswith(('http://', 'https://', '/')): raise ValueError('Invalid image source') return v ``` ## Nested Components Components can render other components: ``` class Alert(ComponentModel): template: str = "components/alert.html" message: str variant: str = "info" class AlertGroup(ComponentModel): template: str = "components/alert_group.html" alerts: list[Alert] = Field(default_factory=list) ``` Template (`components/alert_group.html`): ```
{% for alert in alerts %} {{ alert }} {% endfor %}
``` Usage: ``` alert_group = AlertGroup( alerts=[ Alert(message="Success!", variant="success"), Alert(message="Warning!", variant="warning"), Alert(message="Error!", variant="danger"), ] ) ``` # Error Handling This guide covers error handling, custom exceptions, error pages, and JSON:API error responses. ## Overview Starlette Templates provides comprehensive error handling with: - Custom exception classes with structured error details - Automatic HTML error pages for browser requests - JSON:API compliant error responses for API requests - Custom error page templates - Context processors for error pages ## Custom Exceptions ### AppException AppException is the base exception class for structured error responses. ``` from starlette_templates.errors import AppException, ErrorCode, ErrorSource async def get_user(user_id: int): if not user_exists(user_id): raise AppException( detail=f"User with ID {user_id} not found", status_code=404, code=ErrorCode.NOT_FOUND, source=ErrorSource(parameter="user_id"), meta={"user_id": user_id} ) ``` ### Parameters - **detail** (str, required) - Human-readable explanation of the error - **status_code** (int, default: 500) - HTTP status code - **code** (ErrorCode, optional) - Machine-readable error code - **source** (ErrorSource, optional) - Source of the error - **title** (str, optional) - Short, human-readable error summary - **meta** (dict, optional) - Additional metadata about the error ### ErrorCode ErrorCode provides standard error codes: ``` from starlette_templates.errors import ErrorCode ErrorCode.BAD_REQUEST # 400 ErrorCode.UNAUTHORIZED # 401 ErrorCode.FORBIDDEN # 403 ErrorCode.NOT_FOUND # 404 ErrorCode.METHOD_NOT_ALLOWED # 405 ErrorCode.CONFLICT # 409 ErrorCode.VALIDATION_ERROR # 422 ErrorCode.INTERNAL_SERVER_ERROR # 500 ``` ### ErrorSource ErrorSource identifies where the error occurred: ``` from starlette_templates.errors import ErrorSource # Error in a request parameter ErrorSource(parameter="user_id") # Error in a request header ErrorSource(header="Authorization") # Error in a JSON pointer location ErrorSource(pointer="/data/attributes/email") # Error in a query parameter ErrorSource(parameter="page[limit]") ``` ## Error Pages Create custom error pages by adding templates to your template directory. ### 404.html - Not Found ``` {{ status_code }} - {{ error_title }}

{{ status_code }}

{{ error_title }}

{{ error_message }}

Go Home
``` ### 500.html - Server Error ``` {{ status_code }} - {{ error_title }}

{{ status_code }}

{{ error_title }}

{{ error_message }}

{% if structured_errors %}

Error Details:

{% endif %} {% if request.app.debug %}

Debug Information:

{{ traceback }}
{% endif %}
``` ### error.html - Generic Error Page Generic fallback for any error without a specific template: ``` Error - {{ status_code }}

{{ status_code }}

{{ error_title }}

{{ error_message }}

Go Home
``` ### Available Template Variables All error templates have access to: - `status_code` (int) - HTTP status code - `error_title` (str) - Short error summary - `error_message` (str) - Detailed error explanation - `structured_errors` (list) - List of error objects with structured details - `request` - The Starlette Request object - `traceback` (str) - Stack trace (only in debug mode) ## JSON:API Error Responses For API requests (requests with `Accept: application/json` or `Content-Type: application/json`), errors are automatically returned as JSON:API compliant responses. ### Single Error ``` from starlette_templates.errors import AppException, ErrorCode async def register_user(email: str): raise AppException( detail="Email address is already registered", status_code=409, code=ErrorCode.CONFLICT, source=ErrorSource(pointer="/data/attributes/email"), ) ``` Returns: ``` { "errors": [ { "status": "409", "code": "conflict", "title": "Conflict", "detail": "Email address is already registered", "source": { "pointer": "/data/attributes/email" } } ] } ``` ### Multiple Errors ``` from starlette_templates.errors import AppException, ErrorCode, ErrorSource async def validate_user_input(data: dict): # Validation error with multiple field errors raise AppException( detail="Validation failed", status_code=422, code=ErrorCode.VALIDATION_ERROR, errors=[ { "detail": "Email address is required", "source": ErrorSource(pointer="/data/attributes/email"), "code": "required" }, { "detail": "Password must be at least 8 characters", "source": ErrorSource(pointer="/data/attributes/password"), "code": "min_length" } ] ) ``` Returns: ``` { "errors": [ { "status": "422", "code": "required", "detail": "Email address is required", "source": { "pointer": "/data/attributes/email" } }, { "status": "422", "code": "min_length", "detail": "Password must be at least 8 characters", "source": { "pointer": "/data/attributes/password" } } ] } ``` ### Error Response Format JSON:API error responses follow the [JSON:API specification](https://jsonapi.org/format/#errors): ``` { "errors": [ { "status": "404", "code": "not_found", "title": "Page Not Found", "detail": "The requested resource was not found", "source": { "parameter": "user_id" }, "meta": { "user_id": 123, "timestamp": "2025-12-20T12:00:00Z" } } ] } ``` ## Custom Error Handlers ### Application-level Error Handler Provide a custom error handler for TemplateFiles: ``` from starlette.requests import Request from starlette.responses import Response, JSONResponse from starlette_templates.templating import TemplateFiles import logging logger = logging.getLogger(__name__) async def custom_error_handler(request: Request, exc: Exception) -> Response: # Log the error logger.error( f"Error processing {request.url}: {exc}", exc_info=exc, extra={ "url": str(request.url), "method": request.method, "client": request.client.host if request.client else None, } ) # Send notification for critical errors if isinstance(exc, CriticalError): await send_alert_notification(exc) # Return custom response if "application/json" in request.headers.get("accept", ""): return JSONResponse( {"error": "Something went wrong"}, status_code=500 ) return TemplateResponse( "error.html", context={ "status_code": 500, "error_title": "Internal Server Error", "error_message": "An unexpected error occurred", }, status_code=500 ) templates = TemplateFiles( error_handler=custom_error_handler ) ``` ### Route-level Error Handling Handle errors within specific routes: ``` from starlette.routing import Route from starlette.requests import Request from starlette_templates.forms import FormModel from starlette_templates.responses import TemplateResponse from starlette_templates.errors import AppException, ErrorCode class User(FormModel): user_id: int async def user_profile(request: Request) -> TemplateResponse: user = await User.from_request(request) try: # Fetch user profile, may raise exceptions UserNotFound, PermissionDenied user = await get_user(user.user_id) return TemplateResponse("profile.html", context={"user": user}) except UserNotFound: raise AppException( detail=f"User {user.user_id} not found", status_code=404, code=ErrorCode.NOT_FOUND, ) except PermissionDenied: raise AppException( detail="You don't have permission to view this profile", status_code=403, code=ErrorCode.FORBIDDEN, ) app = Starlette( routes=[ Route("/users/{user_id}", user_profile), ] ) ``` ## Form Validation Errors Handle Pydantic validation errors from forms: ``` from pydantic import ValidationError from starlette.requests import Request from starlette_templates.responses import TemplateResponse from starlette_templates.forms import FormModel async def signup(request: Request) -> TemplateResponse: try: form = await SignupForm.from_request(request, raise_on_error=True) if form.is_valid(request): user = await create_user(form) return TemplateResponse("success.html", context={"user": user}) except ValidationError as e: # Return form with validation errors return TemplateResponse( "signup.html", context={ "errors": e.errors(), "error_message": "Please correct the errors below", }, status_code=400 ) # Show empty form for GET requests return TemplateResponse("signup.html", context={"form": SignupForm()}) ``` ## Error Page Context Processors Add custom context to error pages: ``` from starlette.requests import Request async def add_error_context(request: Request) -> dict: return { "support_email": "support@example.com", "status_page_url": "https://status.example.com", "request_id": request.headers.get("X-Request-ID"), } templates = TemplateFiles( context_processors=[add_error_context] ) ``` Use in error templates: ```

{{ status_code }}

{{ error_message }}

Need help? Contact us at {{ support_email }}

Request ID: {{ request_id }}

Check service status: {{ status_page_url }}

``` # Reference # API Reference This page contains the complete API reference for Starlette Templates. ## TemplateRouter ``` TemplateRouter( *, directory: PathLike | None = None, extensions: list[str] | None = None, index_files: list[str] | None = None, check_dir: bool = True, follow_symlink: bool = False, cache_max_age: int = 3600, gzip_min_size: int = 500, context_processors: list[ Callable[[Request], Awaitable[dict[str, Any]]] | Route ] | None = None, jinja_env: Environment | None = None, debug: bool = False, error_handler: Callable[ [Request, Exception], Awaitable[Response] ] | None = None, ) ``` ASGI app for routing HTTP requests to Jinja2 templates with automatic resolution. Routes incoming requests to templates with Jinja2 rendering, HTTP caching, gzip compression, and automatic file resolution (extension fallbacks, index files). Architecture with two modes of operation: The system supports two distinct operational modes. When initialized with a directory parameter, it uses filesystem lookups to find templates through direct file access with stat and permissions checking, which is useful when templates are in a known directory. Alternatively, when initialized without a directory parameter (directory=None), it uses jinja_env loaders such as PackageLoader or FileSystemLoader, allowing templates to be served from locations defined in JinjaMiddleware, providing a single source of truth for template locations without configuration duplication. Flow 1. JinjaMiddleware defines template loaders 1. Mount TemplateRouter() without directory parameter 1. Request comes in: /forms 1. TemplateRouter.get_response() called 1. Delegates to render_template_from_loaders() 1. Gets jinja_env from request.state 1. Tries candidates: forms.html, forms.htm, etc. 1. Finds template using jinja_env.loader.get_source() 1. Renders template with context {"request": request} 1. Returns ContentResponse with caching headers Context processors are callables that accept a Request and return a dict of additional context variables to be merged into the template context. Parameters: - **`directory`** (`PathLike | None`, default: `None` ) – Optional path to templates directory (None = use jinja_env loaders) - **`extensions`** (`list[str] | None`, default: `None` ) – List of allowed file extensions (default: [".html", ".htm", ".jinja", ".jinja2"]) - **`index_files`** (`list[str] | None`, default: `None` ) – List of index filenames to search for (default: ["index.html", "index.htm"]) - **`check_dir`** (`bool`, default: `True` ) – Whether to check if directory exists on init - **`follow_symlink`** (`bool`, default: `False` ) – Whether to follow symlinks in directory - **`cache_max_age`** (`int`, default: `3600` ) – Max age for HTTP caching (in seconds) - **`gzip_min_size`** (`int`, default: `500` ) – Minimum size (in bytes) to apply gzip compression - **`context_processors`** (`list[Callable[[Request], Awaitable[dict[str, Any]]] | Route] | None`, default: `None` ) – List of async callables to add to template context - **`jinja_env`** (`Environment | None`, default: `None` ) – Optional Jinja2 Environment (falls back to request.state.jinja_env) - **`debug`** (`bool`, default: `False` ) – Whether to enable debug mode (shows detailed error pages) - **`error_handler`** (`Callable[[Request, Exception], Awaitable[Response]] | None`, default: `None` ) – Callable to handle exceptions and return custom responses (uses built-in handler if None) Example ``` app = Starlette( routes=[ # Override specific paths, like / which would map to index.html Route("/", homepage, name="home"), # Catch-all for templates Mount("/", TemplateRouter()), ], middleware=[ Middleware( JinjaMiddleware, template_loaders=[PackageLoader("myapp", "templates")] ) ] ) ``` ## StaticFiles ``` StaticFiles( *, directories: List[PathLike] | None = None, packages: list[str | tuple[str, str]] | None = None, html: bool = False, check_dir: bool = True, follow_symlink: bool = False, ) ``` Enhanced StaticFiles with gzip compression, caching, and multi-directory support. Features: - Automatic gzip handling for .gz files with proper Content-Encoding headers - Strong HTTP caching (ETag, Cache-Control, Last-Modified) - Multi-directory fallback support (searches directories in priority order) When a .gz file is requested directly, this class automatically: 1. Strips the .gz extension from the Content-Type detection 1. Sets Content-Encoding: gzip header 1. Adds Vary: Accept-Encoding header 1. Adds strong caching headers (Cache-Control: public, max-age=31536000, immutable) When multiple directories are provided, files are searched in priority order, allowing application static files to override framework static files. Parameters: - **`directories`** (`List[PathLike] | None`, default: `None` ) – Directory paths to search (in priority order) - **`packages`** (`list[str | tuple[str, str]] | None`, default: `None` ) – Package resources to serve - **`html`** (`bool`, default: `False` ) – Enable HTML mode - **`check_dir`** (`bool`, default: `True` ) – Whether to check if directories exist on initialization - **`follow_symlink`** (`bool`, default: `False` ) – Whether to follow symbolic links Example with single directory ``` static = StaticFiles(directories=["static"]) ``` Example with multiple directories (priority-based override): ``` static = StaticFiles( directories=[Path("app/static"), Path("framework/static")], packages=[("myapp", "static")], ) ``` Example serving pre-compressed files ``` ``` The browser receives: - Content-Type: text/css - Content-Encoding: gzip - Cache-Control: public, max-age=31536000, immutable - And automatically decompresses the content ## TemplateResponse ``` TemplateResponse( template_name: str, context: dict[str, Any] | None = None, status_code: int = 200, headers: Mapping[str, str] | None = None, media_type: str | None = None, background: Any = None, ) ``` HTMLResponse subclass that renders Jinja2 templates. This class provides a convenient way to render Jinja2 templates from route handlers without manually accessing the Jinja2 environment. The template will have access to standard context variables: - request: The Starlette Request object - url_for: Function to generate URLs for named routes - absurl: Function to generate absolute URLs - base_url: The base URL of the application - url: Function to build full URLs from paths - jsonify: Function to serialize data to JSON for embedding in HTML - Plus any custom context variables provided Parameters: - **`template_name`** (`str`) – The name of the Jinja2 template to render - **`request`** – The Starlette Request object (required for accessing app's Jinja2 env) - **`context`** (`dict[str, Any] | None`, default: `None` ) – The context data to pass to the template - **`status_code`** (`int`, default: `200` ) – The HTTP status code for the response - **`headers`** (`Mapping[str, str] | None`, default: `None` ) – Additional headers for the response - **`media_type`** (`str | None`, default: `None` ) – The media type for the response - **`background`** (`Any`, default: `None` ) – A background task to run after the response is sent Example ``` from starlette_templates import Application, TemplateResponse, route, model_from_request from starlette.requests import Request from pydantic import BaseModel class UserModel(BaseModel): id: int name: str @route("/users/{user_id}") async def user_profile(request: Request) -> TemplateResponse: user = model_from_request(request, UserModel) return TemplateResponse( "user_profile.html", context={"user": user}, ) app = Application( routes=[user_profile], package_name="myapp", ) ``` ## JinjaMiddleware ``` JinjaMiddleware( app: ASGIApp, *, state_key: str = "jinja_env", include_websocket: bool = True, template_loaders: list[ Union[PackageLoader, FileSystemLoader] ] | None = None, include_default_loader: bool = True, extra_components: ModuleType | type[ComponentModel] | Sequence[ModuleType | type[ComponentModel]] | None = None, jinja_globals: dict[str, Any] | None = None, jinja_filters: dict[str, Callable] | None = None, ) ``` Middleware to inject Jinja2 environment into request state. This middleware creates a Jinja2 environment with the provided template loaders and makes it available in the request scope under a specified key, by default "jinja_env". This allows downstream components to access the Jinja2 environment for template rendering. Parameters: - **`app`** (`ASGIApp`) – The ASGI application - **`state_key`** (`str`, default: `'jinja_env'` ) – The key under which to store the Jinja2 environment in request state - **`include_websocket`** (`bool`, default: `True` ) – Whether to include WebSocket connections for Jinja2 env injection - **`template_loaders`** (`list[Union[PackageLoader, FileSystemLoader]] | None`, default: `None` ) – List of Jinja2 template loaders to configure the environment. - **`include_default_loader`** (`bool`, default: `True` ) – Include the default PackageLoader for "starlette_templates" - **`component_modules`** – Optional module, ComponentModel class, or sequence of either to auto-register components from. Can be a single module, a single ComponentModel class, or a list containing any mix of modules and ComponentModel classes. Example ``` from starlette.applications import Starlette from starlette_templates.middleware import JinjaMiddleware from jinja2 import PackageLoader, FileSystemLoader from myapp import custom_components # A module with custom components from myapp.components import CustomButton, CustomCard # Individual component classes app = Starlette() app.add_middleware( JinjaMiddleware, template_loaders=[ PackageLoader("myapp", "templates"), FileSystemLoader("custom/templates"), ], include_default_loader=True, # Can pass a single module, a single component, or a list of either component_modules=[custom_components, CustomButton, CustomCard], ) ``` ## FormModel ``` FormModel(**data) ``` Base class for form models with rendering capabilities. Attributes: - **`model_config`** – Configuration for form rendering and behavior Example Define a form model by inheriting from `FormModel` and using form field helpers: ``` import datetime from starlette_templates.forms import FormModel, SelectField, DateField, SubmitButtonField class Filters(FormModel): storefront: str = SelectField( default="ww", choices={"ww": "Worldwide", "us": "United States"}, label="Storefront", ) start_date: datetime.date = DateField( default_factory=lambda: datetime.date.today(), label="Start Date", ) submit: str = SubmitButtonField(text="Submit") ``` Add forms to a specific template using `TemplateResponse` ``` async def overview(request: Request) -> TemplateResponse: form = await Filters.from_request(request) return TemplateResponse("overview.html", {"form": form}) app = Starlette(routes=[ Route("/overview", overview, methods=["GET", "POST"], name="overview") ]) ``` Add forms to all templates using `context_processors` parameter of `TemplateRouter` ``` async def form_context_processor(request: Request) -> dict: form = await Filters.from_request(request) return {"form": form} app = Starlette( routes=[ Mount("/", TemplateRouter( context_processors=[form_context_processor], )), ], middleware=[Middleware(Jinja2TemplatesMiddleware)], ) ``` Or assign forms to specific routes using `Route` in `context_processors` ``` app = Starlette( routes=[ Route( "/", TemplateRouter( context_processors=[ # Only add form to /search route templates Route("/search", form_context_processor) ], ), ), ], middleware=[Middleware(Jinja2TemplatesMiddleware)], ) ``` Methods: - **`from_request`** – Create form instance from request data. - **`is_valid`** – Check if form has no validation errors. If request is provided, - **`render`** – Render a specific field in HTML. This is a Jinja2 template method. - **`has_errors`** – Check if form has validation errors. If name is provided, checks for errors on that specific field. - **`get_field_error`** – Get validation error message for a specific field, if any. - **`get_error_banner_text`** – Get the error banner text from form config. This is text that can be displayed - **`model_dump`** – Override model_dump to exclude fields marked with exclude_from_dump. - **`__call__`** – Render the entire form. Alias for render_form(). ### from_request ``` from_request( request: Request, raise_on_error: bool = True ) -> FormModel ``` Create form instance from request data. This class method parses data from the request (path params, query params, form data, and JSON body) and validates it against the form model and returns an instance of the form model. If validation errors occur, they are either raised as ValidationError or captured in the instance based on the `raise_on_error` flag. If a field with the same name appears in multiple sources, the precedence is as follows: path params < query params < form data < JSON body. For example, if a field is present in both query params and form data, the value from form data will be used. Use the `.is_valid()` method on the returned instance to check if the form data is valid when `raise_on_error` is False. Parameters: - **`request`** (`Request`) – Starlette Request object - **`raise_on_error`** (`bool`, default: `True` ) – If True, raises ValidationError. If False, captures errors in instance. Returns: - `FormModel` – FormModel instance (with errors if raise_on_error=False) ### is_valid ``` is_valid(request: Request | None = None) -> bool ``` Check if form has no validation errors. If request is provided, checks for both HTTP method and field errors to determine validity. If `request` is provided, the form is only considered valid if the request method is the same as the form's configured method (default: POST) and there are no field errors. This means `is_valid()` will return False for GET requests even if there are no field errors and will only return True for requests with the correct method and no field errors, which is the typical behavior for form submissions. Parameters: - **`request`** (`Request | None`, default: `None` ) – Starlette Request object (optional) Returns: - `bool` – True if form is valid, False otherwise Example ``` async def search_page(request: Request): form = await SearchForm.from_request(request) if form.is_valid(request): # Process valid form submission ... return TemplateResponse("search.html", {"form": form}) ``` ### render ``` render( ctx: Context, field_name: str, request: Request | None = None, ) -> Markup ``` Render a specific field in HTML. This is a Jinja2 template method. This calls the component's render method with appropriate parameters defined in the form. If the request parameter is not provided, it will attempt to get it from the template context. If the request is still not found, an error will be raised. Parameters: - **`ctx`** (`Context`) – Jinja2 context - **`field_name`** (`str`) – Name of the field to render - **`request`** (`Request | None`, default: `None` ) – Starlette request object Returns: - `Markup` – Markup of the rendered component HTML Example In a Jinja2 templates, use sync or async to render the field: ``` {{ form.render('start_date', request) }} {{ await form.render('start_date', request) }} ``` You can also omit the request parameter if the template context has a `request` variable: ``` {{ form.render('start_date') }} ``` Raises: - `ValueError` – If the request object is not provided or found in context - `ValueError` – If the field name is not found in the form - `ValueError` – If the field does not have a component_cls defined ### has_errors ``` has_errors(name: str | None = None) -> bool ``` Check if form has validation errors. If name is provided, checks for errors on that specific field. Parameters: - **`name`** (`str | None`, default: `None` ) – Name of the field to check for errors (optional) Returns: - `bool` – True if there are validation errors, False otherwise Example ``` if form.has_errors(): print("Form has validation errors") if form.has_errors('email'): print("Email field has a validation error") ``` You can also use this in Jinja2 templates: ``` {% if form.has_errors() %}
{{ form.get_error_banner_text() }}
{% endif %} {% if form.has_errors('email') %}
{{ form.get_field_error('email') }}
{% endif %} ``` ### get_field_error ``` get_field_error( name: str, default: str | None = None ) -> str | None ``` Get validation error message for a specific field, if any. Parameters: - **`name`** (`str`) – Name of the field - **`default`** (`str | None`, default: `None` ) – Default value to return if no error exists for the field Returns: - `str | None` – The error message for the field, or the default value if no error exists Example ``` error_message = form.get_field_error('email', default='No error') ``` ### get_error_banner_text ``` get_error_banner_text() -> str ``` Get the error banner text from form config. This is text that can be displayed at the top of the form when there are validation errors. ### model_dump ``` model_dump(**kwargs) -> dict[str, Any] ``` Override model_dump to exclude fields marked with exclude_from_dump. ### __call__ ``` __call__( ctx: Context, request: Request | None = None, *, id: str | None = None, action: str | None = None, method: str | None = None, enctype: str | None = None, classes: list[str] | None = None, novalidate: bool = False, ) -> Markup ``` Render the entire form. Alias for render_form(). Example ``` {{ form() }} {{ form(action='/submit', classes=['my-form'], enctype='multipart/form-data') }} ``` ## TextField ``` TextField( default: Any = PydanticUndefined, *, min_length: int | None = None, max_length: int | None = None, regex: str | None = None, name: Any = PydanticUndefined, id: Any = PydanticUndefined, label: Any = PydanticUndefined, title: str = PydanticUndefined, placeholder: str = PydanticUndefined, description: str = PydanticUndefined, required: bool = False, hidden: bool = False, disabled: bool = False, readonly: bool = False, col_class: str | None = None, **kwargs: Any, ) -> Any ``` Create a string form field with validation and formatting options. Parameters: - **`default`** (`Any`, default: `PydanticUndefined` ) – Default value for the field - **`min_length`** (`int | None`, default: `None` ) – Minimum length validation - **`max_length`** (`int | None`, default: `None` ) – Maximum length validation - **`regex`** (`str | None`, default: `None` ) – Regular expression pattern for validation - **`name`** (`Any`, default: `PydanticUndefined` ) – Field name attribute - **`id`** (`Any`, default: `PydanticUndefined` ) – Field id attribute - **`label`** (`Any`, default: `PydanticUndefined` ) – Field label for display - **`title`** (`str`, default: `PydanticUndefined` ) – Field title - **`placeholder`** (`str`, default: `PydanticUndefined` ) – Placeholder text for input - **`description`** (`str`, default: `PydanticUndefined` ) – Field description - **`required`** (`bool`, default: `False` ) – Whether field is required - **`hidden`** (`bool`, default: `False` ) – Whether field is hidden - **`disabled`** (`bool`, default: `False` ) – Whether field is disabled - **`readonly`** (`bool`, default: `False` ) – Whether field is readonly - **`col_class`** (`str | None`, default: `None` ) – Bootstrap column class for horizontal layout (e.g., "col-md-4") - **`**kwargs`** (`Any`, default: `{}` ) – Additional field attributes Returns: - `Any` – A Pydantic Field configured for string validation ## TextAreaField ``` TextAreaField( default: Any = PydanticUndefined, *, min_length: int | None = None, max_length: int | None = None, rows: int = 3, name: Any = PydanticUndefined, id: Any = PydanticUndefined, label: Any = PydanticUndefined, title: str = PydanticUndefined, placeholder: str = PydanticUndefined, description: str = PydanticUndefined, required: bool = False, disabled: bool = False, readonly: bool = False, **kwargs: Any, ) -> Any ``` Create a textarea form field for multi-line text input. Parameters: - **`default`** (`Any`, default: `PydanticUndefined` ) – Default value for the field - **`min_length`** (`int | None`, default: `None` ) – Minimum length validation - **`max_length`** (`int | None`, default: `None` ) – Maximum length validation - **`rows`** (`int`, default: `3` ) – Number of rows for textarea - **`name`** (`Any`, default: `PydanticUndefined` ) – Field name attribute - **`id`** (`Any`, default: `PydanticUndefined` ) – Field id attribute - **`label`** (`Any`, default: `PydanticUndefined` ) – Field label for display - **`title`** (`str`, default: `PydanticUndefined` ) – Field title - **`placeholder`** (`str`, default: `PydanticUndefined` ) – Placeholder text for textarea - **`description`** (`str`, default: `PydanticUndefined` ) – Field description - **`required`** (`bool`, default: `False` ) – Whether field is required - **`disabled`** (`bool`, default: `False` ) – Whether field is disabled - **`readonly`** (`bool`, default: `False` ) – Whether field is readonly - **`**kwargs`** (`Any`, default: `{}` ) – Additional field attributes Returns: - `Any` – A Pydantic Field configured for textarea input ## EmailField ``` EmailField( default: Any = PydanticUndefined, *, max_length: int | None = None, name: Any = PydanticUndefined, id: Any = PydanticUndefined, label: Any = PydanticUndefined, title: str = PydanticUndefined, placeholder: str = PydanticUndefined, description: str = PydanticUndefined, required: bool = False, hidden: bool = False, disabled: bool = False, readonly: bool = False, **kwargs: Any, ) -> Any ``` Create an email form field with email validation. Parameters: - **`default`** (`Any`, default: `PydanticUndefined` ) – Default value for the field - **`max_length`** (`int | None`, default: `None` ) – Maximum length validation - **`name`** (`Any`, default: `PydanticUndefined` ) – Field name attribute - **`id`** (`Any`, default: `PydanticUndefined` ) – Field id attribute - **`label`** (`Any`, default: `PydanticUndefined` ) – Field label for display - **`title`** (`str`, default: `PydanticUndefined` ) – Field title - **`placeholder`** (`str`, default: `PydanticUndefined` ) – Placeholder text for input - **`description`** (`str`, default: `PydanticUndefined` ) – Field description - **`required`** (`bool`, default: `False` ) – Whether field is required - **`hidden`** (`bool`, default: `False` ) – Whether field is hidden - **`disabled`** (`bool`, default: `False` ) – Whether field is disabled - **`readonly`** (`bool`, default: `False` ) – Whether field is readonly - **`**kwargs`** (`Any`, default: `{}` ) – Additional field attributes Returns: - `Any` – A Pydantic Field configured for email validation ## IntegerField ``` IntegerField( default: Any = PydanticUndefined, *, gt: int | None = None, ge: int | None = None, lt: int | None = None, le: int | None = None, multiple_of: int | None = None, name: Any = PydanticUndefined, id: Any = PydanticUndefined, label: Any = PydanticUndefined, title: str = PydanticUndefined, placeholder: str = PydanticUndefined, description: str = PydanticUndefined, required: bool = False, hidden: bool = False, disabled: bool = False, readonly: bool = False, **kwargs: Any, ) -> Any ``` Create an integer form field with numeric validation options. Parameters: - **`default`** (`Any`, default: `PydanticUndefined` ) – Default value for the field - **`gt`** (`int | None`, default: `None` ) – Greater than validation - **`ge`** (`int | None`, default: `None` ) – Greater than or equal validation - **`lt`** (`int | None`, default: `None` ) – Less than validation - **`le`** (`int | None`, default: `None` ) – Less than or equal validation - **`multiple_of`** (`int | None`, default: `None` ) – Multiple of validation - **`name`** (`Any`, default: `PydanticUndefined` ) – Field name attribute - **`id`** (`Any`, default: `PydanticUndefined` ) – Field id attribute - **`label`** (`Any`, default: `PydanticUndefined` ) – Field label for display - **`title`** (`str`, default: `PydanticUndefined` ) – Field title - **`placeholder`** (`str`, default: `PydanticUndefined` ) – Placeholder text for input - **`description`** (`str`, default: `PydanticUndefined` ) – Field description - **`required`** (`bool`, default: `False` ) – Whether field is required - **`hidden`** (`bool`, default: `False` ) – Whether field is hidden - **`disabled`** (`bool`, default: `False` ) – Whether field is disabled - **`readonly`** (`bool`, default: `False` ) – Whether field is readonly - **`**kwargs`** (`Any`, default: `{}` ) – Additional field attributes Returns: - `Any` – A Pydantic Field configured for integer validation ## FloatField ``` FloatField( default: Any = PydanticUndefined, *, gt: float | None = None, ge: float | None = None, lt: float | None = None, le: float | None = None, multiple_of: float | None = None, name: Any = PydanticUndefined, id: Any = PydanticUndefined, label: Any = PydanticUndefined, title: str = PydanticUndefined, placeholder: str = PydanticUndefined, description: str = PydanticUndefined, required: bool = False, hidden: bool = False, disabled: bool = False, readonly: bool = False, **kwargs: Any, ) -> Any ``` Create a float form field with numeric validation options. Parameters: - **`default`** (`Any`, default: `PydanticUndefined` ) – Default value for the field - **`gt`** (`float | None`, default: `None` ) – Greater than validation - **`ge`** (`float | None`, default: `None` ) – Greater than or equal validation - **`lt`** (`float | None`, default: `None` ) – Less than validation - **`le`** (`float | None`, default: `None` ) – Less than or equal validation - **`multiple_of`** (`float | None`, default: `None` ) – Multiple of validation - **`name`** (`Any`, default: `PydanticUndefined` ) – Field name attribute - **`id`** (`Any`, default: `PydanticUndefined` ) – Field id attribute - **`label`** (`Any`, default: `PydanticUndefined` ) – Field label for display - **`title`** (`str`, default: `PydanticUndefined` ) – Field title - **`placeholder`** (`str`, default: `PydanticUndefined` ) – Placeholder text for input - **`description`** (`str`, default: `PydanticUndefined` ) – Field description - **`required`** (`bool`, default: `False` ) – Whether field is required - **`hidden`** (`bool`, default: `False` ) – Whether field is hidden - **`disabled`** (`bool`, default: `False` ) – Whether field is disabled - **`readonly`** (`bool`, default: `False` ) – Whether field is readonly - **`**kwargs`** (`Any`, default: `{}` ) – Additional field attributes Returns: - `Any` – A Pydantic Field configured for float validation ## CheckboxField ``` CheckboxField( default: Any = PydanticUndefined, *, name: Any = PydanticUndefined, id: Any = PydanticUndefined, label: Any = PydanticUndefined, title: str = PydanticUndefined, description: str = PydanticUndefined, value: str = "1", required: bool = False, disabled: bool = False, inline: bool = False, **kwargs: Any, ) -> Any ``` Create a checkbox form field for boolean input. Parameters: - **`default`** (`Any`, default: `PydanticUndefined` ) – Default value for the field (True/False) - **`name`** (`Any`, default: `PydanticUndefined` ) – Field name attribute - **`id`** (`Any`, default: `PydanticUndefined` ) – Field id attribute - **`label`** (`Any`, default: `PydanticUndefined` ) – Field label for display - **`title`** (`str`, default: `PydanticUndefined` ) – Field title - **`description`** (`str`, default: `PydanticUndefined` ) – Field description - **`value`** (`str`, default: `'1'` ) – Value submitted when checkbox is checked - **`required`** (`bool`, default: `False` ) – Whether field is required - **`disabled`** (`bool`, default: `False` ) – Whether field is disabled - **`inline`** (`bool`, default: `False` ) – Whether to display inline - **`**kwargs`** (`Any`, default: `{}` ) – Additional field attributes Returns: - `Any` – A Pydantic Field configured for checkbox input ## SelectField ``` SelectField( default: Any = PydanticUndefined, default_factory: Callable[[], Any] | None = PydanticUndefined, *, choices: Dict[Any, str] | List[Any] | List[Choice] | None = None, choices_factory: Callable[ [], Dict[Any, str] | List[Any] | List[Choice] ] | None = None, multiple: bool = False, size: int | None = None, name: Any = PydanticUndefined, id: Any = PydanticUndefined, label: Any = PydanticUndefined, title: str = PydanticUndefined, placeholder: str = PydanticUndefined, required: bool = False, description: str = PydanticUndefined, disabled: bool = False, search_enabled: bool = True, search_placeholder: str = "Type to search", remove_button: bool = True, max_item_count: int | None = None, col_class: str | None = None, **kwargs: Any, ) -> Any ``` Create a select form field with options. Parameters: - **`default`** (`Any`, default: `PydanticUndefined` ) – Default value for the field - **`choices`** (`Dict[Any, str] | List[Any] | List[Choice] | None`, default: `None` ) – Dict of {value: label}, list of values where value=label, or list of Choice objects. Order is preserved in all formats. - **`choices_factory`** (`Callable[[], Dict[Any, str] | List[Any] | List[Choice]] | None`, default: `None` ) – Callable that returns a dict, list, or list of Choice objects - **`multiple`** (`bool`, default: `False` ) – Whether multiple selections are allowed (uses ChoicesSelect when True) - **`size`** (`int | None`, default: `None` ) – Number of visible options (for Select component) - **`name`** (`Any`, default: `PydanticUndefined` ) – Field name attribute - **`id`** (`Any`, default: `PydanticUndefined` ) – Field id attribute - **`label`** (`Any`, default: `PydanticUndefined` ) – Field label for display - **`title`** (`str`, default: `PydanticUndefined` ) – Field title - **`placeholder`** (`str`, default: `PydanticUndefined` ) – Placeholder text (for ChoicesSelect) - **`required`** (`bool`, default: `False` ) – Whether field is required - **`description`** (`str`, default: `PydanticUndefined` ) – Field description - **`hidden`** – Whether field is hidden - **`disabled`** (`bool`, default: `False` ) – Whether field is disabled - **`search_enabled`** (`bool`, default: `True` ) – Enable search functionality (for ChoicesSelect) - **`search_placeholder`** (`str`, default: `'Type to search'` ) – Search placeholder text (for ChoicesSelect) - **`remove_button`** (`bool`, default: `True` ) – Show remove button for selected items (for ChoicesSelect) - **`max_item_count`** (`int | None`, default: `None` ) – Max items that can be selected (for ChoicesSelect with multiple) - **`col_class`** (`str | None`, default: `None` ) – Bootstrap column class for horizontal layout (e.g., "col-md-4") - **`**kwargs`** (`Any`, default: `{}` ) – Additional field attributes Returns: - `Any` – A Pydantic Field configured for select input Example Single select with dict choices (different value and label) ``` status = SelectField( choices={"active": "Active", "inactive": "Inactive", "pending": "Pending"}, label="Status" ) ``` Single select with list choices (same value and label) ``` fruits = SelectField( choices=["Apple", "Orange", "Banana"], label="Fruit" ) ``` Single select with Choice objects ``` country = SelectField( choices=[ Choice(value="us", label="United States"), Choice(value="ca", label="Canada"), Choice(value="uk", label="United Kingdom"), ], label="Country" ) ``` Multiple select (uses ChoicesSelect component) ``` interests = SelectField( choices={"sports": "Sports", "music": "Music", "reading": "Reading"}, multiple=True, label="Interests" ) ``` Dynamic choices using a factory function ``` def get_country_choices(): return {"us": "United States", "uk": "United Kingdom", "ca": "Canada"} country = SelectField( choices_factory=get_country_choices, label="Country" ) ``` ## DateField ``` DateField( default: Any = PydanticUndefined, default_factory: Callable[[], Any] | None = PydanticUndefined, *, min: Any = PydanticUndefined, max: Any = PydanticUndefined, name: Any = PydanticUndefined, id: Any = PydanticUndefined, label: Any = PydanticUndefined, title: str = PydanticUndefined, placeholder: str = "Select date", description: str = PydanticUndefined, required: bool = False, disabled: bool = False, mode: str = "single", enable_time: bool = False, time_24hr: bool = True, date_format: str = "Y-m-d", inline: bool = False, col_class: str | None = None, **kwargs: Any, ) -> Any ``` Create a date form field with Flatpickr date picker. This field renders using the Flatpickr date picker component with advanced date selection features. Parameters: - **`default`** (`Any`, default: `PydanticUndefined` ) – Default value for the field (can be date, datetime, or string) - **`default_factory`** (`Callable[[], Any] | None`, default: `PydanticUndefined` ) – Factory function to generate default value - **`min`** (`Any`, default: `PydanticUndefined` ) – Minimum allowed date (date, datetime, or ISO string) - **`max`** (`Any`, default: `PydanticUndefined` ) – Maximum allowed date (date, datetime, or ISO string) - **`name`** (`Any`, default: `PydanticUndefined` ) – Field name attribute - **`id`** (`Any`, default: `PydanticUndefined` ) – Field id attribute - **`label`** (`Any`, default: `PydanticUndefined` ) – Field label for display - **`title`** (`str`, default: `PydanticUndefined` ) – Field title - **`placeholder`** (`str`, default: `'Select date'` ) – Placeholder text for input - **`description`** (`str`, default: `PydanticUndefined` ) – Field description - **`required`** (`bool`, default: `False` ) – Whether field is required - **`hidden`** – Whether field is hidden - **`disabled`** (`bool`, default: `False` ) – Whether field is disabled - **`mode`** (`str`, default: `'single'` ) – Selection mode: "single", "multiple", or "range" - **`enable_time`** (`bool`, default: `False` ) – Enable time picker - **`time_24hr`** (`bool`, default: `True` ) – Use 24-hour time format - **`date_format`** (`str`, default: `'Y-m-d'` ) – Date format string (Flatpickr format) - **`inline`** (`bool`, default: `False` ) – Display calendar inline - **`col_class`** (`str | None`, default: `None` ) – Bootstrap column class for horizontal layout (e.g., "col-md-4") - **`**kwargs`** (`Any`, default: `{}` ) – Additional field attributes Returns: - `Any` – A Pydantic Field configured for date input Example Basic date field ``` birth_date = DateField(label="Birth Date", required=True) ``` Date field with range constraints ``` event_date = DateField( label="Event Date", min="2024-01-01", max="2024-12-31" ) ``` Date field with default value ``` from datetime import date appointment_date = DateField( label="Appointment Date", default=date.today(), required=True ) ``` Date range picker ``` date_range = DateField( label="Date Range", mode="range" ) ``` Date and time picker ``` appointment = DateField( label="Appointment", enable_time=True, date_format="Y-m-d H:i" ) ``` ## HiddenField ``` HiddenField( default: Any = PydanticUndefined, *, name: Any = PydanticUndefined, id: Any = PydanticUndefined, title: str = PydanticUndefined, description: str = PydanticUndefined, **kwargs: Any, ) -> Any ``` Create a hidden form field for passing data without user interaction. Parameters: - **`default`** (`Any`, default: `PydanticUndefined` ) – Default value for the field - **`name`** (`Any`, default: `PydanticUndefined` ) – Field name attribute - **`id`** (`Any`, default: `PydanticUndefined` ) – Field id attribute - **`title`** (`str`, default: `PydanticUndefined` ) – Field title - **`description`** (`str`, default: `PydanticUndefined` ) – Field description - **`**kwargs`** (`Any`, default: `{}` ) – Additional field attributes Returns: - `Any` – A Pydantic Field configured for hidden input Example Basic hidden field ``` csrf_token = HiddenField(default="abc123") ``` Hidden field with custom name ``` user_id = HiddenField(default=42, name="user_id") ``` Hidden field that excludes from validation ``` session_key = HiddenField( default="session_xyz", exclude_from_validation=True ) ``` ## SubmitButtonField ``` SubmitButtonField( text: str = "Submit", *, button_type: str = "submit", name: Any = PydanticUndefined, id: Any = PydanticUndefined, classes: list[str] | None = None, disabled: bool = False, form: Any = PydanticUndefined, formaction: Any = PydanticUndefined, formenctype: Any = PydanticUndefined, formmethod: Any = PydanticUndefined, formnovalidate: bool = False, formtarget: Any = PydanticUndefined, col_class: str | None = None, **kwargs: Any, ) -> Any ``` Create a submit button form field with customization options. Parameters: - **`text`** (`str`, default: `'Submit'` ) – Button text/label - **`button_type`** (`str`, default: `'submit'` ) – Button type (submit, button, reset) - **`name`** (`Any`, default: `PydanticUndefined` ) – Button name attribute - **`id`** (`Any`, default: `PydanticUndefined` ) – Button id attribute - **`classes`** (`list[str] | None`, default: `None` ) – CSS classes for the button - **`disabled`** (`bool`, default: `False` ) – Whether button is disabled - **`form`** (`Any`, default: `PydanticUndefined` ) – Form element the button is associated with - **`formaction`** (`Any`, default: `PydanticUndefined` ) – URL where form data is sent when button is clicked - **`formenctype`** (`Any`, default: `PydanticUndefined` ) – How form data is encoded when button is clicked - **`formmethod`** (`Any`, default: `PydanticUndefined` ) – HTTP method when button is clicked - **`formnovalidate`** (`bool`, default: `False` ) – Whether form validation is bypassed when button is clicked - **`formtarget`** (`Any`, default: `PydanticUndefined` ) – Where to display response when button is clicked - **`col_class`** (`str | None`, default: `None` ) – Bootstrap column class for horizontal layout (e.g., "col-md-4") - **`template`** – Jinja2 template for rendering the field - **`**kwargs`** (`Any`, default: `{}` ) – Additional button attributes Returns: - `Any` – A Pydantic Field configured for submit button Example Basic submit button ``` submit = SubmitButtonField("Save Changes") ``` Customized submit button ``` submit = SubmitButtonField( text="Create Account", classes=["btn", "btn-primary", "btn-lg"], id="create-account-btn" ) ``` Button with form attributes ``` delete_btn = SubmitButtonField( text="Delete", button_type="submit", formmethod="DELETE", formnovalidate=True, classes=["btn", "btn-danger"] ) ``` ## TagField ``` TagField( default: Any = PydanticUndefined, *, separator: str = ",", placeholder: str = "Enter tags separated by commas", max_tags: int | None = None, tag_pattern: str | None = None, badge_class: str = "badge bg-primary text-white", show_validation: bool = True, name: Any = PydanticUndefined, id: Any = PydanticUndefined, label: Any = PydanticUndefined, title: str = PydanticUndefined, description: str = PydanticUndefined, required: bool = False, hidden: bool = False, disabled: bool = False, **kwargs: Any, ) -> Any ``` Create a tag input form field for comma-separated values. This field renders as a text input with live tag visualization using Bootstrap badges. It supports tag validation, max tag limits, and custom styling. Parameters: - **`default`** (`Any`, default: `PydanticUndefined` ) – Default value for the field (can be list or comma-separated string) - **`separator`** (`str`, default: `','` ) – Character used to separate tags (default: comma) - **`placeholder`** (`str`, default: `'Enter tags separated by commas'` ) – Placeholder text for the input field - **`max_tags`** (`int | None`, default: `None` ) – Maximum number of tags allowed (None for unlimited) - **`tag_pattern`** (`str | None`, default: `None` ) – Regex pattern for individual tag validation - **`badge_class`** (`str`, default: `'badge bg-primary text-white'` ) – Bootstrap badge CSS classes for tag display - **`show_validation`** (`bool`, default: `True` ) – Whether to show validation styling (is-valid/is-invalid) - **`name`** (`Any`, default: `PydanticUndefined` ) – Field name attribute - **`id`** (`Any`, default: `PydanticUndefined` ) – Field id attribute - **`label`** (`Any`, default: `PydanticUndefined` ) – Field label for display - **`title`** (`str`, default: `PydanticUndefined` ) – Field title - **`description`** (`str`, default: `PydanticUndefined` ) – Field description - **`required`** (`bool`, default: `False` ) – Whether field is required - **`template`** – Jinja2 template for rendering the field - **`**kwargs`** (`Any`, default: `{}` ) – Additional field attributes Returns: - `Any` – A Pydantic Field configured for tag input Example Basic tag field ``` tags = TagField(label="Tags", default=["python", "javascript"]) ``` Tag field with custom separator ``` keywords = TagField( label="Keywords", separator=";", placeholder="Enter keywords separated by semicolons" ) ``` Limited tag field with custom styling ``` categories = TagField( label="Categories", max_tags=5, badge_class="badge bg-secondary text-white", required=True ) ``` Tag field without validation styling ``` draft_tags = TagField( label="Draft Tags", show_validation=False, badge_class="badge border border-primary text-primary" ) ``` ## model_from_request ``` model_from_request(request: Request, model: Type[T]) -> T ``` ``` model_from_request( request: Request, model: None = None ) -> dict ``` ``` model_from_request( request: Request, model: Type[T] | None = None ) -> T | dict ``` Parse and validate request body against a Pydantic model. Loads data from path params, query params, and request body (JSON or form), in that order, overriding earlier values. Form fields that allow multiple values (like multi-select) are handled appropriately. This function can be used to populate a Pydantic model instance from request data, or return a dict of parameters if no model is provided. Parameters: - **`request`** (`Request`) – Starlette Request object - **`model`** (`Type[T] | None`, default: `None` ) – Pydantic model class to validate against (optional) Returns: - `T | dict` – An instance of the Pydantic model if provided, otherwise a dict of parameters. ## AppException ``` AppException( detail: str, status_code: int = 400, code: ErrorCode | str | None = None, source: ErrorSource | None = None, meta: dict[str, Any] | None = None, ) ``` Base exception for all application errors with JSON:API compliance. This exception provides structured error information that can be rendered as either JSON (for API requests) or HTML (for browser requests). Parameters: - **`detail`** (`str`) – Human-readable description of the error - **`status_code`** (`int`, default: `400` ) – HTTP status code (default: 400) - **`code`** (`ErrorCode | str | None`, default: `None` ) – Machine-readable error code - **`source`** (`ErrorSource | None`, default: `None` ) – Location of the error (JSON pointer, parameter name, etc.) - **`meta`** (`dict[str, Any] | None`, default: `None` ) – Additional metadata about the error Example ``` raise AppException( detail="User with email 'john@example.com' already exists", status_code=409, code=ErrorCode.DUPLICATE_RESOURCE, source=ErrorSource(parameter="email"), meta={"email": "john@example.com"} ) ``` ## ErrorCode Error codes for API responses following JSON:API specification. Attributes: - **`INTERNAL_ERROR`** – Internal server error not caused by client request. - **`INVALID_REQUEST`** – The request is malformed or contains invalid parameters. - **`INVALID_PARAMETER`** – A specific parameter in the request is invalid. - **`NOT_FOUND`** – The requested resource could not be found. - **`VALIDATION_ERROR`** – The request data failed validation checks. - **`UNAUTHORIZED`** – Authentication is required and has failed or not been provided. - **`FORBIDDEN`** – Access to the requested resource is forbidden. - **`METHOD_NOT_ALLOWED`** – The HTTP method used is not allowed for the requested resource. ### INTERNAL_ERROR ``` INTERNAL_ERROR = 'internal_error' ``` Internal server error not caused by client request. ### INVALID_REQUEST ``` INVALID_REQUEST = 'invalid_request' ``` The request is malformed or contains invalid parameters. ### INVALID_PARAMETER ``` INVALID_PARAMETER = 'invalid_parameter' ``` A specific parameter in the request is invalid. ### NOT_FOUND ``` NOT_FOUND = 'not_found' ``` The requested resource could not be found. ### VALIDATION_ERROR ``` VALIDATION_ERROR = 'validation_error' ``` The request data failed validation checks. ### UNAUTHORIZED ``` UNAUTHORIZED = 'unauthorized' ``` Authentication is required and has failed or not been provided. ### FORBIDDEN ``` FORBIDDEN = 'forbidden' ``` Access to the requested resource is forbidden. ### METHOD_NOT_ALLOWED ``` METHOD_NOT_ALLOWED = 'method_not_allowed' ``` The HTTP method used is not allowed for the requested resource. ## ErrorSource JSON:API error source object indicating where the error originated. Attributes: - **`pointer`** (`str | None`) – - **`parameter`** (`str | None`) – - **`header`** (`str | None`) – ### pointer ``` pointer: str | None = None ``` ### parameter ``` parameter: str | None = None ``` ### header ``` header: str | None = None ``` ______________________________________________________________________ ## context Jinja2 context builders and custom filters for starlette_templates templates. Functions: - **`jsonify`** – Convert data to a JSON string for embedding in HTML templates. - **`url_for`** – Generate URL for a named route. - **`url`** – Generate URL for a mounted application. - **`absurl`** – Build absolute URL from path. - **`register_jinja_globals_filters`** – Register global functions and filters in the Jinja2 environment. ### jsonify ``` jsonify(data: Any, indent: int | None = None) -> Markup ``` Convert data to a JSON string for embedding in HTML templates. Uses Pydantic's built-in JSON serialization to handle complex data types like datetime, Decimal, and BaseModel instances. Parameters: - **`data`** (`Any`) – The data to serialize to JSON Returns: - `Markup` – A Markup-safe JSON string representation of the data ### url_for ``` url_for( context: dict, name: str, **path_params: Any ) -> str ``` Generate URL for a named route. This function uses @pass_context to access the request from the template context. The request must be added to the context by TemplateRouter or TemplateResponse. Parameters: - **`context`** (`dict`) – Jinja2 context (automatically passed by Jinja2) - **`name`** (`str`) – Route name - **`**path_params`** (`Any`, default: `{}` ) – Path parameters for the route Returns: - `str` – URL path for the named route Example Assuming a route defined as: ``` async def user_profile(request: Request): ... app = Starlette(routes=[ Route("/users/{user_id}/profile", user_profile, name="user_profile"), ]) ``` You can generate the URL by name in a template like this: ``` {{ url_for('user_profile', user_id=123) }} ``` Note This is registered as a Jinja2 global by register_jinja_globals_filters() and can be used directly in templates without importing. ### url ``` url(context: dict, name: str, path: str = '/') -> str ``` Generate URL for a mounted application. This function builds URLs for Starlette Mount instances by name. It's a convenience wrapper around url_for specifically for mounts. Parameters: - **`context`** (`dict`) – Jinja2 context (automatically passed by Jinja2) - **`name`** (`str`) – Mount name - **`path`** (`str`, default: `'/'` ) – Path within the mount (default: "/") Returns: - `str` – URL path for the mounted application Example Assuming a mount defined as: ``` app = Starlette(routes=[ Mount("/static", StaticFiles(directory="static"), name="static_files"), ]) ``` You can generate URLs for files within the mount in a template like this: ``` {{ url('static_files', '/css/style.css') }} ``` Note For named routes, use url_for instead. This is specifically for Mount instances. ### absurl ``` absurl(context: dict, path: str) -> str ``` Build absolute URL from path. Converts a relative path to an absolute URL by combining it with the request's base URL (scheme + host). Parameters: - **`context`** (`dict`) – Jinja2 context (automatically passed by Jinja2) - **`path`** (`str`) – URL path (relative or absolute) Returns: - `str` – Full absolute URL with scheme and host Example Creates an absolute URL for the given path: ``` {{ absurl('/path/to/resource') }} ``` Note Different from url_for which uses named routes. This takes a literal path. ### register_jinja_globals_filters ``` register_jinja_globals_filters( jinja_env: Environment, jinja_globals: dict[str, Any] | None = None, jinja_filters: dict[str, Callable] | None = None, ) -> None ``` Register global functions and filters in the Jinja2 environment. This function adds commonly used utilities to the Jinja2 environment so they can be accessed directly in templates without importing. Called by JinjaMiddleware during initialization to set up the environment. Uses setdefault to avoid overriding any custom globals that may have been set, following the same pattern as Starlette's built-in template functions. Parameters: - **`jinja_env`** (`Environment`) – The Jinja2 Environment instance Flow 1. JinjaMiddleware creates jinja_env with template loaders 1. Calls this function to register globals/filters 1. Stores jinja_env in request.state.jinja_env 1. Templates can use these functions: {{ url_for(...) }}, {{ data|jsonify }}, etc. 1. @pass_context functions access request from context["request"] # Components API Reference This page contains the API reference for component classes and form components that are bundled with Starlette-Templates. All components are built using Pydantic models for type safety and validation. The base class for all components is ComponentModel. All components extend this class and define their own properties and templates. Components must specify a `template` property that points to the Jinja2 template used for rendering. The template path is relative to the template loaders configured in the Jinja environment in JinjaEnvMiddleware. The Jinja2 template can access all properties defined on the component model. ## ComponentModel Base class for renderable UI components. Components should inherit from this class. Components are rendered using Jinja2 templates, which can be organized and loaded via the application's Jinja2 environment template loaders configured in `JinjaMiddleware`. ``` class HeaderComponent(ComponentModel): template: str = "components/header.html" title: str subtitle: str header = HeaderComponent(title="Welcome", subtitle="Enjoy your stay") rendered_html = await header.render(request) ``` The `context` method can be overridden to provide custom context data for the Jinja templates. ``` class UserCardComponent(ComponentModel): template: str = "components/user_card.html" user_id: int def context(self) -> dict[str, t.Any]: user = get_user_from_db(self.user_id) return {"user": user} user_card = UserCardComponent(user_id=42) rendered_html = await user_card.render(request) ``` Attributes: - **`id`** (`str`) – Unique identifier for this component. Auto-generated by default. - **`template`** (`str`) – Path to the Jinja2 template used to render this component. Required. Methods: - **`prepare_field_params`** – Transform form field params before component instantiation. - **`context`** – Context variables to set in parent template when this component is used. - **`render`** – Render this component to HTML. ### model_config ``` model_config = ConfigDict( arbitrary_types_allowed=True, extra="allow" ) ``` ### id ``` id: str = Field(default_factory=generate_component_id) ``` Unique identifier for this component. Auto-generated by default. ### template ``` template: str ``` Path to the Jinja2 template used to render this component. ### prepare_field_params ``` prepare_field_params( params: dict[str, Any], field_value: Any ) -> dict[str, Any] ``` Transform form field params before component instantiation. Override in subclasses that need special handling when used with FormModel. For example, Select components need to convert choices to SelectOption objects, and Checkbox components need to set checked state from the field value. By default, this method sets the 'value' param to the field value. Parameters: - **`params`** (`dict[str, Any]`) – Parameters from the form field's json_schema_extra - **`field_value`** (`Any`) – The current value of the form field Returns: - `dict[str, Any]` – Modified params dict ready for component instantiation ### context ``` context( jinja_context: dict[str, Any], request: Request ) -> dict[str, Any] ``` Context variables to set in parent template when this component is used. Override this method to set context variables that should be available to the parent template. For example, components can set library inclusion flags like `include_datatables` or any other context variables. Parameters: - **`jinja_context`** (`dict[str, Any]`) – The current Jinja2 rendering context - **`request`** (`Request`) – The Starlette Request object Returns: - `dict[str, Any]` – Dictionary of context variables to merge into parent template context ### render ``` render(request: Request, **kwargs) -> Markup ``` Render this component to HTML. ## Form Components These components are designed to work with the [FormModel](https://starlette-templates.tycho.engineering/forms/index.md) for building Pydantic-based forms with validation and rendering. ## Input Bootstrap form input component. Methods: - **`prepare_field_params`** – Convert field value to string for input element. Attributes: - **`template`** (`str`) – Bootstrap input form control component. - **`name`** (`str`) – Input name attribute. - **`label`** (`str | None`) – Input label. - **`type`** (`str`) – Input type, e.g., text, email, password, number, tel, url, etc. - **`value`** (`str | None`) – Input value. - **`placeholder`** (`str | None`) – Placeholder text. - **`help_text`** (`str | None`) – Help text below input. - **`required`** (`bool`) – Whether the input is required. - **`disabled`** (`bool`) – Whether the input is disabled. - **`readonly`** (`bool`) – Whether the input is readonly. - **`validation_state`** (`str | None`) – Validation state: valid, invalid. - **`validation_message`** (`str | None`) – Validation feedback message. - **`prepend`** (`str | None`) – Prepend text/icon to input. - **`append`** (`str | None`) – Append text/icon to input. - **`size`** (`str | None`) – Input size: sm, lg. ### template ``` template: str = Field('components/input.html', frozen=True) ``` Bootstrap input form control component. ### name ``` name: str = Field(..., description='Input name attribute') ``` Input name attribute. ### label ``` label: str | None = Field( default=None, description="Input label" ) ``` Input label. ### type ``` type: str = Field( default="text", description="Input type: text, email, password, number, tel, url, etc.", ) ``` Input type, e.g., text, email, password, number, tel, url, etc. ### value ``` value: str | None = Field( default=None, description="Input value" ) ``` Input value. ### placeholder ``` placeholder: str | None = Field( default=None, description="Placeholder text" ) ``` Placeholder text. ### help_text ``` help_text: str | None = Field( default=None, description="Help text below input" ) ``` Help text below input. ### required ``` required: bool = Field( default=False, description="Required field" ) ``` Whether the input is required. ### disabled ``` disabled: bool = Field( default=False, description="Disabled input" ) ``` Whether the input is disabled. ### readonly ``` readonly: bool = Field( default=False, description="Readonly input" ) ``` Whether the input is readonly. ### validation_state ``` validation_state: str | None = Field( default=None, description="Validation state: valid, invalid", ) ``` Validation state: valid, invalid. ### validation_message ``` validation_message: str | None = Field( default=None, description="Validation feedback message" ) ``` Validation feedback message. ### prepend ``` prepend: str | None = Field( default=None, description="Prepend text/icon to input" ) ``` Prepend text/icon to input. ### append ``` append: str | None = Field( default=None, description="Append text/icon to input" ) ``` Append text/icon to input. ### size ``` size: str | None = Field( default=None, description="Input size: sm, lg" ) ``` Input size: sm, lg. ### prepare_field_params ``` prepare_field_params( params: dict[str, Any], field_value: Any ) -> dict[str, Any] ``` Convert field value to string for input element. ## Textarea Bootstrap textarea component. Methods: - **`prepare_field_params`** – Convert field value to string for textarea element. Attributes: - **`template`** (`str`) – Bootstrap textarea component template. - **`name`** (`str`) – Textarea name attribute. - **`label`** (`str | None`) – Textarea label. - **`value`** (`str | None`) – Textarea value. - **`placeholder`** (`str | None`) – Placeholder text. - **`help_text`** (`str | None`) – Help text below textarea. - **`rows`** (`int`) – Number of rows. - **`required`** (`bool`) – Whether the textarea is required. - **`disabled`** (`bool`) – Whether the textarea is disabled. - **`readonly`** (`bool`) – Whether the textarea is readonly. - **`validation_state`** (`str | None`) – Validation state: valid, invalid. - **`validation_message`** (`str | None`) – Validation feedback message. ### template ``` template: str = Field( "components/textarea.html", frozen=True ) ``` Bootstrap textarea component template. ### name ``` name: str = Field( ..., description="Textarea name attribute" ) ``` Textarea name attribute. ### label ``` label: str | None = Field( default=None, description="Textarea label" ) ``` Textarea label. ### value ``` value: str | None = Field( default=None, description="Textarea value" ) ``` Textarea value. ### placeholder ``` placeholder: str | None = Field( default=None, description="Placeholder text" ) ``` Placeholder text. ### help_text ``` help_text: str | None = Field( default=None, description="Help text below textarea" ) ``` Help text below textarea. ### rows ``` rows: int = Field(default=3, description='Number of rows') ``` Number of rows. ### required ``` required: bool = Field( default=False, description="Required field" ) ``` Whether the textarea is required. ### disabled ``` disabled: bool = Field( default=False, description="Disabled textarea" ) ``` Whether the textarea is disabled. ### readonly ``` readonly: bool = Field( default=False, description="Readonly textarea" ) ``` Whether the textarea is readonly. ### validation_state ``` validation_state: str | None = Field( default=None, description="Validation state: valid, invalid", ) ``` Validation state: valid, invalid. ### validation_message ``` validation_message: str | None = Field( default=None, description="Validation feedback message" ) ``` Validation feedback message. ### prepare_field_params ``` prepare_field_params( params: dict[str, Any], field_value: Any ) -> dict[str, Any] ``` Convert field value to string for textarea element. ## Select Bootstrap select dropdown component. Methods: - **`prepare_field_params`** – Convert choices to SelectOption objects with proper selected state. Attributes: - **`template`** (`str`) – Bootstrap select dropdown component template. - **`name`** (`str`) – Select name attribute. - **`label`** (`str | None`) – Select label. - **`options`** (`list[SelectOption]`) – Select options. - **`help_text`** (`str | None`) – Help text below select. - **`required`** (`bool`) – Whether the select is required. - **`disabled`** (`bool`) – Whether the select is disabled. - **`multiple`** (`bool`) – Allow multiple selections. - **`size`** (`str | None`) – Select size: sm, lg. - **`validation_state`** (`str | None`) – Validation state: valid, invalid. - **`validation_message`** (`str | None`) – Validation feedback message. ### template ``` template: str = Field("components/select.html", frozen=True) ``` Bootstrap select dropdown component template. ### name ``` name: str = Field(..., description='Select name attribute') ``` Select name attribute. ### label ``` label: str | None = Field( default=None, description="Select label" ) ``` Select label. ### options ``` options: list[SelectOption] = Field( default_factory=list, description="Select options" ) ``` Select options. ### help_text ``` help_text: str | None = Field( default=None, description="Help text below select" ) ``` Help text below select. ### required ``` required: bool = Field( default=False, description="Required field" ) ``` Whether the select is required. ### disabled ``` disabled: bool = Field( default=False, description="Disabled select" ) ``` Whether the select is disabled. ### multiple ``` multiple: bool = Field( default=False, description="Allow multiple selections" ) ``` Allow multiple selections. ### size ``` size: str | None = Field( default=None, description="Select size: sm, lg" ) ``` Select size: sm, lg. ### validation_state ``` validation_state: str | None = Field( default=None, description="Validation state: valid, invalid", ) ``` Validation state: valid, invalid. ### validation_message ``` validation_message: str | None = Field( default=None, description="Validation feedback message" ) ``` Validation feedback message. ### prepare_field_params ``` prepare_field_params( params: dict[str, Any], field_value: Any ) -> dict[str, Any] ``` Convert choices to SelectOption objects with proper selected state. ## Checkbox Bootstrap checkbox component. Methods: - **`prepare_field_params`** – Set checked state from field value instead of using value. Attributes: - **`template`** (`str`) – Bootstrap checkbox component template. - **`name`** (`str`) – Checkbox name attribute. - **`label`** (`str`) – Checkbox label. - **`value`** (`str`) – Checkbox value. - **`checked`** (`bool`) – Checked state. - **`disabled`** (`bool`) – Whether the checkbox is disabled. - **`inline`** (`bool`) – Display inline. - **`validation_state`** (`str | None`) – Validation state: valid, invalid. - **`validation_message`** (`str | None`) – Validation feedback message. ### template ``` template: str = Field( "components/checkbox.html", frozen=True ) ``` Bootstrap checkbox component template. ### name ``` name: str = Field( ..., description="Checkbox name attribute" ) ``` Checkbox name attribute. ### label ``` label: str = Field(..., description='Checkbox label') ``` Checkbox label. ### value ``` value: str = Field( default="1", description="Checkbox value" ) ``` Checkbox value. ### checked ``` checked: bool = Field( default=False, description="Checked state" ) ``` Checked state. ### disabled ``` disabled: bool = Field( default=False, description="Disabled checkbox" ) ``` Whether the checkbox is disabled. ### inline ``` inline: bool = Field( default=False, description="Display inline" ) ``` Display inline. ### validation_state ``` validation_state: str | None = Field( default=None, description="Validation state: valid, invalid", ) ``` Validation state: valid, invalid. ### validation_message ``` validation_message: str | None = Field( default=None, description="Validation feedback message" ) ``` Validation feedback message. ### prepare_field_params ``` prepare_field_params( params: dict[str, Any], field_value: Any ) -> dict[str, Any] ``` Set checked state from field value instead of using value. ## Radio Bootstrap radio button component. Attributes: - **`template`** (`str`) – Bootstrap radio button component template. - **`name`** (`str`) – Radio name attribute (same for group). - **`label`** (`str`) – Radio label. - **`value`** (`str`) – Radio value. - **`checked`** (`bool`) – Checked state. - **`disabled`** (`bool`) – Whether the radio is disabled. - **`inline`** (`bool`) – Display inline. - **`validation_state`** (`str | None`) – Validation state: valid, invalid. - **`validation_message`** (`str | None`) – Validation feedback message. ### template ``` template: str = Field('components/radio.html', frozen=True) ``` Bootstrap radio button component template. ### name ``` name: str = Field( ..., description="Radio name attribute (same for group)" ) ``` Radio name attribute (same for group). ### label ``` label: str = Field(..., description='Radio label') ``` Radio label. ### value ``` value: str = Field(..., description='Radio value') ``` Radio value. ### checked ``` checked: bool = Field( default=False, description="Checked state" ) ``` Checked state. ### disabled ``` disabled: bool = Field( default=False, description="Disabled radio" ) ``` Whether the radio is disabled. ### inline ``` inline: bool = Field( default=False, description="Display inline" ) ``` Display inline. ### validation_state ``` validation_state: str | None = Field( default=None, description="Validation state: valid, invalid", ) ``` Validation state: valid, invalid. ### validation_message ``` validation_message: str | None = Field( default=None, description="Validation feedback message" ) ``` Validation feedback message. ## Switch Bootstrap switch component (styled checkbox). Attributes: - **`template`** (`str`) – Bootstrap switch component template. - **`name`** (`str`) – Switch name attribute. - **`label`** (`str`) – Switch label. - **`value`** (`str`) – Switch value. - **`checked`** (`bool`) – Checked state. - **`disabled`** (`bool`) – Whether the switch is disabled. - **`validation_state`** (`str | None`) – Validation state: valid, invalid. - **`validation_message`** (`str | None`) – Validation feedback message. ### template ``` template: str = Field("components/switch.html", frozen=True) ``` Bootstrap switch component template. ### name ``` name: str = Field(..., description='Switch name attribute') ``` Switch name attribute. ### label ``` label: str = Field(..., description='Switch label') ``` Switch label. ### value ``` value: str = Field(default='1', description='Switch value') ``` Switch value. ### checked ``` checked: bool = Field( default=False, description="Checked state" ) ``` Checked state. ### disabled ``` disabled: bool = Field( default=False, description="Disabled switch" ) ``` Whether the switch is disabled. ### validation_state ``` validation_state: str | None = Field( default=None, description="Validation state: valid, invalid", ) ``` Validation state: valid, invalid. ### validation_message ``` validation_message: str | None = Field( default=None, description="Validation feedback message" ) ``` Validation feedback message. ## FileInput Bootstrap file input component. Attributes: - **`template`** (`str`) – Bootstrap file input component template. - **`name`** (`str`) – File input name attribute. - **`label`** (`str | None`) – File input label. - **`help_text`** (`str | None`) – Help text below input. - **`required`** (`bool`) – Whether the file input is required. - **`disabled`** (`bool`) – Whether the file input is disabled. - **`multiple`** (`bool`) – Allow multiple files. - **`accept`** (`str | None`) – Accepted file types (e.g., 'image/\*'). - **`validation_state`** (`str | None`) – Validation state: valid, invalid. - **`validation_message`** (`str | None`) – Validation feedback message. ### template ``` template: str = Field( "components/file_input.html", frozen=True ) ``` Bootstrap file input component template. ### name ``` name: str = Field( ..., description="File input name attribute" ) ``` File input name attribute. ### label ``` label: str | None = Field( default=None, description="File input label" ) ``` File input label. ### help_text ``` help_text: str | None = Field( default=None, description="Help text below input" ) ``` Help text below input. ### required ``` required: bool = Field( default=False, description="Required field" ) ``` Whether the file input is required. ### disabled ``` disabled: bool = Field( default=False, description="Disabled input" ) ``` Whether the file input is disabled. ### multiple ``` multiple: bool = Field( default=False, description="Allow multiple files" ) ``` Allow multiple files. ### accept ``` accept: str | None = Field( default=None, description="Accepted file types (e.g., 'image/*')", ) ``` Accepted file types (e.g., 'image/\*'). ### validation_state ``` validation_state: str | None = Field( default=None, description="Validation state: valid, invalid", ) ``` Validation state: valid, invalid. ### validation_message ``` validation_message: str | None = Field( default=None, description="Validation feedback message" ) ``` Validation feedback message. ## Range Bootstrap range slider component. Attributes: - **`template`** (`str`) – Bootstrap range slider component template. - **`name`** (`str`) – Range name attribute. - **`label`** (`str | None`) – Range label. - **`min`** (`float`) – Minimum value. - **`max`** (`float`) – Maximum value. - **`step`** (`float`) – Step increment. - **`value`** (`float`) – Current value. - **`disabled`** (`bool`) – Whether the range is disabled. - **`help_text`** (`str | None`) – Help text below range. - **`validation_state`** (`str | None`) – Validation state: valid, invalid. - **`validation_message`** (`str | None`) – Validation feedback message. ### template ``` template: str = Field('components/range.html', frozen=True) ``` Bootstrap range slider component template. ### name ``` name: str = Field(..., description='Range name attribute') ``` Range name attribute. ### label ``` label: str | None = Field( default=None, description="Range label" ) ``` Range label. ### min ``` min: float = Field(default=0, description='Minimum value') ``` Minimum value. ### max ``` max: float = Field(default=100, description="Maximum value") ``` Maximum value. ### step ``` step: float = Field(default=1, description="Step increment") ``` Step increment. ### value ``` value: float = Field( default=50, description="Current value" ) ``` Current value. ### disabled ``` disabled: bool = Field( default=False, description="Disabled range" ) ``` Whether the range is disabled. ### help_text ``` help_text: str | None = Field( default=None, description="Help text below range" ) ``` Help text below range. ### validation_state ``` validation_state: str | None = Field( default=None, description="Validation state: valid, invalid", ) ``` Validation state: valid, invalid. ### validation_message ``` validation_message: str | None = Field( default=None, description="Validation feedback message" ) ``` Validation feedback message. ## ChoicesSelect Choices.js enhanced select component. Methods: - **`prepare_field_params`** – Convert choices to ChoiceOption objects with proper selected state. Attributes: - **`id`** (`str`) – Element ID for the select. - **`template`** (`str`) – Choices.js select component template. - **`name`** (`str`) – Select name attribute. - **`label`** (`str | None`) – Select label. - **`options`** (`list[ChoiceOption | ChoiceGroup]`) – Select options or option groups. - **`placeholder`** (`str`) – Placeholder text. - **`search_enabled`** (`bool`) – Enable search functionality. - **`search_placeholder`** (`str`) – Search placeholder. - **`multiple`** (`bool`) – Allow multiple selections. - **`remove_button`** (`bool`) – Show remove button for selected items. - **`max_item_count`** (`int | None`) – Max items that can be selected (for multiple). - **`help_text`** (`str | None`) – Help text below select. - **`required`** (`bool`) – Whether the select is required. - **`disabled`** (`bool`) – Whether the select is disabled. - **`validation_state`** (`str | None`) – Validation state: valid, invalid. - **`validation_message`** (`str | None`) – Validation feedback message. ### id ``` id: str ``` Element ID for the select. ### template ``` template: str = Field( "components/choices_select.html", frozen=True ) ``` Choices.js select component template. ### name ``` name: str = Field(..., description='Select name attribute') ``` Select name attribute. ### label ``` label: str | None = Field( default=None, description="Select label" ) ``` Select label. ### options ``` options: list[ChoiceOption | ChoiceGroup] = Field( default_factory=list ) ``` Select options or option groups. ### placeholder ``` placeholder: str = Field( default="Select an option", description="Placeholder text", ) ``` Placeholder text. ### search_enabled ``` search_enabled: bool = Field( default=True, description="Enable search functionality" ) ``` Enable search functionality. ### search_placeholder ``` search_placeholder: str = Field( default="Type to search", description="Search placeholder", ) ``` Search placeholder. ### multiple ``` multiple: bool = Field( default=False, description="Allow multiple selections" ) ``` Allow multiple selections. ### remove_button ``` remove_button: bool = Field( default=True, description="Show remove button for selected items", ) ``` Show remove button for selected items. ### max_item_count ``` max_item_count: int | None = Field( default=None, description="Max items that can be selected (for multiple)", ) ``` Max items that can be selected (for multiple). ### help_text ``` help_text: str | None = Field( default=None, description="Help text below select" ) ``` Help text below select. ### required ``` required: bool = Field( default=False, description="Required field" ) ``` Whether the select is required. ### disabled ``` disabled: bool = Field( default=False, description="Disabled select" ) ``` Whether the select is disabled. ### validation_state ``` validation_state: str | None = Field( default=None, description="Validation state: valid, invalid", ) ``` Validation state: valid, invalid. ### validation_message ``` validation_message: str | None = Field( default=None, description="Validation feedback message" ) ``` Validation feedback message. ### prepare_field_params ``` prepare_field_params( params: dict[str, Any], field_value: Any ) -> dict[str, Any] ``` Convert choices to ChoiceOption objects with proper selected state. ## DatePicker Flatpickr date picker component. Methods: - **`prepare_field_params`** – Convert date/datetime values to ISO string format. Attributes: - **`id`** (`str`) – Element ID for the date picker. - **`template`** (`str`) – Flatpickr date picker component template. - **`name`** (`str`) – Input name attribute. - **`label`** (`str | None`) – Input label. - **`value`** (`str | None`) – Initial date value (YYYY-MM-DD). - **`placeholder`** (`str | None`) – Placeholder text. - **`mode`** (`str`) – Selection mode: single, multiple, range. - **`enable_time`** (`bool`) – Enable time picker. - **`time_24hr`** (`bool`) – Use 24-hour time format. - **`date_format`** (`str`) – Date format string. - **`min_date`** (`str | None`) – Minimum selectable date. - **`max_date`** (`str | None`) – Maximum selectable date. - **`disable_dates`** (`list[str]`) – Dates to disable. - **`inline`** (`bool`) – Display calendar inline. - **`help_text`** (`str | None`) – Help text below input. - **`required`** (`bool`) – Whether the date picker is required. - **`disabled`** (`bool`) – Whether the date picker is disabled. - **`validation_state`** (`str | None`) – Validation state: valid, invalid. - **`validation_message`** (`str | None`) – Validation feedback message. ### id ``` id: str ``` Element ID for the date picker. ### template ``` template: str = Field( "components/datepicker.html", frozen=True ) ``` Flatpickr date picker component template. ### name ``` name: str = Field(..., description='Input name attribute') ``` Input name attribute. ### label ``` label: str | None = Field( default=None, description="Input label" ) ``` Input label. ### value ``` value: str | None = Field( default=None, description="Initial date value (YYYY-MM-DD)", ) ``` Initial date value (YYYY-MM-DD). ### placeholder ``` placeholder: str | None = Field( default="Select date", description="Placeholder text" ) ``` Placeholder text. ### mode ``` mode: str = Field( default="single", description="Selection mode: single, multiple, range", ) ``` Selection mode: single, multiple, range. ### enable_time ``` enable_time: bool = Field( default=False, description="Enable time picker" ) ``` Enable time picker. ### time_24hr ``` time_24hr: bool = Field( default=True, description="Use 24-hour time format" ) ``` Use 24-hour time format. ### date_format ``` date_format: str = Field( default="Y-m-d", description="Date format string" ) ``` Date format string. ### min_date ``` min_date: str | None = Field( default=None, description="Minimum selectable date" ) ``` Minimum selectable date. ### max_date ``` max_date: str | None = Field( default=None, description="Maximum selectable date" ) ``` Maximum selectable date. ### disable_dates ``` disable_dates: list[str] = Field( default_factory=list, description="Dates to disable" ) ``` Dates to disable. ### inline ``` inline: bool = Field( default=False, description="Display calendar inline" ) ``` Display calendar inline. ### help_text ``` help_text: str | None = Field( default=None, description="Help text below input" ) ``` Help text below input. ### required ``` required: bool = Field( default=False, description="Required field" ) ``` Whether the date picker is required. ### disabled ``` disabled: bool = Field( default=False, description="Disabled input" ) ``` Whether the date picker is disabled. ### validation_state ``` validation_state: str | None = Field( default=None, description="Validation state: valid, invalid", ) ``` Validation state: valid, invalid. ### validation_message ``` validation_message: str | None = Field( default=None, description="Validation feedback message" ) ``` Validation feedback message. ### prepare_field_params ``` prepare_field_params( params: dict[str, Any], field_value: Any ) -> dict[str, Any] ``` Convert date/datetime values to ISO string format. ## SubmitButton Bootstrap submit button component for forms. Methods: - **`prepare_field_params`** – Submit buttons don't use field values. Attributes: - **`template`** (`str`) – Bootstrap submit button component template. - **`text`** (`str`) – Button text. - **`button_type`** (`str`) – Button type: submit, button, reset. - **`name`** (`str | None`) – Button name attribute. - **`classes`** (`list[str]`) – CSS classes. - **`disabled`** (`bool`) – Whether the button is disabled. - **`form`** (`str | None`) – Form element the button is associated with. - **`formaction`** (`str | None`) – URL where form data is sent. - **`formenctype`** (`str | None`) – How form data is encoded. - **`formmethod`** (`str | None`) – HTTP method when button is clicked. - **`formnovalidate`** (`bool`) – Whether to bypass form validation. - **`formtarget`** (`str | None`) – Where to display response. ### template ``` template: str = Field( "components/submit_button.html", frozen=True ) ``` Bootstrap submit button component template. ### text ``` text: str = Field( default="Submit", description="Button text" ) ``` Button text. ### button_type ``` button_type: str = Field( default="submit", description="Button type: submit, button, reset", ) ``` Button type: submit, button, reset. ### name ``` name: str | None = Field( default=None, description="Button name attribute" ) ``` Button name attribute. ### classes ``` classes: list[str] = Field( default_factory=lambda: ["btn", "btn-primary"], description="CSS classes", ) ``` CSS classes. ### disabled ``` disabled: bool = Field( default=False, description="Disabled button" ) ``` Whether the button is disabled. ### form ``` form: str | None = Field( default=None, description="Form element the button is associated with", ) ``` Form element the button is associated with. ### formaction ``` formaction: str | None = Field( default=None, description="URL where form data is sent" ) ``` URL where form data is sent. ### formenctype ``` formenctype: str | None = Field( default=None, description="How form data is encoded" ) ``` How form data is encoded. ### formmethod ``` formmethod: str | None = Field( default=None, description="HTTP method when button is clicked", ) ``` HTTP method when button is clicked. ### formnovalidate ``` formnovalidate: bool = Field( default=False, description="Whether to bypass form validation", ) ``` Whether to bypass form validation. ### formtarget ``` formtarget: str | None = Field( default=None, description="Where to display response" ) ``` Where to display response. ### prepare_field_params ``` prepare_field_params( params: dict[str, Any], field_value: Any ) -> dict[str, Any] ``` Submit buttons don't use field values.