Skip to content

Commit e4efc87

Browse files
authored
Switch the default async MySQL driver to asyncmy (#75218)
1 parent bfb620d commit e4efc87

9 files changed

Lines changed: 153 additions & 10 deletions

File tree

‎airflow-core/docs/howto/set-up-database.rst‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ We recommend using the ``mysqlclient`` driver and specifying it in your SqlAlche
373373
mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
374374
375375
.. important::
376-
The integration of MySQL backend has only been validated using the ``mysqlclient`` driver
376+
The synchronous integration of MySQL backend has only been validated using the ``mysqlclient`` driver
377377
during Apache Airflow's continuous integration (CI) process.
378378

379379
If you want to use other drivers visit the `MySQL Dialect <https://docs.sqlalchemy.org/en/20/dialects/mysql.html>`__ in SQLAlchemy documentation for more information regarding download
@@ -389,6 +389,32 @@ In addition, you also should pay particular attention to MySQL's encoding. Altho
389389
Read https://stackoverflow.com/questions/9192027/invalid-default-value-for-create-date-timestamp-field for how to disable it.
390390
See `SQL Mode - NO_ZERO_DATE <https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html#sqlmode_no_zero_date>`__ for more information.
391391

392+
Async SQLAlchemy engine and the async driver
393+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
394+
395+
When ``[database] sql_alchemy_conn_async`` is not set, Airflow derives the async MySQL
396+
metadata database URL from ``sql_alchemy_conn``. It selects ``asyncmy`` when installed:
397+
398+
.. code-block:: text
399+
400+
mysql+asyncmy://<user>:<password>@<host>[:<port>]/<dbname>
401+
402+
The MySQL provider installs ``asyncmy`` by default. If an older provider release does not
403+
install it, core falls back to ``aiomysql``, so upgrading core alone keeps working.
404+
The provider also keeps installing ``aiomysql`` for older supported core releases that
405+
always derive a ``mysql+aiomysql://`` URL.
406+
407+
An explicit ``sql_alchemy_conn_async`` is never rewritten. To keep using ``aiomysql``,
408+
install ``apache-airflow-providers-mysql[aiomysql]`` and set:
409+
410+
.. code-block:: ini
411+
412+
[database]
413+
sql_alchemy_conn_async = mysql+aiomysql://<user>:<password>@<host>[:<port>]/<dbname>
414+
415+
Review ``sql_alchemy_connect_args_async`` when changing drivers: driver-specific
416+
connection arguments must be supported by the selected driver.
417+
392418
MsSQL Database
393419
--------------
394420

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Default async MySQL driver changed from aiomysql to asyncmy
2+
3+
When ``[database] sql_alchemy_conn_async`` is not set, Airflow now derives a
4+
``mysql+asyncmy://`` metadata database URL when asyncmy is installed. The MySQL
5+
provider installs asyncmy by default. If asyncmy is absent, core keeps deriving
6+
a ``mysql+aiomysql://`` URL, preserving compatibility with older provider releases.
7+
8+
The provider retains aiomysql as a default dependency for older supported core
9+
releases that cannot select asyncmy. An explicit ``sql_alchemy_conn_async`` is
10+
never rewritten. To keep using aiomysql, install
11+
``apache-airflow-providers-mysql[aiomysql]`` and configure a
12+
``mysql+aiomysql://`` async URL. Review driver-specific
13+
``sql_alchemy_connect_args_async`` values when switching drivers.

‎airflow-core/src/airflow/settings.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from collections.abc import Callable, Mapping
2727
from functools import cache, partial
2828
from importlib import metadata
29+
from importlib.util import find_spec
2930
from typing import TYPE_CHECKING, Any, Literal
3031

3132
import pluggy
@@ -63,13 +64,13 @@
6364

6465
_USE_PSYCOPG3: bool
6566
try:
66-
from importlib.util import find_spec
67-
6867
_USE_PSYCOPG3 = find_spec("psycopg") is not None
6968

7069
except (ImportError, ModuleNotFoundError):
7170
_USE_PSYCOPG3 = False
7271

72+
_USE_ASYNCMY = find_spec("asyncmy") is not None
73+
7374
if TYPE_CHECKING:
7475
from sqlalchemy.engine import Engine
7576

@@ -266,7 +267,7 @@ def _get_async_conn_uri_from_sync(sync_uri):
266267
AIO_LIBS_MAPPING = {
267268
"sqlite": "aiosqlite",
268269
"postgresql": "psycopg_async" if _USE_PSYCOPG3 else "asyncpg",
269-
"mysql": "aiomysql",
270+
"mysql": "asyncmy" if _USE_ASYNCMY else "aiomysql",
270271
}
271272

272273
scheme, rest = sync_uri.split(":", maxsplit=1)

‎airflow-core/tests/unit/core/test_settings.py‎

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,6 @@ def test_sqlite_relative_path(value, expectation):
545545
("postgresql+psycopg2://user:pass@host/db", True, "postgresql+psycopg_async://user:pass@host/db"),
546546
("postgresql+psycopg2://user:pass@host/db", False, "postgresql+asyncpg://user:pass@host/db"),
547547
("sqlite:////root/airflow.db", True, "sqlite+aiosqlite:////root/airflow.db"),
548-
("mysql://user:pass@host/db", True, "mysql+aiomysql://user:pass@host/db"),
549548
("mssql://user:pass@host/db", True, "mssql://user:pass@host/db"),
550549
],
551550
)
@@ -556,10 +555,28 @@ def test_get_async_conn_uri_from_sync(sync_uri, use_psycopg3, expected_async_uri
556555
assert settings._get_async_conn_uri_from_sync(sync_uri) == expected_async_uri
557556

558557

559-
def test_explicit_sql_alchemy_conn_async_is_not_rewritten():
558+
@pytest.mark.parametrize("sync_scheme", ["mysql", "mysql+mysqldb", "mysql+pymysql"])
559+
@pytest.mark.parametrize(("use_asyncmy", "expected_driver"), [(True, "asyncmy"), (False, "aiomysql")])
560+
def test_get_async_mysql_conn_uri_from_sync(monkeypatch, sync_scheme, use_asyncmy, expected_driver):
561+
monkeypatch.setattr(settings, "_USE_ASYNCMY", use_asyncmy)
562+
rest = "user:p%40ss@host:3307/db?charset=utf8mb4&unix_socket=%2Ftmp%2Fmysql.sock"
563+
564+
assert settings._get_async_conn_uri_from_sync(f"{sync_scheme}://{rest}") == (
565+
f"mysql+{expected_driver}://{rest}"
566+
)
567+
568+
569+
@pytest.mark.parametrize(
570+
"explicit_async_uri",
571+
[
572+
"postgresql+asyncpg://user:pass@host/db",
573+
"mysql+aiomysql://user:pass@host/db",
574+
"mysql+asyncmy://user:pass@host/db",
575+
],
576+
)
577+
def test_explicit_sql_alchemy_conn_async_is_not_rewritten(explicit_async_uri):
560578
from airflow import settings
561579

562-
explicit_async_uri = "postgresql+asyncpg://user:pass@host/db"
563580
try:
564581
with conf_vars({("database", "sql_alchemy_conn_async"): explicit_async_uri}):
565582
settings.configure_vars()
@@ -693,6 +710,7 @@ def test_failed_disposal_preserves_resources_for_retry(self, monkeypatch, error)
693710
pytest.param("postgresql+psycopg_async", marks=pytest.mark.backend("postgres")),
694711
pytest.param("postgresql+asyncpg", marks=pytest.mark.backend("postgres")),
695712
pytest.param("mysql+aiomysql", marks=pytest.mark.backend("mysql")),
713+
pytest.param("mysql+asyncmy", marks=pytest.mark.backend("mysql")),
696714
pytest.param("sqlite+aiosqlite", marks=pytest.mark.backend("sqlite")),
697715
],
698716
)
@@ -723,6 +741,8 @@ async def run():
723741
pass
724742
else:
725743
raise AssertionError("connection remained open")
744+
elif driver == "mysql+asyncmy":
745+
assert not raw.connected
726746
else:
727747
assert raw.is_closed() if driver == "postgresql+asyncpg" else raw.closed
728748

