Skip to content

Commit

Permalink
config: jinja2 "tojson" template filter to use custom JSONEncoder class
Browse files Browse the repository at this point in the history
This is similar to renderers.py, unfortunately it isn't really possible to share code here.

This is because the JSON renderer uses adapters through add_adapter, while the jinja2 template tag needs a custom JSONEncoder class instead.

You cannot pass a default= argument when constructing the JSON renderer constructor, as it already does this internally, resulting in an error that default= is listed twice.

Closes #56
  • Loading branch information
robvdl committed Mar 17, 2024
1 parent 804be88 commit 75b54fe
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/sambal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

with Configurator(settings=SETTINGS) as config:
config.include("pyramid_jinja2")
config.include("sambal.template")
config.include("sambal.renderers")
config.include("sambal.routes")
config.include("pyramid_session_redis")
Expand Down
57 changes: 57 additions & 0 deletions src/sambal/template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import json
from datetime import datetime
from decimal import Decimal
from enum import Enum

from ldb import Dn, MessageElement, Result
from samba.dcerpc.security import descriptor


def includeme(config):
"""Configure the Jinja2 template environment.
Register any custom template filters and custom tags here.
"""

def setup_jinja2_env():
"""Configure Jinja2 tojson filter to use the custom JSONEncoder."""
env = config.get_jinja2_environment()

# Jinja2 cannot use adapters so use a custom JSONEncoder instead.
env.policies["json.dumps_kwargs"].update(
{
"cls": JSONEncoder,
"indent": 2,
"sort_keys": True,
}
)

config.action(None, setup_jinja2_env, order=999)


class JSONEncoder(json.JSONEncoder):
"""Custom JSON encoder to deal with special datatypes.
This feels like repetition from renderers.py, but with pyramid_jinja2
you can only replace the JSONEncoder, while with the Pyramid JSON
renderer you add adapters instead.
Trying to get the Pyramid JSON renderer to use a different encoder
class leads to nothing but trouble.
"""

def default(self, obj):
if isinstance(obj, (Decimal, Dn, MessageElement)):
return str(obj)
if isinstance(obj, Result):
return obj.msgs
elif isinstance(obj, Enum):
return str(obj.value)
elif isinstance(obj, datetime):
return obj.isoformat()
elif isinstance(obj, descriptor):
return obj.as_sddl()
elif getattr(obj, "__json__", None) and callable(obj.__json__):
# The request can be retrieved by thread-local but only if needed.
return obj.__json__(request=None)
return super().default(obj)

0 comments on commit 75b54fe

Please sign in to comment.