c56bef871b
Sync docs with Docusaurus / sync (push) Waiting to run
Tests / Check if changed (push) Waiting to run
Tests / format (push) Blocked by required conditions
Tests / check-imports (push) Blocked by required conditions
Tests / Unit / macos-latest (push) Blocked by required conditions
Tests / Unit / ubuntu-latest (push) Blocked by required conditions
Tests / Unit / windows-latest (push) Blocked by required conditions
Tests / mypy (push) Blocked by required conditions
Tests / Integration / ubuntu-latest (push) Blocked by required conditions
Tests / Integration / macos-latest (push) Blocked by required conditions
Tests / Integration / windows-latest (push) Blocked by required conditions
Tests / notify-slack-on-failure (push) Blocked by required conditions
Tests / Mark tests as completed (push) Blocked by required conditions
Docker image release / Build base image (push) Waiting to run
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
135 lines
5.6 KiB
Python
135 lines
5.6 KiB
Python
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
from typing import Any
|
|
|
|
from jinja2 import Environment, meta, nodes
|
|
from jinja2.ext import Extension
|
|
|
|
from haystack.lazy_imports import LazyImport
|
|
|
|
with LazyImport(message='Run "pip install arrow>=1.3.0"') as arrow_import:
|
|
import arrow
|
|
|
|
|
|
class Jinja2TimeExtension(Extension):
|
|
"""A Jinja2 extension for formatting dates and times."""
|
|
|
|
# Syntax for current date
|
|
tags = {"now"}
|
|
|
|
def __init__(self, environment: Environment) -> None:
|
|
"""
|
|
Initializes the JinjaTimeExtension object.
|
|
|
|
:param environment: The Jinja2 environment to initialize the extension with.
|
|
It provides the context where the extension will operate.
|
|
"""
|
|
arrow_import.check()
|
|
super().__init__(environment)
|
|
|
|
@staticmethod
|
|
def _get_datetime(
|
|
timezone: str, operator: str | None = None, offset: str | None = None, datetime_format: str | None = None
|
|
) -> str:
|
|
"""
|
|
Get the current datetime based on timezone, apply any offset if provided, and format the result.
|
|
|
|
:param timezone: The timezone string (e.g., 'UTC' or 'America/New_York') for which the current
|
|
time should be fetched.
|
|
:param operator: The operator ('+' or '-') to apply to the offset (used for adding/subtracting intervals).
|
|
Defaults to None if no offset is applied, otherwise default is '+'.
|
|
:param offset: The offset string in the format 'interval=value' (e.g., 'hours=2,days=1') specifying how much
|
|
to adjust the datetime. The intervals can be any valid interval accepted
|
|
by Arrow (e.g., hours, days, weeks, months). Defaults to None if no adjustment is needed.
|
|
:param datetime_format: The format string to use for formatting the output datetime.
|
|
Defaults to '%Y-%m-%d %H:%M:%S' if not provided.
|
|
"""
|
|
try:
|
|
dt = arrow.now(timezone)
|
|
except Exception as e:
|
|
raise ValueError(f"Invalid timezone {timezone}: {e}") from e
|
|
|
|
if offset and operator:
|
|
try:
|
|
# Parse the offset and apply it to the datetime object
|
|
replace_params: dict[str, Any] = {
|
|
interval.strip(): float(operator + value.strip())
|
|
for param in offset.split(",")
|
|
for interval, value in [param.split("=")]
|
|
}
|
|
# Shift the datetime fields based on the parsed offset
|
|
dt = dt.shift(**replace_params)
|
|
except (ValueError, AttributeError) as e:
|
|
raise ValueError(f"Invalid offset or operator {offset}, {operator}: {e}") from e
|
|
|
|
# Use the provided format or fallback to the default one
|
|
datetime_format = datetime_format or "%Y-%m-%d %H:%M:%S"
|
|
|
|
return dt.strftime(datetime_format)
|
|
|
|
def parse(self, parser: Any) -> nodes.Node | list[nodes.Node]:
|
|
"""
|
|
Parse the template expression to determine how to handle the datetime formatting.
|
|
|
|
:param parser: The parser object that processes the template expressions and manages the syntax tree.
|
|
It's used to interpret the template's structure.
|
|
"""
|
|
lineno = next(parser.stream).lineno
|
|
node = parser.parse_expression()
|
|
# Check if a custom datetime format is provided after a comma
|
|
datetime_format = parser.parse_expression() if parser.stream.skip_if("comma") else nodes.Const(None)
|
|
|
|
# Default Add when no operator is provided
|
|
operator = "+" if isinstance(node, nodes.Add) else "-"
|
|
# Call the _get_datetime method with the appropriate operator and offset, if exist
|
|
call_method = self.call_method(
|
|
"_get_datetime",
|
|
[node.left, nodes.Const(operator), node.right, datetime_format]
|
|
if isinstance(node, (nodes.Add, nodes.Sub))
|
|
else [node, nodes.Const(None), nodes.Const(None), datetime_format],
|
|
lineno=lineno,
|
|
)
|
|
|
|
return nodes.Output([call_method], lineno=lineno)
|
|
|
|
|
|
def _collect_assigned_variables(ast: nodes.Template) -> set[str]:
|
|
"""
|
|
Extract variables assigned within the Jinja2 template AST.
|
|
|
|
:param ast: The Jinja2 Abstract Syntax Tree (AST) of the template.
|
|
|
|
:returns:
|
|
A set of variable names that are assigned within the template.
|
|
"""
|
|
# Collect all variables assigned inside the template via {% set %}
|
|
assigned_variables = set()
|
|
|
|
for node in ast.find_all(nodes.Assign):
|
|
if isinstance(node.target, nodes.Name):
|
|
assigned_variables.add(node.target.name)
|
|
elif isinstance(node.target, (nodes.List, nodes.Tuple)):
|
|
for name_node in node.target.items:
|
|
if isinstance(name_node, nodes.Name):
|
|
assigned_variables.add(name_node.name)
|
|
|
|
return assigned_variables
|
|
|
|
|
|
def _extract_template_variables_and_assignments(env: Environment, template: str) -> tuple[set[str], set[str]]:
|
|
"""
|
|
Extract variables from a Jinja2 template and variables assigned within it.
|
|
|
|
:param env: A Jinja2 environment.
|
|
:param template: A Jinja2 template string.
|
|
:returns: A tuple of (assigned_variables, template_variables) where:
|
|
- assigned_variables: Variables assigned within the template (e.g., via {% set %})
|
|
- template_variables: All undeclared variables used in the template
|
|
"""
|
|
jinja2_ast = env.parse(template)
|
|
template_variables = meta.find_undeclared_variables(jinja2_ast)
|
|
assigned_variables = _collect_assigned_variables(jinja2_ast)
|
|
return assigned_variables, template_variables
|