diff --git a/example/vite-app/bootstrap/application.py b/example/vite-app/bootstrap/application.py index 0fbc882a..9dc64939 100644 --- a/example/vite-app/bootstrap/application.py +++ b/example/vite-app/bootstrap/application.py @@ -4,6 +4,7 @@ from fastapi_startkit.logging import LogProvider from fastapi_startkit.vite import ViteProvider +# from config.vite import ViteConfig from providers.fastapi_provider import FastAPIProvider app: Application = Application( @@ -11,8 +12,6 @@ providers=[ LogProvider, FastAPIProvider, - # ViteProvider auto-binds a Jinja2Templates engine (with the vite() - # globals injected) at the configured templates directory. - (ViteProvider, {"templates_directory": "templates"}), + ViteProvider, ], ) diff --git a/example/vite-app/providers/fastapi_provider.py b/example/vite-app/providers/fastapi_provider.py index e43b4331..2ae5d739 100644 --- a/example/vite-app/providers/fastapi_provider.py +++ b/example/vite-app/providers/fastapi_provider.py @@ -1,4 +1,7 @@ +from pathlib import Path + from fastapi import FastAPI +from starlette.templating import Jinja2Templates from fastapi_startkit.fastapi import FastAPIProvider as BaseFastAPIProvider @@ -11,6 +14,11 @@ def register(self) -> None: ) self.app.use_fastapi(fastapi) + # Bind Jinja2Templates so ViteProvider can inject vite() globals into it. + templates_dir = Path(self.app.base_path) / "templates" + templates = Jinja2Templates(directory=str(templates_dir)) + self.app.bind("templates", templates) + def boot(self) -> None: super().boot() diff --git a/example/vite-app/routes/web.py b/example/vite-app/routes/web.py index 73b952b1..78a3f090 100644 --- a/example/vite-app/routes/web.py +++ b/example/vite-app/routes/web.py @@ -1,14 +1,15 @@ -from fastapi import APIRouter +from fastapi import APIRouter, Request from fastapi.responses import HTMLResponse -from fastapi_startkit.vite import template - web = APIRouter() @web.get("/", response_class=HTMLResponse) -async def index(): - return template("index.html") +async def index(request: Request): + from bootstrap.application import app + + templates = app.make("templates") + return templates.TemplateResponse(request, "index.html") @web.get("/api/health") diff --git a/fastapi_startkit/src/fastapi_startkit/fastapi/context.py b/fastapi_startkit/src/fastapi_startkit/fastapi/context.py deleted file mode 100644 index 625224f6..00000000 --- a/fastapi_startkit/src/fastapi_startkit/fastapi/context.py +++ /dev/null @@ -1,9 +0,0 @@ -from contextvars import ContextVar -from typing import Optional - -from starlette.requests import Request - -# Holds the request currently being handled so helpers such as the Vite -# `template()` view renderer can resolve it without it being passed explicitly. -# Set by RequestContextMiddleware for the duration of each request. -current_request: ContextVar[Optional[Request]] = ContextVar("current_request", default=None) diff --git a/fastapi_startkit/src/fastapi_startkit/fastapi/middleware.py b/fastapi_startkit/src/fastapi_startkit/fastapi/middleware.py deleted file mode 100644 index c855ea64..00000000 --- a/fastapi_startkit/src/fastapi_startkit/fastapi/middleware.py +++ /dev/null @@ -1,20 +0,0 @@ -from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint -from starlette.requests import Request -from starlette.responses import Response - -from fastapi_startkit.fastapi.context import current_request - - -class RequestContextMiddleware(BaseHTTPMiddleware): - """Expose the active request through a ContextVar for the request lifetime. - - Lets helpers that have no access to the handler signature (e.g. the Vite - ``template()`` view renderer) resolve the current request implicitly. - """ - - async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response: - token = current_request.set(request) - try: - return await call_next(request) - finally: - current_request.reset(token) diff --git a/fastapi_startkit/src/fastapi_startkit/fastapi/providers/fastapi_provider.py b/fastapi_startkit/src/fastapi_startkit/fastapi/providers/fastapi_provider.py index 627ec535..8639e782 100644 --- a/fastapi_startkit/src/fastapi_startkit/fastapi/providers/fastapi_provider.py +++ b/fastapi_startkit/src/fastapi_startkit/fastapi/providers/fastapi_provider.py @@ -24,11 +24,8 @@ def register(self) -> None: def boot(self): import os - from fastapi_startkit.fastapi.middleware import RequestContextMiddleware - self.commands([ServeCommand]) self._register_exception_handlers() - self.app.add_middleware(RequestContextMiddleware) source = os.path.abspath(os.path.join(os.path.dirname(__file__), "../config/fastapi.py")) self.publishes({source: "config/fastapi.py"}) diff --git a/fastapi_startkit/src/fastapi_startkit/vite/__init__.py b/fastapi_startkit/src/fastapi_startkit/vite/__init__.py index 4447516b..c6c8cf31 100644 --- a/fastapi_startkit/src/fastapi_startkit/vite/__init__.py +++ b/fastapi_startkit/src/fastapi_startkit/vite/__init__.py @@ -1,13 +1,10 @@ from .vite import Vite from .providers.provider import ViteProvider -from .template import Template, template from .exceptions import ViteException, ViteManifestNotFoundException __all__ = [ "Vite", "ViteProvider", - "Template", - "template", "ViteException", "ViteManifestNotFoundException", ] diff --git a/fastapi_startkit/src/fastapi_startkit/vite/config/vite.py b/fastapi_startkit/src/fastapi_startkit/vite/config/vite.py index 80102997..71431700 100644 --- a/fastapi_startkit/src/fastapi_startkit/vite/config/vite.py +++ b/fastapi_startkit/src/fastapi_startkit/vite/config/vite.py @@ -10,5 +10,3 @@ class ViteConfig: asset_url: str = "" static_url: str = "/build" mount_static: bool = True - template: bool = True - templates_directory: str = "resources/templates" diff --git a/fastapi_startkit/src/fastapi_startkit/vite/providers/provider.py b/fastapi_startkit/src/fastapi_startkit/vite/providers/provider.py index 22269276..4e0e7c33 100644 --- a/fastapi_startkit/src/fastapi_startkit/vite/providers/provider.py +++ b/fastapi_startkit/src/fastapi_startkit/vite/providers/provider.py @@ -28,25 +28,6 @@ def register(self) -> None: self.app.bind("vite", vite) - self.register_templates(config) - - def register_templates(self, config: ViteConfig) -> None: - # Provide a template engine out of the box so fresh apps can render - # views and have the vite() globals injected during boot. An existing - # binding always wins, keeping this backward-compatible. - if not config.template or self.app.has("templates"): - return - - try: - from starlette.templating import Jinja2Templates - except ImportError as exc: - raise ImportError( - "Rendering templates requires Jinja2. Install it with: pip install fastapi-startkit[vite]" - ) from exc - - templates_dir = self.app.base_path / config.templates_directory - self.app.bind("templates", Jinja2Templates(directory=str(templates_dir))) - def boot(self) -> None: vite: Vite = self.app.make("vite") config = self.app.make("config").get(self.provider_key) diff --git a/fastapi_startkit/src/fastapi_startkit/vite/template.py b/fastapi_startkit/src/fastapi_startkit/vite/template.py deleted file mode 100644 index 8735c5ae..00000000 --- a/fastapi_startkit/src/fastapi_startkit/vite/template.py +++ /dev/null @@ -1,52 +0,0 @@ -from typing import Optional - -from .exceptions import ViteException - - -def _resolve_request(context: dict): - """Return the request from the context, falling back to the request ContextVar.""" - request = context.pop("request", None) - if request is not None: - return request - - try: - from fastapi_startkit.fastapi.context import current_request - except ImportError: - return None - - return current_request.get() - - -def template(name: str, context: Optional[dict] = None): - """Render a Jinja2 template by name, Laravel ``view()`` style. - - The current request does not need to be passed explicitly: it is taken from - ``context['request']`` when given, otherwise from the per-request ContextVar - set by ``RequestContextMiddleware``. - """ - from fastapi_startkit.application import app as container - - if not container().has("templates"): - raise ViteException( - "No 'templates' binding found. Register the ViteProvider (with " - "`template` enabled) or bind a Jinja2Templates instance as 'templates'." - ) - - templates = container().make("templates") - context = dict(context or {}) - request = _resolve_request(context) - - try: - return templates.TemplateResponse(request, name, context) - except TypeError: - # Starlette < 0.29 only supports the legacy signature where the request - # is supplied inside the context dict. - return templates.TemplateResponse(name, {"request": request, **context}) - - -class Template: - """Static-style accessor for rendering templates.""" - - @staticmethod - def render(name: str, context: Optional[dict] = None): - return template(name, context) diff --git a/fastapi_startkit/tests/vite/test_template.py b/fastapi_startkit/tests/vite/test_template.py deleted file mode 100644 index d4ff6223..00000000 --- a/fastapi_startkit/tests/vite/test_template.py +++ /dev/null @@ -1,85 +0,0 @@ -import pytest -from starlette.requests import Request -from starlette.responses import Response - -from fastapi_startkit.application import Application -from fastapi_startkit.providers import Provider -from fastapi_startkit.vite import Template, ViteProvider, template -from fastapi_startkit.vite.exceptions import ViteException - - -def make_request() -> Request: - return Request( - { - "type": "http", - "method": "GET", - "path": "/", - "headers": [], - "query_string": b"", - } - ) - - -def make_app(tmp_path, providers=None, write_template=False) -> Application: - if write_template: - templates_dir = tmp_path / "resources" / "templates" - templates_dir.mkdir(parents=True) - (templates_dir / "index.html").write_text("