SQLAlchemy 2.1.0rc1__cp315-cp315-win_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. sqlalchemy/__init__.py +299 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +171 -0
  4. sqlalchemy/connectors/asyncio.py +476 -0
  5. sqlalchemy/connectors/pyodbc.py +248 -0
  6. sqlalchemy/dialects/__init__.py +62 -0
  7. sqlalchemy/dialects/_typing.py +29 -0
  8. sqlalchemy/dialects/mssql/__init__.py +88 -0
  9. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  10. sqlalchemy/dialects/mssql/base.py +4833 -0
  11. sqlalchemy/dialects/mssql/information_schema.py +345 -0
  12. sqlalchemy/dialects/mssql/json.py +140 -0
  13. sqlalchemy/dialects/mssql/mssqlpython.py +242 -0
  14. sqlalchemy/dialects/mssql/provision.py +196 -0
  15. sqlalchemy/dialects/mssql/pymssql.py +130 -0
  16. sqlalchemy/dialects/mssql/pyodbc.py +697 -0
  17. sqlalchemy/dialects/mysql/__init__.py +106 -0
  18. sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
  19. sqlalchemy/dialects/mysql/aiomysql.py +260 -0
  20. sqlalchemy/dialects/mysql/asyncmy.py +241 -0
  21. sqlalchemy/dialects/mysql/base.py +3896 -0
  22. sqlalchemy/dialects/mysql/cymysql.py +107 -0
  23. sqlalchemy/dialects/mysql/dml.py +279 -0
  24. sqlalchemy/dialects/mysql/enumerated.py +277 -0
  25. sqlalchemy/dialects/mysql/expression.py +146 -0
  26. sqlalchemy/dialects/mysql/json.py +92 -0
  27. sqlalchemy/dialects/mysql/mariadb.py +67 -0
  28. sqlalchemy/dialects/mysql/mariadbconnector.py +314 -0
  29. sqlalchemy/dialects/mysql/mysqlconnector.py +291 -0
  30. sqlalchemy/dialects/mysql/mysqldb.py +318 -0
  31. sqlalchemy/dialects/mysql/provision.py +153 -0
  32. sqlalchemy/dialects/mysql/pymysql.py +188 -0
  33. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  34. sqlalchemy/dialects/mysql/reflection.py +724 -0
  35. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  36. sqlalchemy/dialects/mysql/types.py +845 -0
  37. sqlalchemy/dialects/oracle/__init__.py +85 -0
  38. sqlalchemy/dialects/oracle/base.py +3847 -0
  39. sqlalchemy/dialects/oracle/cx_oracle.py +1736 -0
  40. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  41. sqlalchemy/dialects/oracle/json.py +157 -0
  42. sqlalchemy/dialects/oracle/oracledb.py +898 -0
  43. sqlalchemy/dialects/oracle/provision.py +288 -0
  44. sqlalchemy/dialects/oracle/types.py +367 -0
  45. sqlalchemy/dialects/oracle/vector.py +366 -0
  46. sqlalchemy/dialects/postgresql/__init__.py +170 -0
  47. sqlalchemy/dialects/postgresql/_psycopg_common.py +232 -0
  48. sqlalchemy/dialects/postgresql/array.py +534 -0
  49. sqlalchemy/dialects/postgresql/asyncpg.py +1318 -0
  50. sqlalchemy/dialects/postgresql/base.py +5935 -0
  51. sqlalchemy/dialects/postgresql/bitstring.py +327 -0
  52. sqlalchemy/dialects/postgresql/dml.py +360 -0
  53. sqlalchemy/dialects/postgresql/ext.py +599 -0
  54. sqlalchemy/dialects/postgresql/hstore.py +422 -0
  55. sqlalchemy/dialects/postgresql/json.py +411 -0
  56. sqlalchemy/dialects/postgresql/named_types.py +535 -0
  57. sqlalchemy/dialects/postgresql/operators.py +129 -0
  58. sqlalchemy/dialects/postgresql/pg8000.py +655 -0
  59. sqlalchemy/dialects/postgresql/pg_catalog.py +345 -0
  60. sqlalchemy/dialects/postgresql/provision.py +202 -0
  61. sqlalchemy/dialects/postgresql/psycopg.py +800 -0
  62. sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
  63. sqlalchemy/dialects/postgresql/psycopg2cffi.py +62 -0
  64. sqlalchemy/dialects/postgresql/ranges.py +1002 -0
  65. sqlalchemy/dialects/postgresql/types.py +388 -0
  66. sqlalchemy/dialects/sqlite/__init__.py +59 -0
  67. sqlalchemy/dialects/sqlite/aiosqlite.py +375 -0
  68. sqlalchemy/dialects/sqlite/base.py +3103 -0
  69. sqlalchemy/dialects/sqlite/dml.py +314 -0
  70. sqlalchemy/dialects/sqlite/json.py +134 -0
  71. sqlalchemy/dialects/sqlite/provision.py +237 -0
  72. sqlalchemy/dialects/sqlite/pysqlcipher.py +166 -0
  73. sqlalchemy/dialects/sqlite/pysqlite.py +959 -0
  74. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  75. sqlalchemy/engine/__init__.py +62 -0
  76. sqlalchemy/engine/_processors_cy.cp315-win_arm64.pyd +0 -0
  77. sqlalchemy/engine/_processors_cy.py +92 -0
  78. sqlalchemy/engine/_result_cy.cp315-win_arm64.pyd +0 -0
  79. sqlalchemy/engine/_result_cy.py +711 -0
  80. sqlalchemy/engine/_row_cy.cp315-win_arm64.pyd +0 -0
  81. sqlalchemy/engine/_row_cy.py +232 -0
  82. sqlalchemy/engine/_util_cy.cp315-win_arm64.pyd +0 -0
  83. sqlalchemy/engine/_util_cy.py +136 -0
  84. sqlalchemy/engine/base.py +3357 -0
  85. sqlalchemy/engine/characteristics.py +155 -0
  86. sqlalchemy/engine/create.py +877 -0
  87. sqlalchemy/engine/cursor.py +2425 -0
  88. sqlalchemy/engine/default.py +2627 -0
  89. sqlalchemy/engine/events.py +965 -0
  90. sqlalchemy/engine/interfaces.py +3636 -0
  91. sqlalchemy/engine/mock.py +133 -0
  92. sqlalchemy/engine/processors.py +83 -0
  93. sqlalchemy/engine/reflection.py +2141 -0
  94. sqlalchemy/engine/result.py +2012 -0
  95. sqlalchemy/engine/row.py +397 -0
  96. sqlalchemy/engine/strategies.py +16 -0
  97. sqlalchemy/engine/url.py +922 -0
  98. sqlalchemy/engine/util.py +164 -0
  99. sqlalchemy/event/__init__.py +26 -0
  100. sqlalchemy/event/api.py +220 -0
  101. sqlalchemy/event/attr.py +675 -0
  102. sqlalchemy/event/base.py +473 -0
  103. sqlalchemy/event/legacy.py +259 -0
  104. sqlalchemy/event/registry.py +391 -0
  105. sqlalchemy/events.py +17 -0
  106. sqlalchemy/exc.py +939 -0
  107. sqlalchemy/ext/__init__.py +10 -0
  108. sqlalchemy/ext/associationproxy.py +2073 -0
  109. sqlalchemy/ext/asyncio/__init__.py +29 -0
  110. sqlalchemy/ext/asyncio/base.py +281 -0
  111. sqlalchemy/ext/asyncio/engine.py +1487 -0
  112. sqlalchemy/ext/asyncio/exc.py +21 -0
  113. sqlalchemy/ext/asyncio/result.py +994 -0
  114. sqlalchemy/ext/asyncio/scoping.py +1679 -0
  115. sqlalchemy/ext/asyncio/session.py +2006 -0
  116. sqlalchemy/ext/automap.py +1702 -0
  117. sqlalchemy/ext/baked.py +558 -0
  118. sqlalchemy/ext/compiler.py +601 -0
  119. sqlalchemy/ext/declarative/__init__.py +65 -0
  120. sqlalchemy/ext/declarative/extensions.py +561 -0
  121. sqlalchemy/ext/horizontal_shard.py +481 -0
  122. sqlalchemy/ext/hybrid.py +1877 -0
  123. sqlalchemy/ext/indexable.py +364 -0
  124. sqlalchemy/ext/instrumentation.py +450 -0
  125. sqlalchemy/ext/mutable.py +1081 -0
  126. sqlalchemy/ext/orderinglist.py +440 -0
  127. sqlalchemy/ext/serializer.py +184 -0
  128. sqlalchemy/future/__init__.py +17 -0
  129. sqlalchemy/future/engine.py +15 -0
  130. sqlalchemy/inspection.py +188 -0
  131. sqlalchemy/log.py +279 -0
  132. sqlalchemy/orm/__init__.py +176 -0
  133. sqlalchemy/orm/_orm_constructors.py +2694 -0
  134. sqlalchemy/orm/_typing.py +180 -0
  135. sqlalchemy/orm/attributes.py +2868 -0
  136. sqlalchemy/orm/base.py +991 -0
  137. sqlalchemy/orm/bulk_persistence.py +2168 -0
  138. sqlalchemy/orm/clsregistry.py +630 -0
  139. sqlalchemy/orm/collections.py +1569 -0
  140. sqlalchemy/orm/context.py +3475 -0
  141. sqlalchemy/orm/decl_api.py +2283 -0
  142. sqlalchemy/orm/decl_base.py +2320 -0
  143. sqlalchemy/orm/dependency.py +1306 -0
  144. sqlalchemy/orm/descriptor_props.py +1183 -0
  145. sqlalchemy/orm/dynamic.py +306 -0
  146. sqlalchemy/orm/evaluator.py +378 -0
  147. sqlalchemy/orm/events.py +3387 -0
  148. sqlalchemy/orm/exc.py +237 -0
  149. sqlalchemy/orm/identity.py +302 -0
  150. sqlalchemy/orm/instrumentation.py +749 -0
  151. sqlalchemy/orm/interfaces.py +1595 -0
  152. sqlalchemy/orm/loading.py +1712 -0
  153. sqlalchemy/orm/mapped_collection.py +557 -0
  154. sqlalchemy/orm/mapper.py +4465 -0
  155. sqlalchemy/orm/path_registry.py +907 -0
  156. sqlalchemy/orm/persistence.py +1790 -0
  157. sqlalchemy/orm/properties.py +972 -0
  158. sqlalchemy/orm/query.py +3528 -0
  159. sqlalchemy/orm/relationships.py +3608 -0
  160. sqlalchemy/orm/scoping.py +2233 -0
  161. sqlalchemy/orm/session.py +5468 -0
  162. sqlalchemy/orm/state.py +1175 -0
  163. sqlalchemy/orm/state_changes.py +196 -0
  164. sqlalchemy/orm/strategies.py +3552 -0
  165. sqlalchemy/orm/strategy_options.py +2648 -0
  166. sqlalchemy/orm/sync.py +164 -0
  167. sqlalchemy/orm/unitofwork.py +797 -0
  168. sqlalchemy/orm/util.py +2461 -0
  169. sqlalchemy/orm/writeonly.py +701 -0
  170. sqlalchemy/pool/__init__.py +41 -0
  171. sqlalchemy/pool/base.py +1540 -0
  172. sqlalchemy/pool/events.py +375 -0
  173. sqlalchemy/pool/impl.py +583 -0
  174. sqlalchemy/py.typed +0 -0
  175. sqlalchemy/schema.py +75 -0
  176. sqlalchemy/sql/__init__.py +156 -0
  177. sqlalchemy/sql/_annotated_cols.py +402 -0
  178. sqlalchemy/sql/_cache_key_cy.cp315-win_arm64.pyd +0 -0
  179. sqlalchemy/sql/_cache_key_cy.py +363 -0
  180. sqlalchemy/sql/_dml_constructors.py +132 -0
  181. sqlalchemy/sql/_elements_constructors.py +2190 -0
  182. sqlalchemy/sql/_orm_types.py +19 -0
  183. sqlalchemy/sql/_selectable_constructors.py +840 -0
  184. sqlalchemy/sql/_typing.py +500 -0
  185. sqlalchemy/sql/_util_cy.cp315-win_arm64.pyd +0 -0
  186. sqlalchemy/sql/_util_cy.pxd +11 -0
  187. sqlalchemy/sql/_util_cy.py +127 -0
  188. sqlalchemy/sql/annotation.py +590 -0
  189. sqlalchemy/sql/base.py +2702 -0
  190. sqlalchemy/sql/cache_key.py +915 -0
  191. sqlalchemy/sql/coercions.py +1373 -0
  192. sqlalchemy/sql/compiler.py +8453 -0
  193. sqlalchemy/sql/crud.py +1816 -0
  194. sqlalchemy/sql/ddl.py +1962 -0
  195. sqlalchemy/sql/default_comparator.py +660 -0
  196. sqlalchemy/sql/dml.py +2018 -0
  197. sqlalchemy/sql/elements.py +6057 -0
  198. sqlalchemy/sql/events.py +458 -0
  199. sqlalchemy/sql/expression.py +171 -0
  200. sqlalchemy/sql/functions.py +2380 -0
  201. sqlalchemy/sql/lambdas.py +1442 -0
  202. sqlalchemy/sql/naming.py +204 -0
  203. sqlalchemy/sql/operators.py +2909 -0
  204. sqlalchemy/sql/roles.py +332 -0
  205. sqlalchemy/sql/schema.py +7075 -0
  206. sqlalchemy/sql/selectable.py +7634 -0
  207. sqlalchemy/sql/sqltypes.py +4130 -0
  208. sqlalchemy/sql/traversals.py +1041 -0
  209. sqlalchemy/sql/type_api.py +2450 -0
  210. sqlalchemy/sql/util.py +1496 -0
  211. sqlalchemy/sql/visitors.py +1153 -0
  212. sqlalchemy/testing/__init__.py +97 -0
  213. sqlalchemy/testing/assertions.py +1007 -0
  214. sqlalchemy/testing/assertsql.py +519 -0
  215. sqlalchemy/testing/asyncio.py +128 -0
  216. sqlalchemy/testing/cancellation.py +237 -0
  217. sqlalchemy/testing/config.py +440 -0
  218. sqlalchemy/testing/engines.py +482 -0
  219. sqlalchemy/testing/entities.py +117 -0
  220. sqlalchemy/testing/exclusions.py +501 -0
  221. sqlalchemy/testing/fixtures/__init__.py +30 -0
  222. sqlalchemy/testing/fixtures/base.py +426 -0
  223. sqlalchemy/testing/fixtures/mypy.py +247 -0
  224. sqlalchemy/testing/fixtures/orm.py +227 -0
  225. sqlalchemy/testing/fixtures/sql.py +538 -0
  226. sqlalchemy/testing/pickleable.py +155 -0
  227. sqlalchemy/testing/plugin/__init__.py +6 -0
  228. sqlalchemy/testing/plugin/bootstrap.py +50 -0
  229. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  230. sqlalchemy/testing/plugin/pytestplugin.py +896 -0
  231. sqlalchemy/testing/profiles_file.py +350 -0
  232. sqlalchemy/testing/profiling.py +294 -0
  233. sqlalchemy/testing/provision.py +633 -0
  234. sqlalchemy/testing/requirements.py +1971 -0
  235. sqlalchemy/testing/schema.py +198 -0
  236. sqlalchemy/testing/suite/__init__.py +19 -0
  237. sqlalchemy/testing/suite/test_cte.py +237 -0
  238. sqlalchemy/testing/suite/test_ddl.py +420 -0
  239. sqlalchemy/testing/suite/test_dialect.py +776 -0
  240. sqlalchemy/testing/suite/test_insert.py +630 -0
  241. sqlalchemy/testing/suite/test_reflection.py +3815 -0
  242. sqlalchemy/testing/suite/test_results.py +660 -0
  243. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  244. sqlalchemy/testing/suite/test_select.py +2112 -0
  245. sqlalchemy/testing/suite/test_sequence.py +317 -0
  246. sqlalchemy/testing/suite/test_table_via_select.py +686 -0
  247. sqlalchemy/testing/suite/test_types.py +2271 -0
  248. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  249. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  250. sqlalchemy/testing/util.py +575 -0
  251. sqlalchemy/testing/warnings.py +52 -0
  252. sqlalchemy/types.py +75 -0
  253. sqlalchemy/util/__init__.py +165 -0
  254. sqlalchemy/util/_collections.py +688 -0
  255. sqlalchemy/util/_collections_cy.cp315-win_arm64.pyd +0 -0
  256. sqlalchemy/util/_collections_cy.pxd +8 -0
  257. sqlalchemy/util/_collections_cy.py +516 -0
  258. sqlalchemy/util/_has_cython.py +48 -0
  259. sqlalchemy/util/_immutabledict_cy.cp315-win_arm64.pyd +0 -0
  260. sqlalchemy/util/_immutabledict_cy.py +240 -0
  261. sqlalchemy/util/compat.py +298 -0
  262. sqlalchemy/util/concurrency.py +272 -0
  263. sqlalchemy/util/cython.py +95 -0
  264. sqlalchemy/util/deprecations.py +401 -0
  265. sqlalchemy/util/langhelpers.py +2797 -0
  266. sqlalchemy/util/preloaded.py +153 -0
  267. sqlalchemy/util/queue.py +304 -0
  268. sqlalchemy/util/tool_support.py +202 -0
  269. sqlalchemy/util/topological.py +120 -0
  270. sqlalchemy/util/typing.py +709 -0
  271. sqlalchemy-2.1.0rc1.dist-info/METADATA +270 -0
  272. sqlalchemy-2.1.0rc1.dist-info/RECORD +276 -0
  273. sqlalchemy-2.1.0rc1.dist-info/WHEEL +5 -0
  274. sqlalchemy-2.1.0rc1.dist-info/licenses/AUTHORS +30 -0
  275. sqlalchemy-2.1.0rc1.dist-info/licenses/LICENSE +19 -0
  276. sqlalchemy-2.1.0rc1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,242 @@
