mirror of
https://github.com/apache/superset.git
synced 2026-04-25 02:55:07 +00:00
feat(Jinja): to_datetime filter (#32781)
This commit is contained in:
@@ -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 `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.
|
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 %}
|
||||||
|
```
|
||||||
|
|||||||
@@ -561,6 +561,24 @@ class WhereInMacro: # pylint: disable=too-few-public-methods
|
|||||||
return result
|
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:
|
class BaseTemplateProcessor:
|
||||||
"""
|
"""
|
||||||
Base class for database-specific jinja context
|
Base class for database-specific jinja context
|
||||||
@@ -596,6 +614,7 @@ class BaseTemplateProcessor:
|
|||||||
|
|
||||||
# custom filters
|
# custom filters
|
||||||
self.env.filters["where_in"] = WhereInMacro(database.get_dialect())
|
self.env.filters["where_in"] = WhereInMacro(database.get_dialect())
|
||||||
|
self.env.filters["to_datetime"] = to_datetime
|
||||||
|
|
||||||
def set_context(self, **kwargs: Any) -> None:
|
def set_context(self, **kwargs: Any) -> None:
|
||||||
self._context.update(kwargs)
|
self._context.update(kwargs)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
# pylint: disable=invalid-name, unused-argument
|
# pylint: disable=invalid-name, unused-argument
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -38,6 +39,7 @@ from superset.jinja_context import (
|
|||||||
metric_macro,
|
metric_macro,
|
||||||
safe_proxy,
|
safe_proxy,
|
||||||
TimeFilter,
|
TimeFilter,
|
||||||
|
to_datetime,
|
||||||
WhereInMacro,
|
WhereInMacro,
|
||||||
)
|
)
|
||||||
from superset.models.core import Database
|
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
|
assert where_in([], default_to_none=True) is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"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
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"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:
|
def test_dataset_macro(mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
Test the ``dataset_macro`` macro.
|
Test the ``dataset_macro`` macro.
|
||||||
|
|||||||
Reference in New Issue
Block a user