‎providers/mysql/README.rst‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ PIP package Version required
5959
``mysqlclient`` ``>=2.2.5; sys_platform != "darwin"``
6060
``mysql-connector-python`` ``>=9.1.0; python_version < "3.12"``
6161
``mysql-connector-python`` ``>=9.1.0,!=9.7.0; python_version >= "3.12"``
62+
``asyncmy`` ``>=0.2.16``
6263
``aiomysql`` ``>=0.2.0``
6364
``pymysql`` ``>=1.0.3,<1.2``
6465
========================================== =============================================
@@ -92,6 +93,7 @@ Optional dependencies
9293
========================== ========================================
9394
Extra Dependencies
9495
========================== ========================================
96+
``aiomysql`` ``aiomysql>=0.2.0``
9597
``mysql-connector-python``
9698
``amazon`` ``apache-airflow-providers-amazon``
9799
``openlineage`` ``apache-airflow-providers-openlineage``

‎providers/mysql/docs/changelog.rst‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@
2626
Changelog
2727
---------
2828

29+
.. note::
30+
31+
The provider now installs ``asyncmy`` for the async metadata database engine.
32+
Core releases that include asyncmy selection derive ``mysql+asyncmy://`` URLs
33+
when the driver is installed. Older core releases continue to use ``aiomysql``,
34+
which remains installed by default for compatibility.
35+
36+
To explicitly select the previous driver, set ``[database] sql_alchemy_conn_async``
37+
to a ``mysql+aiomysql://`` URL. The ``[aiomysql]`` extra also declares this driver
38+
for installations that need it after it becomes optional.
39+
2940
6.7.1
3041
.....
3142

