starlette-async-jinja


Namestarlette-async-jinja JSON
Version 1.12.6 PyPI version JSON
download
home_pageNone
SummaryNone
upload_time2025-07-16 06:38:56
maintainerNone
docs_urlNone
authorNone
requires_python>=3.13
licenseBSD-3-Clause
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # starlette-async-jinja

[![Code style: crackerjack](https://img.shields.io/badge/code%20style-crackerjack-000042)](https://github.com/lesleslie/crackerjack)
[![Python: 3.13+](https://img.shields.io/badge/python-3.13%2B-green)](https://www.python.org/downloads/)

An asynchronous Jinja2 template integration for Starlette and FastAPI, built on top of the `jinja2-async-environment` package.

## Features

- **Fully async template rendering** - Load templates and render them asynchronously
- **Seamless framework integration** - Works with Starlette and FastAPI request/response cycles
- **Template fragments** - Render specific blocks from templates
- **Template partials** - Include sub-templates with their own context using `render_block`
- **Fast JSON responses** - Enhanced JSON responses using `msgspec` for faster serialization
- **Context processors** - Add global context to all templates
- **Performance optimizations** - Context processor caching, fragment caching, and memory pooling
- **Configurable caching** - Fine-tune cache sizes and TTL for optimal performance

## Installation

```bash
pip install starlette-async-jinja
```

## Requirements

- Python 3.13+
- Starlette
- Jinja2
- jinja2-async-environment
- anyio
- msgspec

## Basic Usage

### Starlette Example

```python
from starlette.applications import Starlette
from starlette.routing import Route
from anyio import Path as AsyncPath
from starlette_async_jinja import AsyncJinja2Templates

# Initialize templates with an async path
templates = AsyncJinja2Templates(directory=AsyncPath("templates"))


async def homepage(request):
    return await templates.TemplateResponse(
        request, "index.html", {"message": "Hello, world!"}
    )


# Or using the alias
async def about(request):
    return await templates.render_template(
        request, "about.html", {"message": "About page"}
    )


# Define routes
app = Starlette(routes=[Route("/", homepage), Route("/about", about)])
```

### FastAPI Example

```python
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from anyio import Path as AsyncPath
from starlette_async_jinja import AsyncJinja2Templates, JsonResponse

app = FastAPI()

# Initialize templates with an async path
templates = AsyncJinja2Templates(directory=AsyncPath("templates"))


@app.get("/", response_class=HTMLResponse)
async def homepage(request: Request):
    """Render the homepage template."""
    return await templates.TemplateResponse(
        request,
        "index.html",
        {
            "title": "FastAPI with Async Jinja2",
            "message": "Welcome to FastAPI with async template rendering!",
        },
    )


@app.get("/about", response_class=HTMLResponse)
async def about(request: Request):
    """Render the about page template."""
    return await templates.render_template(
        request,
        "about.html",
        {"title": "About Us", "message": "Learn more about our company"},
    )


@app.get("/api/data")
async def get_data():
    """Return JSON data using the optimized JsonResponse."""
    return JsonResponse(
        {
            "items": [
                {"id": 1, "name": "Item 1"},
                {"id": 2, "name": "Item 2"},
                {"id": 3, "name": "Item 3"},
            ]
        }
    )
```

## Using Template Partials with `render_block`

The `render_block` feature allows you to render entire template files as reusable components, inspired by [jinja_partials](https://github.com/mikeckennedy/jinja_partials). This is especially useful for creating modular template components.

### Component Templates

**templates/components/alert.html**

```html
<div class="alert alert-{{ type | default('info') }}">
    <h4 class="alert-heading">{{ title }}</h4>
    <p>{{ message }}</p>
    {% if dismissible %}
    <button type="button" class="close" data-dismiss="alert">×</button>
    {% endif %}
</div>
```

### Using Components in Your Templates

**templates/index.html**

```html
{% extends "base.html" %}

{% block content %}
<div class="container">
    <h1>Welcome to {{ site_name }}</h1>

    {# Instead of a macro, use render_block #}
    {{ render_block('components/alert.html',
                     type='warning',
                     title='Attention!',
                     message='This is an important notice.',
                     dismissible=True) }}

    {% for item in items %}
        {# Another component rendered with render_block #}
        {{ render_block('components/card.html',
                         title=item.title,
                         content=item.description,
                         image_url=item.image) }}
    {% endfor %}
</div>
{% endblock %}
```

### Important Notes on `render_block`

- Each component only receives the variables explicitly passed to it
- `render_block` renders entire template files as components
- The `markup=True` parameter (default) ensures proper HTML escaping

## Context Processors

Context processors allow you to add global context to all templates:

```python
def global_context(request):
    return {"site_name": "My Awesome Site", "current_year": 2024}


templates = AsyncJinja2Templates(
    directory=AsyncPath("templates"), context_processors=[global_context]
)

# Now all templates will have access to site_name and current_year
```

### Context Processors with FastAPI

```python
from fastapi import FastAPI, Request
from anyio import Path as AsyncPath
from starlette_async_jinja import AsyncJinja2Templates

app = FastAPI()


# Define context processors
def global_context(request):
    return {"site_name": "My FastAPI App", "current_year": 2024, "version": "1.0.0"}


def user_context(request):
    # In a real app, you might get this from a session or JWT
    return {"user": {"name": "Guest User"}}


# Initialize templates with context processors
templates = AsyncJinja2Templates(
    directory=AsyncPath("templates"), context_processors=[global_context, user_context]
)


@app.get("/")
async def homepage(request: Request):
    # These variables will be automatically available in all templates:
    # - site_name
    # - current_year
    # - version
    # - user
    return await templates.TemplateResponse(
        request, "index.html", {"title": "Home Page"}
    )
```

## Using Template Fragments

Fragments allow you to render specific blocks from within a template:

```html
<!-- In your template (page.html) -->
{% block header %}
  <h1>Welcome to {{ site_name }}</h1>
{% endblock %}

{% block footer %}
  <footer>© {{ year }} {{ company_name }}</footer>
{% endblock %}
```

### Starlette Example

```python
from starlette.responses import HTMLResponse


# In your route handler:
async def render_header(request):
    content = await templates.render_fragment(
        "page.html", "header", site_name="My Awesome Site"
    )
    return HTMLResponse(content)
```

### FastAPI Example

```python
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse

app = FastAPI()
templates = AsyncJinja2Templates(directory=AsyncPath("templates"))


@app.get("/header", response_class=HTMLResponse)
async def get_header(request: Request):
    """Return just the header fragment."""
    content = await templates.render_fragment(
        "page.html", "header", site_name="My FastAPI Site"
    )
    return HTMLResponse(content)


@app.get("/footer", response_class=HTMLResponse)
async def get_footer(request: Request):
    """Return just the footer fragment."""
    content = await templates.render_fragment(
        "page.html", "footer", year=2024, company_name="My Company"
    )
    return HTMLResponse(content)
```

## JsonResponse

Enhanced JSON response using `msgspec` for faster serialization:

```python
from starlette_async_jinja import JsonResponse


# Starlette example
async def api_endpoint(request):
    data = {"name": "John", "email": "john@example.com"}
    return JsonResponse(data)


# FastAPI example
@app.get("/api/user")
async def get_user():
    data = {"name": "John", "email": "john@example.com"}
    return JsonResponse(data)  # Faster than FastAPI's default JSONResponse
```

## Jinja2 Macros Support

Jinja2 macros are fully supported in async templates with the updated `jinja2-async-environment>=0.13`:

```html
<!-- templates/components.html -->
{% macro alert(type, message) %}
<div class="alert alert-{{ type }}">{{ message }}</div>
{% endmacro %}

{% macro button(text, style='primary') %}
<button class="btn btn-{{ style }}">{{ text }}</button>
{% endmacro %}
```

### Using Macros in Templates

Macros work seamlessly with `render_block` when called within templates:

```html
<!-- templates/page.html -->
{% from 'components.html' import alert, button %}

<h1>Welcome!</h1>
{{ alert('info', 'Welcome to our site!') }}
{{ button('Get Started', 'success') }}
```

```python
# This works perfectly with macros inside
content = await templates.render_block("page.html", {})
```

### Direct Macro Access

For advanced use cases, macros can be called directly from template modules:

```python
async def render_macro_component():
    template = await templates.env.get_template_async("components.html")
    module = await template.make_module_async()

    # Call macro directly and await the result
    alert_html = await module.alert("warning", "Direct macro call")
    return alert_html
```

### Choosing Your Approach

- **Macros**: Traditional Jinja2 components defined within templates, great for simple reusable elements
- **render_block**: Renders entire template files as components, useful for complex reusable template partials
- **render_fragment**: Renders specific named blocks from templates, ideal for partial page updates

## Issues and Limitations

- Only [asynchronous template loaders](https://github.com/lesleslie/jinja2-async-environment/blob/main/jinja2_async_environment/loaders.py) are fully supported
- The Jinja bytecodecache requires an asynchronous Redis backend

## API Reference

### AsyncJinja2Templates

```python
templates = AsyncJinja2Templates(
    directory=AsyncPath("templates"),
    context_processors=[global_context],
    # Performance optimization options
    context_cache_size=128,  # Context processor cache size
    context_cache_ttl=300.0,  # Context cache TTL (seconds)
    fragment_cache_size=64,  # Fragment block cache size
    fragment_cache_ttl=600.0,  # Fragment cache TTL (seconds)
    context_pool_size=10,  # Context object pool size
    fragment_stringio_threshold=1024,  # StringIO threshold for large fragments
    # Standard Jinja2 environment options
    autoescape=True,
    **env_options,
)
```

#### Methods

- `async TemplateResponse(request, name, context={}, status_code=200, headers=None, media_type=None, background=None)` - Render a template to a response
- `async render_template(request, name, context={}, status_code=200, headers=None, media_type=None, background=None)` - Alias for TemplateResponse
- `async render_fragment(template_name, block_name, **kwargs)` - Render a specific block from a template
- `async render_block(template_name, markup=True, **data)` - Render a template as a partial with optional markup escaping (default: True)
- `async get_template_async(name)` - Get a template by name

### JsonResponse

Enhanced JSON response using `msgspec` for faster serialization.

### BlockNotFoundError

Exception raised when attempting to render a template block that doesn't exist.

## Performance Optimizations

This package includes several built-in performance optimizations:

### Context Processor Caching

Context processors are automatically cached based on request properties to avoid recomputation:

```python
def expensive_context_processor(request):
    # This will only run once per unique request path/method combination
    # and be cached for subsequent requests
    return {
        "expensive_data": fetch_expensive_data(),
        "computed_value": perform_complex_calculation(),
    }


templates = AsyncJinja2Templates(
    directory=AsyncPath("templates"),
    context_processors=[expensive_context_processor],
    context_cache_size=128,  # Number of cache entries
    context_cache_ttl=300.0,  # Cache for 5 minutes
)
```

### Fragment Rendering Optimizations

Fragment rendering includes several optimizations:

- **Block function caching** - Compiled block functions are cached to avoid re-extraction
- **Context object pooling** - Context dictionaries are reused to reduce memory allocations
- **Adaptive string building** - Large fragments use StringIO for better performance

```python
# Fragments are automatically optimized
content = await templates.render_fragment(
    "components/card.html",
    "card_block",
    title="Product Name",
    description="Product description...",
)
```

### Configuration Options

```python
templates = AsyncJinja2Templates(
    directory=AsyncPath("templates"),
    # Context processor caching
    context_cache_size=128,  # Max cached context entries
    context_cache_ttl=300.0,  # Cache TTL in seconds
    # Fragment rendering optimizations
    fragment_cache_size=64,  # Max cached block functions
    fragment_cache_ttl=600.0,  # Block cache TTL in seconds
    context_pool_size=10,  # Context object pool size
    fragment_stringio_threshold=1024,  # Use StringIO for fragments > 1KB
)
```

### Performance Benefits

- **20-40%** faster template rendering with context processors
- **30-50%** faster repeated fragment rendering
- **10-20%** reduction in memory allocations
- **15-25%** faster rendering of large template fragments

## Advanced Usage

### With Redis Bytecode Caching

For production environments, you can use Redis for bytecode caching:

```python
from anyio import Path as AsyncPath
from starlette_async_jinja import AsyncJinja2Templates
from jinja2_async_environment.bccache import AsyncRedisBytecodeCache
import redis.asyncio as redis

# Create a Redis client
redis_client = redis.Redis(host="localhost", port=6379, db=0)

# Set up bytecode caching
bytecode_cache = AsyncRedisBytecodeCache(redis_client, prefix="jinja2_")

# Create templates with caching
templates = AsyncJinja2Templates(
    directory=AsyncPath("templates"), bytecode_cache=bytecode_cache
)
```

### Using Different Loaders

You can use different loader types from `jinja2-async-environment`:

```python
from anyio import Path as AsyncPath
from starlette_async_jinja import AsyncJinja2Templates
from jinja2_async_environment.loaders import (
    AsyncFileSystemLoader,
    AsyncPackageLoader,
    AsyncChoiceLoader,
)

# Load templates from filesystem
fs_loader = AsyncFileSystemLoader("templates")

# Load templates from a Python package
package_loader = AsyncPackageLoader("your_package", "templates")

# Create a loader that tries multiple sources
choice_loader = AsyncChoiceLoader(
    [
        fs_loader,  # First try the filesystem
        package_loader,  # Then try the package
    ]
)

# Create templates with the choice loader
templates = AsyncJinja2Templates(
    directory=AsyncPath("templates"),  # This is still required
    loader=choice_loader,  # But we override the loader
)
```

## Type Annotations

This package is fully typed with Python's type annotations and is compatible with static type checkers like mypy and pyright.

## Acknowledgements

- [jinja_partials](https://github.com/mikeckennedy/jinja_partials)
- [jinja2-fragments](https://github.com/sponsfreixes/jinja2-fragments)
- [jinja2-async-environment](https://github.com/lesleslie/jinja2-async-environment)

## License

BSD-3-Clause

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "starlette-async-jinja",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.13",
    "maintainer_email": null,
    "keywords": null,
    "author": null,
    "author_email": "lesleslie <les@wedgwoodwebworks.com>",
    "download_url": "https://files.pythonhosted.org/packages/c0/d2/80931f9ac07969f267ad4c23b39690df871f4b307280e4624a4a57e90577/starlette_async_jinja-1.12.6.tar.gz",
    "platform": null,
    "description": "# starlette-async-jinja\n\n[![Code style: crackerjack](https://img.shields.io/badge/code%20style-crackerjack-000042)](https://github.com/lesleslie/crackerjack)\n[![Python: 3.13+](https://img.shields.io/badge/python-3.13%2B-green)](https://www.python.org/downloads/)\n\nAn asynchronous Jinja2 template integration for Starlette and FastAPI, built on top of the `jinja2-async-environment` package.\n\n## Features\n\n- **Fully async template rendering** - Load templates and render them asynchronously\n- **Seamless framework integration** - Works with Starlette and FastAPI request/response cycles\n- **Template fragments** - Render specific blocks from templates\n- **Template partials** - Include sub-templates with their own context using `render_block`\n- **Fast JSON responses** - Enhanced JSON responses using `msgspec` for faster serialization\n- **Context processors** - Add global context to all templates\n- **Performance optimizations** - Context processor caching, fragment caching, and memory pooling\n- **Configurable caching** - Fine-tune cache sizes and TTL for optimal performance\n\n## Installation\n\n```bash\npip install starlette-async-jinja\n```\n\n## Requirements\n\n- Python 3.13+\n- Starlette\n- Jinja2\n- jinja2-async-environment\n- anyio\n- msgspec\n\n## Basic Usage\n\n### Starlette Example\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.routing import Route\nfrom anyio import Path as AsyncPath\nfrom starlette_async_jinja import AsyncJinja2Templates\n\n# Initialize templates with an async path\ntemplates = AsyncJinja2Templates(directory=AsyncPath(\"templates\"))\n\n\nasync def homepage(request):\n    return await templates.TemplateResponse(\n        request, \"index.html\", {\"message\": \"Hello, world!\"}\n    )\n\n\n# Or using the alias\nasync def about(request):\n    return await templates.render_template(\n        request, \"about.html\", {\"message\": \"About page\"}\n    )\n\n\n# Define routes\napp = Starlette(routes=[Route(\"/\", homepage), Route(\"/about\", about)])\n```\n\n### FastAPI Example\n\n```python\nfrom fastapi import FastAPI, Request\nfrom fastapi.responses import HTMLResponse\nfrom anyio import Path as AsyncPath\nfrom starlette_async_jinja import AsyncJinja2Templates, JsonResponse\n\napp = FastAPI()\n\n# Initialize templates with an async path\ntemplates = AsyncJinja2Templates(directory=AsyncPath(\"templates\"))\n\n\n@app.get(\"/\", response_class=HTMLResponse)\nasync def homepage(request: Request):\n    \"\"\"Render the homepage template.\"\"\"\n    return await templates.TemplateResponse(\n        request,\n        \"index.html\",\n        {\n            \"title\": \"FastAPI with Async Jinja2\",\n            \"message\": \"Welcome to FastAPI with async template rendering!\",\n        },\n    )\n\n\n@app.get(\"/about\", response_class=HTMLResponse)\nasync def about(request: Request):\n    \"\"\"Render the about page template.\"\"\"\n    return await templates.render_template(\n        request,\n        \"about.html\",\n        {\"title\": \"About Us\", \"message\": \"Learn more about our company\"},\n    )\n\n\n@app.get(\"/api/data\")\nasync def get_data():\n    \"\"\"Return JSON data using the optimized JsonResponse.\"\"\"\n    return JsonResponse(\n        {\n            \"items\": [\n                {\"id\": 1, \"name\": \"Item 1\"},\n                {\"id\": 2, \"name\": \"Item 2\"},\n                {\"id\": 3, \"name\": \"Item 3\"},\n            ]\n        }\n    )\n```\n\n## Using Template Partials with `render_block`\n\nThe `render_block` feature allows you to render entire template files as reusable components, inspired by [jinja_partials](https://github.com/mikeckennedy/jinja_partials). This is especially useful for creating modular template components.\n\n### Component Templates\n\n**templates/components/alert.html**\n\n```html\n<div class=\"alert alert-{{ type | default('info') }}\">\n    <h4 class=\"alert-heading\">{{ title }}</h4>\n    <p>{{ message }}</p>\n    {% if dismissible %}\n    <button type=\"button\" class=\"close\" data-dismiss=\"alert\">\u00d7</button>\n    {% endif %}\n</div>\n```\n\n### Using Components in Your Templates\n\n**templates/index.html**\n\n```html\n{% extends \"base.html\" %}\n\n{% block content %}\n<div class=\"container\">\n    <h1>Welcome to {{ site_name }}</h1>\n\n    {# Instead of a macro, use render_block #}\n    {{ render_block('components/alert.html',\n                     type='warning',\n                     title='Attention!',\n                     message='This is an important notice.',\n                     dismissible=True) }}\n\n    {% for item in items %}\n        {# Another component rendered with render_block #}\n        {{ render_block('components/card.html',\n                         title=item.title,\n                         content=item.description,\n                         image_url=item.image) }}\n    {% endfor %}\n</div>\n{% endblock %}\n```\n\n### Important Notes on `render_block`\n\n- Each component only receives the variables explicitly passed to it\n- `render_block` renders entire template files as components\n- The `markup=True` parameter (default) ensures proper HTML escaping\n\n## Context Processors\n\nContext processors allow you to add global context to all templates:\n\n```python\ndef global_context(request):\n    return {\"site_name\": \"My Awesome Site\", \"current_year\": 2024}\n\n\ntemplates = AsyncJinja2Templates(\n    directory=AsyncPath(\"templates\"), context_processors=[global_context]\n)\n\n# Now all templates will have access to site_name and current_year\n```\n\n### Context Processors with FastAPI\n\n```python\nfrom fastapi import FastAPI, Request\nfrom anyio import Path as AsyncPath\nfrom starlette_async_jinja import AsyncJinja2Templates\n\napp = FastAPI()\n\n\n# Define context processors\ndef global_context(request):\n    return {\"site_name\": \"My FastAPI App\", \"current_year\": 2024, \"version\": \"1.0.0\"}\n\n\ndef user_context(request):\n    # In a real app, you might get this from a session or JWT\n    return {\"user\": {\"name\": \"Guest User\"}}\n\n\n# Initialize templates with context processors\ntemplates = AsyncJinja2Templates(\n    directory=AsyncPath(\"templates\"), context_processors=[global_context, user_context]\n)\n\n\n@app.get(\"/\")\nasync def homepage(request: Request):\n    # These variables will be automatically available in all templates:\n    # - site_name\n    # - current_year\n    # - version\n    # - user\n    return await templates.TemplateResponse(\n        request, \"index.html\", {\"title\": \"Home Page\"}\n    )\n```\n\n## Using Template Fragments\n\nFragments allow you to render specific blocks from within a template:\n\n```html\n<!-- In your template (page.html) -->\n{% block header %}\n  <h1>Welcome to {{ site_name }}</h1>\n{% endblock %}\n\n{% block footer %}\n  <footer>\u00a9 {{ year }} {{ company_name }}</footer>\n{% endblock %}\n```\n\n### Starlette Example\n\n```python\nfrom starlette.responses import HTMLResponse\n\n\n# In your route handler:\nasync def render_header(request):\n    content = await templates.render_fragment(\n        \"page.html\", \"header\", site_name=\"My Awesome Site\"\n    )\n    return HTMLResponse(content)\n```\n\n### FastAPI Example\n\n```python\nfrom fastapi import FastAPI, Request\nfrom fastapi.responses import HTMLResponse\n\napp = FastAPI()\ntemplates = AsyncJinja2Templates(directory=AsyncPath(\"templates\"))\n\n\n@app.get(\"/header\", response_class=HTMLResponse)\nasync def get_header(request: Request):\n    \"\"\"Return just the header fragment.\"\"\"\n    content = await templates.render_fragment(\n        \"page.html\", \"header\", site_name=\"My FastAPI Site\"\n    )\n    return HTMLResponse(content)\n\n\n@app.get(\"/footer\", response_class=HTMLResponse)\nasync def get_footer(request: Request):\n    \"\"\"Return just the footer fragment.\"\"\"\n    content = await templates.render_fragment(\n        \"page.html\", \"footer\", year=2024, company_name=\"My Company\"\n    )\n    return HTMLResponse(content)\n```\n\n## JsonResponse\n\nEnhanced JSON response using `msgspec` for faster serialization:\n\n```python\nfrom starlette_async_jinja import JsonResponse\n\n\n# Starlette example\nasync def api_endpoint(request):\n    data = {\"name\": \"John\", \"email\": \"john@example.com\"}\n    return JsonResponse(data)\n\n\n# FastAPI example\n@app.get(\"/api/user\")\nasync def get_user():\n    data = {\"name\": \"John\", \"email\": \"john@example.com\"}\n    return JsonResponse(data)  # Faster than FastAPI's default JSONResponse\n```\n\n## Jinja2 Macros Support\n\nJinja2 macros are fully supported in async templates with the updated `jinja2-async-environment>=0.13`:\n\n```html\n<!-- templates/components.html -->\n{% macro alert(type, message) %}\n<div class=\"alert alert-{{ type }}\">{{ message }}</div>\n{% endmacro %}\n\n{% macro button(text, style='primary') %}\n<button class=\"btn btn-{{ style }}\">{{ text }}</button>\n{% endmacro %}\n```\n\n### Using Macros in Templates\n\nMacros work seamlessly with `render_block` when called within templates:\n\n```html\n<!-- templates/page.html -->\n{% from 'components.html' import alert, button %}\n\n<h1>Welcome!</h1>\n{{ alert('info', 'Welcome to our site!') }}\n{{ button('Get Started', 'success') }}\n```\n\n```python\n# This works perfectly with macros inside\ncontent = await templates.render_block(\"page.html\", {})\n```\n\n### Direct Macro Access\n\nFor advanced use cases, macros can be called directly from template modules:\n\n```python\nasync def render_macro_component():\n    template = await templates.env.get_template_async(\"components.html\")\n    module = await template.make_module_async()\n\n    # Call macro directly and await the result\n    alert_html = await module.alert(\"warning\", \"Direct macro call\")\n    return alert_html\n```\n\n### Choosing Your Approach\n\n- **Macros**: Traditional Jinja2 components defined within templates, great for simple reusable elements\n- **render_block**: Renders entire template files as components, useful for complex reusable template partials\n- **render_fragment**: Renders specific named blocks from templates, ideal for partial page updates\n\n## Issues and Limitations\n\n- Only [asynchronous template loaders](https://github.com/lesleslie/jinja2-async-environment/blob/main/jinja2_async_environment/loaders.py) are fully supported\n- The Jinja bytecodecache requires an asynchronous Redis backend\n\n## API Reference\n\n### AsyncJinja2Templates\n\n```python\ntemplates = AsyncJinja2Templates(\n    directory=AsyncPath(\"templates\"),\n    context_processors=[global_context],\n    # Performance optimization options\n    context_cache_size=128,  # Context processor cache size\n    context_cache_ttl=300.0,  # Context cache TTL (seconds)\n    fragment_cache_size=64,  # Fragment block cache size\n    fragment_cache_ttl=600.0,  # Fragment cache TTL (seconds)\n    context_pool_size=10,  # Context object pool size\n    fragment_stringio_threshold=1024,  # StringIO threshold for large fragments\n    # Standard Jinja2 environment options\n    autoescape=True,\n    **env_options,\n)\n```\n\n#### Methods\n\n- `async TemplateResponse(request, name, context={}, status_code=200, headers=None, media_type=None, background=None)` - Render a template to a response\n- `async render_template(request, name, context={}, status_code=200, headers=None, media_type=None, background=None)` - Alias for TemplateResponse\n- `async render_fragment(template_name, block_name, **kwargs)` - Render a specific block from a template\n- `async render_block(template_name, markup=True, **data)` - Render a template as a partial with optional markup escaping (default: True)\n- `async get_template_async(name)` - Get a template by name\n\n### JsonResponse\n\nEnhanced JSON response using `msgspec` for faster serialization.\n\n### BlockNotFoundError\n\nException raised when attempting to render a template block that doesn't exist.\n\n## Performance Optimizations\n\nThis package includes several built-in performance optimizations:\n\n### Context Processor Caching\n\nContext processors are automatically cached based on request properties to avoid recomputation:\n\n```python\ndef expensive_context_processor(request):\n    # This will only run once per unique request path/method combination\n    # and be cached for subsequent requests\n    return {\n        \"expensive_data\": fetch_expensive_data(),\n        \"computed_value\": perform_complex_calculation(),\n    }\n\n\ntemplates = AsyncJinja2Templates(\n    directory=AsyncPath(\"templates\"),\n    context_processors=[expensive_context_processor],\n    context_cache_size=128,  # Number of cache entries\n    context_cache_ttl=300.0,  # Cache for 5 minutes\n)\n```\n\n### Fragment Rendering Optimizations\n\nFragment rendering includes several optimizations:\n\n- **Block function caching** - Compiled block functions are cached to avoid re-extraction\n- **Context object pooling** - Context dictionaries are reused to reduce memory allocations\n- **Adaptive string building** - Large fragments use StringIO for better performance\n\n```python\n# Fragments are automatically optimized\ncontent = await templates.render_fragment(\n    \"components/card.html\",\n    \"card_block\",\n    title=\"Product Name\",\n    description=\"Product description...\",\n)\n```\n\n### Configuration Options\n\n```python\ntemplates = AsyncJinja2Templates(\n    directory=AsyncPath(\"templates\"),\n    # Context processor caching\n    context_cache_size=128,  # Max cached context entries\n    context_cache_ttl=300.0,  # Cache TTL in seconds\n    # Fragment rendering optimizations\n    fragment_cache_size=64,  # Max cached block functions\n    fragment_cache_ttl=600.0,  # Block cache TTL in seconds\n    context_pool_size=10,  # Context object pool size\n    fragment_stringio_threshold=1024,  # Use StringIO for fragments > 1KB\n)\n```\n\n### Performance Benefits\n\n- **20-40%** faster template rendering with context processors\n- **30-50%** faster repeated fragment rendering\n- **10-20%** reduction in memory allocations\n- **15-25%** faster rendering of large template fragments\n\n## Advanced Usage\n\n### With Redis Bytecode Caching\n\nFor production environments, you can use Redis for bytecode caching:\n\n```python\nfrom anyio import Path as AsyncPath\nfrom starlette_async_jinja import AsyncJinja2Templates\nfrom jinja2_async_environment.bccache import AsyncRedisBytecodeCache\nimport redis.asyncio as redis\n\n# Create a Redis client\nredis_client = redis.Redis(host=\"localhost\", port=6379, db=0)\n\n# Set up bytecode caching\nbytecode_cache = AsyncRedisBytecodeCache(redis_client, prefix=\"jinja2_\")\n\n# Create templates with caching\ntemplates = AsyncJinja2Templates(\n    directory=AsyncPath(\"templates\"), bytecode_cache=bytecode_cache\n)\n```\n\n### Using Different Loaders\n\nYou can use different loader types from `jinja2-async-environment`:\n\n```python\nfrom anyio import Path as AsyncPath\nfrom starlette_async_jinja import AsyncJinja2Templates\nfrom jinja2_async_environment.loaders import (\n    AsyncFileSystemLoader,\n    AsyncPackageLoader,\n    AsyncChoiceLoader,\n)\n\n# Load templates from filesystem\nfs_loader = AsyncFileSystemLoader(\"templates\")\n\n# Load templates from a Python package\npackage_loader = AsyncPackageLoader(\"your_package\", \"templates\")\n\n# Create a loader that tries multiple sources\nchoice_loader = AsyncChoiceLoader(\n    [\n        fs_loader,  # First try the filesystem\n        package_loader,  # Then try the package\n    ]\n)\n\n# Create templates with the choice loader\ntemplates = AsyncJinja2Templates(\n    directory=AsyncPath(\"templates\"),  # This is still required\n    loader=choice_loader,  # But we override the loader\n)\n```\n\n## Type Annotations\n\nThis package is fully typed with Python's type annotations and is compatible with static type checkers like mypy and pyright.\n\n## Acknowledgements\n\n- [jinja_partials](https://github.com/mikeckennedy/jinja_partials)\n- [jinja2-fragments](https://github.com/sponsfreixes/jinja2-fragments)\n- [jinja2-async-environment](https://github.com/lesleslie/jinja2-async-environment)\n\n## License\n\nBSD-3-Clause\n",
    "bugtrack_url": null,
    "license": "BSD-3-Clause",
    "summary": null,
    "version": "1.12.6",
    "project_urls": {
        "Documentation": "https://github.com/lesleslie/starlette-async-jinja",
        "Homepage": "https://github.com/lesleslie/starlette-async-jinja",
        "Repository": "https://github.com/lesleslie/starlette-async-jinja"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "a2a8b8a31194ba13f23974053bdd2652c2297aab1f50607594a63a9e728a7f77",
                "md5": "6c0d49fcf8572f803667b9a693898ce8",
                "sha256": "ca14989dae72f7884e6d3d93593e8e040d8cd09f4c35b6ce8e88ad605b3ebd0c"
            },
            "downloads": -1,
            "filename": "starlette_async_jinja-1.12.6-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "6c0d49fcf8572f803667b9a693898ce8",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.13",
            "size": 10575,
            "upload_time": "2025-07-16T06:38:54",
            "upload_time_iso_8601": "2025-07-16T06:38:54.637409Z",
            "url": "https://files.pythonhosted.org/packages/a2/a8/b8a31194ba13f23974053bdd2652c2297aab1f50607594a63a9e728a7f77/starlette_async_jinja-1.12.6-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "c0d280931f9ac07969f267ad4c23b39690df871f4b307280e4624a4a57e90577",
                "md5": "95f093750d4fdf46e8b6f604b8917bb1",
                "sha256": "ff02f27752605d8a2a052b2d9a3698878d5b86b75dcd21ccb14e9902955ed103"
            },
            "downloads": -1,
            "filename": "starlette_async_jinja-1.12.6.tar.gz",
            "has_sig": false,
            "md5_digest": "95f093750d4fdf46e8b6f604b8917bb1",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.13",
            "size": 1210047,
            "upload_time": "2025-07-16T06:38:56",
            "upload_time_iso_8601": "2025-07-16T06:38:56.480548Z",
            "url": "https://files.pythonhosted.org/packages/c0/d2/80931f9ac07969f267ad4c23b39690df871f4b307280e4624a4a57e90577/starlette_async_jinja-1.12.6.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-16 06:38:56",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "lesleslie",
    "github_project": "starlette-async-jinja",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "starlette-async-jinja"
}
        
Elapsed time: 2.66646s