This is an automated email from the ASF dual-hosted git repository.
dstandish pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 438ba41e14 Control permissibility of driver config in extra from
airflow.cfg (#31754)
438ba41e14 is described below
commit 438ba41e142593f2b0916893eccbd08fbe4d277b
Author: Daniel Standish <[email protected]>
AuthorDate: Wed Jun 7 15:12:11 2023 -0700
Control permissibility of driver config in extra from airflow.cfg (#31754)
Refines https://github.com/apache/airflow/pull/31713, which disabled (by
default) setting driver through extra. Here we make it so that the flag to
enable is located in airflow config instead of hook param.
---
airflow/configuration.py | 2 +-
airflow/providers/odbc/hooks/odbc.py | 37 +++++++++++-----------
.../connections/odbc.rst | 9 ++++++
docs/apache-airflow/howto/set-config.rst | 13 ++++++++
tests/core/test_configuration.py | 19 ++++++++++-
tests/providers/odbc/hooks/test_odbc.py | 5 +--
6 files changed, 63 insertions(+), 22 deletions(-)
diff --git a/airflow/configuration.py b/airflow/configuration.py
index 694032cd9e..66e117cae4 100644
--- a/airflow/configuration.py
+++ b/airflow/configuration.py
@@ -486,7 +486,7 @@ class AirflowConfigParser(ConfigParser):
)
def _env_var_name(self, section: str, key: str) -> str:
- return f"{ENV_VAR_PREFIX}{section.upper()}__{key.upper()}"
+ return f"{ENV_VAR_PREFIX}{section.replace('.',
'_').upper()}__{key.upper()}"
def _get_env_var_option(self, section: str, key: str):
# must have format AIRFLOW__{SECTION}__{KEY} (note double underscore)
diff --git a/airflow/providers/odbc/hooks/odbc.py
b/airflow/providers/odbc/hooks/odbc.py
index 1b125ae2f2..5f65517c87 100644
--- a/airflow/providers/odbc/hooks/odbc.py
+++ b/airflow/providers/odbc/hooks/odbc.py
@@ -30,21 +30,21 @@ class OdbcHook(DbApiHook):
"""
Interact with odbc data sources using pyodbc.
+ To configure driver, in addition to supplying as constructor arg, the
following are also supported:
+ * set ``driver`` parameter in ``hook_params`` dictionary when
instantiating hook by SQL operators.
+ * set ``driver`` extra in the connection and set
``allow_driver_in_extra`` to True in
+ section ``providers.odbc`` section of airflow config.
+ * patch ``OdbcHook.default_driver`` in ``local_settings.py`` file.
+
See :doc:`/connections/odbc` for full documentation.
:param args: passed to DbApiHook
:param database: database to use -- overrides connection ``schema``
- :param driver: name of driver or path to driver. You can also set the
driver via:
- * setting ``driver`` parameter in ``hook_params`` dictionary when
instantiating hook by SQL operators.
- * setting `driver`` extra in the connection and setting
``allow_driver_extra`` to True.
- * setting ``OdbcHook.default_driver`` in ``local_settings.py`` file.
+ :param driver: name of driver or path to driver. see above for more info
:param dsn: name of DSN to use. overrides DSN supplied in connection
``extra``
:param connect_kwargs: keyword arguments passed to ``pyodbc.connect``
:param sqlalchemy_scheme: Scheme sqlalchemy connection. Default is
``mssql+pyodbc`` Only used for
``get_sqlalchemy_engine`` and ``get_sqlalchemy_connection`` methods.
- :param allow_driver_extra: If True, allows to use driver extra in
connection string (default False).
- You should make sure that you trust the users who can edit
connections in the UI to not use it
- maliciously.
:param kwargs: passed to DbApiHook
"""
@@ -65,7 +65,6 @@ class OdbcHook(DbApiHook):
dsn: str | None = None,
connect_kwargs: dict | None = None,
sqlalchemy_scheme: str | None = None,
- allow_driver_extra: bool = False,
**kwargs,
) -> None:
super().__init__(*args, **kwargs)
@@ -76,7 +75,6 @@ class OdbcHook(DbApiHook):
self._sqlalchemy_scheme = sqlalchemy_scheme
self._connection = None
self._connect_kwargs = connect_kwargs
- self._allow_driver_extra = allow_driver_extra
@property
def connection(self):
@@ -112,15 +110,18 @@ class OdbcHook(DbApiHook):
def driver(self) -> str | None:
"""Driver from init param if given; else try to find one in connection
extra."""
extra_driver = self.connection_extra_lower.get("driver")
- if extra_driver:
- if self._allow_driver_extra:
- self._driver = extra_driver
- else:
- self.log.warning(
- "Please provide driver via 'driver' parameter of the Hook
constructor"
- " or via 'hook_params' dictionary 'driver' key when
instantiating hook by the"
- " SQL operators. The 'driver' extra will not be used."
- )
+ from airflow.configuration import conf
+
+ if extra_driver and conf.getboolean("providers.odbc",
"allow_driver_in_extra", fallback=False):
+ self._driver = extra_driver
+ else:
+ self.log.warning(
+ "You have supplied 'driver' via connection extra but it will
not be used. In order to "
+ "use 'driver' from extra you must set airflow config setting
`allow_driver_in_extra = True` "
+ "in section `providers.odbc`. Alternatively you may specify
driver via 'driver' parameter of "
+ "the hook constructor or via 'hook_params' dictionary with key
'driver' if using SQL "
+ "operators."
+ )
if not self._driver:
self._driver = self.default_driver
return self._driver.strip().lstrip("{").rstrip("}").strip() if
self._driver else None
diff --git a/docs/apache-airflow-providers-odbc/connections/odbc.rst
b/docs/apache-airflow-providers-odbc/connections/odbc.rst
index 176977d8fe..11d382eeb6 100644
--- a/docs/apache-airflow-providers-odbc/connections/odbc.rst
+++ b/docs/apache-airflow-providers-odbc/connections/odbc.rst
@@ -67,6 +67,15 @@ Extra (optional)
* This is only used when ``get_uri`` is invoked in
:py:meth:`~airflow.providers.common.sql.hooks.sql.DbApiHook.get_sqlalchemy_engine`.
By default, the hook uses
scheme ``mssql+pyodbc``. You may pass a string value here to
override.
+ - ``driver``
+ * The name of the driver to use on your system. Note that this is
only considered if ``allow_driver_in_extra``
+ is set to True in airflow config section ``providers.odbc`` (by
default it is not considered). Note: if setting
+ this config from env vars, use
``AIRFLOW__PROVIDERS_ODBC__ALLOW_DRIVER_IN_EXTRA=true``.
+
+ .. note::
+ If setting ``allow_driver_extra``to True, this allows users to set the
driver via the Airflow Connection's
+ ``extra`` field. By default this is not allowed. If enabling this
functionality, you should make sure
+ that you trust the users who can edit connections in the UI to not use
it maliciously.
.. note::
You are responsible for installing an ODBC driver on your system.
diff --git a/docs/apache-airflow/howto/set-config.rst
b/docs/apache-airflow/howto/set-config.rst
index 02fa9bac2d..b443cff9c8 100644
--- a/docs/apache-airflow/howto/set-config.rst
+++ b/docs/apache-airflow/howto/set-config.rst
@@ -38,6 +38,19 @@ or by creating a corresponding environment variable:
export AIRFLOW__DATABASE__SQL_ALCHEMY_CONN=my_conn_string
+Note that when the section name has a dot in it, you must replace it with an
underscore when setting the env var.
+For example consider the pretend section ``providers.some_provider``:
+
+.. code-block:: ini
+
+ [providers.some_provider>]
+ this_param = true
+
+.. code-block:: bash
+
+ export AIRFLOW__PROVIDERS_SOME_PROVIDER__THIS_PARAM=true
+
+
You can also derive the connection string at run time by appending ``_cmd`` to
the key like this:
diff --git a/tests/core/test_configuration.py b/tests/core/test_configuration.py
index 961bc3c7cc..2afc78bcaf 100644
--- a/tests/core/test_configuration.py
+++ b/tests/core/test_configuration.py
@@ -226,6 +226,24 @@ key6 = value6
assert "key4" not in cfg_dict["test"]
assert "printf key4_result" == cfg_dict["test"]["key4_cmd"]
+ def test_can_read_dot_section(self):
+ test_config = """[test.abc]
+key1 = true
+"""
+ test_conf = AirflowConfigParser()
+ test_conf.read_string(test_config)
+ section = "test.abc"
+ key = "key1"
+ assert test_conf.getboolean(section, key) is True
+
+ with mock.patch.dict(
+ "os.environ",
+ {
+ "AIRFLOW__TEST_ABC__KEY1": "false", # note that the '.' is
converted to '_'
+ },
+ ):
+ assert test_conf.getboolean(section, key) is False
+
@mock.patch("airflow.providers.hashicorp._internal_client.vault_client.hvac")
@conf_vars(
{
@@ -596,7 +614,6 @@ notacommand = OK
@pytest.mark.parametrize("display_sensitive, result", [(True, "OK"),
(False, "< hidden >")])
def test_as_dict_display_sensitivewith_command_from_env(self,
display_sensitive, result):
-
test_cmdenv_conf = AirflowConfigParser()
test_cmdenv_conf.sensitive_config_values.add(("testcmdenv",
"itsacommand"))
with mock.patch.dict("os.environ"):
diff --git a/tests/providers/odbc/hooks/test_odbc.py
b/tests/providers/odbc/hooks/test_odbc.py
index 3d21806cf9..a7ea20a77d 100644
--- a/tests/providers/odbc/hooks/test_odbc.py
+++ b/tests/providers/odbc/hooks/test_odbc.py
@@ -184,9 +184,10 @@ class TestOdbcHook:
def test_driver_extra_raises_warning_by_default(self, caplog):
with caplog.at_level(logging.WARNING,
logger="airflow.providers.odbc.hooks.test_odbc"):
driver = self.get_hook(conn_params=dict(extra='{"driver": "Blah
driver"}')).driver
- assert "Please provide driver via 'driver' parameter of the Hook"
in caplog.text
+ assert "You have supplied 'driver' via connection extra but it
will not be used" in caplog.text
assert driver is None
+ @mock.patch.dict("os.environ",
{"AIRFLOW__PROVIDERS_ODBC__ALLOW_DRIVER_IN_EXTRA": "TRUE"})
def test_driver_extra_works_when_allow_driver_extra(self):
hook = self.get_hook(
conn_params=dict(extra='{"driver": "Blah driver"}'),
hook_params=dict(allow_driver_extra=True)
@@ -211,7 +212,7 @@ class TestOdbcHook:
with patch.object(OdbcHook, "default_driver", "Blah driver"):
with caplog.at_level(logging.WARNING,
logger="airflow.providers.odbc.hooks.test_odbc"):
driver = self.get_hook(conn_params=dict(extra='{"driver":
"Blah driver2"}')).driver
- assert "Please provide driver via 'driver' parameter of the
Hook" in caplog.text
+ assert "have supplied 'driver' via connection extra but it
will not be used" in caplog.text
assert driver == "Blah driver"
def test_database(self):