From a4bb502cf95ea3523e4d383c4377e50f402d7d52 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Thu, 17 Feb 2022 13:43:04 -0500 Subject: pep-484 for engine All modules in sqlalchemy.engine are strictly typed with the exception of cursor, default, and reflection. cursor and default pass with non-strict typing, reflection is waiting on the multi-reflection refactor. Behavioral changes: * create_connect_args() methods return a tuple of list, dict, rather than a list of list, dict * removed allow_chars parameter from pyodbc connector ._get_server_version_info() method * the parameter list passed to do_executemany is now a list in all cases. previously, this was being run through dialect.execute_sequence_format, which defaults to tuple and was only intended for individual tuple params. * broke up dialect.dbapi into dialect.import_dbapi class method and dialect.dbapi module object. added a deprecation path for legacy dialects. it's not really feasible to type a single attr as a classmethod vs. module type. The "type_compiler" attribute also has this problem with greater ability to work around, left that one for now. * lots of constants changing to be Enum, so that we can type them. for fixed tuple-position constants in cursor.py / compiler.py (which are used to avoid the speed overhead of namedtuple), using Literal[value] which seems to work well * some tightening up in Row regarding __getitem__, which we can do since we are on full 2.0 style result use * altered the set_connection_execution_options and set_engine_execution_options event flows so that the dictionary of options may be mutated within the event hook, where it will then take effect as the actual options used. Previously, changing the dict would be silently ignored which seems counter-intuitive and not very useful. * A lot of DefaultDialect/DefaultExecutionContext methods and attributes, including underscored ones, move to interfaces. This is not fully ideal as it means the Dialect/ExecutionContext interfaces aren't publicly subclassable directly, but their current purpose is more of documentation for dialect authors who should (and certainly are) still be subclassing the DefaultXYZ versions in all cases Overall, Result was the most extremely difficult class hierarchy to type here as this hierarchy passes through largely amorphous "row" datatypes throughout, which can in fact by all kinds of different things, like raw DBAPI rows, or Row objects, or "scalar"/Any, but at the same time these types have meaning so I tried still maintaining some level of semantic markings for these, it highlights how complex Result is now, as it's trying to be extremely efficient and inlined while also being very open-ended and extensible. Change-Id: I98b75c0c09eab5355fc7a33ba41dd9874274f12a --- lib/sqlalchemy/engine/util.py | 55 ++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 16 deletions(-) (limited to 'lib/sqlalchemy/engine/util.py') diff --git a/lib/sqlalchemy/engine/util.py b/lib/sqlalchemy/engine/util.py index f9ee65bef..213485cc9 100644 --- a/lib/sqlalchemy/engine/util.py +++ b/lib/sqlalchemy/engine/util.py @@ -7,18 +7,30 @@ from __future__ import annotations +import typing +from typing import Any +from typing import Callable +from typing import TypeVar + from .. import exc from .. import util +from ..util._has_cy import HAS_CYEXTENSION + +if typing.TYPE_CHECKING or not HAS_CYEXTENSION: + from ._py_util import _distill_params_20 as _distill_params_20 + from ._py_util import _distill_raw_params as _distill_raw_params +else: + from sqlalchemy.cyextension.util import ( + _distill_params_20 as _distill_params_20, + ) + from sqlalchemy.cyextension.util import ( + _distill_raw_params as _distill_raw_params, + ) -try: - from sqlalchemy.cyextension.util import _distill_params_20 # noqa - from sqlalchemy.cyextension.util import _distill_raw_params # noqa -except ImportError: - from ._py_util import _distill_params_20 # noqa - from ._py_util import _distill_raw_params # noqa +_C = TypeVar("_C", bound=Callable[[], Any]) -def connection_memoize(key): +def connection_memoize(key: str) -> Callable[[_C], _C]: """Decorator, memoize a function in a connection.info stash. Only applicable to functions which take no arguments other than a @@ -26,7 +38,7 @@ def connection_memoize(key): """ @util.decorator - def decorated(fn, self, connection): + def decorated(fn, self, connection): # type: ignore connection = connection.connect() try: return connection.info[key] @@ -34,7 +46,7 @@ def connection_memoize(key): connection.info[key] = val = fn(self, connection) return val - return decorated + return decorated # type: ignore[return-value] class TransactionalContext: @@ -47,13 +59,13 @@ class TransactionalContext: __slots__ = ("_outer_trans_ctx", "_trans_subject", "__weakref__") - def _transaction_is_active(self): + def _transaction_is_active(self) -> bool: raise NotImplementedError() - def _transaction_is_closed(self): + def _transaction_is_closed(self) -> bool: raise NotImplementedError() - def _rollback_can_be_called(self): + def _rollback_can_be_called(self) -> bool: """indicates the object is in a state that is known to be acceptable for rollback() to be called. @@ -70,11 +82,20 @@ class TransactionalContext: """ raise NotImplementedError() - def _get_subject(self): + def _get_subject(self) -> Any: + raise NotImplementedError() + + def commit(self) -> None: + raise NotImplementedError() + + def rollback(self) -> None: + raise NotImplementedError() + + def close(self) -> None: raise NotImplementedError() @classmethod - def _trans_ctx_check(cls, subject): + def _trans_ctx_check(cls, subject: Any) -> None: trans_context = subject._trans_context_manager if trans_context: if not trans_context._transaction_is_active(): @@ -84,7 +105,7 @@ class TransactionalContext: "before emitting further commands." ) - def __enter__(self): + def __enter__(self) -> TransactionalContext: subject = self._get_subject() # none for outer transaction, may be non-None for nested @@ -96,7 +117,7 @@ class TransactionalContext: subject._trans_context_manager = self return self - def __exit__(self, type_, value, traceback): + def __exit__(self, type_: Any, value: Any, traceback: Any) -> None: subject = getattr(self, "_trans_subject", None) # simplistically we could assume that @@ -119,6 +140,7 @@ class TransactionalContext: self.rollback() finally: if not out_of_band_exit: + assert subject is not None subject._trans_context_manager = self._outer_trans_ctx self._trans_subject = self._outer_trans_ctx = None else: @@ -131,5 +153,6 @@ class TransactionalContext: self.rollback() finally: if not out_of_band_exit: + assert subject is not None subject._trans_context_manager = self._outer_trans_ctx self._trans_subject = self._outer_trans_ctx = None -- cgit v1.2.1