-
Notifications
You must be signed in to change notification settings - Fork 17.5k
Migrate Exasol provider to pyexasol 2.x #69695
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,21 +67,36 @@ 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. | ||
|
|
||
| :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): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This rejects any mapping but a dict. This is probably not necessary? |
||
| 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 +160,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 +199,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." | ||
| ) | ||
|
Comment on lines
+208
to
+212
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is duplicated too many times. I would refactor it into a private function called by each place. You can use |
||
| 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 +224,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 +359,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)) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The list note stuff but nothing here strike as airflow breaking change is it?
Can you just remove the breaking changes title?
also, did you validate these statements? This feels AI generated. Please provide evidences that the points here are valid. Just referencing to the upstream docs/code should be enough
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two things I can confirm with the changelog
with_column_namesboolean fix: I can confirm that pyexasol's changelog has a dedicated bugfix (https://github.com/exasol/pyexasol/releases#release-2.2.1 - # 265)). So any value used to get forced to beTrue. Now it should require a real Booleanpy.typedmarker: Confirmed. In the Same Changelog batch above (# 298) pyexasol added it when swapping the deprecatedrsadependency forcryptography. I think that this is the actual mechanism not a breaking change.However,
get_records()/get_first()only accepting a single SQL string. I cannot confirm this one tho. No changelog says explicitly this changed in 2.x. However, it's consistent with every example in pyexasol's docs since they only show a single string. This means that's always the case.Parameters should be
dict/Mapping: Can't confirm either, but no changelog ref to point to.So, two claims are true, two are unverified assumptions.
I suggest to drop the "Breaking changes" title since it is not a change to the provider's own interface and drop the two unverifiable bullets entirely. Keep only the two we can back: