API refactor
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-10-07 16:25:52 +09:00
parent 76d0d86211
commit 91c7e04474
1171 changed files with 81940 additions and 44117 deletions

View File

@@ -1,10 +1,9 @@
# mysql/aiomysql.py
# Copyright (C) 2005-2023 the SQLAlchemy authors and contributors <see AUTHORS
# dialects/mysql/aiomysql.py
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors <see AUTHORS
# file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
# mypy: ignore-errors
r"""
.. dialect:: mysql+aiomysql
@@ -23,207 +22,108 @@ This dialect should normally be used only with the
:func:`_asyncio.create_async_engine` engine creation function::
from sqlalchemy.ext.asyncio import create_async_engine
engine = create_async_engine("mysql+aiomysql://user:pass@hostname/dbname?charset=utf8mb4")
engine = create_async_engine(
"mysql+aiomysql://user:pass@hostname/dbname?charset=utf8mb4"
)
""" # noqa
from __future__ import annotations
from types import ModuleType
from typing import Any
from typing import Dict
from typing import Optional
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union
from .pymysql import MySQLDialect_pymysql
from ... import pool
from ... import util
from ...engine import AdaptedConnection
from ...util.concurrency import asyncio
from ...connectors.asyncio import AsyncAdapt_dbapi_connection
from ...connectors.asyncio import AsyncAdapt_dbapi_cursor
from ...connectors.asyncio import AsyncAdapt_dbapi_module
from ...connectors.asyncio import AsyncAdapt_dbapi_ss_cursor
from ...util.concurrency import await_fallback
from ...util.concurrency import await_only
if TYPE_CHECKING:
class AsyncAdapt_aiomysql_cursor:
# TODO: base on connectors/asyncio.py
# see #10415
server_side = False
__slots__ = (
"_adapt_connection",
"_connection",
"await_",
"_cursor",
"_rows",
)
from ...connectors.asyncio import AsyncIODBAPIConnection
from ...connectors.asyncio import AsyncIODBAPICursor
from ...engine.interfaces import ConnectArgsType
from ...engine.interfaces import DBAPIConnection
from ...engine.interfaces import DBAPICursor
from ...engine.interfaces import DBAPIModule
from ...engine.interfaces import PoolProxiedConnection
from ...engine.url import URL
def __init__(self, adapt_connection):
self._adapt_connection = adapt_connection
self._connection = adapt_connection._connection
self.await_ = adapt_connection.await_
cursor = self._connection.cursor(adapt_connection.dbapi.Cursor)
class AsyncAdapt_aiomysql_cursor(AsyncAdapt_dbapi_cursor):
__slots__ = ()
# see https://github.com/aio-libs/aiomysql/issues/543
self._cursor = self.await_(cursor.__aenter__())
self._rows = []
def _make_new_cursor(
self, connection: AsyncIODBAPIConnection
) -> AsyncIODBAPICursor:
return connection.cursor(self._adapt_connection.dbapi.Cursor)
@property
def description(self):
return self._cursor.description
@property
def rowcount(self):
return self._cursor.rowcount
class AsyncAdapt_aiomysql_ss_cursor(
AsyncAdapt_dbapi_ss_cursor, AsyncAdapt_aiomysql_cursor
):
__slots__ = ()
@property
def arraysize(self):
return self._cursor.arraysize
@arraysize.setter
def arraysize(self, value):
self._cursor.arraysize = value
@property
def lastrowid(self):
return self._cursor.lastrowid
def close(self):
# note we aren't actually closing the cursor here,
# we are just letting GC do it. to allow this to be async
# we would need the Result to change how it does "Safe close cursor".
# MySQL "cursors" don't actually have state to be "closed" besides
# exhausting rows, which we already have done for sync cursor.
# another option would be to emulate aiosqlite dialect and assign
# cursor only if we are doing server side cursor operation.
self._rows[:] = []
def execute(self, operation, parameters=None):
return self.await_(self._execute_async(operation, parameters))
def executemany(self, operation, seq_of_parameters):
return self.await_(
self._executemany_async(operation, seq_of_parameters)
def _make_new_cursor(
self, connection: AsyncIODBAPIConnection
) -> AsyncIODBAPICursor:
return connection.cursor(
self._adapt_connection.dbapi.aiomysql.cursors.SSCursor
)
async def _execute_async(self, operation, parameters):
async with self._adapt_connection._execute_mutex:
result = await self._cursor.execute(operation, parameters)
if not self.server_side:
# aiomysql has a "fake" async result, so we have to pull it out
# of that here since our default result is not async.
# we could just as easily grab "_rows" here and be done with it
# but this is safer.
self._rows = list(await self._cursor.fetchall())
return result
async def _executemany_async(self, operation, seq_of_parameters):
async with self._adapt_connection._execute_mutex:
return await self._cursor.executemany(operation, seq_of_parameters)
def setinputsizes(self, *inputsizes):
pass
def __iter__(self):
while self._rows:
yield self._rows.pop(0)
def fetchone(self):
if self._rows:
return self._rows.pop(0)
else:
return None
def fetchmany(self, size=None):
if size is None:
size = self.arraysize
retval = self._rows[0:size]
self._rows[:] = self._rows[size:]
return retval
def fetchall(self):
retval = self._rows[:]
self._rows[:] = []
return retval
class AsyncAdapt_aiomysql_ss_cursor(AsyncAdapt_aiomysql_cursor):
# TODO: base on connectors/asyncio.py
# see #10415
class AsyncAdapt_aiomysql_connection(AsyncAdapt_dbapi_connection):
__slots__ = ()
server_side = True
def __init__(self, adapt_connection):
self._adapt_connection = adapt_connection
self._connection = adapt_connection._connection
self.await_ = adapt_connection.await_
_cursor_cls = AsyncAdapt_aiomysql_cursor
_ss_cursor_cls = AsyncAdapt_aiomysql_ss_cursor
cursor = self._connection.cursor(adapt_connection.dbapi.SSCursor)
def ping(self, reconnect: bool) -> None:
assert not reconnect
self.await_(self._connection.ping(reconnect))
self._cursor = self.await_(cursor.__aenter__())
def character_set_name(self) -> Optional[str]:
return self._connection.character_set_name() # type: ignore[no-any-return] # noqa: E501
def close(self):
if self._cursor is not None:
self.await_(self._cursor.close())
self._cursor = None
def fetchone(self):
return self.await_(self._cursor.fetchone())
def fetchmany(self, size=None):
return self.await_(self._cursor.fetchmany(size=size))
def fetchall(self):
return self.await_(self._cursor.fetchall())
class AsyncAdapt_aiomysql_connection(AdaptedConnection):
# TODO: base on connectors/asyncio.py
# see #10415
await_ = staticmethod(await_only)
__slots__ = ("dbapi", "_execute_mutex")
def __init__(self, dbapi, connection):
self.dbapi = dbapi
self._connection = connection
self._execute_mutex = asyncio.Lock()
def ping(self, reconnect):
return self.await_(self._connection.ping(reconnect))
def character_set_name(self):
return self._connection.character_set_name()
def autocommit(self, value):
def autocommit(self, value: Any) -> None:
self.await_(self._connection.autocommit(value))
def cursor(self, server_side=False):
if server_side:
return AsyncAdapt_aiomysql_ss_cursor(self)
else:
return AsyncAdapt_aiomysql_cursor(self)
def get_autocommit(self) -> bool:
return self._connection.get_autocommit() # type: ignore
def rollback(self):
self.await_(self._connection.rollback())
def commit(self):
self.await_(self._connection.commit())
def close(self):
def terminate(self) -> None:
# it's not awaitable.
self._connection.close()
def close(self) -> None:
self.await_(self._connection.ensure_closed())
class AsyncAdaptFallback_aiomysql_connection(AsyncAdapt_aiomysql_connection):
# TODO: base on connectors/asyncio.py
# see #10415
__slots__ = ()
await_ = staticmethod(await_fallback)
class AsyncAdapt_aiomysql_dbapi:
def __init__(self, aiomysql, pymysql):
class AsyncAdapt_aiomysql_dbapi(AsyncAdapt_dbapi_module):
def __init__(self, aiomysql: ModuleType, pymysql: ModuleType):
self.aiomysql = aiomysql
self.pymysql = pymysql
self.paramstyle = "format"
self._init_dbapi_attributes()
self.Cursor, self.SSCursor = self._init_cursors_subclasses()
def _init_dbapi_attributes(self):
def _init_dbapi_attributes(self) -> None:
for name in (
"Warning",
"Error",
@@ -249,7 +149,7 @@ class AsyncAdapt_aiomysql_dbapi:
):
setattr(self, name, getattr(self.pymysql, name))
def connect(self, *arg, **kw):
def connect(self, *arg: Any, **kw: Any) -> AsyncAdapt_aiomysql_connection:
async_fallback = kw.pop("async_fallback", False)
creator_fn = kw.pop("async_creator_fn", self.aiomysql.connect)
@@ -264,17 +164,23 @@ class AsyncAdapt_aiomysql_dbapi:
await_only(creator_fn(*arg, **kw)),
)
def _init_cursors_subclasses(self):
def _init_cursors_subclasses(
self,
) -> Tuple[AsyncIODBAPICursor, AsyncIODBAPICursor]:
# suppress unconditional warning emitted by aiomysql
class Cursor(self.aiomysql.Cursor):
async def _show_warnings(self, conn):
class Cursor(self.aiomysql.Cursor): # type: ignore[misc, name-defined]
async def _show_warnings(
self, conn: AsyncIODBAPIConnection
) -> None:
pass
class SSCursor(self.aiomysql.SSCursor):
async def _show_warnings(self, conn):
class SSCursor(self.aiomysql.SSCursor): # type: ignore[misc, name-defined] # noqa: E501
async def _show_warnings(
self, conn: AsyncIODBAPIConnection
) -> None:
pass
return Cursor, SSCursor
return Cursor, SSCursor # type: ignore[return-value]
class MySQLDialect_aiomysql(MySQLDialect_pymysql):
@@ -285,15 +191,16 @@ class MySQLDialect_aiomysql(MySQLDialect_pymysql):
_sscursor = AsyncAdapt_aiomysql_ss_cursor
is_async = True
has_terminate = True
@classmethod
def import_dbapi(cls):
def import_dbapi(cls) -> AsyncAdapt_aiomysql_dbapi:
return AsyncAdapt_aiomysql_dbapi(
__import__("aiomysql"), __import__("pymysql")
)
@classmethod
def get_pool_class(cls, url):
def get_pool_class(cls, url: URL) -> type:
async_fallback = url.query.get("async_fallback", False)
if util.asbool(async_fallback):
@@ -301,25 +208,37 @@ class MySQLDialect_aiomysql(MySQLDialect_pymysql):
else:
return pool.AsyncAdaptedQueuePool
def create_connect_args(self, url):
def do_terminate(self, dbapi_connection: DBAPIConnection) -> None:
dbapi_connection.terminate()
def create_connect_args(
self, url: URL, _translate_args: Optional[Dict[str, Any]] = None
) -> ConnectArgsType:
return super().create_connect_args(
url, _translate_args=dict(username="user", database="db")
)
def is_disconnect(self, e, connection, cursor):
def is_disconnect(
self,
e: DBAPIModule.Error,
connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
cursor: Optional[DBAPICursor],
) -> bool:
if super().is_disconnect(e, connection, cursor):
return True
else:
str_e = str(e).lower()
return "not connected" in str_e
def _found_rows_client_flag(self):
from pymysql.constants import CLIENT
def _found_rows_client_flag(self) -> int:
from pymysql.constants import CLIENT # type: ignore
return CLIENT.FOUND_ROWS
return CLIENT.FOUND_ROWS # type: ignore[no-any-return]
def get_driver_connection(self, connection):
return connection._connection
def get_driver_connection(
self, connection: DBAPIConnection
) -> AsyncIODBAPIConnection:
return connection._connection # type: ignore[no-any-return]
dialect = MySQLDialect_aiomysql