Source code for tet.renderers.json

"""
JSON rendering with custom type adapters for Tet applications.

This module provides a JSON renderer with sensible defaults and the ability
to register custom type adapters. It is included automatically when using
the ``renderers.json`` feature.

Features
--------

- Automatic serialization of :class:`datetime.datetime` and :class:`datetime.date`
  to ISO 8601 format
- SQLAlchemy keyed tuple support (when SQLAlchemy is installed)
- Extensible via custom type adapters

Example
-------

Adding a custom adapter for a model class::

    from tet.config import application_factory

    @application_factory(included_features=["renderers.json"])
    def main(config):
        config.add_json_adapter(
            for_=MyModel,
            adapter=lambda obj, request: {"id": obj.id, "name": obj.name},
        )
        config.scan()

Using a custom JSON renderer factory::

    from pyramid.renderers import JSON
    from tet.renderers.json import construct_default_renderer

    # Create a renderer with custom settings
    renderer = construct_default_renderer(
        renderer_factory=JSON,
        serializer=custom_serializer,
    )
"""
import datetime
from typing import Any, Callable, Dict

from pyramid.config import Configurator
from pyramid.renderers import JSON


def _get_json_renderer_registry(config: Configurator) -> Dict[str, Any]:
    if not hasattr(config.registry, 'tet_json_renderers'):
        config.registry.tet_json_renderers = {}

    return config.registry.tet_json_renderers


[docs] def hook_json_renderer( config: Configurator, *, renderer: Any, name: str = "json" ): """ Register a JSON renderer with the given name. :param config: Pyramid Configurator :param renderer: The JSON renderer instance :param name: Name for the renderer (default: 'json') """ config.add_renderer(name, renderer) _get_json_renderer_registry(config)[name] = renderer
[docs] def add_json_adapter( config: Configurator, *, for_: type, adapter: Callable[[Any], Any], renderer: str = "json", ): """ Add a type adapter to a JSON renderer. :param config: Pyramid Configurator :param for_: The type to adapt :param adapter: Callable taking (obj, request) and returning JSON-serializable data :param renderer: Name of the renderer to add the adapter to (default: 'json') """ _get_json_renderer_registry(config)[renderer].add_adapter( type_or_iface=for_, adapter=adapter )
[docs] def construct_default_renderer( renderer_factory: Callable[..., Any] = JSON, **renderer_args ): """ Construct a JSON renderer with default type adapters. Adds adapters for: - ``datetime.datetime`` and ``datetime.date`` (ISO 8601 format) - SQLAlchemy ``AbstractKeyedTuple`` (converted to dict) :param renderer_factory: Factory callable for creating the renderer :param renderer_args: Additional arguments passed to the factory :return: Configured JSON renderer instance """ json_renderer = renderer_factory(**renderer_args) try: from sqlalchemy.util._collections import AbstractKeyedTuple json_renderer.add_adapter(AbstractKeyedTuple, lambda o, req: o._asdict()) del AbstractKeyedTuple except ImportError: pass json_renderer.add_adapter(datetime.datetime, lambda d, req: d.isoformat()) json_renderer.add_adapter(datetime.date, lambda d, req: d.isoformat()) return json_renderer
[docs] def includeme(config: Configurator): """ Pyramid includeme function for JSON rendering. Registers a default JSON renderer and adds configuration directives: - ``config.add_json_renderer()`` - ``config.add_json_adapter()`` """ renderer = construct_default_renderer() hook_json_renderer(config, renderer=renderer) config.add_directive("add_json_renderer", hook_json_renderer) config.add_directive("add_json_adapter", add_json_adapter)