Files
video/server/.venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/mssqlpython.py
T
2026-10-15 20:36:59 +08:00

252 lines
7.6 KiB
Python

# dialects/mssql/mssqlpython.py
# Copyright (C) 2005-2026 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
"""
.. dialect:: mssql+mssqlpython
:name: mssqlpython
:dbapi: mssql-python
:connectstring: mssql+mssqlpython://<username>:<password>@<host>:<port>/<dbname>
:url: https://github.com/microsoft/mssql-python
mssql-python is a driver for Microsoft SQL Server produced by Microsoft.
.. versionadded:: 2.1.0b2
The driver is generally similar to pyodbc in most aspects as it is based
on the same ODBC framework.
Connection Strings
------------------
Examples of connecting with the mssql-python driver::
from sqlalchemy import create_engine
# Basic connection
engine = create_engine(
"mssql+mssqlpython://user:password@hostname/database"
)
# With Windows Authentication
engine = create_engine(
"mssql+mssqlpython://hostname/database?authentication=ActiveDirectoryIntegrated"
)
""" # noqa
from __future__ import annotations
import re
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import TYPE_CHECKING
from typing import Union
from .base import MSDialect
from .pyodbc import _ms_numeric_pyodbc
from ... import util
from ...sql import sqltypes
if TYPE_CHECKING:
from ... import pool
from ...engine import interfaces
from ...engine.interfaces import ConnectArgsType
from ...engine.interfaces import DBAPIModule
from ...engine.interfaces import IsolationLevel
from ...engine.interfaces import URL
class _MSNumeric_mssqlpython(_ms_numeric_pyodbc, sqltypes.Numeric):
pass
class _MSFloat_mssqlpython(_ms_numeric_pyodbc, sqltypes.Float):
pass
class MSDialect_mssqlpython(MSDialect):
driver = "mssqlpython"
supports_statement_cache = True
supports_sane_rowcount_returning = True
supports_sane_multi_rowcount = True
supports_native_uuid = True
scope_identity_must_be_embedded = True
supports_native_decimal = True
_disconnect_driver_errors = frozenset(
{
# mssql-python converts the originating SQLSTATE into these
# values and exposes them on ``Exception.driver_error``.
"Disconnect error", # 01002
"Client unable to establish connection", # 08001
"Connection not open", # 08003
"Connection failure during transaction", # 08007
"Communication link failure", # 08S01
# mssql-python does not map these SQLSTATE values to a
# dedicated driver error yet.
"An error occurred with SQLSTATE code: 08S02",
"An error occurred with SQLSTATE code: 10054",
"Connection timeout expired", # HYT01
"Function sequence error", # HY010
}
)
colspecs = util.update_copy(
MSDialect.colspecs,
{
sqltypes.Numeric: _MSNumeric_mssqlpython,
sqltypes.Float: _MSFloat_mssqlpython,
},
)
def __init__(self, enable_pooling=False, **kw):
super().__init__(**kw)
if not enable_pooling and self.dbapi is not None:
self.loaded_dbapi.pooling(enabled=False)
# used by pyodbc _ms_numeric_pyodbc class. mssql-python 1.15
# binds every Decimal as SQL_NUMERIC; prior versions bound Decimal
# values within the MONEY range as VARCHAR, so that very large and
# very small values needed to be passed as strings as well. On
# 1.15 those strings are instead coerced to the NUMERIC type of
# the Decimal values alongside them, which overflows.
# a version which can't be determined is assumed to be an old one
version = self._dbapi_version_or_none
self._need_decimal_fix = bool(self.dbapi) and (
version is None or version < (1, 15)
)
@classmethod
def import_dbapi(cls) -> DBAPIModule:
return __import__("mssql_python")
def create_connect_args(self, url: URL) -> ConnectArgsType:
opts = url.translate_connect_args(username="user")
opts.update(url.query)
keys = opts
query = url.query
connect_args: Dict[str, Any] = {}
connectors: List[str]
def check_quote(token: str) -> str:
if (
";" in str(token)
or "}" in str(token)
or str(token).startswith("{")
):
token = "{%s}" % token.replace("}", "}}")
return token
keys = {k: check_quote(v) for k, v in keys.items()}
port = ""
if "port" in keys and "port" not in query:
port = ",%d" % int(keys.pop("port"))
connectors = []
connectors.extend(
[
"Server=%s%s" % (keys.pop("host", ""), port),
"Database=%s" % keys.pop("database", ""),
]
)
user = keys.pop("user", None)
if user:
connectors.append("UID=%s" % user)
pwd = keys.pop("password", "")
if pwd:
connectors.append("PWD=%s" % pwd)
else:
authentication = keys.pop("authentication", None)
if authentication:
connectors.append("Authentication=%s" % authentication)
connectors.extend(
["%s=%s" % (check_quote(k), v) for k, v in keys.items()]
)
return ((";".join(connectors),), connect_args)
def is_disconnect(
self,
e: Exception,
connection: Optional[
Union[pool.PoolProxiedConnection, interfaces.DBAPIConnection]
],
cursor: Optional[interfaces.DBAPICursor],
) -> bool:
if not isinstance(e, self.loaded_dbapi.Error):
return False
if getattr(e, "driver_error", None) in self._disconnect_driver_errors:
return True
if isinstance(e, self.loaded_dbapi.ProgrammingError):
return (
"The cursor's connection has been closed." in str(e)
or "Attempt to use a closed connection." in str(e)
or "Driver Error: Operation cannot be performed" in str(e)
)
if isinstance(e, self.loaded_dbapi.InterfaceError):
return bool(
re.search(r"Cannot .* on (?:a )?closed connection", str(e))
)
return False
def retrieve_dbapi_version(
self, dbapi: interfaces.DBAPIModule
) -> util.VersionInfo:
return util.parse_version_string(getattr(dbapi, "__version__", None))
def _get_server_version_info(self, connection):
vers = connection.exec_driver_sql("select @@version").scalar()
m = re.match(r"Microsoft .*? - (\d+)\.(\d+)\.(\d+)\.(\d+)", vers)
if m:
return tuple(int(x) for x in m.group(1, 2, 3, 4))
else:
return None
def get_isolation_level_values(
self, dbapi_connection: interfaces.DBAPIConnection
) -> List[IsolationLevel]:
return [
*super().get_isolation_level_values(dbapi_connection),
"AUTOCOMMIT",
]
def set_isolation_level(
self,
dbapi_connection: interfaces.DBAPIConnection,
level: IsolationLevel,
) -> None:
if level == "AUTOCOMMIT":
dbapi_connection.autocommit = True
else:
dbapi_connection.autocommit = False
super().set_isolation_level(dbapi_connection, level)
def detect_autocommit_setting(
self, dbapi_conn: interfaces.DBAPIConnection
) -> bool:
return bool(dbapi_conn.autocommit)
dialect = MSDialect_mssqlpython