Files
mono/artdag/common/artdag_common/middleware/content_negotiation.py
giles 1a74d811f7 Incorporate art-dag-mono repo into artdag/ subfolder
Merges full history from art-dag/mono.git into the monorepo
under the artdag/ directory. Contains: core (DAG engine),
l1 (Celery rendering server), l2 (ActivityPub registry),
common (shared templates/middleware), client (CLI), test (e2e).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

git-subtree-dir: artdag
git-subtree-mainline: 1a179de547
git-subtree-split: 4c2e716558
2026-02-27 09:07:23 +00:00

175 lines
3.7 KiB
Python

"""
Content negotiation utilities.
Helps determine what response format the client wants.
"""
from enum import Enum
from typing import Optional
from fastapi import Request
class ContentType(Enum):
"""Response content types."""
HTML = "text/html"
JSON = "application/json"
ACTIVITY_JSON = "application/activity+json"
XML = "application/xml"
def wants_html(request: Request) -> bool:
"""
Check if the client wants HTML response.
Returns True if:
- Accept header contains text/html
- Accept header contains application/xhtml+xml
- No Accept header (browser default)
Args:
request: FastAPI request
Returns:
True if HTML is preferred
"""
accept = request.headers.get("accept", "")
# No accept header usually means browser
if not accept:
return True
# Check for HTML preferences
if "text/html" in accept:
return True
if "application/xhtml" in accept:
return True
return False
def wants_json(request: Request) -> bool:
"""
Check if the client wants JSON response.
Returns True if:
- Accept header contains application/json
- Accept header does NOT contain text/html
- Request has .json suffix (convention)
Args:
request: FastAPI request
Returns:
True if JSON is preferred
"""
accept = request.headers.get("accept", "")
# Explicit JSON preference
if "application/json" in accept:
# But not if HTML is also requested (browsers often send both)
if "text/html" not in accept:
return True
# Check URL suffix convention
if request.url.path.endswith(".json"):
return True
return False
def wants_activity_json(request: Request) -> bool:
"""
Check if the client wants ActivityPub JSON-LD response.
Used for federation with other ActivityPub servers.
Args:
request: FastAPI request
Returns:
True if ActivityPub format is preferred
"""
accept = request.headers.get("accept", "")
if "application/activity+json" in accept:
return True
if "application/ld+json" in accept:
return True
return False
def get_preferred_type(request: Request) -> ContentType:
"""
Determine the preferred content type from Accept header.
Args:
request: FastAPI request
Returns:
ContentType enum value
"""
if wants_activity_json(request):
return ContentType.ACTIVITY_JSON
if wants_json(request):
return ContentType.JSON
return ContentType.HTML
def is_htmx_request(request: Request) -> bool:
"""
Check if this is an HTMX request (partial page update).
HTMX requests set the HX-Request header.
Args:
request: FastAPI request
Returns:
True if this is an HTMX request
"""
return request.headers.get("HX-Request") == "true"
def get_htmx_target(request: Request) -> Optional[str]:
"""
Get the HTMX target element ID.
Args:
request: FastAPI request
Returns:
Target element ID or None
"""
return request.headers.get("HX-Target")
def get_htmx_trigger(request: Request) -> Optional[str]:
"""
Get the HTMX trigger element ID.
Args:
request: FastAPI request
Returns:
Trigger element ID or None
"""
return request.headers.get("HX-Trigger")
def is_ios_request(request: Request) -> bool:
"""
Check if request is from iOS device.
Useful for video format selection (iOS prefers MP4).
Args:
request: FastAPI request
Returns:
True if iOS user agent detected
"""
user_agent = request.headers.get("user-agent", "").lower()
return "iphone" in user_agent or "ipad" in user_agent