Skip to content

Configuring, extending, and diagnosing views

View renders server-side HTML with async Jinja2 and returns an Orionis response.

Paths are searched in order: a template in the first directory shadows the same name in later directories. The __orionis__/default/ prefix is reserved for packaged pages and resolves before application paths. Relative search and cache paths resolve against app.basePath.

Jinja’s template cache differs from bytecode caching: cache_size bounds compiled objects in memory; cache_path persists bytecode on disk. Setting cache_path=None disables only the latter. With auto_reload disabled, restart or rebuild the environment when deploying changes: editing a template does not force a warm process to update. Neither cache stores complete HTML responses or controls cache headers.

Undefined is permissive: a missing variable can render empty text. This helps optional views but can hide naming mistakes. Prepare explicit context and verify pages with populated data, empty data, and guests. Templates are trusted application code: this environment is not a sandbox for arbitrary user templates.

A custom provider must run after View services have been registered. Add its class to the application provider registry according to Foundation configuration. This example shows the extension body; it does not automatically register the class:

from orionis.container.providers.service_provider import ServiceProvider
from orionis.view.contracts.environment import IViewEnvironment
class ViewHelpersProvider(ServiceProvider):
async def boot(self) -> None:
environment: IViewEnvironment = await self.app.make(IViewEnvironment)
environment.addFilter("money", lambda value: f"${value:,.2f}")
environment.addTest("positive", lambda value: value > 0)
environment.addGlobal("support_email", "help@example.com")

Save this provider as app/providers/view_helpers_provider.py. Import ViewHelpersProvider in bootstrap/app.py and add it to the existing app.withProviders(...) call before app.create(). Preserve the providers already registered there; adding a class file alone does not execute its boot method.

Use them with {{ total | money }}, {% if total is positive %}, and {{ support_email }}. addGlobal/addFilter/addTest replace existing names; avoid accidentally overriding fundamental helpers. addExtension accepts Jinja extensions and wraps registration failures in ViewException.

For an alternative engine, implement async IViewEngine.render(template, context) -> str and replace that binding before resolving IViewFactory. The factory and engine are singletons: changing a binding after resolution does not automatically rebuild existing references. Customize the environment during boot and treat it as shared read-only state during requests. Never store a user or tenant in a mutable global.

Environment variableDefaultMeaning
VIEW_PATHS["resources/views"]Ordered template search directories.
VIEW_CACHE_SIZE400Compiled-template memory cache; 0 disables, -1 is unlimited.
VIEW_CACHE_PATHstorage/framework/viewsDisk bytecode-cache directory; use None in configuration to disable.
APP_DEBUGTrueSupplies auto_reload; normally disable in production.
VIEW_AUTOESCAPETrueAutomatic HTML escaping for application templates.

Paths and the cache directory may be absolute; relative values resolve against the application base path. paths must be a non-empty list or tuple of strings, and cache_size must be at least -1.

Apply these options in your application’s config/view.py or in the environment entries it reads. The configuration object below only validates its values; assigning it to a local Python variable does not reconfigure the application.

from orionis.foundation.config.view import View
config = View(
paths=["resources/views", "packages/admin/views"],
cache_size=400,
cache_path="storage/framework/views",
auto_reload=False,
autoescape=True,
)
assert config.paths[0] == "resources/views"
assert config.autoescape is True
  • ViewEnvironment.addGlobal(name, value) registers a template-global value or callable.
  • addFilter(name, callback) registers a value | filter operation.
  • addTest(name, callback) registers a value is test predicate.
  • addExtension(extension) registers a Jinja2 extension and wraps failures in ViewException.
  • getJinjaEnvironment() returns the shared environment; external code should treat it as read-only.
  • Missing templates preserve ViewTemplateNotFoundException; other pending-render failures are wrapped in ViewRenderException with the original cause.
  • Jinja template errors are wrapped in ViewRenderException by the engine.
  • Unknown chained attributes on PendingView raise AttributeError; only callable HTMLResponse attributes may be queued.
  • Flash operations silently do nothing when no session service is available.
  • errors.first() returns the first available message and the error bag normalizes scalar messages to lists.
  • The JSON filter returns str(value) when msgspec cannot encode a value.
  • The Markdown filter emits HTML; autoescaping rules still determine whether that string is treated as markup when inserted into a template.
  • The {% csrf %} extension raises ViewRenderException if csrf_field is not registered.
  • An unknown named route or a missing path parameter raises ViewRouteException.
  • Jinja’s default Undefined is permissive; a missing variable usually renders empty rather than raising.

The environment, engine, and factory are singletons. Jinja keeps up to cache_size compiled templates in memory and optionally persists bytecode to disk. Cache filenames include a readable flattened template name plus an eight-character SHA-1 digest to prevent extension/path collisions.

Template-name normalization uses a lock-protected LRU cache capped at 1,024 entries. Named-route interpolation plans are memoized; racing threads can only calculate and store the same deterministic value. Rendering is async, while the shared environment is mutated only during application boot. Register custom globals, filters, tests, and extensions during provider boot rather than during concurrent requests.

The module requires Python 3.14+ and Jinja2 async rendering. Templates use Jinja2 syntax, not Python format strings. PendingView returns Orionis’s native HTMLResponse, so response headers, cookies, background work, and downstream transports follow the HTTP module’s contracts.

orionis.view has no root re-exports; use orionis.support.facades.View for application code or import concrete classes from their defining modules for extensions and isolated tests.

OrionisBytecodeCache uses readable names plus an eight-character digest to distinguish paths and extensions. This is a regenerable compiled cache; do not use it as business-data storage. Inspect a key without opening files:

from orionis.view.cache import OrionisBytecodeCache
cache = OrionisBytecodeCache("storage/framework/views")
key = cache.getCacheKey("users/profile.html")
assert key.startswith("users.profile.")
assert len(key.rsplit(".", 1)[-1]) == 8
assert key != cache.getCacheKey("users/profile.j2")

Index · foundation · container · failure