From 76ecff5896f9d321fe6d6ad296fd05ec08136068 Mon Sep 17 00:00:00 2001 From: Joshua Varghese Date: Fri, 10 Jul 2026 17:14:02 +1000 Subject: [PATCH 1/3] Migrate Exasol provider to pyexasol 2.x --- providers/exasol/README.rst | 2 +- providers/exasol/docs/changelog.rst | 20 ++ providers/exasol/docs/index.rst | 2 +- providers/exasol/pyproject.toml | 6 +- .../airflow/providers/exasol/hooks/exasol.py | 79 +++-- .../tests/unit/exasol/hooks/test_exasol.py | 35 ++- uv.lock | 275 +++++++++--------- 7 files changed, 257 insertions(+), 162 deletions(-) diff --git a/providers/exasol/README.rst b/providers/exasol/README.rst index c74b2a483679b..a0860a41cb897 100644 --- a/providers/exasol/README.rst +++ b/providers/exasol/README.rst @@ -56,7 +56,7 @@ PIP package Version required ``apache-airflow`` ``>=2.11.0`` ``apache-airflow-providers-common-compat`` ``>=1.12.0`` ``apache-airflow-providers-common-sql`` ``>=1.32.0`` -``pyexasol`` ``>=0.26.0,<2`` +``pyexasol`` ``>=2.0.0,<3`` ``pandas`` ``>=2.1.2; python_version < "3.13"`` ``pandas`` ``>=2.2.3; python_version >= "3.13" and python_version < "3.14"`` ``pandas`` ``>=2.3.3; python_version >= "3.14"`` diff --git a/providers/exasol/docs/changelog.rst b/providers/exasol/docs/changelog.rst index 3fa86a1f71e6a..4f67e81b28c94 100644 --- a/providers/exasol/docs/changelog.rst +++ b/providers/exasol/docs/changelog.rst @@ -27,6 +27,26 @@ Changelog --------- +Breaking changes +~~~~~~~~~~~~~~~~ + +* ``Migrate exasol provider to pyexasol 2.x; remove the <2 cap (#69123)`` + + This provider now requires ``pyexasol>=2.0.0,<3``. pyexasol 2.0.0 ships a ``py.typed`` marker + (`pyexasol changelog `__), which + surfaces several previously-hidden type mismatches in the Exasol hook: + + * ``ExasolHook.get_records()`` and ``ExasolHook.get_first()`` now only accept a single SQL string, + not a list of statements (pyexasol's underlying ``execute()`` never actually supported a list; use + ``ExasolHook.run()`` for executing multiple statements in sequence). + * ``parameters``/``query_params`` passed to ``get_records()``, ``get_first()``, ``run()``, and + ``get_df()``/``get_pandas_df()`` must be a ``dict``/``Mapping``. Positional-style parameters + (lists/tuples) were never actually supported by pyexasol at runtime and will now raise a clear + ``TypeError`` instead of failing deep inside pyexasol (or silently doing the wrong thing). + * If you pass ``export_params={"with_column_names": ...}`` to ``ExasolHook.export_to_file()`` or + ``ExasolToS3Operator``, note that pyexasol 2.0.0 fixed this option to be interpreted as a real + boolean; previously any value (including ``False``) was treated as ``True``. + 4.10.4 ...... diff --git a/providers/exasol/docs/index.rst b/providers/exasol/docs/index.rst index 13cb541eb1206..be2a05d21eb68 100644 --- a/providers/exasol/docs/index.rst +++ b/providers/exasol/docs/index.rst @@ -101,7 +101,7 @@ PIP package Version required ``apache-airflow`` ``>=2.11.0`` ``apache-airflow-providers-common-compat`` ``>=1.12.0`` ``apache-airflow-providers-common-sql`` ``>=1.32.0`` -``pyexasol`` ``>=0.26.0,<2`` +``pyexasol`` ``>=2.0.0,<3`` ``pandas`` ``>=2.1.2; python_version < "3.13"`` ``pandas`` ``>=2.2.3; python_version >= "3.13" and python_version < "3.14"`` ``pandas`` ``>=2.3.3; python_version >= "3.14"`` diff --git a/providers/exasol/pyproject.toml b/providers/exasol/pyproject.toml index 636109b984099..49032d42a39ed 100644 --- a/providers/exasol/pyproject.toml +++ b/providers/exasol/pyproject.toml @@ -62,9 +62,9 @@ dependencies = [ "apache-airflow>=2.11.0", "apache-airflow-providers-common-compat>=1.12.0", "apache-airflow-providers-common-sql>=1.32.0", - # Capped to 1.x: pyexasol 2.x ships stricter types that break the exasol hook. - # Remove the cap after migrating; tracked at https://github.com/apache/airflow/issues/69123 - "pyexasol>=0.26.0,<2", + # Migrated to pyexasol 2.x (adds a py.typed marker; see hooks/exasol.py for the resulting fixes). + # Capped to 2.x since we haven't yet verified compatibility with a future 3.x major release. + "pyexasol>=2.0.0,<3", 'pandas>=2.1.2; python_version <"3.13"', 'pandas>=2.2.3; python_version >="3.13" and python_version <"3.14"', 'pandas>=2.3.3; python_version >="3.14"', diff --git a/providers/exasol/src/airflow/providers/exasol/hooks/exasol.py b/providers/exasol/src/airflow/providers/exasol/hooks/exasol.py index 476c5259ba5b5..2ce063e0aed47 100644 --- a/providers/exasol/src/airflow/providers/exasol/hooks/exasol.py +++ b/providers/exasol/src/airflow/providers/exasol/hooks/exasol.py @@ -67,21 +67,47 @@ def __init__(self, *args, sqlalchemy_scheme: str | None = None, **kwargs) -> Non self.schema = kwargs.pop("schema", None) self._sqlalchemy_scheme = sqlalchemy_scheme + @staticmethod + def _validate_query_params(parameters: Iterable | Mapping[str, Any] | None) -> dict[str, Any] | None: + """ + Validate that query parameters are a dict, as required by pyexasol. + + pyexasol's ``ExaConnection.execute``/``export_to_pandas``/``export_to_file`` have only ever + accepted a ``dict`` for query parameters (they are interpolated into the SQL via + ``str.format(**query_params)``, and pyexasol's own type stubs declare ``query_params: dict | + None``). Positional-style parameters (lists/tuples) were never actually supported at runtime, + even though this hook's signatures (inherited from + :class:`~airflow.providers.common.sql.hooks.sql.DbApiHook`) advertise the broader + ``Iterable | Mapping[str, Any] | None`` type. As of pyexasol 2.x, which ships a ``py.typed`` + marker, mypy now enforces this at the type level too. We validate explicitly here so that + passing a non-dict fails fast with a clear message instead of an opaque error surfacing deep + inside pyexasol. + + :param parameters: The parameters as passed to this hook's public methods. + :return: The same parameters, narrowed to ``dict[str, Any] | None`` for pyexasol/mypy. + """ + if parameters is not None and not isinstance(parameters, dict): + raise TypeError( + f"Exasol (pyexasol) only supports named/dict-style query parameters, got " + f"{type(parameters).__name__!r}. Pass a dict (e.g. {{'col1': 'value'}}) instead." + ) + return parameters + def get_conn(self) -> ExaConnection: - conn = self.get_connection(self.get_conn_id()) + airflow_conn = self.get_connection(self.get_conn_id()) conn_args = { - "dsn": f"{conn.host}:{conn.port}", - "user": conn.login, - "password": conn.password, - "schema": self.schema or conn.schema, + "dsn": f"{airflow_conn.host}:{airflow_conn.port}", + "user": airflow_conn.login, + "password": airflow_conn.password, + "schema": self.schema or airflow_conn.schema, } # check for parameters in conn.extra - for arg_name, arg_val in conn.extra_dejson.items(): + for arg_name, arg_val in airflow_conn.extra_dejson.items(): if arg_name in ["compression", "encryption", "json_lib", "client_name"]: conn_args[arg_name] = arg_val - conn = pyexasol.connect(**conn_args) - return conn + exa_conn = pyexasol.connect(**conn_args) + return exa_conn @property def sqlalchemy_scheme(self) -> str: @@ -145,7 +171,7 @@ def _get_pandas_df( ``pyexasol.ExaConnection.export_to_pandas``. """ with closing(self.get_conn()) as conn: - df = conn.export_to_pandas(sql, query_params=parameters, **kwargs) + df = conn.export_to_pandas(sql, query_params=self._validate_query_params(parameters), **kwargs) return df @deprecated( @@ -184,11 +210,19 @@ def get_records( """ Execute the SQL and return a set of records. - :param sql: the sql statement to be executed (str) or a list of - sql statements to execute - :param parameters: The parameters to render the SQL query with. + :param sql: the sql statement to be executed. Must be a single ``str``; pyexasol's ``execute()`` + does not accept a list of statements. For executing multiple statements in sequence, use + :meth:`run` with ``handler=exasol_fetch_all_handler`` instead. + :param parameters: The parameters to render the SQL query with. Must be a ``dict``/``Mapping``; + pyexasol does not support positional (list/tuple) query parameters. """ - with closing(self.get_conn()) as conn, closing(conn.execute(sql, parameters)) as cur: + if not isinstance(sql, str): + raise TypeError( + "ExasolHook.get_records() only supports a single SQL string, not a list of statements. " + "Use ExasolHook.run() with a handler for executing multiple statements." + ) + query_params = self._validate_query_params(parameters) + with closing(self.get_conn()) as conn, closing(conn.execute(sql, query_params)) as cur: send_sql_hook_lineage( context=self, sql=sql, @@ -201,11 +235,19 @@ def get_first(self, sql: str | list[str], parameters: Iterable | Mapping[str, An """ Execute the SQL and return the first resulting row. - :param sql: the sql statement to be executed (str) or a list of - sql statements to execute - :param parameters: The parameters to render the SQL query with. + :param sql: the sql statement to be executed. Must be a single ``str``; pyexasol's ``execute()`` + does not accept a list of statements. For executing multiple statements in sequence, use + :meth:`run` with ``handler=exasol_fetch_one_handler`` instead. + :param parameters: The parameters to render the SQL query with. Must be a ``dict``/``Mapping``; + pyexasol does not support positional (list/tuple) query parameters. """ - with closing(self.get_conn()) as conn, closing(conn.execute(sql, parameters)) as cur: + if not isinstance(sql, str): + raise TypeError( + "ExasolHook.get_first() only supports a single SQL string, not a list of statements. " + "Use ExasolHook.run() with a handler for executing multiple statements." + ) + query_params = self._validate_query_params(parameters) + with closing(self.get_conn()) as conn, closing(conn.execute(sql, query_params)) as cur: send_sql_hook_lineage( context=self, sql=sql, @@ -328,13 +370,14 @@ def run( self.log.debug("Executing following statements against Exasol DB: %s", list(sql_list)) else: raise ValueError("List of SQL statements is empty") + query_params = self._validate_query_params(parameters) _last_result = None with closing(self.get_conn()) as conn: self.set_autocommit(conn, autocommit) results = [] for sql_statement in sql_list: self.log.info("Running statement: %s, parameters: %s", sql_statement, parameters) - with closing(conn.execute(sql_statement, parameters)) as exa_statement: + with closing(conn.execute(sql_statement, query_params)) as exa_statement: if handler is not None: result = self._make_common_data_structure(handler(exa_statement)) diff --git a/providers/exasol/tests/unit/exasol/hooks/test_exasol.py b/providers/exasol/tests/unit/exasol/hooks/test_exasol.py index 098a0ff3292fe..16f133f7a8867 100644 --- a/providers/exasol/tests/unit/exasol/hooks/test_exasol.py +++ b/providers/exasol/tests/unit/exasol/hooks/test_exasol.py @@ -194,12 +194,19 @@ def test_run_with_autocommit(self): def test_run_with_parameters(self): sql = "SQL" - parameters = ("param1", "param2") + parameters = {"param1": "value1", "param2": "value2"} self.db_hook.run(sql, autocommit=True, parameters=parameters) self.conn.set_autocommit.assert_called_once_with(True) self.conn.execute.assert_called_once_with(sql, parameters) self.conn.commit.assert_not_called() + def test_run_with_non_mapping_parameters_raises(self): + sql = "SQL" + parameters = ("param1", "param2") + with pytest.raises(TypeError, match="only supports named/dict-style query parameters"): + self.db_hook.run(sql, autocommit=True, parameters=parameters) + self.conn.execute.assert_not_called() + def test_run_multi_queries(self): sql = ["SQL1", "SQL2"] self.db_hook.run(sql, autocommit=True) @@ -251,6 +258,27 @@ def test_get_first_hook_lineage(self, mock_send_lineage): assert call_kw["sql_parameters"] is None assert call_kw["cur"] is self.cur + @pytest.mark.parametrize("method_name", ["get_records", "get_first"]) + def test_get_records_and_get_first_reject_list_of_statements(self, method_name): + """Regression test: pyexasol's execute() never accepted a list of SQL statements. + + ExasolHook.get_records()/get_first() are typed as accepting ``str | list[str]`` (matching the + DbApiHook base class contract), but they call pyexasol's ``conn.execute()`` directly, which has + only ever accepted a single ``str`` query. Passing a list here was always broken at runtime; we + now raise a clear error instead of forwarding it straight to pyexasol. + """ + method = getattr(self.db_hook, method_name) + with pytest.raises(TypeError, match="only supports a single SQL string"): + method(["SELECT 1", "SELECT 2"]) + self.conn.execute.assert_not_called() + + @pytest.mark.parametrize("method_name", ["get_records", "get_first"]) + def test_get_records_and_get_first_reject_non_mapping_parameters(self, method_name): + method = getattr(self.db_hook, method_name) + with pytest.raises(TypeError, match="only supports named/dict-style query parameters"): + method("SELECT 1", parameters=("param1", "param2")) + self.conn.execute.assert_not_called() + @mock.patch("airflow.providers.common.sql.hooks.sql.send_sql_hook_lineage") def test_get_df_hook_lineage(self, mock_send_lineage): statement = "SQL" @@ -334,3 +362,8 @@ def test_get_df_pandas(self): def test_get_df_polars(self): with pytest.raises(NotImplementedError): self.db_hook.get_df("SQL", df_type="polars") + + def test_get_df_pandas_rejects_non_mapping_parameters(self): + with pytest.raises(TypeError, match="only supports named/dict-style query parameters"): + self.db_hook.get_df("SQL", parameters=("param1", "param2"), df_type="pandas") + self.conn.export_to_pandas.assert_not_called() diff --git a/uv.lock b/uv.lock index 051202e9993a9..c673dcb1fa9f1 100644 --- a/uv.lock +++ b/uv.lock @@ -330,7 +330,7 @@ name = "adbc-driver-manager" version = "1.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e9/5e/50aab18cb501e42d3aca3cd2cc26c6637094fcaf5b6576e350c444188f1f/adbc_driver_manager-1.11.0.tar.gz", hash = "sha256:c64aaabeb5810109ab3d2961008f1b014e9f2d87b3df4416c2a080a40237af50", size = 233059, upload-time = "2026-04-07T00:17:28.263Z" } wheels = [ @@ -375,8 +375,8 @@ name = "adbc-driver-postgresql" version = "1.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "adbc-driver-manager", marker = "python_full_version < '3.13'" }, - { name = "importlib-resources", marker = "python_full_version < '3.13'" }, + { name = "adbc-driver-manager" }, + { name = "importlib-resources" }, ] sdist = { url = "https://files.pythonhosted.org/packages/68/10/2962c25035887cd03af3b348eac3302493936f45048c220021a802d07f12/adbc_driver_postgresql-1.11.0.tar.gz", hash = "sha256:f5688b8648ac7a86d8b89340231bb3686ac5df56ee95d1ca0b875dad5d52b48a", size = 32328, upload-time = "2026-04-07T00:17:29.232Z" } wheels = [ @@ -392,8 +392,8 @@ name = "adbc-driver-sqlite" version = "1.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "adbc-driver-manager", marker = "python_full_version < '3.13'" }, - { name = "importlib-resources", marker = "python_full_version < '3.13'" }, + { name = "adbc-driver-manager" }, + { name = "importlib-resources" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3a/dd/8a5f4908aa4bdec64dcd672734fa314d692517458ce169591639d0123fe1/adbc_driver_sqlite-1.11.0.tar.gz", hash = "sha256:a4c6b4962610f7cd67cd754c42dd74e18a2c11fabeec9488c5501d73ae62dc62", size = 28885, upload-time = "2026-04-07T00:17:31.325Z" } wheels = [ @@ -456,7 +456,7 @@ resolution-markers = [ "(python_full_version < '3.11' and platform_machine != 'arm64') or (python_full_version < '3.11' and sys_platform != 'darwin')", ] dependencies = [ - { name = "caio", marker = "python_full_version < '3.11'" }, + { name = "caio" }, ] sdist = { url = "https://files.pythonhosted.org/packages/67/e2/d7cb819de8df6b5c1968a2756c3cb4122d4fa2b8fc768b53b7c9e5edb646/aiofile-3.9.0.tar.gz", hash = "sha256:e5ad718bb148b265b6df1b3752c4d1d83024b93da9bd599df74b9d9ffcf7919b", size = 17943, upload-time = "2024-10-08T10:39:35.846Z" } wheels = [ @@ -480,7 +480,7 @@ resolution-markers = [ "(python_full_version == '3.11.*' and platform_machine != 'arm64') or (python_full_version == '3.11.*' and sys_platform != 'darwin')", ] dependencies = [ - { name = "caio", marker = "python_full_version >= '3.11'" }, + { name = "caio" }, ] sdist = { url = "https://files.pythonhosted.org/packages/48/41/2fea7e193e061ce54eacc3b7bc0e6a99e4fcff43c78cf0a76dd781ed8334/aiofile-3.11.1.tar.gz", hash = "sha256:1f91912c6643d2a4e49ca4ae3514f0bf3867ce948a36d99a6411b8f4755f4cf9", size = 19342, upload-time = "2026-05-16T08:18:33.538Z" } wheels = [ @@ -5243,7 +5243,7 @@ requires-dist = [ { name = "pandas", marker = "python_full_version < '3.13'", specifier = ">=2.1.2" }, { name = "pandas", marker = "python_full_version == '3.13.*'", specifier = ">=2.2.3" }, { name = "pandas", marker = "python_full_version >= '3.14'", specifier = ">=2.3.3" }, - { name = "pyexasol", specifier = ">=0.26.0,<2" }, + { name = "pyexasol", specifier = ">=2.0.0,<3" }, { name = "sqlalchemy", marker = "extra == 'sqlalchemy'", specifier = ">=1.4.54" }, ] provides-extras = ["sqlalchemy"] @@ -10428,8 +10428,8 @@ name = "cassandra-driver" version = "3.30.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "deprecated", marker = "python_full_version < '3.14'" }, - { name = "geomet", marker = "python_full_version < '3.14'" }, + { name = "deprecated" }, + { name = "geomet" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bb/ed/4e16210e194660f107929ee494f1cf18557252655067d9b39029c241be2d/cassandra_driver-3.30.1.tar.gz", hash = "sha256:0c6a3e1428f7c6a9aa6c944b9c47a37cd2cdbeb5b5a82d42c33afdd56f14e398", size = 289233, upload-time = "2026-07-06T20:14:31.37Z" } wheels = [ @@ -12496,7 +12496,7 @@ name = "geomet" version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click", marker = "python_full_version < '3.14'" }, + { name = "click" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2a/8c/dde022aa6747b114f6b14a7392871275dea8867e2bd26cddb80cc6d66620/geomet-1.1.0.tar.gz", hash = "sha256:51e92231a0ef6aaa63ac20c443377ba78a303fd2ecd179dc3567de79f3c11605", size = 28732, upload-time = "2023-11-14T15:43:36.764Z" } wheels = [ @@ -13929,7 +13929,7 @@ name = "gssapi" version = "1.11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "decorator", marker = "python_full_version < '3.15' or sys_platform != 'win32'" }, + { name = "decorator" }, ] sdist = { url = "https://files.pythonhosted.org/packages/23/52/c1e90623c259a42ab0587078bb04f959867b970add46ff66750ead8fc7c5/gssapi-1.11.1.tar.gz", hash = "sha256:2049ee4b1d0c363163a1344b7282a363f9f4094e51d2c36de0cf01d4735e0ae2", size = 95233, upload-time = "2026-01-26T21:01:39.463Z" } wheels = [ @@ -14714,17 +14714,17 @@ resolution-markers = [ "(python_full_version < '3.11' and platform_machine != 'arm64') or (python_full_version < '3.11' and sys_platform != 'darwin')", ] dependencies = [ - { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version < '3.11'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "jedi", marker = "python_full_version < '3.11'" }, - { name = "matplotlib-inline", marker = "python_full_version < '3.11'" }, - { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version < '3.11'" }, - { name = "pygments", marker = "python_full_version < '3.11'" }, - { name = "stack-data", marker = "python_full_version < '3.11'" }, - { name = "traitlets", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "exceptiongroup" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/40/18/f8598d287006885e7136451fdea0755af4ebcbfe342836f24deefaed1164/ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", size = 5513971, upload-time = "2026-03-27T10:02:13.94Z" } wheels = [ @@ -14748,18 +14748,18 @@ resolution-markers = [ "(python_full_version == '3.11.*' and platform_machine != 'arm64') or (python_full_version == '3.11.*' and sys_platform != 'darwin')", ] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version >= '3.11'" }, - { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, - { name = "jedi", marker = "python_full_version >= '3.11'" }, - { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, - { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, - { name = "psutil", marker = "python_full_version >= '3.11' and sys_platform != 'cygwin' and sys_platform != 'emscripten'" }, - { name = "pygments", marker = "python_full_version >= '3.11'" }, - { name = "stack-data", marker = "python_full_version >= '3.11'" }, - { name = "traitlets", marker = "python_full_version >= '3.11'" }, - { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "psutil", marker = "sys_platform != 'cygwin' and sys_platform != 'emscripten'" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/53/59/165d3b4d75cc34add3122c4417ecb229085140ac573103c223cd01dde96f/ipython-9.15.0.tar.gz", hash = "sha256:da2819ce2aa83135257df830660b1176d986c3d2876db24df01974fa955b2756", size = 4442580, upload-time = "2026-06-26T11:03:35.913Z" } wheels = [ @@ -14771,7 +14771,7 @@ name = "ipython-pygments-lexers" version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } wheels = [ @@ -18099,9 +18099,9 @@ wheels = [ [package.optional-dependencies] sql-other = [ - { name = "adbc-driver-postgresql", marker = "python_full_version < '3.13'" }, - { name = "adbc-driver-sqlite", marker = "python_full_version < '3.13'" }, - { name = "sqlalchemy", marker = "python_full_version < '3.13'" }, + { name = "adbc-driver-postgresql" }, + { name = "adbc-driver-sqlite" }, + { name = "sqlalchemy" }, ] [[package]] @@ -19579,17 +19579,16 @@ wheels = [ [[package]] name = "pyexasol" -version = "1.3.0" +version = "2.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "packaging" }, - { name = "rsa" }, { name = "websocket-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/f4/be269ba104e7ff469f9b27ab136479932591506fdb79a17081281880af53/pyexasol-1.3.0.tar.gz", hash = "sha256:44e5be7245b92343634b0b66a81b62be2fb4b1ca485d4e0f86c0583d5a2cf6b5", size = 60189, upload-time = "2025-11-17T15:01:59.614Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/80/98a3fc207191080a58f0f49e5fd7dbada4c5f7d71e2d7e8e7c3f86032e9d/pyexasol-2.2.2.tar.gz", hash = "sha256:fb97a1579f4cc159cff04eabdb05fab1a376624abe2a45d339e7e735b7b3d83f", size = 61760, upload-time = "2026-06-24T06:20:13.308Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/4b/67204f07f804e65059c8d42b481583efc9edb7f5f4aecb3fddbd183f0c9b/pyexasol-1.3.0-py3-none-any.whl", hash = "sha256:0bb076346dcb33b36031a2633d6e7b9982f8411b95e770ad8017801ee438a689", size = 71906, upload-time = "2025-11-17T15:01:58.205Z" }, + { url = "https://files.pythonhosted.org/packages/45/76/bbac7c97bdd37345c0961f608a4aa1bd62d0f73f501c1d61b3e1137a0698/pyexasol-2.2.2-py3-none-any.whl", hash = "sha256:ed694df2f4e424f1c4a8427c638d35541a7b7289c89825d038a3bdc99dbcf717", size = 73446, upload-time = "2026-06-24T06:20:14.394Z" }, ] [[package]] @@ -20509,9 +20508,9 @@ name = "python3-saml" version = "1.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "isodate", marker = "python_full_version < '3.13'" }, - { name = "lxml", marker = "python_full_version < '3.13'" }, - { name = "xmlsec", marker = "python_full_version < '3.13'" }, + { name = "isodate" }, + { name = "lxml" }, + { name = "xmlsec" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5d/98/6e0268c3a9893af3d4c5cf670183e0314cd6b5cb034a612d6a7cc5060df8/python3-saml-1.16.0.tar.gz", hash = "sha256:97c9669aecabc283c6e5fb4eb264f446b6e006f5267d01c9734f9d8bffdac133", size = 83468, upload-time = "2023-10-09T10:37:43.128Z" } wheels = [ @@ -21644,10 +21643,10 @@ resolution-markers = [ "(python_full_version < '3.11' and platform_machine != 'arm64') or (python_full_version < '3.11' and sys_platform != 'darwin')", ] dependencies = [ - { name = "joblib", marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "threadpoolctl", marker = "python_full_version < '3.11'" }, + { name = "joblib" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" } }, + { name = "threadpoolctl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/37/59/44985a2bdc95c74e34fef3d10cb5d93ce13b0e2a7baefffe1b53853b502d/scikit_learn-1.5.2.tar.gz", hash = "sha256:b4237ed7b3fdd0a4882792e68ef2545d5baa50aca3bb45aa7df468138ad8f94d", size = 7001680, upload-time = "2024-09-11T15:50:10.957Z" } wheels = [ @@ -21690,13 +21689,13 @@ resolution-markers = [ "(python_full_version == '3.11.*' and platform_machine != 'arm64') or (python_full_version == '3.11.*' and sys_platform != 'darwin')", ] dependencies = [ - { name = "joblib", marker = "python_full_version >= '3.11'" }, - { name = "narwhals", marker = "python_full_version >= '3.11'" }, - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "joblib" }, + { name = "narwhals" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "threadpoolctl", marker = "python_full_version >= '3.11'" }, + { name = "threadpoolctl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fa/6f/37092bdb25f712817231799fc5674d8e704066a8a70c1d2d40517e18b4ab/scikit_learn-1.9.0.tar.gz", hash = "sha256:8833266989d3a5110178a9fae30783675460724d0e1efb13b14901d2c660c557", size = 7750767, upload-time = "2026-06-02T11:54:32.706Z" } wheels = [ @@ -21741,7 +21740,7 @@ resolution-markers = [ "(python_full_version < '3.11' and platform_machine != 'arm64') or (python_full_version < '3.11' and sys_platform != 'darwin')", ] dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } wheels = [ @@ -21801,7 +21800,7 @@ resolution-markers = [ "(python_full_version == '3.11.*' and platform_machine != 'arm64') or (python_full_version == '3.11.*' and sys_platform != 'darwin')", ] dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } wheels = [ @@ -21882,7 +21881,7 @@ resolution-markers = [ "(python_full_version == '3.12.*' and platform_machine != 'arm64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", ] dependencies = [ - { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } wheels = [ @@ -21967,8 +21966,8 @@ name = "secretstorage" version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cryptography", marker = "python_full_version >= '3.14' or platform_machine != 'arm64' or sys_platform != 'darwin'" }, - { name = "jeepney", marker = "python_full_version >= '3.14' or platform_machine != 'arm64' or sys_platform != 'darwin'" }, + { name = "cryptography" }, + { name = "jeepney" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } wheels = [ @@ -22271,15 +22270,15 @@ name = "snowflake-snowpark-python" version = "1.53.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cloudpickle", version = "3.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, - { name = "protobuf", marker = "python_full_version < '3.14'" }, - { name = "python-dateutil", marker = "python_full_version < '3.14'" }, - { name = "pyyaml", marker = "python_full_version < '3.14'" }, - { name = "setuptools", marker = "python_full_version < '3.14'" }, - { name = "snowflake-connector-python", marker = "python_full_version < '3.14'" }, - { name = "typing-extensions", marker = "python_full_version < '3.14'" }, - { name = "tzlocal", marker = "python_full_version < '3.14'" }, - { name = "wheel", marker = "python_full_version < '3.14'" }, + { name = "cloudpickle", version = "3.1.1", source = { registry = "https://pypi.org/simple" } }, + { name = "protobuf" }, + { name = "python-dateutil" }, + { name = "pyyaml" }, + { name = "setuptools" }, + { name = "snowflake-connector-python" }, + { name = "typing-extensions" }, + { name = "tzlocal" }, + { name = "wheel" }, ] sdist = { url = "https://files.pythonhosted.org/packages/67/98/38189e919c54fc6f09a43e778d850ebf2116ced491595971ae5f9ca01b0c/snowflake_snowpark_python-1.53.0.tar.gz", hash = "sha256:6eab04d5703fac72982f3666140c006d6fb9964d46a04fd953766410af8fc62e", size = 1783158, upload-time = "2026-07-09T16:15:19.599Z" } wheels = [ @@ -22326,23 +22325,23 @@ resolution-markers = [ "(python_full_version < '3.11' and platform_machine != 'arm64') or (python_full_version < '3.11' and sys_platform != 'darwin')", ] dependencies = [ - { name = "alabaster", marker = "python_full_version < '3.11'" }, - { name = "babel", marker = "python_full_version < '3.11'" }, - { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, - { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "imagesize", marker = "python_full_version < '3.11'" }, - { name = "jinja2", marker = "python_full_version < '3.11'" }, - { name = "packaging", marker = "python_full_version < '3.11'" }, - { name = "pygments", marker = "python_full_version < '3.11'" }, - { name = "requests", marker = "python_full_version < '3.11'" }, - { name = "snowballstemmer", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11'" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" } }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, + { name = "tomli" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } wheels = [ @@ -22358,23 +22357,23 @@ resolution-markers = [ "(python_full_version == '3.11.*' and platform_machine != 'arm64') or (python_full_version == '3.11.*' and sys_platform != 'darwin')", ] dependencies = [ - { name = "alabaster", marker = "python_full_version == '3.11.*'" }, - { name = "babel", marker = "python_full_version == '3.11.*'" }, - { name = "colorama", marker = "python_full_version == '3.11.*' and sys_platform == 'win32'" }, - { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "imagesize", marker = "python_full_version == '3.11.*'" }, - { name = "jinja2", marker = "python_full_version == '3.11.*'" }, - { name = "packaging", marker = "python_full_version == '3.11.*'" }, - { name = "pygments", marker = "python_full_version == '3.11.*'" }, - { name = "requests", marker = "python_full_version == '3.11.*'" }, - { name = "roman-numerals", marker = "python_full_version == '3.11.*'" }, - { name = "snowballstemmer", marker = "python_full_version == '3.11.*'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version == '3.11.*'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version == '3.11.*'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version == '3.11.*'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version == '3.11.*'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version == '3.11.*'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version == '3.11.*'" }, + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" } }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" } wheels = [ @@ -22396,23 +22395,23 @@ resolution-markers = [ "(python_full_version == '3.12.*' and platform_machine != 'arm64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", ] dependencies = [ - { name = "alabaster", marker = "python_full_version >= '3.12'" }, - { name = "babel", marker = "python_full_version >= '3.12'" }, - { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, - { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "imagesize", marker = "python_full_version >= '3.12'" }, - { name = "jinja2", marker = "python_full_version >= '3.12'" }, - { name = "packaging", marker = "python_full_version >= '3.12'" }, - { name = "pygments", marker = "python_full_version >= '3.12'" }, - { name = "requests", marker = "python_full_version >= '3.12'" }, - { name = "roman-numerals", marker = "python_full_version >= '3.12'" }, - { name = "snowballstemmer", marker = "python_full_version >= '3.12'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.12'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.12'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.12'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.12'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.12'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.12'" }, + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" } }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } wheels = [ @@ -22478,12 +22477,12 @@ resolution-markers = [ "(python_full_version < '3.11' and platform_machine != 'arm64') or (python_full_version < '3.11' and sys_platform != 'darwin')", ] dependencies = [ - { name = "colorama", marker = "python_full_version < '3.11'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "starlette", marker = "python_full_version < '3.11'" }, - { name = "uvicorn", marker = "python_full_version < '3.11'" }, - { name = "watchfiles", marker = "python_full_version < '3.11'" }, - { name = "websockets", marker = "python_full_version < '3.11'" }, + { name = "colorama" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" } }, + { name = "starlette" }, + { name = "uvicorn" }, + { name = "watchfiles" }, + { name = "websockets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a5/2c/155e1de2c1ba96a72e5dba152c509a8b41e047ee5c2def9e9f0d812f8be7/sphinx_autobuild-2024.10.3.tar.gz", hash = "sha256:248150f8f333e825107b6d4b86113ab28fa51750e5f9ae63b59dc339be951fb1", size = 14023, upload-time = "2024-10-02T23:15:30.172Z" } wheels = [ @@ -22507,13 +22506,13 @@ resolution-markers = [ "(python_full_version == '3.11.*' and platform_machine != 'arm64') or (python_full_version == '3.11.*' and sys_platform != 'darwin')", ] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.11'" }, - { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "colorama" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "starlette", marker = "python_full_version >= '3.11'" }, - { name = "uvicorn", marker = "python_full_version >= '3.11'" }, - { name = "watchfiles", marker = "python_full_version >= '3.11'" }, - { name = "websockets", marker = "python_full_version >= '3.11'" }, + { name = "starlette" }, + { name = "uvicorn" }, + { name = "watchfiles" }, + { name = "websockets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e0/3c/a59a3a453d4133777f7ed2e83c80b7dc817d43c74b74298ca0af869662ad/sphinx_autobuild-2025.8.25.tar.gz", hash = "sha256:9cf5aab32853c8c31af572e4fecdc09c997e2b8be5a07daf2a389e270e85b213", size = 15200, upload-time = "2025-08-25T18:44:55.436Z" } wheels = [ @@ -22543,7 +22542,7 @@ resolution-markers = [ "(python_full_version < '3.11' and platform_machine != 'arm64') or (python_full_version < '3.11' and sys_platform != 'darwin')", ] dependencies = [ - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/2b/69/b34e0cb5336f09c6866d53b4a19d76c227cdec1bbc7ac4de63ca7d58c9c7/sphinx_design-0.6.1.tar.gz", hash = "sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632", size = 2193689, upload-time = "2024-08-02T13:48:44.277Z" } wheels = [ @@ -22567,7 +22566,7 @@ resolution-markers = [ "(python_full_version == '3.11.*' and platform_machine != 'arm64') or (python_full_version == '3.11.*' and sys_platform != 'darwin')", ] dependencies = [ - { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/13/7b/804f311da4663a4aecc6cf7abd83443f3d4ded970826d0c958edc77d4527/sphinx_design-0.7.0.tar.gz", hash = "sha256:d2a3f5b19c24b916adb52f97c5f00efab4009ca337812001109084a740ec9b7a", size = 2203582, upload-time = "2026-01-19T13:12:53.297Z" } @@ -24298,11 +24297,11 @@ wheels = [ [[package]] name = "websocket-client" -version = "1.8.0" +version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, ] [[package]] @@ -24523,7 +24522,7 @@ name = "xmlsec" version = "1.3.17" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "lxml", marker = "python_full_version < '3.13'" }, + { name = "lxml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/49/14/538b75379e6ab8f688f14d8663e2ab138d9c778bac4999d155b5f33c71c1/xmlsec-1.3.17.tar.gz", hash = "sha256:f3fac9ae679f66585925cc00c5f6839ae36c1d03157619571dee18acc05b9c01", size = 115637, upload-time = "2025-11-11T16:20:46.019Z" } wheels = [ From d6e3e404c91a23b8aae1b0b883605f7544c41259 Mon Sep 17 00:00:00 2001 From: Joshua Varghese Date: Fri, 10 Jul 2026 17:40:12 +1000 Subject: [PATCH 2/3] Update exasol.py --- .../src/airflow/providers/exasol/hooks/exasol.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/providers/exasol/src/airflow/providers/exasol/hooks/exasol.py b/providers/exasol/src/airflow/providers/exasol/hooks/exasol.py index 2ce063e0aed47..fda4990d92447 100644 --- a/providers/exasol/src/airflow/providers/exasol/hooks/exasol.py +++ b/providers/exasol/src/airflow/providers/exasol/hooks/exasol.py @@ -72,17 +72,6 @@ def _validate_query_params(parameters: Iterable | Mapping[str, Any] | None) -> d """ Validate that query parameters are a dict, as required by pyexasol. - pyexasol's ``ExaConnection.execute``/``export_to_pandas``/``export_to_file`` have only ever - accepted a ``dict`` for query parameters (they are interpolated into the SQL via - ``str.format(**query_params)``, and pyexasol's own type stubs declare ``query_params: dict | - None``). Positional-style parameters (lists/tuples) were never actually supported at runtime, - even though this hook's signatures (inherited from - :class:`~airflow.providers.common.sql.hooks.sql.DbApiHook`) advertise the broader - ``Iterable | Mapping[str, Any] | None`` type. As of pyexasol 2.x, which ships a ``py.typed`` - marker, mypy now enforces this at the type level too. We validate explicitly here so that - passing a non-dict fails fast with a clear message instead of an opaque error surfacing deep - inside pyexasol. - :param parameters: The parameters as passed to this hook's public methods. :return: The same parameters, narrowed to ``dict[str, Any] | None`` for pyexasol/mypy. """ From 612378a15727bcf6051092e01a2ef452e171f2ef Mon Sep 17 00:00:00 2001 From: Joshua Varghese Date: Fri, 10 Jul 2026 17:44:49 +1000 Subject: [PATCH 3/3] Update test_exasol.py --- providers/exasol/tests/unit/exasol/hooks/test_exasol.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/providers/exasol/tests/unit/exasol/hooks/test_exasol.py b/providers/exasol/tests/unit/exasol/hooks/test_exasol.py index 16f133f7a8867..5d110e7b93929 100644 --- a/providers/exasol/tests/unit/exasol/hooks/test_exasol.py +++ b/providers/exasol/tests/unit/exasol/hooks/test_exasol.py @@ -260,13 +260,6 @@ def test_get_first_hook_lineage(self, mock_send_lineage): @pytest.mark.parametrize("method_name", ["get_records", "get_first"]) def test_get_records_and_get_first_reject_list_of_statements(self, method_name): - """Regression test: pyexasol's execute() never accepted a list of SQL statements. - - ExasolHook.get_records()/get_first() are typed as accepting ``str | list[str]`` (matching the - DbApiHook base class contract), but they call pyexasol's ``conn.execute()`` directly, which has - only ever accepted a single ``str`` query. Passing a list here was always broken at runtime; we - now raise a clear error instead of forwarding it straight to pyexasol. - """ method = getattr(self.db_hook, method_name) with pytest.raises(TypeError, match="only supports a single SQL string"): method(["SELECT 1", "SELECT 2"])