summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/connectors
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-02-17 13:43:04 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2022-03-01 09:09:02 -0500
commita4bb502cf95ea3523e4d383c4377e50f402d7d52 (patch)
tree124400f741b6b91f0e9e582b510268607394dfaa /lib/sqlalchemy/connectors
parent60fca2ac8cf44bdaf68552ab5c69854a6776c73c (diff)
downloadsqlalchemy-a4bb502cf95ea3523e4d383c4377e50f402d7d52.tar.gz
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
Diffstat (limited to 'lib/sqlalchemy/connectors')
-rw-r--r--lib/sqlalchemy/connectors/__init__.py12
-rw-r--r--lib/sqlalchemy/connectors/pyodbc.py88
2 files changed, 71 insertions, 29 deletions
diff --git a/lib/sqlalchemy/connectors/__init__.py b/lib/sqlalchemy/connectors/__init__.py
index 132a0a4de..f4fa5b66b 100644
--- a/lib/sqlalchemy/connectors/__init__.py
+++ b/lib/sqlalchemy/connectors/__init__.py
@@ -6,5 +6,13 @@
# the MIT License: https://www.opensource.org/licenses/mit-license.php
-class Connector:
- pass
+from ..engine.interfaces import Dialect
+
+
+class Connector(Dialect):
+ """Base class for dialect mixins, for DBAPIs that work
+ across entirely different database backends.
+
+ Currently the only such mixin is pyodbc.
+
+ """
diff --git a/lib/sqlalchemy/connectors/pyodbc.py b/lib/sqlalchemy/connectors/pyodbc.py
index f7d01ce43..c5f07de07 100644
--- a/lib/sqlalchemy/connectors/pyodbc.py
+++ b/lib/sqlalchemy/connectors/pyodbc.py
@@ -5,12 +5,27 @@
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
+from __future__ import annotations
+
import re
+from types import ModuleType
+from typing import Any
+from typing import Dict
+from typing import List
+from typing import Optional
+from typing import Tuple
+from typing import Union
from urllib.parse import unquote_plus
from . import Connector
+from .. import ExecutionContext
+from .. import pool
from .. import util
+from ..engine import ConnectArgsType
+from ..engine import Connection
from ..engine import interfaces
+from ..engine import URL
+from ..sql.type_api import TypeEngine
class PyODBCConnector(Connector):
@@ -25,18 +40,20 @@ class PyODBCConnector(Connector):
# for non-DSN connections, this *may* be used to
# hold the desired driver name
- pyodbc_driver_name = None
+ pyodbc_driver_name: Optional[str] = None
+
+ dbapi: ModuleType
- def __init__(self, use_setinputsizes=False, **kw):
+ def __init__(self, use_setinputsizes: bool = False, **kw: Any):
super(PyODBCConnector, self).__init__(**kw)
if use_setinputsizes:
self.bind_typing = interfaces.BindTyping.SETINPUTSIZES
@classmethod
- def dbapi(cls):
+ def import_dbapi(cls) -> ModuleType:
return __import__("pyodbc")
- def create_connect_args(self, url):
+ def create_connect_args(self, url: URL) -> ConnectArgsType:
opts = url.translate_connect_args(username="user")
opts.update(url.query)
@@ -44,7 +61,9 @@ class PyODBCConnector(Connector):
query = url.query
- connect_args = {}
+ connect_args: Dict[str, Any] = {}
+ connectors: List[str]
+
for param in ("ansi", "unicode_results", "autocommit"):
if param in keys:
connect_args[param] = util.asbool(keys.pop(param))
@@ -53,7 +72,7 @@ class PyODBCConnector(Connector):
connectors = [unquote_plus(keys.pop("odbc_connect"))]
else:
- def check_quote(token):
+ def check_quote(token: str) -> str:
if ";" in str(token):
token = "{%s}" % token.replace("}", "}}")
return token
@@ -115,9 +134,14 @@ class PyODBCConnector(Connector):
connectors.extend(["%s=%s" % (k, v) for k, v in keys.items()])
- return [[";".join(connectors)], connect_args]
+ return ((";".join(connectors),), connect_args)
- def is_disconnect(self, e, connection, cursor):
+ def is_disconnect(
+ self,
+ e: Exception,
+ connection: Optional[pool.PoolProxiedConnection],
+ cursor: Optional[interfaces.DBAPICursor],
+ ) -> bool:
if isinstance(e, self.dbapi.ProgrammingError):
return "The cursor's connection has been closed." in str(
e
@@ -125,36 +149,44 @@ class PyODBCConnector(Connector):
else:
return False
- def _dbapi_version(self):
+ def _dbapi_version(self) -> interfaces.VersionInfoType:
if not self.dbapi:
return ()
return self._parse_dbapi_version(self.dbapi.version)
- def _parse_dbapi_version(self, vers):
+ def _parse_dbapi_version(self, vers: str) -> interfaces.VersionInfoType:
m = re.match(r"(?:py.*-)?([\d\.]+)(?:-(\w+))?", vers)
if not m:
return ()
- vers = tuple([int(x) for x in m.group(1).split(".")])
+ vers_tuple: interfaces.VersionInfoType = tuple(
+ [int(x) for x in m.group(1).split(".")]
+ )
if m.group(2):
- vers += (m.group(2),)
- return vers
+ vers_tuple += (m.group(2),)
+ return vers_tuple
- def _get_server_version_info(self, connection, allow_chars=True):
+ def _get_server_version_info(
+ self, connection: Connection
+ ) -> interfaces.VersionInfoType:
# NOTE: this function is not reliable, particularly when
# freetds is in use. Implement database-specific server version
# queries.
- dbapi_con = connection.connection
- version = []
+ dbapi_con = connection.connection.dbapi_connection
+ version: Tuple[Union[int, str], ...] = ()
r = re.compile(r"[.\-]")
- for n in r.split(dbapi_con.getinfo(self.dbapi.SQL_DBMS_VER)):
+ for n in r.split(dbapi_con.getinfo(self.dbapi.SQL_DBMS_VER)): # type: ignore[union-attr] # noqa E501
try:
- version.append(int(n))
+ version += (int(n),)
except ValueError:
- if allow_chars:
- version.append(n)
+ pass
return tuple(version)
- def do_set_input_sizes(self, cursor, list_of_tuples, context):
+ def do_set_input_sizes(
+ self,
+ cursor: interfaces.DBAPICursor,
+ list_of_tuples: List[Tuple[str, Any, TypeEngine[Any]]],
+ context: ExecutionContext,
+ ) -> None:
# the rules for these types seems a little strange, as you can pass
# non-tuples as well as tuples, however it seems to assume "0"
# for the subsequent values if you don't pass a tuple which fails
@@ -174,12 +206,16 @@ class PyODBCConnector(Connector):
]
)
- def get_isolation_level_values(self, dbapi_connection):
- return super().get_isolation_level_values(dbapi_connection) + [
+ def get_isolation_level_values(
+ self, dbapi_connection: interfaces.DBAPIConnection
+ ) -> List[str]:
+ return super().get_isolation_level_values(dbapi_connection) + [ # type: ignore # noqa E501
"AUTOCOMMIT"
]
- def set_isolation_level(self, dbapi_connection, level):
+ def set_isolation_level(
+ self, dbapi_connection: interfaces.DBAPIConnection, level: str
+ ) -> None:
# adjust for ConnectionFairy being present
# allows attribute set e.g. "connection.autocommit = True"
# to work properly
@@ -188,6 +224,4 @@ class PyODBCConnector(Connector):
dbapi_connection.autocommit = True
else:
dbapi_connection.autocommit = False
- super(PyODBCConnector, self).set_isolation_level(
- dbapi_connection, level
- )
+ super().set_isolation_level(dbapi_connection, level)