866e6e308fb4831e0d059c0e29152278b0785adb
- Document Jinja2 templating system with examples - Document authentication middleware (UserContext, cookie/header parsing) - Document content negotiation utilities - List all reusable template components - Document custom Jinja2 filters 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
artdag-common
Shared components for Art-DAG L1 (celery) and L2 (activity-pub) servers.
Features
- Jinja2 Templating: Unified template environment with shared base templates
- Reusable Components: Cards, tables, pagination, DAG visualization, media preview
- Authentication Middleware: Cookie and JWT token parsing
- Content Negotiation: HTML/JSON/ActivityPub format detection
- Utility Functions: Hash truncation, file size formatting, status colors
Installation
pip install -e /path/to/artdag-common
# Or add to requirements.txt
-e file:../common
Quick Start
from fastapi import FastAPI, Request
from artdag_common import create_jinja_env, render
app = FastAPI()
# Initialize templates with app-specific directory
templates = create_jinja_env("app/templates")
@app.get("/")
async def home(request: Request):
return render(templates, "home.html", request, title="Home")
Package Structure
artdag_common/
├── __init__.py # Package exports
├── constants.py # CDN URLs, colors, configs
├── rendering.py # Jinja2 environment and helpers
├── middleware/
│ ├── auth.py # Authentication utilities
│ └── content_negotiation.py # Accept header parsing
├── models/
│ ├── requests.py # Shared request models
│ └── responses.py # Shared response models
├── utils/
│ ├── formatting.py # Text/date formatting
│ ├── media.py # Media type detection
│ └── pagination.py # Pagination helpers
└── templates/
├── base.html # Base layout template
└── components/
├── badge.html # Status/type badges
├── card.html # Info cards
├── dag.html # Cytoscape DAG visualization
├── media_preview.html # Video/image/audio preview
├── pagination.html # HTMX pagination
└── table.html # Styled tables
Jinja2 Templates
Base Template
The base.html template provides:
- Dark theme with Tailwind CSS
- HTMX integration
- Navigation slot
- Content block
- Optional Cytoscape.js block
{% extends "base.html" %}
{% block title %}My Page{% endblock %}
{% block content %}
<h1>Hello World</h1>
{% endblock %}
Reusable Components
Card
{% include "components/card.html" %}
<!-- Usage in your template -->
<div class="...card styles...">
{% block card_title %}Title{% endblock %}
{% block card_content %}Content{% endblock %}
</div>
Badge
Status and type badges with appropriate colors:
{% from "components/badge.html" import status_badge, type_badge %}
{{ status_badge("completed") }} <!-- Green -->
{{ status_badge("failed") }} <!-- Red -->
{{ type_badge("video") }}
DAG Visualization
Interactive Cytoscape.js graph:
{% include "components/dag.html" %}
Requires passing nodes and edges data to template context.
Media Preview
Responsive media preview with format detection:
{% include "components/media_preview.html" %}
Supports video, audio, and image formats.
Pagination
HTMX-powered infinite scroll pagination:
{% include "components/pagination.html" %}
Template Rendering
Full Page Render
from artdag_common import render
@app.get("/runs/{run_id}")
async def run_detail(run_id: str, request: Request):
run = get_run(run_id)
return render(templates, "runs/detail.html", request, run=run)
Fragment Render (HTMX)
from artdag_common import render_fragment
@app.get("/runs/{run_id}/status")
async def run_status_fragment(run_id: str):
run = get_run(run_id)
html = render_fragment(templates, "components/status.html", status=run.status)
return HTMLResponse(html)
Authentication Middleware
UserContext
from artdag_common.middleware.auth import UserContext, get_user_from_cookie
@app.get("/profile")
async def profile(request: Request):
user = get_user_from_cookie(request)
if not user:
return RedirectResponse("/login")
return {"username": user.username, "actor_id": user.actor_id}
Token Parsing
from artdag_common.middleware.auth import get_user_from_header, decode_jwt_claims
@app.get("/api/me")
async def api_me(request: Request):
user = get_user_from_header(request)
if not user:
raise HTTPException(401, "Not authenticated")
return {"user": user.username}
Content Negotiation
Detect what response format the client wants:
from artdag_common.middleware.content_negotiation import wants_html, wants_json, wants_activity_json
@app.get("/users/{username}")
async def user_profile(username: str, request: Request):
user = get_user(username)
if wants_activity_json(request):
return ActivityPubActor(user)
elif wants_json(request):
return user.dict()
else:
return render(templates, "users/profile.html", request, user=user)
Constants
CDN URLs
from artdag_common import TAILWIND_CDN, HTMX_CDN, CYTOSCAPE_CDN
# Available in templates as globals:
# {{ TAILWIND_CDN }}
# {{ HTMX_CDN }}
# {{ CYTOSCAPE_CDN }}
Node Colors
from artdag_common import NODE_COLORS
# {
# "SOURCE": "#3b82f6", # Blue
# "EFFECT": "#22c55e", # Green
# "OUTPUT": "#a855f7", # Purple
# "ANALYSIS": "#f59e0b", # Amber
# "_LIST": "#6366f1", # Indigo
# "default": "#6b7280", # Gray
# }
Status Colors
STATUS_COLORS = {
"completed": "bg-green-600",
"cached": "bg-blue-600",
"running": "bg-yellow-600",
"pending": "bg-gray-600",
"failed": "bg-red-600",
}
Custom Jinja2 Filters
The following filters are available in all templates:
| Filter | Usage | Description |
|---|---|---|
truncate_hash |
{{ hash|truncate_hash }} |
Shorten hash to 16 chars with ellipsis |
format_size |
{{ bytes|format_size }} |
Format bytes as KB/MB/GB |
status_color |
{{ status|status_color }} |
Get Tailwind class for status |
Example:
<span class="{{ run.status|status_color }}">
{{ run.status }}
</span>
<code>{{ content_hash|truncate_hash }}</code>
<span>{{ file_size|format_size }}</span>
Development
cd /root/art-dag/common
# Install in development mode
pip install -e .
# Run tests
pytest
Dependencies
fastapi>=0.100.0- Web frameworkjinja2>=3.1.0- Templating enginepydantic>=2.0.0- Data validation
Description
Languages
Python
64.1%
HTML
35.9%