‎providers/mysql/docs/index.rst‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ PIP package Version required
106106
``mysqlclient`` ``>=2.2.5; sys_platform != "darwin"``
107107
``mysql-connector-python`` ``>=9.1.0; python_version < "3.12"``
108108
``mysql-connector-python`` ``>=9.1.0,!=9.7.0; python_version >= "3.12"``
109+
``asyncmy`` ``>=0.2.16``
109110
``aiomysql`` ``>=0.2.0``
110111
``pymysql`` ``>=1.0.3,<1.2``
111112
========================================== =============================================
@@ -141,12 +142,13 @@ Install them when installing from PyPI. For example:
141142

142143
.. code-block:: bash
143144
144-
pip install apache-airflow-providers-mysql[mysql-connector-python]
145+
pip install apache-airflow-providers-mysql[aiomysql]
145146
146147
147148
========================== ========================================
148149
Extra Dependencies
149150
========================== ========================================
151+
``aiomysql`` ``aiomysql>=0.2.0``
150152
``mysql-connector-python``
151153
``amazon`` ``apache-airflow-providers-amazon``
152154
``openlineage`` ``apache-airflow-providers-openlineage``

‎providers/mysql/pyproject.toml‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,23 @@ dependencies = [
6767
'mysqlclient>=2.2.5; sys_platform != "darwin"',
6868
'mysql-connector-python>=9.1.0; python_version < "3.12"',
6969
'mysql-connector-python>=9.1.0, !=9.7.0; python_version >= "3.12"',
70+
"asyncmy>=0.2.16",
71+
# Older supported Airflow cores derive mysql+aiomysql URLs unconditionally.
72+
# Keep aiomysql installed until every supported core can select asyncmy.
73+
"aiomysql>=0.2.0",
7074
# pymysql 1.2.0 changed ping() to require reconnect as a positional arg;
7175
# SQLAlchemy's AsyncAdapt_aiomysql_connection.ping has no default for it — cap
7276
# until the SQLAlchemy fix is released. Tracked upstream at
7377
# https://github.com/sqlalchemy/sqlalchemy/issues/13306
74-
"aiomysql>=0.2.0",
7578
"pymysql>=1.0.3,<1.2",
7679
]
7780

7881
# The optional dependencies should be modified in place in the generated file
7982
# Any change in the dependencies is preserved when the file is regenerated
8083
[project.optional-dependencies]
84+
"aiomysql" = [
85+
"aiomysql>=0.2.0",
86+
]
8187
# only needed for backwards compatibility
8288
"mysql-connector-python" = [
8389
]

0 commit comments

Comments
 (0)