This is an automated email from the ASF dual-hosted git repository.

vavila pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new cedd186c21 feat(Jinja): to_datetime filter (#32781)
cedd186c21 is described below

commit cedd186c21e46df134bdfd2cf4c8df753977f1b6
Author: Vitor Avila <[email protected]>
AuthorDate: Mon Mar 24 16:55:37 2025 -0300

    feat(Jinja): to_datetime filter (#32781)
---
 docs/docs/configuration/sql-templating.mdx | 34 ++++++++++++++++++
 superset/jinja_context.py                  | 19 +++++++++++
 tests/unit_tests/jinja_context_test.py     | 55 ++++++++++++++++++++++++++++++
 3 files changed, 108 insertions(+)

diff --git a/docs/docs/configuration/sql-templating.mdx 
b/docs/docs/configuration/sql-templating.mdx
index 0e618fd9c1..7f094f1d87 100644
--- a/docs/docs/configuration/sql-templating.mdx
+++ b/docs/docs/configuration/sql-templating.mdx
@@ -461,3 +461,37 @@ This macro avoids copy/paste, allowing users to centralize 
the metric definition
 
 The `dataset_id` parameter is optional, and if not provided Superset will use 
the current dataset from context (for example, when using this macro in the 
Chart Builder, by default the `macro_key` will be searched in the dataset 
powering the chart).
 The parameter can be used in SQL Lab, or when fetching a metric from another 
dataset.
+
+## Available Filters
+
+Superset supports [builtin filters from the Jinja2 templating 
package](https://jinja.palletsprojects.com/en/stable/templates/#builtin-filters).
 Custom filters have also been implemented:
+
+**Where In**
+Parses a list into a SQL-compatible statement. This is useful with macros that 
return an array (for example the `filter_values` macro):
+
+```
+Dashboard filter with "First", "Second" and "Third" options selected
+{{ filter_values('column') }} => ["First", "Second", "Third"]
+{{ filter_values('column')|where_in }} => ('First', 'Second', 'Third')
+```
+
+By default, this filter returns `()` (as a string) in case the value is null. 
The `default_to_none` parameter can be se to `True` to return null in this case:
+
+```
+Dashboard filter without any value applied
+{{ filter_values('column') }} => ()
+{{ filter_values('column')|where_in(default_to_none=True) }} => None
+```
+
+**To Datetime**
+
+Loads a string as a `datetime` object. This is useful when performing date 
operations. For example:
+```
+{% set from_expr = get_time_filter("dttm", strftime="%Y-%m-%d").from_expr %}
+{% set to_expr = get_time_filter("dttm", strftime="%Y-%m-%d").to_expr %}
+{% if (to_expr|to_datetime(format="%Y-%m-%d") - 
from_expr|to_datetime(format="%Y-%m-%d")).days > 100 %}
+   do something
+{% else %}
+   do something else
+{% endif %}
+```
diff --git a/superset/jinja_context.py b/superset/jinja_context.py
index a4d8e6d6a4..0f56886dd2 100644
--- a/superset/jinja_context.py
+++ b/superset/jinja_context.py
@@ -561,6 +561,24 @@ class WhereInMacro:  # pylint: 
disable=too-few-public-methods
         return result
 
 
+def to_datetime(
+    value: str | None, format: str = "%Y-%m-%d %H:%M:%S"
+) -> datetime | None:
+    """
+    Parses a string into a datetime object.
+
+    :param value: the string to parse.
+    :param format: the format to parse the string with.
+    :returns: the parsed datetime object.
+    """
+    if not value:
+        return None
+
+    # This value might come from a macro that could be including wrapping 
quotes
+    value = value.strip("'\"")
+    return datetime.strptime(value, format)
+
+
 class BaseTemplateProcessor:
     """
     Base class for database-specific jinja context
@@ -596,6 +614,7 @@ class BaseTemplateProcessor:
 
         # custom filters
         self.env.filters["where_in"] = WhereInMacro(database.get_dialect())
+        self.env.filters["to_datetime"] = to_datetime
 
     def set_context(self, **kwargs: Any) -> None:
         self._context.update(kwargs)
diff --git a/tests/unit_tests/jinja_context_test.py 
b/tests/unit_tests/jinja_context_test.py
index be6e5fb55e..5cc6f218a8 100644
--- a/tests/unit_tests/jinja_context_test.py
+++ b/tests/unit_tests/jinja_context_test.py
@@ -17,6 +17,7 @@
 # pylint: disable=invalid-name, unused-argument
 from __future__ import annotations
 
+from datetime import datetime
 from typing import Any
 
 import pytest
@@ -38,6 +39,7 @@ from superset.jinja_context import (
     metric_macro,
     safe_proxy,
     TimeFilter,
+    to_datetime,
     WhereInMacro,
 )
 from superset.models.core import Database
@@ -429,6 +431,59 @@ def test_where_in_empty_list() -> None:
     assert where_in([], default_to_none=True) is None
 
 
[email protected](
+    "value,format,output",
+    [
+        ("2025-03-20 15:55:00", None, datetime(2025, 3, 20, 15, 55)),
+        (None, None, None),
+        ("2025-03-20", "%Y-%m-%d", datetime(2025, 3, 20)),
+        ("'2025-03-20'", "%Y-%m-%d", datetime(2025, 3, 20)),
+    ],
+)
+def test_to_datetime(
+    value: str | None, format: str | None, output: datetime | None
+) -> None:
+    """
+    Test the ``to_datetime`` custom filter.
+    """
+
+    result = (
+        to_datetime(value, format=format) if format is not None else 
to_datetime(value)
+    )
+    assert result == output
+
+
[email protected](
+    "value,format,match",
+    [
+        (
+            "2025-03-20",
+            None,
+            "time data '2025-03-20' does not match format '%Y-%m-%d %H:%M:%S'",
+        ),
+        (
+            "2025-03-20 15:55:00",
+            "%Y-%m-%d",
+            "unconverted data remains:  15:55:00",
+        ),
+    ],
+)
+def test_to_datetime_raises(value: str, format: str | None, match: str) -> 
None:
+    """
+    Test the ``to_datetime`` custom filter raises with an incorrect
+    format.
+    """
+    with pytest.raises(
+        ValueError,
+        match=match,
+    ):
+        (
+            to_datetime(value, format=format)
+            if format is not None
+            else to_datetime(value)
+        )
+
+
 def test_dataset_macro(mocker: MockerFixture) -> None:
     """
     Test the ``dataset_macro`` macro.

Reply via email to