From 1f0d4ae777657e2d7bad180a95c8de5021cd5022 Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Thu, 30 Jan 2025 17:39:41 +0530 Subject: [PATCH 1/8] add support for parseable --- pyproject.toml | 1 + superset/db_engine_specs/parseable.py | 54 +++++++++++++++ .../db_engine_specs/test_parseable.py | 69 +++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 superset/db_engine_specs/parseable.py create mode 100644 tests/unit_tests/db_engine_specs/test_parseable.py diff --git a/pyproject.toml b/pyproject.toml index 0dc3d3f64e9b..032e31eeb204 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -156,6 +156,7 @@ ocient = [ "geojson", ] oracle = ["cx-Oracle>8.0.0, <8.1"] +parseable = ["sqlalchemy-parseable>=0.1.0, <0.2.0"] pinot = ["pinotdb>=5.0.0, <6.0.0"] playwright = ["playwright>=1.37.0, <2"] postgres = ["psycopg2-binary==2.9.6"] diff --git a/superset/db_engine_specs/parseable.py b/superset/db_engine_specs/parseable.py new file mode 100644 index 000000000000..ce8e9ff704d3 --- /dev/null +++ b/superset/db_engine_specs/parseable.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +from datetime import datetime +from typing import Any, TYPE_CHECKING + +from sqlalchemy import types + +from superset.constants import TimeGrain +from superset.db_engine_specs.base import BaseEngineSpec + +if TYPE_CHECKING: + from superset.connectors.sqla.models import TableColumn + + +class ParseableEngineSpec(BaseEngineSpec): + engine = "parseable" + engine_name = "Parseable" + + _time_grain_expressions = { + None: "{col}", + TimeGrain.SECOND: "date_trunc('second', {col})", + TimeGrain.MINUTE: "date_trunc('minute', {col})", + TimeGrain.HOUR: "date_trunc('hour', {col})", + TimeGrain.DAY: "date_trunc('day', {col})", + TimeGrain.WEEK: "date_trunc('week', {col})", + TimeGrain.MONTH: "date_trunc('month', {col})", + TimeGrain.QUARTER: "date_trunc('quarter', {col})", + TimeGrain.YEAR: "date_trunc('year', {col})", + } + + @classmethod + def epoch_to_dttm(cls) -> str: + return "to_timestamp({col})" + + @classmethod + def epoch_ms_to_dttm(cls) -> str: + return "to_timestamp({col} / 1000)" + + @classmethod + def convert_dttm( + cls, target_type: str, dttm: datetime, db_extra: dict[str, Any] | None = None + ) -> str | None: + sqla_type = cls.get_sqla_column_type(target_type) + + if isinstance(sqla_type, types.TIMESTAMP): + return f"'{dttm.strftime('%Y-%m-%dT%H:%M:%S.000')}'" + return None + + @classmethod + + def alter_new_orm_column(cls, orm_col: TableColumn) -> None: + if orm_col.column_name == "p_timestamp": + orm_col.python_date_format = "epoch_ms" + orm_col.is_dttm = True diff --git a/tests/unit_tests/db_engine_specs/test_parseable.py b/tests/unit_tests/db_engine_specs/test_parseable.py new file mode 100644 index 000000000000..9c5836e9a5b6 --- /dev/null +++ b/tests/unit_tests/db_engine_specs/test_parseable.py @@ -0,0 +1,69 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from superset.connectors.sqla.models import SqlaTable, TableColumn +from superset.db_engine_specs.parseable import ParseableEngineSpec +from superset.models.core import Database +from tests.db_engine_specs.base_tests import TestDbEngineSpec + + +class TestParseableEngineSpec(TestDbEngineSpec): + def test_convert_dttm(self): + """ + DB Eng Specs (parseable): Test conversion to date time + """ + dttm = self.get_dttm() + assert ParseableEngineSpec.convert_dttm("TIMESTAMP", dttm) == "to_timestamp('2019-01-02T03:04:05.678900')" + + def test_epoch_to_dttm(self): + """ + DB Eng Specs (parseable): Test epoch to dttm + """ + assert ParseableEngineSpec.epoch_to_dttm() == "to_timestamp({col})" + + def test_epoch_ms_to_dttm(self): + """ + DB Eng Specs (parseable): Test epoch ms to dttm + """ + assert ParseableEngineSpec.epoch_ms_to_dttm() == "to_timestamp({col} / 1000)" + + def test_alter_new_orm_column(self): + """ + DB Eng Specs (parseable): Test alter orm column + """ + database = Database(database_name="parseable", sqlalchemy_uri="parseable://db") + tbl = SqlaTable(table_name="logs", database=database) + col = TableColumn(column_name="p_timestamp", type="TIMESTAMP", table=tbl) + ParseableEngineSpec.alter_new_orm_column(col) + assert col.python_date_format == "epoch_ms" + + def test_time_grain_expressions(self): + """ + DB Eng Specs (parseable): Test time grain expressions + """ + col = "p_timestamp" + sqls = ParseableEngineSpec.get_time_grain_expressions() + + # Test different time grains + assert sqls["Second"] == f"date_trunc('second', {col})" + assert sqls["Minute"] == f"date_trunc('minute', {col})" + assert sqls["Hour"] == f"date_trunc('hour', {col})" + assert sqls["Day"] == f"date_trunc('day', {col})" + assert sqls["Week"] == f"date_trunc('week', {col})" + assert sqls["Month"] == f"date_trunc('month', {col})" + assert sqls["Quarter"] == f"date_trunc('quarter', {col})" + assert sqls["Year"] == f"date_trunc('year', {col})" From 4cd9ca29eefd1386e96a60e9f55f5afd0f6c4d07 Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Fri, 31 Jan 2025 03:04:04 +0530 Subject: [PATCH 2/8] update parseable --- pyproject.toml | 2 +- superset/db_engine_specs/parseable.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 032e31eeb204..509351a36bbe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -156,7 +156,7 @@ ocient = [ "geojson", ] oracle = ["cx-Oracle>8.0.0, <8.1"] -parseable = ["sqlalchemy-parseable>=0.1.0, <0.2.0"] +parseable = ["sqlalchemy-parseable==0.1.3"] pinot = ["pinotdb>=5.0.0, <6.0.0"] playwright = ["playwright>=1.37.0, <2"] postgres = ["psycopg2-binary==2.9.6"] diff --git a/superset/db_engine_specs/parseable.py b/superset/db_engine_specs/parseable.py index ce8e9ff704d3..f742be192a83 100644 --- a/superset/db_engine_specs/parseable.py +++ b/superset/db_engine_specs/parseable.py @@ -1,3 +1,5 @@ +# In superset/db_engine_specs/parseable.py + from __future__ import annotations from datetime import datetime @@ -16,6 +18,7 @@ class ParseableEngineSpec(BaseEngineSpec): engine = "parseable" engine_name = "Parseable" + # Use same expressions as SQLAlchemy dialect for consistency _time_grain_expressions = { None: "{col}", TimeGrain.SECOND: "date_trunc('second', {col})", @@ -47,8 +50,19 @@ def convert_dttm( return None @classmethod - def alter_new_orm_column(cls, orm_col: TableColumn) -> None: + """Handle p_timestamp column specifically for Parseable.""" if orm_col.column_name == "p_timestamp": orm_col.python_date_format = "epoch_ms" orm_col.is_dttm = True + + @classmethod + def get_extra_params(cls, database: "Database") -> dict[str, Any]: + """Additional parameters for Parseable connections.""" + return { + "engine_params": { + "connect_args": { + "timeout": 300, # 5 minutes timeout + } + } + } \ No newline at end of file From a1156d9742441333e647c796f0a4f9ce631da1aa Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Fri, 31 Jan 2025 03:16:52 +0530 Subject: [PATCH 3/8] run tests with pytest --- .../db_engine_specs/test_parseable.py | 100 ++++++++++-------- 1 file changed, 54 insertions(+), 46 deletions(-) diff --git a/tests/unit_tests/db_engine_specs/test_parseable.py b/tests/unit_tests/db_engine_specs/test_parseable.py index 9c5836e9a5b6..2f0ed31189da 100644 --- a/tests/unit_tests/db_engine_specs/test_parseable.py +++ b/tests/unit_tests/db_engine_specs/test_parseable.py @@ -14,56 +14,64 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +from datetime import datetime +from typing import Optional -from superset.connectors.sqla.models import SqlaTable, TableColumn -from superset.db_engine_specs.parseable import ParseableEngineSpec -from superset.models.core import Database -from tests.db_engine_specs.base_tests import TestDbEngineSpec +import pytest +from tests.unit_tests.db_engine_specs.utils import assert_convert_dttm +from tests.unit_tests.fixtures.common import dttm # noqa: F401 -class TestParseableEngineSpec(TestDbEngineSpec): - def test_convert_dttm(self): - """ - DB Eng Specs (parseable): Test conversion to date time - """ - dttm = self.get_dttm() - assert ParseableEngineSpec.convert_dttm("TIMESTAMP", dttm) == "to_timestamp('2019-01-02T03:04:05.678900')" - def test_epoch_to_dttm(self): - """ - DB Eng Specs (parseable): Test epoch to dttm - """ - assert ParseableEngineSpec.epoch_to_dttm() == "to_timestamp({col})" +def test_epoch_to_dttm() -> None: + """ + DB Eng Specs (parseable): Test epoch to dttm + """ + from superset.db_engine_specs.parseable import ParseableEngineSpec - def test_epoch_ms_to_dttm(self): - """ - DB Eng Specs (parseable): Test epoch ms to dttm - """ - assert ParseableEngineSpec.epoch_ms_to_dttm() == "to_timestamp({col} / 1000)" + assert ParseableEngineSpec.epoch_to_dttm() == "to_timestamp({col})" - def test_alter_new_orm_column(self): - """ - DB Eng Specs (parseable): Test alter orm column - """ - database = Database(database_name="parseable", sqlalchemy_uri="parseable://db") - tbl = SqlaTable(table_name="logs", database=database) - col = TableColumn(column_name="p_timestamp", type="TIMESTAMP", table=tbl) - ParseableEngineSpec.alter_new_orm_column(col) - assert col.python_date_format == "epoch_ms" - def test_time_grain_expressions(self): - """ - DB Eng Specs (parseable): Test time grain expressions - """ - col = "p_timestamp" - sqls = ParseableEngineSpec.get_time_grain_expressions() - - # Test different time grains - assert sqls["Second"] == f"date_trunc('second', {col})" - assert sqls["Minute"] == f"date_trunc('minute', {col})" - assert sqls["Hour"] == f"date_trunc('hour', {col})" - assert sqls["Day"] == f"date_trunc('day', {col})" - assert sqls["Week"] == f"date_trunc('week', {col})" - assert sqls["Month"] == f"date_trunc('month', {col})" - assert sqls["Quarter"] == f"date_trunc('quarter', {col})" - assert sqls["Year"] == f"date_trunc('year', {col})" +def test_epoch_ms_to_dttm() -> None: + """ + DB Eng Specs (parseable): Test epoch ms to dttm + """ + from superset.db_engine_specs.parseable import ParseableEngineSpec + + assert ParseableEngineSpec.epoch_ms_to_dttm() == "to_timestamp({col} / 1000)" + + +def test_alter_new_orm_column() -> None: + """ + DB Eng Specs (parseable): Test alter orm column + """ + from superset.connectors.sqla.models import SqlaTable, TableColumn + from superset.db_engine_specs.parseable import ParseableEngineSpec + from superset.models.core import Database + + database = Database(database_name="parseable", sqlalchemy_uri="parseable://db") + tbl = SqlaTable(table_name="tbl", database=database) + col = TableColumn(column_name="p_timestamp", type="TIMESTAMP", table=tbl) + ParseableEngineSpec.alter_new_orm_column(col) + assert col.python_date_format == "epoch_ms" + assert col.is_dttm is True + + +@pytest.mark.parametrize( + "target_type,expected_result", + [ + ("TIMESTAMP", "'2019-01-02T03:04:05.000'"), + ("UnknownType", None), + ], +) +def test_convert_dttm( + target_type: str, + expected_result: Optional[str], + dttm: datetime, # noqa: F811 +) -> None: + """ + DB Eng Specs (parseable): Test conversion to date time + """ + from superset.db_engine_specs.parseable import ParseableEngineSpec as spec + + assert_convert_dttm(spec, target_type, expected_result, dttm) \ No newline at end of file From 0a05c7045b94f80ed9464fe6275f10d45be85516 Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Sat, 1 Feb 2025 02:44:09 +0530 Subject: [PATCH 4/8] address code review --- docs/docs/configuration/databases.mdx | 19 ++++++++++++++++++- pyproject.toml | 2 +- superset/db_engine_specs/parseable.py | 18 ++++++++++++++++-- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/docs/docs/configuration/databases.mdx b/docs/docs/configuration/databases.mdx index 16fe59a434f6..f402d18b5b2e 100644 --- a/docs/docs/configuration/databases.mdx +++ b/docs/docs/configuration/databases.mdx @@ -69,8 +69,9 @@ are compatible with Superset. | [MySQL](/docs/configuration/databases#mysql) | `pip install mysqlclient` | `mysql://:@/` | | [OceanBase](/docs/configuration/databases#oceanbase) | `pip install oceanbase_py` | `oceanbase://:@/` | | [Oracle](/docs/configuration/databases#oracle) | `pip install cx_Oracle` | `oracle://` | +| [Parseable](/docs/configuration/databases#parseable) | `pip install sqlalchemy-parseable` | `parseable://:@/` | | [PostgreSQL](/docs/configuration/databases#postgres) | `pip install psycopg2` | `postgresql://:@/` | -| [Presto](/docs/configuration/databases#presto) | `pip install pyhive` | `presto://` | +| [Presto](/docs/configuration/databases#presto) | `pip install pyhive` | [Rockset](/docs/configuration/databases#rockset) | `pip install rockset-sqlalchemy` | `rockset://:@` | | [SAP Hana](/docs/configuration/databases#hana) | `pip install hdbcli sqlalchemy-hana` or `pip install apache-superset[hana]` | `hana://{username}:{password}@{host}:{port}` | | [StarRocks](/docs/configuration/databases#starrocks) | `pip install starrocks` | `starrocks://:@:/.` | @@ -1074,6 +1075,22 @@ The connection string is formatted as follows: oracle://:@: ``` +#### Parseable + +[Parseable](https://www.parseable.io) is a distributed log analytics database that provides SQL-like query interface for log data. The recommended connector library is [sqlalchemy-parseable](https://github.com/parseablehq/sqlalchemy-parseable). + +The connection string is formatted as follows: + +``` +parseable://:@:/ +``` + +For example: +``` +parseable://admin:admin@demo.parseable.com:443/ingress-nginx +``` + +The stream_name in the URI represents the Parseable logstream you want to query. You can use both HTTP (port 80) and HTTPS (port 443) connections. #### Apache Pinot diff --git a/pyproject.toml b/pyproject.toml index 509351a36bbe..0d93f9e96f36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -156,7 +156,7 @@ ocient = [ "geojson", ] oracle = ["cx-Oracle>8.0.0, <8.1"] -parseable = ["sqlalchemy-parseable==0.1.3"] +parseable = ["sqlalchemy-parseable>=0.1.3,<0.2.0"] pinot = ["pinotdb>=5.0.0, <6.0.0"] playwright = ["playwright>=1.37.0, <2"] postgres = ["psycopg2-binary==2.9.6"] diff --git a/superset/db_engine_specs/parseable.py b/superset/db_engine_specs/parseable.py index f742be192a83..cb6f1d906137 100644 --- a/superset/db_engine_specs/parseable.py +++ b/superset/db_engine_specs/parseable.py @@ -1,5 +1,19 @@ -# In superset/db_engine_specs/parseable.py - +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. from __future__ import annotations from datetime import datetime From da7fda5e4dc204a96d56647a3feefa69818d0870 Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Sat, 1 Feb 2025 02:58:39 +0530 Subject: [PATCH 5/8] align docs --- docs/docs/configuration/databases.mdx | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/docs/docs/configuration/databases.mdx b/docs/docs/configuration/databases.mdx index f402d18b5b2e..abaf3c4a3a47 100644 --- a/docs/docs/configuration/databases.mdx +++ b/docs/docs/configuration/databases.mdx @@ -69,9 +69,9 @@ are compatible with Superset. | [MySQL](/docs/configuration/databases#mysql) | `pip install mysqlclient` | `mysql://:@/` | | [OceanBase](/docs/configuration/databases#oceanbase) | `pip install oceanbase_py` | `oceanbase://:@/` | | [Oracle](/docs/configuration/databases#oracle) | `pip install cx_Oracle` | `oracle://` | -| [Parseable](/docs/configuration/databases#parseable) | `pip install sqlalchemy-parseable` | `parseable://:@/` | +| [Parseable](/docs/configuration/databases#parseable) | `pip install sqlalchemy-parseable` | `parseable://:@/` | | [PostgreSQL](/docs/configuration/databases#postgres) | `pip install psycopg2` | `postgresql://:@/` | -| [Presto](/docs/configuration/databases#presto) | `pip install pyhive` +| [Presto](/docs/configuration/databases#presto) | `pip install pyhive` | `presto://` | | [Rockset](/docs/configuration/databases#rockset) | `pip install rockset-sqlalchemy` | `rockset://:@` | | [SAP Hana](/docs/configuration/databases#hana) | `pip install hdbcli sqlalchemy-hana` or `pip install apache-superset[hana]` | `hana://{username}:{password}@{host}:{port}` | | [StarRocks](/docs/configuration/databases#starrocks) | `pip install starrocks` | `starrocks://:@:/.` | @@ -1075,22 +1075,6 @@ The connection string is formatted as follows: oracle://:@: ``` -#### Parseable - -[Parseable](https://www.parseable.io) is a distributed log analytics database that provides SQL-like query interface for log data. The recommended connector library is [sqlalchemy-parseable](https://github.com/parseablehq/sqlalchemy-parseable). - -The connection string is formatted as follows: - -``` -parseable://:@:/ -``` - -For example: -``` -parseable://admin:admin@demo.parseable.com:443/ingress-nginx -``` - -The stream_name in the URI represents the Parseable logstream you want to query. You can use both HTTP (port 80) and HTTPS (port 443) connections. #### Apache Pinot From 5d19e0d599539132a337912428a4e44baf82c2ed Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Sat, 1 Feb 2025 03:10:20 +0530 Subject: [PATCH 6/8] update docs --- docs/docs/configuration/databases.mdx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/docs/configuration/databases.mdx b/docs/docs/configuration/databases.mdx index abaf3c4a3a47..16af4451c067 100644 --- a/docs/docs/configuration/databases.mdx +++ b/docs/docs/configuration/databases.mdx @@ -1075,6 +1075,23 @@ The connection string is formatted as follows: oracle://:@: ``` +#### Parseable + +[Parseable](https://www.parseable.io) is a distributed log analytics database that provides SQL-like query interface for log data. The recommended connector library is [sqlalchemy-parseable](https://github.com/parseablehq/sqlalchemy-parseable). + +The connection string is formatted as follows: + +``` +parseable://:@:/ +``` + +For example: + +``` +parseable://admin:admin@demo.parseable.com:443/ingress-nginx +``` + +Note: The stream_name in the URI represents the Parseable logstream you want to query. You can use both HTTP (port 80) and HTTPS (port 443) connections. #### Apache Pinot From eb3ddcf85e2aabda150c1365f8b02f4c53619547 Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Sat, 1 Feb 2025 03:19:57 +0530 Subject: [PATCH 7/8] address pre-commit checks --- superset/db_engine_specs/parseable.py | 6 ++++-- tests/unit_tests/db_engine_specs/test_parseable.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/superset/db_engine_specs/parseable.py b/superset/db_engine_specs/parseable.py index cb6f1d906137..3fc7b89a5cd9 100644 --- a/superset/db_engine_specs/parseable.py +++ b/superset/db_engine_specs/parseable.py @@ -26,13 +26,15 @@ if TYPE_CHECKING: from superset.connectors.sqla.models import TableColumn + from superset.models.core import Database class ParseableEngineSpec(BaseEngineSpec): + """Engine spec for Parseable log analytics database.""" + engine = "parseable" engine_name = "Parseable" - # Use same expressions as SQLAlchemy dialect for consistency _time_grain_expressions = { None: "{col}", TimeGrain.SECOND: "date_trunc('second', {col})", @@ -71,7 +73,7 @@ def alter_new_orm_column(cls, orm_col: TableColumn) -> None: orm_col.is_dttm = True @classmethod - def get_extra_params(cls, database: "Database") -> dict[str, Any]: + def get_extra_params(cls, database: Database) -> dict[str, Any]: """Additional parameters for Parseable connections.""" return { "engine_params": { diff --git a/tests/unit_tests/db_engine_specs/test_parseable.py b/tests/unit_tests/db_engine_specs/test_parseable.py index 2f0ed31189da..51d07ed4fd1a 100644 --- a/tests/unit_tests/db_engine_specs/test_parseable.py +++ b/tests/unit_tests/db_engine_specs/test_parseable.py @@ -72,6 +72,6 @@ def test_convert_dttm( """ DB Eng Specs (parseable): Test conversion to date time """ - from superset.db_engine_specs.parseable import ParseableEngineSpec as spec + from superset.db_engine_specs.parseable import ParseableEngineSpec - assert_convert_dttm(spec, target_type, expected_result, dttm) \ No newline at end of file + assert_convert_dttm(ParseableEngineSpec, target_type, expected_result, dttm) \ No newline at end of file From 726c9ed9176d38caaedc42d6c55a2acd847250f0 Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Sat, 1 Feb 2025 03:35:55 +0530 Subject: [PATCH 8/8] fix lint issues --- superset/db_engine_specs/parseable.py | 2 +- tests/unit_tests/db_engine_specs/test_parseable.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/superset/db_engine_specs/parseable.py b/superset/db_engine_specs/parseable.py index 3fc7b89a5cd9..dbca2116276c 100644 --- a/superset/db_engine_specs/parseable.py +++ b/superset/db_engine_specs/parseable.py @@ -81,4 +81,4 @@ def get_extra_params(cls, database: Database) -> dict[str, Any]: "timeout": 300, # 5 minutes timeout } } - } \ No newline at end of file + } diff --git a/tests/unit_tests/db_engine_specs/test_parseable.py b/tests/unit_tests/db_engine_specs/test_parseable.py index 51d07ed4fd1a..4e5a5783042b 100644 --- a/tests/unit_tests/db_engine_specs/test_parseable.py +++ b/tests/unit_tests/db_engine_specs/test_parseable.py @@ -74,4 +74,4 @@ def test_convert_dttm( """ from superset.db_engine_specs.parseable import ParseableEngineSpec - assert_convert_dttm(ParseableEngineSpec, target_type, expected_result, dttm) \ No newline at end of file + assert_convert_dttm(ParseableEngineSpec, target_type, expected_result, dttm)