1
+ # dialects/mssql/mssqlpython.py
2
+ # Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3
+ # <see AUTHORS file>
4
+ #
5
+ # This module is part of SQLAlchemy and is released under
6
+ # the MIT License: https://www.opensource.org/licenses/mit-license.php
7
+ # mypy: ignore-errors
8
+
9
+ """
10
+ .. dialect:: mssql+mssqlpython
11
+ :name: mssqlpython
12
+ :dbapi: mssql-python
13
+ :connectstring: mssql+mssqlpython://<username>:<password>@<host>:<port>/<dbname>
14
+ :url: https://github.com/microsoft/mssql-python
15
+
16
+ mssql-python is a driver for Microsoft SQL Server produced by Microsoft.
17
+
18
+ .. versionadded:: 2.1.0b2
19
+
20
+
21
+ The driver is generally similar to pyodbc in most aspects as it is based
22
+ on the same ODBC framework.
23
+
24
+ Connection Strings
25
+ ------------------
26
+
27
+ Examples of connecting with the mssql-python driver::
28
+
29
+ from sqlalchemy import create_engine
30
+
31
+ # Basic connection
32
+ engine = create_engine(
33
+ "mssql+mssqlpython://user:password@hostname/database"
34
+ )
35
+
36
+ # With Windows Authentication
37
+ engine = create_engine(
38
+ "mssql+mssqlpython://hostname/database?authentication=ActiveDirectoryIntegrated"
39
+ )
40
+
41
+ """ # noqa
42
+
43
+ from __future__ import annotations
44
+
45
+ import re
46
+ from typing import Any
47
+ from typing import Dict
48
+ from typing import List
49
+ from typing import Optional
50
+ from typing import TYPE_CHECKING
51
+ from typing import Union
52
+
53
+ from .base import MSDialect
54
+ from .pyodbc import _ms_numeric_pyodbc
55
+ from ... import util
56
+ from ...sql import sqltypes
57
+
58
+ if TYPE_CHECKING:
59
+ from ... import pool
60
+ from ...engine import interfaces
61
+ from ...engine.interfaces import ConnectArgsType
62
+ from ...engine.interfaces import DBAPIModule
63
+ from ...engine.interfaces import IsolationLevel
64
+ from ...engine.interfaces import URL
65
+
66
+
67
+ class _MSNumeric_mssqlpython(_ms_numeric_pyodbc, sqltypes.Numeric):
68
+ pass
69
+
70
+
71
+ class _MSFloat_mssqlpython(_ms_numeric_pyodbc, sqltypes.Float):
72
+ pass
73
+
74
+
75
+ class MSDialect_mssqlpython(MSDialect):
76
+ driver = "mssqlpython"
77
+ supports_statement_cache = True
78
+
79
+ supports_sane_rowcount_returning = True
80
+ supports_sane_multi_rowcount = True
81
+ supports_native_uuid = True
82
+ scope_identity_must_be_embedded = True
83
+
84
+ supports_native_decimal = True
85
+
86
+ _disconnect_driver_errors = frozenset(
87
+ {
88
+ # mssql-python converts the originating SQLSTATE into these
89
+ # values and exposes them on ``Exception.driver_error``.
90
+ "Disconnect error", # 01002
91
+ "Client unable to establish connection", # 08001
92
+ "Connection not open", # 08003
93
+ "Connection failure during transaction", # 08007
94
+ "Communication link failure", # 08S01
95
+ # mssql-python does not map these SQLSTATE values to a
96
+ # dedicated driver error yet.
97
+ "An error occurred with SQLSTATE code: 08S02",
98
+ "An error occurred with SQLSTATE code: 10054",
99
+ "Connection timeout expired", # HYT01
100
+ "Function sequence error", # HY010
101
+ }
102
+ )
103
+
104
+ # used by pyodbc _ms_numeric_pyodbc class
105
+ _need_decimal_fix = True
106
+
107
+ colspecs = util.update_copy(
108
+ MSDialect.colspecs,
109
+ {
110
+ sqltypes.Numeric: _MSNumeric_mssqlpython,
111
+ sqltypes.Float: _MSFloat_mssqlpython,
112
+ },
113
+ )
114
+
115
+ def __init__(self, enable_pooling=False, **kw):
116
+ super().__init__(**kw)
117
+ if not enable_pooling and self.dbapi is not None:
118
+ self.loaded_dbapi.pooling(enabled=False)
119
+
120
+ @classmethod
121
+ def import_dbapi(cls) -> DBAPIModule:
122
+ return __import__("mssql_python")
123
+
124
+ def create_connect_args(self, url: URL) -> ConnectArgsType:
125
+ opts = url.translate_connect_args(username="user")
126
+ opts.update(url.query)
127
+
128
+ keys = opts
129
+
130
+ query = url.query
131
+
132
+ connect_args: Dict[str, Any] = {}
133
+ connectors: List[str]
134
+
135
+ def check_quote(token: str) -> str:
136
+ if (
137
+ ";" in str(token)
138
+ or "}" in str(token)
139
+ or str(token).startswith("{")
140
+ ):
141
+ token = "{%s}" % token.replace("}", "}}")
142
+ return token
143
+
144
+ keys = {k: check_quote(v) for k, v in keys.items()}
145
+
146
+ port = ""
147
+ if "port" in keys and "port" not in query:
148
+ port = ",%d" % int(keys.pop("port"))
149
+
150
+ connectors = []
151
+
152
+ connectors.extend(
153
+ [
154
+ "Server=%s%s" % (keys.pop("host", ""), port),
155
+ "Database=%s" % keys.pop("database", ""),
156
+ ]
157
+ )
158
+
159
+ user = keys.pop("user", None)
160
+ if user:
161
+ connectors.append("UID=%s" % user)
162
+ pwd = keys.pop("password", "")
163
+ if pwd:
164
+ connectors.append("PWD=%s" % pwd)
165
+ else:
166
+ authentication = keys.pop("authentication", None)
167
+ if authentication:
168
+ connectors.append("Authentication=%s" % authentication)
169
+
170
+ connectors.extend(
171
+ ["%s=%s" % (check_quote(k), v) for k, v in keys.items()]
172
+ )
173
+
174
+ return ((";".join(connectors),), connect_args)
175
+
176
+ def is_disconnect(
177
+ self,
178
+ e: Exception,
179
+ connection: Optional[
180
+ Union[pool.PoolProxiedConnection, interfaces.DBAPIConnection]
181
+ ],
182
+ cursor: Optional[interfaces.DBAPICursor],
183
+ ) -> bool:
184
+ if not isinstance(e, self.loaded_dbapi.Error):
185
+ return False
186
+
187
+ if getattr(e, "driver_error", None) in self._disconnect_driver_errors:
188
+ return True
189
+
190
+ if isinstance(e, self.loaded_dbapi.ProgrammingError):
191
+ return (
192
+ "The cursor's connection has been closed." in str(e)
193
+ or "Attempt to use a closed connection." in str(e)
194
+ or "Driver Error: Operation cannot be performed" in str(e)
195
+ )
196
+
197
+ if isinstance(e, self.loaded_dbapi.InterfaceError):
198
+ return bool(
199
+ re.search(r"Cannot .* on (?:a )?closed connection", str(e))
200
+ )
201
+
202
+ return False
203
+
204
+ def retrieve_dbapi_version(
205
+ self, dbapi: interfaces.DBAPIModule
206
+ ) -> util.VersionInfo:
207
+ return util.parse_version_string(dbapi.version)
208
+
209
+ def _get_server_version_info(self, connection):
210
+ vers = connection.exec_driver_sql("select @@version").scalar()
211
+ m = re.match(r"Microsoft .*? - (\d+)\.(\d+)\.(\d+)\.(\d+)", vers)
212
+ if m:
213
+ return tuple(int(x) for x in m.group(1, 2, 3, 4))
214
+ else:
215
+ return None
216
+
217
+ def get_isolation_level_values(
218
+ self, dbapi_connection: interfaces.DBAPIConnection
219
+ ) -> List[IsolationLevel]:
220
+ return [
221
+ *super().get_isolation_level_values(dbapi_connection),
222
+ "AUTOCOMMIT",
223
+ ]
224
+
225
+ def set_isolation_level(
226
+ self,
227
+ dbapi_connection: interfaces.DBAPIConnection,
228
+ level: IsolationLevel,
229
+ ) -> None:
230
+ if level == "AUTOCOMMIT":
231
+ dbapi_connection.autocommit = True
232
+ else:
233
+ dbapi_connection.autocommit = False
234
+ super().set_isolation_level(dbapi_connection, level)
235
+
236
+ def detect_autocommit_setting(
237
+ self, dbapi_conn: interfaces.DBAPIConnection
238
+ ) -> bool:
239
+ return bool(dbapi_conn.autocommit)
240
+
241
+
242
+ dialect = MSDialect_mssqlpython
@@ -0,0 +1,196 @@
1
+ # dialects/mssql/provision.py
2
+ # Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3
+ # <see AUTHORS file>
4
+ #
5
+ # This module is part of SQLAlchemy and is released under
6
+ # the MIT License: https://www.opensource.org/licenses/mit-license.php
7
+ # mypy: ignore-errors
8
+
9
+ from sqlalchemy import inspect
10
+ from sqlalchemy import Integer
11
+ from ... import create_engine
12
+ from ... import exc
13
+ from ...schema import Column
14
+ from ...schema import DropConstraint
15
+ from ...schema import ForeignKeyConstraint
16
+ from ...schema import MetaData
17
+ from ...schema import Table
18
+ from ...testing.provision import create_db
19
+ from ...testing.provision import dbapi_error
20
+ from ...testing.provision import drop_all_schema_objects_pre_tables
21
+ from ...testing.provision import drop_db
22
+ from ...testing.provision import generate_driver_url
23
+ from ...testing.provision import get_temp_table_name
24
+ from ...testing.provision import log
25
+ from ...testing.provision import normalize_sequence
26
+ from ...testing.provision import post_configure_engine
27
+ from ...testing.provision import run_reap_dbs
28
+ from ...testing.provision import temp_table_keyword_args
29
+
30
+
31
+ @post_configure_engine.for_db("mssql")
32
+ def post_configure_engine(url, engine, follower_ident):
33
+ if engine.driver == "pyodbc":
34
+ engine.dialect.dbapi.pooling = False
35
+
36
+
37
+ @generate_driver_url.for_db("mssql")
38
+ def generate_driver_url(url, driver, query_str):
39
+ backend = url.get_backend_name()
40
+
41
+ new_url = url.set(drivername="%s+%s" % (backend, driver))
42
+
43
+ if driver == "pymssql" and url.get_driver_name() != "pymssql":
44
+ new_url = new_url.set(query="")
45
+ elif driver == "mssqlpython" and url.get_driver_name() != "mssqlpython":
46
+ new_url = new_url.set(query={"Encrypt": "No"})
47
+
48
+ if driver == "aioodbc":
49
+ new_url = new_url.update_query_dict({"MARS_Connection": "Yes"})
50
+
51
+ if query_str:
52
+ new_url = new_url.update_query_string(query_str)
53
+
54
+ try:
55
+ new_url.get_dialect()
56
+ except exc.NoSuchModuleError:
57
+ return None
58
+ else:
59
+ return new_url
60
+
61
+
62
+ @create_db.for_db("mssql")
63
+ def _mssql_create_db(cfg, eng, ident):
64
+ with eng.connect().execution_options(isolation_level="AUTOCOMMIT") as conn:
65
+ conn.exec_driver_sql("create database %s" % ident)
66
+ conn.exec_driver_sql(
67
+ "ALTER DATABASE %s SET ALLOW_SNAPSHOT_ISOLATION ON" % ident
68
+ )
69
+ conn.exec_driver_sql(
70
+ "ALTER DATABASE %s SET READ_COMMITTED_SNAPSHOT ON" % ident
71
+ )
72
+ conn.exec_driver_sql("use %s" % ident)
73
+ conn.exec_driver_sql("create schema test_schema")
74
+ conn.exec_driver_sql("create schema test_schema_2")
75
+
76
+
77
+ @drop_db.for_db("mssql")
78
+ def _mssql_drop_db(cfg, eng, ident):
79
+ with eng.connect().execution_options(isolation_level="AUTOCOMMIT") as conn:
80
+ _mssql_drop_ignore(conn, ident)
81
+
82
+
83
+ def _mssql_drop_ignore(conn, ident):
84
+ try:
85
+ # typically when this happens, we can't KILL the session anyway,
86
+ # so let the cleanup process drop the DBs
87
+ # for row in conn.exec_driver_sql(
88
+ # "select session_id from sys.dm_exec_sessions "
89
+ # "where database_id=db_id('%s')" % ident):
90
+ # log.info("killing SQL server session %s", row['session_id'])
91
+ # conn.exec_driver_sql("kill %s" % row['session_id'])
92
+ conn.exec_driver_sql("drop database %s" % ident)
93
+ log.info("Reaped db: %s", ident)
94
+ return True
95
+ except exc.DatabaseError as err:
96
+ log.warning("couldn't drop db: %s", err)
97
+ return False
98
+
99
+
100
+ @run_reap_dbs.for_db("mssql")
101
+ def _reap_mssql_dbs(url, idents):
102
+ log.info("db reaper connecting to %r", url)
103
+ eng = create_engine(url)
104
+ with eng.connect().execution_options(isolation_level="AUTOCOMMIT") as conn:
105
+ log.info("identifiers in file: %s", ", ".join(idents))
106
+
107
+ to_reap = conn.exec_driver_sql(
108
+ "select d.name from sys.databases as d where name "
109
+ "like 'TEST_%' and not exists (select session_id "
110
+ "from sys.dm_exec_sessions "
111
+ "where database_id=d.database_id)"
112
+ )
113
+ all_names = {dbname.lower() for (dbname,) in to_reap}
114
+ to_drop = set()
115
+ for name in all_names:
116
+ if name in idents:
117
+ to_drop.add(name)
118
+
119
+ dropped = total = 0
120
+ for total, dbname in enumerate(to_drop, 1):
121
+ if _mssql_drop_ignore(conn, dbname):
122
+ dropped += 1
123
+ log.info(
124
+ "Dropped %d out of %d stale databases detected", dropped, total
125
+ )
126
+
127
+
128
+ @temp_table_keyword_args.for_db("mssql")
129
+ def _mssql_temp_table_keyword_args(cfg, eng):
130
+ return {}
131
+
132
+
133
+ @get_temp_table_name.for_db("mssql")
134
+ def _mssql_get_temp_table_name(cfg, eng, base_name):
135
+ return "##" + base_name
136
+
137
+
138
+ @drop_all_schema_objects_pre_tables.for_db("mssql")
139
+ def drop_all_schema_objects_pre_tables(cfg, eng):
140
+ with eng.connect().execution_options(isolation_level="AUTOCOMMIT") as conn:
141
+ inspector = inspect(conn)
142
+
143
+ # Drop all full-text indexes before dropping catalogs
144
+ fulltext_indexes = conn.exec_driver_sql(
145
+ "SELECT OBJECT_SCHEMA_NAME(object_id) AS schema_name, "
146
+ "OBJECT_NAME(object_id) AS table_name "
147
+ "FROM sys.fulltext_indexes"
148
+ ).fetchall()
149
+
150
+ for schema_name, table_name in fulltext_indexes:
151
+ if schema_name:
152
+ qualified_name = f"[{schema_name}].[{table_name}]"
153
+ else:
154
+ qualified_name = f"[{table_name}]"
155
+ conn.exec_driver_sql(f"DROP FULLTEXT INDEX ON {qualified_name}")
156
+
157
+ # Now drop all full-text catalogs
158
+ fulltext_catalogs = conn.exec_driver_sql(
159
+ "SELECT name FROM sys.fulltext_catalogs"
160
+ ).fetchall()
161
+
162
+ for (catalog_name,) in fulltext_catalogs:
163
+ conn.exec_driver_sql(f"DROP FULLTEXT CATALOG [{catalog_name}]")
164
+
165
+ for schema in (None, "dbo", cfg.test_schema, cfg.test_schema_2):
166
+ for tname in inspector.get_table_names(schema=schema):
167
+ tb = Table(
168
+ tname,
169
+ MetaData(),
170
+ Column("x", Integer),
171
+ Column("y", Integer),
172
+ schema=schema,
173
+ )
174
+ for fk in inspect(conn).get_foreign_keys(tname, schema=schema):
175
+ conn.execute(
176
+ DropConstraint(
177
+ ForeignKeyConstraint(
178
+ [tb.c.x], [tb.c.y], name=fk["name"]
179
+ ),
180
+ )
181
+ )
182
+
183
+
184
+ @normalize_sequence.for_db("mssql")
185
+ def normalize_sequence(cfg, sequence):
186
+ if sequence.start is None:
187
+ sequence.start = 1
188
+ return sequence
189
+
190
+
191
+ @dbapi_error.for_db("mssql")
192
+ def dbapi_error(cfg, cls, message):
193
+ if cfg.db.driver == "mssqlpython":
194
+ return cls(message, "placeholder for mssqlpython")
195
+ else:
196
+ return cls(message)
@@ -0,0 +1,130 @@
1
+ # dialects/mssql/pymssql.py
2
+ # Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3
+ # <see AUTHORS file>
4
+ #
5
+ # This module is part of SQLAlchemy and is released under
6
+ # the MIT License: https://www.opensource.org/licenses/mit-license.php
7
+ # mypy: ignore-errors
8
+
9
+
10
+ """
11
+ .. dialect:: mssql+pymssql
12
+ :name: pymssql
13
+ :dbapi: pymssql
14
+ :connectstring: mssql+pymssql://<username>:<password>@<freetds_name>/?charset=utf8
15
+
16
+ pymssql is a Python module that provides a Python DBAPI interface around
17
+ `FreeTDS <https://www.freetds.org/>`_.
18
+
19
+ .. versionchanged:: 2.0.5
20
+
21
+ pymssql was restored to SQLAlchemy's continuous integration testing
22
+
23
+
24
+ """ # noqa
25
+
26
+ import re
27
+
28
+ from .base import MSDialect
29
+ from .base import MSIdentifierPreparer
30
+ from ... import types as sqltypes
31
+ from ... import util
32
+ from ...engine import processors
33
+
34
+
35
+ class _MSNumeric_pymssql(sqltypes.Numeric):
36
+ def result_processor(self, dialect, type_):
37
+ if not self.asdecimal:
38
+ return processors.to_float
39
+ else:
40
+ return sqltypes.Numeric.result_processor(self, dialect, type_)
41
+
42
+
43
+ class MSIdentifierPreparer_pymssql(MSIdentifierPreparer):
44
+ def __init__(self, dialect):
45
+ super().__init__(dialect)
46
+ # pymssql has the very unusual behavior that it uses pyformat
47
+ # yet does not require that percent signs be doubled
48
+ self._double_percents = False
49
+
50
+
51
+ class MSDialect_pymssql(MSDialect):
52
+ supports_statement_cache = True
53
+ supports_native_decimal = True
54
+ supports_native_uuid = True
55
+ driver = "pymssql"
56
+
57
+ preparer = MSIdentifierPreparer_pymssql
58
+
59
+ colspecs = util.update_copy(
60
+ MSDialect.colspecs,
61
+ {sqltypes.Numeric: _MSNumeric_pymssql, sqltypes.Float: sqltypes.Float},
62
+ )
63
+
64
+ def retrieve_dbapi_version(self, dbapi):
65
+ return util.parse_version_string(getattr(dbapi, "__version__", None))
66
+
67
+ @classmethod
68
+ def import_dbapi(cls):
69
+ module = __import__("pymssql")
70
+ # pymmsql < 2.1.1 doesn't have a Binary method. we use string
71
+ client_ver = util.parse_version_string(module.__version__)
72
+ if client_ver < (2, 1, 1):
73
+ # TODO: monkeypatching here is less than ideal
74
+ module.Binary = lambda x: x if hasattr(x, "decode") else str(x)
75
+
76
+ if client_ver < (1,):
77
+ util.warn(
78
+ "The pymssql dialect expects at least "
79
+ "the 1.0 series of the pymssql DBAPI."
80
+ )
81
+ return module
82
+
83
+ def _get_server_version_info(self, connection):
84
+ vers = connection.exec_driver_sql("select @@version").scalar()
85
+ m = re.match(r"Microsoft .*? - (\d+)\.(\d+)\.(\d+)\.(\d+)", vers)
86
+ if m:
87
+ return tuple(int(x) for x in m.group(1, 2, 3, 4))
88
+ else:
89
+ return None
90
+
91
+ def create_connect_args(self, url):
92
+ opts = url.translate_connect_args(username="user")
93
+ opts.update(url.query)
94
+ port = opts.pop("port", None)
95
+ if port and "host" in opts:
96
+ opts["host"] = "%s:%s" % (opts["host"], port)
97
+ return ([], opts)
98
+
99
+ def is_disconnect(self, e, connection, cursor):
100
+ for msg in (
101
+ "Adaptive Server connection timed out",
102
+ "Net-Lib error during Connection reset by peer",
103
+ "message 20003", # connection timeout
104
+ "Error 10054",
105
+ "Not connected to any MS SQL server",
106
+ "Connection is closed",
107
+ "message 20006", # Write to the server failed
108
+ "message 20017", # Unexpected EOF from the server
109
+ "message 20047", # DBPROCESS is dead or not enabled
110
+ "The server failed to resume the transaction",
111
+ ):
112
+ if msg in str(e):
113
+ return True
114
+ else:
115
+ return False
116
+
117
+ def get_isolation_level_values(self, dbapi_connection):
118
+ return super().get_isolation_level_values(dbapi_connection) + [
119
+ "AUTOCOMMIT"
120
+ ]
121
+
122
+ def set_isolation_level(self, dbapi_connection, level):
123
+ if level == "AUTOCOMMIT":
124
+ dbapi_connection.autocommit(True)
125
+ else:
126
+ dbapi_connection.autocommit(False)
127
+ super().set_isolation_level(dbapi_connection, level)
128
+
129
+
130
+ dialect = MSDialect_pymssql