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,5 +1,5 @@
# engine/interfaces.py
# Copyright (C) 2005-2023 the SQLAlchemy authors and contributors
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
@@ -10,7 +10,6 @@
from __future__ import annotations
from enum import Enum
from types import ModuleType
from typing import Any
from typing import Awaitable
from typing import Callable
@@ -34,7 +33,7 @@ from typing import Union
from .. import util
from ..event import EventTarget
from ..pool import Pool
from ..pool import PoolProxiedConnection
from ..pool import PoolProxiedConnection as PoolProxiedConnection
from ..sql.compiler import Compiled as Compiled
from ..sql.compiler import Compiled # noqa
from ..sql.compiler import TypeCompiler as TypeCompiler
@@ -51,6 +50,7 @@ if TYPE_CHECKING:
from .base import Engine
from .cursor import CursorResult
from .url import URL
from ..connectors.asyncio import AsyncIODBAPIConnection
from ..event import _ListenerFnType
from ..event import dispatcher
from ..exc import StatementError
@@ -70,6 +70,7 @@ if TYPE_CHECKING:
from ..sql.sqltypes import Integer
from ..sql.type_api import _TypeMemoDict
from ..sql.type_api import TypeEngine
from ..util.langhelpers import generic_fn_descriptor
ConnectArgsType = Tuple[Sequence[str], MutableMapping[str, Any]]
@@ -106,6 +107,22 @@ class ExecuteStyle(Enum):
"""
class DBAPIModule(Protocol):
class Error(Exception):
def __getattr__(self, key: str) -> Any: ...
class OperationalError(Error):
pass
class InterfaceError(Error):
pass
class IntegrityError(Error):
pass
def __getattr__(self, key: str) -> Any: ...
class DBAPIConnection(Protocol):
"""protocol representing a :pep:`249` database connection.
@@ -118,19 +135,17 @@ class DBAPIConnection(Protocol):
""" # noqa: E501
def close(self) -> None:
...
def close(self) -> None: ...
def commit(self) -> None:
...
def commit(self) -> None: ...
def cursor(self) -> DBAPICursor:
...
def cursor(self, *args: Any, **kwargs: Any) -> DBAPICursor: ...
def rollback(self) -> None:
...
def rollback(self) -> None: ...
autocommit: bool
def __getattr__(self, key: str) -> Any: ...
def __setattr__(self, key: str, value: Any) -> None: ...
class DBAPIType(Protocol):
@@ -174,53 +189,43 @@ class DBAPICursor(Protocol):
...
@property
def rowcount(self) -> int:
...
def rowcount(self) -> int: ...
arraysize: int
lastrowid: int
def close(self) -> None:
...
def close(self) -> None: ...
def execute(
self,
operation: Any,
parameters: Optional[_DBAPISingleExecuteParams] = None,
) -> Any:
...
) -> Any: ...
def executemany(
self,
operation: Any,
parameters: Sequence[_DBAPIMultiExecuteParams],
) -> Any:
...
parameters: _DBAPIMultiExecuteParams,
) -> Any: ...
def fetchone(self) -> Optional[Any]:
...
def fetchone(self) -> Optional[Any]: ...
def fetchmany(self, size: int = ...) -> Sequence[Any]:
...
def fetchmany(self, size: int = ...) -> Sequence[Any]: ...
def fetchall(self) -> Sequence[Any]:
...
def fetchall(self) -> Sequence[Any]: ...
def setinputsizes(self, sizes: Sequence[Any]) -> None:
...
def setinputsizes(self, sizes: Sequence[Any]) -> None: ...
def setoutputsize(self, size: Any, column: Any) -> None:
...
def setoutputsize(self, size: Any, column: Any) -> None: ...
def callproc(self, procname: str, parameters: Sequence[Any] = ...) -> Any:
...
def callproc(
self, procname: str, parameters: Sequence[Any] = ...
) -> Any: ...
def nextset(self) -> Optional[bool]:
...
def nextset(self) -> Optional[bool]: ...
def __getattr__(self, key: str) -> Any:
...
def __getattr__(self, key: str) -> Any: ...
_CoreSingleExecuteParams = Mapping[str, Any]
@@ -284,6 +289,7 @@ class _CoreKnownExecutionOptions(TypedDict, total=False):
yield_per: int
insertmanyvalues_page_size: int
schema_translate_map: Optional[SchemaTranslateMapType]
preserve_rowcount: bool
_ExecuteOptions = immutabledict[str, Any]
@@ -593,8 +599,8 @@ class BindTyping(Enum):
"""Use the pep-249 setinputsizes method.
This is only implemented for DBAPIs that support this method and for which
the SQLAlchemy dialect has the appropriate infrastructure for that
dialect set up. Current dialects include cx_Oracle as well as
the SQLAlchemy dialect has the appropriate infrastructure for that dialect
set up. Current dialects include python-oracledb, cx_Oracle as well as
optional support for SQL Server using pyodbc.
When using setinputsizes, dialects also have a means of only using the
@@ -671,7 +677,7 @@ class Dialect(EventTarget):
dialect_description: str
dbapi: Optional[ModuleType]
dbapi: Optional[DBAPIModule]
"""A reference to the DBAPI module object itself.
SQLAlchemy dialects import DBAPI modules using the classmethod
@@ -695,7 +701,7 @@ class Dialect(EventTarget):
"""
@util.non_memoized_property
def loaded_dbapi(self) -> ModuleType:
def loaded_dbapi(self) -> DBAPIModule:
"""same as .dbapi, but is never None; will raise an error if no
DBAPI was set up.
@@ -773,6 +779,14 @@ class Dialect(EventTarget):
default_isolation_level: Optional[IsolationLevel]
"""the isolation that is implicitly present on new connections"""
skip_autocommit_rollback: bool
"""Whether or not the :paramref:`.create_engine.skip_autocommit_rollback`
parameter was set.
.. versionadded:: 2.0.43
"""
# create_engine() -> isolation_level currently goes here
_on_connect_isolation_level: Optional[IsolationLevel]
@@ -792,8 +806,14 @@ class Dialect(EventTarget):
max_identifier_length: int
"""The maximum length of identifier names."""
max_index_name_length: Optional[int]
"""The maximum length of index names if different from
``max_identifier_length``."""
max_constraint_name_length: Optional[int]
"""The maximum length of constraint names if different from
``max_identifier_length``."""
supports_server_side_cursors: bool
supports_server_side_cursors: Union[generic_fn_descriptor[bool], bool]
"""indicates if the dialect supports server side cursors"""
server_side_cursors: bool
@@ -884,12 +904,12 @@ class Dialect(EventTarget):
the statement multiple times for a series of batches when large numbers
of rows are given.
The parameter is False for the default dialect, and is set to
True for SQLAlchemy internal dialects SQLite, MySQL/MariaDB, PostgreSQL,
SQL Server. It remains at False for Oracle, which provides native
"executemany with RETURNING" support and also does not support
``supports_multivalues_insert``. For MySQL/MariaDB, those MySQL
dialects that don't support RETURNING will not report
The parameter is False for the default dialect, and is set to True for
SQLAlchemy internal dialects SQLite, MySQL/MariaDB, PostgreSQL, SQL Server.
It remains at False for Oracle Database, which provides native "executemany
with RETURNING" support and also does not support
``supports_multivalues_insert``. For MySQL/MariaDB, those MySQL dialects
that don't support RETURNING will not report
``insert_executemany_returning`` as True.
.. versionadded:: 2.0
@@ -1073,11 +1093,7 @@ class Dialect(EventTarget):
To implement, establish as a series of tuples, as in::
construct_arguments = [
(schema.Index, {
"using": False,
"where": None,
"ops": None
})
(schema.Index, {"using": False, "where": None, "ops": None}),
]
If the above construct is established on the PostgreSQL dialect,
@@ -1106,7 +1122,8 @@ class Dialect(EventTarget):
established on a :class:`.Table` object which will be passed as
"reflection options" when using :paramref:`.Table.autoload_with`.
Current example is "oracle_resolve_synonyms" in the Oracle dialect.
Current example is "oracle_resolve_synonyms" in the Oracle Database
dialects.
"""
@@ -1130,7 +1147,7 @@ class Dialect(EventTarget):
supports_constraint_comments: bool
"""Indicates if the dialect supports comment DDL on constraints.
.. versionadded: 2.0
.. versionadded:: 2.0
"""
_has_events = False
@@ -1249,7 +1266,7 @@ class Dialect(EventTarget):
raise NotImplementedError()
@classmethod
def import_dbapi(cls) -> ModuleType:
def import_dbapi(cls) -> DBAPIModule:
"""Import the DBAPI module that is used by this dialect.
The Python module object returned here will be assigned as an
@@ -1266,8 +1283,7 @@ class Dialect(EventTarget):
"""
raise NotImplementedError()
@classmethod
def type_descriptor(cls, typeobj: TypeEngine[_T]) -> TypeEngine[_T]:
def type_descriptor(self, typeobj: TypeEngine[_T]) -> TypeEngine[_T]:
"""Transform a generic type to a dialect-specific type.
Dialect classes will usually use the
@@ -1299,12 +1315,9 @@ class Dialect(EventTarget):
"""
pass
if TYPE_CHECKING:
def _overrides_default(self, method_name: str) -> bool:
...
def _overrides_default(self, method_name: str) -> bool: ...
def get_columns(
self,
@@ -1330,6 +1343,7 @@ class Dialect(EventTarget):
def get_multi_columns(
self,
connection: Connection,
*,
schema: Optional[str] = None,
filter_names: Optional[Collection[str]] = None,
**kw: Any,
@@ -1378,6 +1392,7 @@ class Dialect(EventTarget):
def get_multi_pk_constraint(
self,
connection: Connection,
*,
schema: Optional[str] = None,
filter_names: Optional[Collection[str]] = None,
**kw: Any,
@@ -1424,6 +1439,7 @@ class Dialect(EventTarget):
def get_multi_foreign_keys(
self,
connection: Connection,
*,
schema: Optional[str] = None,
filter_names: Optional[Collection[str]] = None,
**kw: Any,
@@ -1583,6 +1599,7 @@ class Dialect(EventTarget):
def get_multi_indexes(
self,
connection: Connection,
*,
schema: Optional[str] = None,
filter_names: Optional[Collection[str]] = None,
**kw: Any,
@@ -1629,6 +1646,7 @@ class Dialect(EventTarget):
def get_multi_unique_constraints(
self,
connection: Connection,
*,
schema: Optional[str] = None,
filter_names: Optional[Collection[str]] = None,
**kw: Any,
@@ -1676,6 +1694,7 @@ class Dialect(EventTarget):
def get_multi_check_constraints(
self,
connection: Connection,
*,
schema: Optional[str] = None,
filter_names: Optional[Collection[str]] = None,
**kw: Any,
@@ -1718,6 +1737,7 @@ class Dialect(EventTarget):
def get_multi_table_options(
self,
connection: Connection,
*,
schema: Optional[str] = None,
filter_names: Optional[Collection[str]] = None,
**kw: Any,
@@ -1769,6 +1789,7 @@ class Dialect(EventTarget):
def get_multi_table_comment(
self,
connection: Connection,
*,
schema: Optional[str] = None,
filter_names: Optional[Collection[str]] = None,
**kw: Any,
@@ -2161,6 +2182,7 @@ class Dialect(EventTarget):
def _deliver_insertmanyvalues_batches(
self,
connection: Connection,
cursor: DBAPICursor,
statement: str,
parameters: _DBAPIMultiExecuteParams,
@@ -2214,7 +2236,7 @@ class Dialect(EventTarget):
def is_disconnect(
self,
e: Exception,
e: DBAPIModule.Error,
connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
cursor: Optional[DBAPICursor],
) -> bool:
@@ -2318,7 +2340,7 @@ class Dialect(EventTarget):
"""
return self.on_connect()
def on_connect(self) -> Optional[Callable[[Any], Any]]:
def on_connect(self) -> Optional[Callable[[Any], None]]:
"""return a callable which sets up a newly created DBAPI connection.
The callable should accept a single argument "conn" which is the
@@ -2467,6 +2489,30 @@ class Dialect(EventTarget):
raise NotImplementedError()
def detect_autocommit_setting(self, dbapi_conn: DBAPIConnection) -> bool:
"""Detect the current autocommit setting for a DBAPI connection.
:param dbapi_connection: a DBAPI connection object
:return: True if autocommit is enabled, False if disabled
:rtype: bool
This method inspects the given DBAPI connection to determine
whether autocommit mode is currently enabled. The specific
mechanism for detecting autocommit varies by database dialect
and DBAPI driver, however it should be done **without** network
round trips.
.. note::
Not all dialects support autocommit detection. Dialects
that do not support this feature will raise
:exc:`NotImplementedError`.
"""
raise NotImplementedError(
"This dialect cannot detect autocommit on a DBAPI connection"
)
def get_default_isolation_level(
self, dbapi_conn: DBAPIConnection
) -> IsolationLevel:
@@ -2491,7 +2537,7 @@ class Dialect(EventTarget):
def get_isolation_level_values(
self, dbapi_conn: DBAPIConnection
) -> List[IsolationLevel]:
) -> Sequence[IsolationLevel]:
"""return a sequence of string isolation level names that are accepted
by this dialect.
@@ -2504,7 +2550,7 @@ class Dialect(EventTarget):
``REPEATABLE READ``. isolation level names will have underscores
converted to spaces before being passed along to the dialect.
* The names for the four standard isolation names to the extent that
they are supported by the backend should be ``READ UNCOMMITTED``
they are supported by the backend should be ``READ UNCOMMITTED``,
``READ COMMITTED``, ``REPEATABLE READ``, ``SERIALIZABLE``
* if the dialect supports an autocommit option it should be provided
using the isolation level name ``AUTOCOMMIT``.
@@ -2665,6 +2711,9 @@ class Dialect(EventTarget):
"""return a Pool class to use for a given URL"""
raise NotImplementedError()
def validate_identifier(self, ident: str) -> None:
"""Validates an identifier name, raising an exception if invalid"""
class CreateEnginePlugin:
"""A set of hooks intended to augment the construction of an
@@ -2690,11 +2739,14 @@ class CreateEnginePlugin:
from sqlalchemy.engine import CreateEnginePlugin
from sqlalchemy import event
class LogCursorEventsPlugin(CreateEnginePlugin):
def __init__(self, url, kwargs):
# consume the parameter "log_cursor_logging_name" from the
# URL query
logging_name = url.query.get("log_cursor_logging_name", "log_cursor")
logging_name = url.query.get(
"log_cursor_logging_name", "log_cursor"
)
self.log = logging.getLogger(logging_name)
@@ -2706,7 +2758,6 @@ class CreateEnginePlugin:
"attach an event listener after the new Engine is constructed"
event.listen(engine, "before_cursor_execute", self._log_event)
def _log_event(
self,
conn,
@@ -2714,19 +2765,19 @@ class CreateEnginePlugin:
statement,
parameters,
context,
executemany):
executemany,
):
self.log.info("Plugin logged cursor event: %s", statement)
Plugins are registered using entry points in a similar way as that
of dialects::
entry_points={
'sqlalchemy.plugins': [
'log_cursor_plugin = myapp.plugins:LogCursorEventsPlugin'
entry_points = {
"sqlalchemy.plugins": [
"log_cursor_plugin = myapp.plugins:LogCursorEventsPlugin"
]
}
A plugin that uses the above names would be invoked from a database
URL as in::
@@ -2743,15 +2794,16 @@ class CreateEnginePlugin:
in the URL::
engine = create_engine(
"mysql+pymysql://scott:tiger@localhost/test?"
"plugin=plugin_one&plugin=plugin_twp&plugin=plugin_three")
"mysql+pymysql://scott:tiger@localhost/test?"
"plugin=plugin_one&plugin=plugin_twp&plugin=plugin_three"
)
The plugin names may also be passed directly to :func:`_sa.create_engine`
using the :paramref:`_sa.create_engine.plugins` argument::
engine = create_engine(
"mysql+pymysql://scott:tiger@localhost/test",
plugins=["myplugin"])
"mysql+pymysql://scott:tiger@localhost/test", plugins=["myplugin"]
)
.. versionadded:: 1.2.3 plugin names can also be specified
to :func:`_sa.create_engine` as a list
@@ -2773,9 +2825,9 @@ class CreateEnginePlugin:
class MyPlugin(CreateEnginePlugin):
def __init__(self, url, kwargs):
self.my_argument_one = url.query['my_argument_one']
self.my_argument_two = url.query['my_argument_two']
self.my_argument_three = kwargs.pop('my_argument_three', None)
self.my_argument_one = url.query["my_argument_one"]
self.my_argument_two = url.query["my_argument_two"]
self.my_argument_three = kwargs.pop("my_argument_three", None)
def update_url(self, url):
return url.difference_update_query(
@@ -2788,9 +2840,9 @@ class CreateEnginePlugin:
from sqlalchemy import create_engine
engine = create_engine(
"mysql+pymysql://scott:tiger@localhost/test?"
"plugin=myplugin&my_argument_one=foo&my_argument_two=bar",
my_argument_three='bat'
"mysql+pymysql://scott:tiger@localhost/test?"
"plugin=myplugin&my_argument_one=foo&my_argument_two=bar",
my_argument_three="bat",
)
.. versionchanged:: 1.4
@@ -2809,15 +2861,15 @@ class CreateEnginePlugin:
def __init__(self, url, kwargs):
if hasattr(CreateEnginePlugin, "update_url"):
# detect the 1.4 API
self.my_argument_one = url.query['my_argument_one']
self.my_argument_two = url.query['my_argument_two']
self.my_argument_one = url.query["my_argument_one"]
self.my_argument_two = url.query["my_argument_two"]
else:
# detect the 1.3 and earlier API - mutate the
# URL directly
self.my_argument_one = url.query.pop('my_argument_one')
self.my_argument_two = url.query.pop('my_argument_two')
self.my_argument_one = url.query.pop("my_argument_one")
self.my_argument_two = url.query.pop("my_argument_two")
self.my_argument_three = kwargs.pop('my_argument_three', None)
self.my_argument_three = kwargs.pop("my_argument_three", None)
def update_url(self, url):
# this method is only called in the 1.4 version
@@ -2992,6 +3044,9 @@ class ExecutionContext:
inline SQL expression value was fired off. Applies to inserts
and updates."""
execution_options: _ExecuteOptions
"""Execution options associated with the current statement execution"""
@classmethod
def _init_ddl(
cls,
@@ -3366,7 +3421,7 @@ class AdaptedConnection:
__slots__ = ("_connection",)
_connection: Any
_connection: AsyncIODBAPIConnection
@property
def driver_connection(self) -> Any:
@@ -3385,11 +3440,14 @@ class AdaptedConnection:
engine = create_async_engine(...)
@event.listens_for(engine.sync_engine, "connect")
def register_custom_types(dbapi_connection, ...):
def register_custom_types(
dbapi_connection, # ...
):
dbapi_connection.run_async(
lambda connection: connection.set_type_codec(
'MyCustomType', encoder, decoder, ...
"MyCustomType", encoder, decoder, ...
)
)