add jinja2 time extension

pull/2544/head
Mauro Schiavinato 5 months ago
parent f527744024
commit 2d819e670a

@ -0,0 +1,67 @@
"""Jinja2 extensions."""
import arrow
from secrets import choice
from jinja2 import nodes
from jinja2.ext import Extension
class TimeExtension(Extension):
"""Jinja2 Extension for dates and times."""
tags = {'now'}
def __init__(self, environment):
"""Jinja2 Extension constructor."""
super().__init__(environment)
environment.extend(datetime_format='%Y-%m-%d')
def _datetime(self, timezone, operator, offset, datetime_format):
d = arrow.now(timezone)
# parse shift params from offset and include operator
shift_params = {}
for param in offset.split(','):
interval, value = param.split('=')
shift_params[interval.strip()] = float(operator + value.strip())
d = d.shift(**shift_params)
if datetime_format is None:
datetime_format = self.environment.datetime_format
return d.strftime(datetime_format)
def _now(self, timezone, datetime_format):
if datetime_format is None:
datetime_format = self.environment.datetime_format
return arrow.now(timezone).strftime(datetime_format)
def parse(self, parser):
"""Parse datetime template and add datetime value."""
lineno = next(parser.stream).lineno
node = parser.parse_expression()
if parser.stream.skip_if('comma'):
datetime_format = parser.parse_expression()
else:
datetime_format = nodes.Const(None)
if isinstance(node, nodes.Add):
call_method = self.call_method(
'_datetime',
[node.left, nodes.Const('+'), node.right, datetime_format],
lineno=lineno,
)
elif isinstance(node, nodes.Sub):
call_method = self.call_method(
'_datetime',
[node.left, nodes.Const('-'), node.right, datetime_format],
lineno=lineno,
)
else:
call_method = self.call_method(
'_now',
[node, datetime_format],
lineno=lineno,
)
return nodes.Output([call_method], lineno=lineno)

@ -12,7 +12,7 @@ JINJA2_MAX_RETURN_PAYLOAD_SIZE = 1024 * int(os.getenv("JINJA2_MAX_RETURN_PAYLOAD
def render(template_str, **args: t.Any) -> str:
jinja2_env = jinja2.sandbox.ImmutableSandboxedEnvironment(extensions=['jinja2_time.TimeExtension'])
jinja2_env = jinja2.sandbox.ImmutableSandboxedEnvironment(extensions=['changedetectionio.jinja_extensions.TimeExtension'])
output = jinja2_env.from_string(template_str).render(args)
return output[:JINJA2_MAX_RETURN_PAYLOAD_SIZE]

@ -3,6 +3,7 @@
import time
from flask import url_for
from .util import live_server_setup, wait_for_all_checks
from ..safe_jinja import render
def test_setup(client, live_server, measure_memory_usage):
@ -56,3 +57,28 @@ def test_jinja2_security_url_query(client, live_server, measure_memory_usage):
assert b'is invalid and cannot be used' in res.data
# Some of the spewed output from the subclasses
assert b'dict_values' not in res.data
def test_add_time(environment):
"""Verify that added time offset can be parsed."""
finalRender = render("{% now 'utc' + 'hours=2,seconds=30' %}")
assert finalRender == "Thu, 10 Dec 2015 01:33:31"
def test_substract_time(environment):
"""Verify that substracted time offset can be parsed."""
finalRender = render("{% now 'utc' - 'minutes=11' %}")
assert finalRender == "Wed, 09 Dec 2015 23:22:01"
def test_offset_with_format(environment):
"""Verify that offset works together with datetime format."""
finalRender = render(
"{% now 'utc' - 'days=2,minutes=33,seconds=1', '%d %b %Y %H:%M:%S' %}"
)
assert finalRender == "07 Dec 2015 23:00:00"

@ -6,7 +6,7 @@
import unittest
import os
from changedetectionio.processors import restock_diff
import changedetectionio.processors.restock_diff.processor as restock_diff
# mostly
class TestDiffBuilder(unittest.TestCase):

@ -63,7 +63,7 @@ werkzeug~=3.0
# Templating, so far just in the URLs but in the future can be for the notifications also
jinja2~=3.1
jinja2-time
arrow
openpyxl
# https://peps.python.org/pep-0508/#environment-markers
# https://github.com/dgtlmoon/changedetection.io/pull/1009

Loading…
Cancel
Save