SQLAlchemy 2.0.47__cp313-cp313t-win32.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.
- sqlalchemy/__init__.py +283 -0
- sqlalchemy/connectors/__init__.py +18 -0
- sqlalchemy/connectors/aioodbc.py +184 -0
- sqlalchemy/connectors/asyncio.py +429 -0
- sqlalchemy/connectors/pyodbc.py +250 -0
- sqlalchemy/cyextension/__init__.py +6 -0
- sqlalchemy/cyextension/collections.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/collections.pyx +409 -0
- sqlalchemy/cyextension/immutabledict.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/immutabledict.pxd +8 -0
- sqlalchemy/cyextension/immutabledict.pyx +133 -0
- sqlalchemy/cyextension/processors.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/processors.pyx +68 -0
- sqlalchemy/cyextension/resultproxy.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/resultproxy.pyx +102 -0
- sqlalchemy/cyextension/util.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/util.pyx +90 -0
- sqlalchemy/dialects/__init__.py +62 -0
- sqlalchemy/dialects/_typing.py +30 -0
- sqlalchemy/dialects/mssql/__init__.py +88 -0
- sqlalchemy/dialects/mssql/aioodbc.py +63 -0
- sqlalchemy/dialects/mssql/base.py +4093 -0
- sqlalchemy/dialects/mssql/information_schema.py +285 -0
- sqlalchemy/dialects/mssql/json.py +129 -0
- sqlalchemy/dialects/mssql/provision.py +185 -0
- sqlalchemy/dialects/mssql/pymssql.py +126 -0
- sqlalchemy/dialects/mssql/pyodbc.py +760 -0
- sqlalchemy/dialects/mysql/__init__.py +104 -0
- sqlalchemy/dialects/mysql/aiomysql.py +250 -0
- sqlalchemy/dialects/mysql/asyncmy.py +231 -0
- sqlalchemy/dialects/mysql/base.py +3949 -0
- sqlalchemy/dialects/mysql/cymysql.py +106 -0
- sqlalchemy/dialects/mysql/dml.py +225 -0
- sqlalchemy/dialects/mysql/enumerated.py +282 -0
- sqlalchemy/dialects/mysql/expression.py +146 -0
- sqlalchemy/dialects/mysql/json.py +91 -0
- sqlalchemy/dialects/mysql/mariadb.py +72 -0
- sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
- sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
- sqlalchemy/dialects/mysql/mysqldb.py +314 -0
- sqlalchemy/dialects/mysql/provision.py +153 -0
- sqlalchemy/dialects/mysql/pymysql.py +158 -0
- sqlalchemy/dialects/mysql/pyodbc.py +157 -0
- sqlalchemy/dialects/mysql/reflection.py +727 -0
- sqlalchemy/dialects/mysql/reserved_words.py +570 -0
- sqlalchemy/dialects/mysql/types.py +835 -0
- sqlalchemy/dialects/oracle/__init__.py +81 -0
- sqlalchemy/dialects/oracle/base.py +3802 -0
- sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
- sqlalchemy/dialects/oracle/dictionary.py +507 -0
- sqlalchemy/dialects/oracle/oracledb.py +941 -0
- sqlalchemy/dialects/oracle/provision.py +297 -0
- sqlalchemy/dialects/oracle/types.py +316 -0
- sqlalchemy/dialects/oracle/vector.py +365 -0
- sqlalchemy/dialects/postgresql/__init__.py +167 -0
- sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
- sqlalchemy/dialects/postgresql/array.py +519 -0
- sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
- sqlalchemy/dialects/postgresql/base.py +5378 -0
- sqlalchemy/dialects/postgresql/dml.py +339 -0
- sqlalchemy/dialects/postgresql/ext.py +540 -0
- sqlalchemy/dialects/postgresql/hstore.py +406 -0
- sqlalchemy/dialects/postgresql/json.py +404 -0
- sqlalchemy/dialects/postgresql/named_types.py +524 -0
- sqlalchemy/dialects/postgresql/operators.py +129 -0
- sqlalchemy/dialects/postgresql/pg8000.py +669 -0
- sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
- sqlalchemy/dialects/postgresql/provision.py +183 -0
- sqlalchemy/dialects/postgresql/psycopg.py +862 -0
- sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
- sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
- sqlalchemy/dialects/postgresql/ranges.py +1031 -0
- sqlalchemy/dialects/postgresql/types.py +313 -0
- sqlalchemy/dialects/sqlite/__init__.py +57 -0
- sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
- sqlalchemy/dialects/sqlite/base.py +3056 -0
- sqlalchemy/dialects/sqlite/dml.py +263 -0
- sqlalchemy/dialects/sqlite/json.py +92 -0
- sqlalchemy/dialects/sqlite/provision.py +229 -0
- sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
- sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
- sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
- sqlalchemy/engine/__init__.py +62 -0
- sqlalchemy/engine/_py_processors.py +136 -0
- sqlalchemy/engine/_py_row.py +128 -0
- sqlalchemy/engine/_py_util.py +74 -0
- sqlalchemy/engine/base.py +3390 -0
- sqlalchemy/engine/characteristics.py +155 -0
- sqlalchemy/engine/create.py +893 -0
- sqlalchemy/engine/cursor.py +2298 -0
- sqlalchemy/engine/default.py +2394 -0
- sqlalchemy/engine/events.py +965 -0
- sqlalchemy/engine/interfaces.py +3471 -0
- sqlalchemy/engine/mock.py +134 -0
- sqlalchemy/engine/processors.py +61 -0
- sqlalchemy/engine/reflection.py +2102 -0
- sqlalchemy/engine/result.py +2399 -0
- sqlalchemy/engine/row.py +400 -0
- sqlalchemy/engine/strategies.py +16 -0
- sqlalchemy/engine/url.py +924 -0
- sqlalchemy/engine/util.py +167 -0
- sqlalchemy/event/__init__.py +26 -0
- sqlalchemy/event/api.py +220 -0
- sqlalchemy/event/attr.py +676 -0
- sqlalchemy/event/base.py +472 -0
- sqlalchemy/event/legacy.py +258 -0
- sqlalchemy/event/registry.py +390 -0
- sqlalchemy/events.py +17 -0
- sqlalchemy/exc.py +832 -0
- sqlalchemy/ext/__init__.py +11 -0
- sqlalchemy/ext/associationproxy.py +2027 -0
- sqlalchemy/ext/asyncio/__init__.py +25 -0
- sqlalchemy/ext/asyncio/base.py +281 -0
- sqlalchemy/ext/asyncio/engine.py +1471 -0
- sqlalchemy/ext/asyncio/exc.py +21 -0
- sqlalchemy/ext/asyncio/result.py +965 -0
- sqlalchemy/ext/asyncio/scoping.py +1599 -0
- sqlalchemy/ext/asyncio/session.py +1947 -0
- sqlalchemy/ext/automap.py +1701 -0
- sqlalchemy/ext/baked.py +570 -0
- sqlalchemy/ext/compiler.py +600 -0
- sqlalchemy/ext/declarative/__init__.py +65 -0
- sqlalchemy/ext/declarative/extensions.py +564 -0
- sqlalchemy/ext/horizontal_shard.py +478 -0
- sqlalchemy/ext/hybrid.py +1535 -0
- sqlalchemy/ext/indexable.py +364 -0
- sqlalchemy/ext/instrumentation.py +450 -0
- sqlalchemy/ext/mutable.py +1085 -0
- sqlalchemy/ext/mypy/__init__.py +6 -0
- sqlalchemy/ext/mypy/apply.py +324 -0
- sqlalchemy/ext/mypy/decl_class.py +515 -0
- sqlalchemy/ext/mypy/infer.py +590 -0
- sqlalchemy/ext/mypy/names.py +335 -0
- sqlalchemy/ext/mypy/plugin.py +303 -0
- sqlalchemy/ext/mypy/util.py +357 -0
- sqlalchemy/ext/orderinglist.py +439 -0
- sqlalchemy/ext/serializer.py +185 -0
- sqlalchemy/future/__init__.py +16 -0
- sqlalchemy/future/engine.py +15 -0
- sqlalchemy/inspection.py +174 -0
- sqlalchemy/log.py +288 -0
- sqlalchemy/orm/__init__.py +171 -0
- sqlalchemy/orm/_orm_constructors.py +2661 -0
- sqlalchemy/orm/_typing.py +179 -0
- sqlalchemy/orm/attributes.py +2845 -0
- sqlalchemy/orm/base.py +971 -0
- sqlalchemy/orm/bulk_persistence.py +2135 -0
- sqlalchemy/orm/clsregistry.py +571 -0
- sqlalchemy/orm/collections.py +1627 -0
- sqlalchemy/orm/context.py +3334 -0
- sqlalchemy/orm/decl_api.py +2004 -0
- sqlalchemy/orm/decl_base.py +2192 -0
- sqlalchemy/orm/dependency.py +1302 -0
- sqlalchemy/orm/descriptor_props.py +1092 -0
- sqlalchemy/orm/dynamic.py +300 -0
- sqlalchemy/orm/evaluator.py +379 -0
- sqlalchemy/orm/events.py +3252 -0
- sqlalchemy/orm/exc.py +237 -0
- sqlalchemy/orm/identity.py +302 -0
- sqlalchemy/orm/instrumentation.py +754 -0
- sqlalchemy/orm/interfaces.py +1496 -0
- sqlalchemy/orm/loading.py +1686 -0
- sqlalchemy/orm/mapped_collection.py +557 -0
- sqlalchemy/orm/mapper.py +4444 -0
- sqlalchemy/orm/path_registry.py +809 -0
- sqlalchemy/orm/persistence.py +1788 -0
- sqlalchemy/orm/properties.py +935 -0
- sqlalchemy/orm/query.py +3459 -0
- sqlalchemy/orm/relationships.py +3508 -0
- sqlalchemy/orm/scoping.py +2148 -0
- sqlalchemy/orm/session.py +5280 -0
- sqlalchemy/orm/state.py +1168 -0
- sqlalchemy/orm/state_changes.py +196 -0
- sqlalchemy/orm/strategies.py +3470 -0
- sqlalchemy/orm/strategy_options.py +2568 -0
- sqlalchemy/orm/sync.py +164 -0
- sqlalchemy/orm/unitofwork.py +796 -0
- sqlalchemy/orm/util.py +2403 -0
- sqlalchemy/orm/writeonly.py +674 -0
- sqlalchemy/pool/__init__.py +44 -0
- sqlalchemy/pool/base.py +1524 -0
- sqlalchemy/pool/events.py +375 -0
- sqlalchemy/pool/impl.py +588 -0
- sqlalchemy/py.typed +0 -0
- sqlalchemy/schema.py +69 -0
- sqlalchemy/sql/__init__.py +145 -0
- sqlalchemy/sql/_dml_constructors.py +132 -0
- sqlalchemy/sql/_elements_constructors.py +1872 -0
- sqlalchemy/sql/_orm_types.py +20 -0
- sqlalchemy/sql/_py_util.py +75 -0
- sqlalchemy/sql/_selectable_constructors.py +763 -0
- sqlalchemy/sql/_typing.py +482 -0
- sqlalchemy/sql/annotation.py +587 -0
- sqlalchemy/sql/base.py +2293 -0
- sqlalchemy/sql/cache_key.py +1057 -0
- sqlalchemy/sql/coercions.py +1404 -0
- sqlalchemy/sql/compiler.py +8081 -0
- sqlalchemy/sql/crud.py +1752 -0
- sqlalchemy/sql/ddl.py +1444 -0
- sqlalchemy/sql/default_comparator.py +551 -0
- sqlalchemy/sql/dml.py +1850 -0
- sqlalchemy/sql/elements.py +5589 -0
- sqlalchemy/sql/events.py +458 -0
- sqlalchemy/sql/expression.py +159 -0
- sqlalchemy/sql/functions.py +2158 -0
- sqlalchemy/sql/lambdas.py +1442 -0
- sqlalchemy/sql/naming.py +209 -0
- sqlalchemy/sql/operators.py +2623 -0
- sqlalchemy/sql/roles.py +323 -0
- sqlalchemy/sql/schema.py +6222 -0
- sqlalchemy/sql/selectable.py +7265 -0
- sqlalchemy/sql/sqltypes.py +3930 -0
- sqlalchemy/sql/traversals.py +1024 -0
- sqlalchemy/sql/type_api.py +2368 -0
- sqlalchemy/sql/util.py +1485 -0
- sqlalchemy/sql/visitors.py +1164 -0
- sqlalchemy/testing/__init__.py +96 -0
- sqlalchemy/testing/assertions.py +994 -0
- sqlalchemy/testing/assertsql.py +520 -0
- sqlalchemy/testing/asyncio.py +135 -0
- sqlalchemy/testing/config.py +434 -0
- sqlalchemy/testing/engines.py +483 -0
- sqlalchemy/testing/entities.py +117 -0
- sqlalchemy/testing/exclusions.py +476 -0
- sqlalchemy/testing/fixtures/__init__.py +28 -0
- sqlalchemy/testing/fixtures/base.py +384 -0
- sqlalchemy/testing/fixtures/mypy.py +332 -0
- sqlalchemy/testing/fixtures/orm.py +227 -0
- sqlalchemy/testing/fixtures/sql.py +482 -0
- sqlalchemy/testing/pickleable.py +155 -0
- sqlalchemy/testing/plugin/__init__.py +6 -0
- sqlalchemy/testing/plugin/bootstrap.py +51 -0
- sqlalchemy/testing/plugin/plugin_base.py +828 -0
- sqlalchemy/testing/plugin/pytestplugin.py +892 -0
- sqlalchemy/testing/profiling.py +329 -0
- sqlalchemy/testing/provision.py +603 -0
- sqlalchemy/testing/requirements.py +1945 -0
- sqlalchemy/testing/schema.py +198 -0
- sqlalchemy/testing/suite/__init__.py +19 -0
- sqlalchemy/testing/suite/test_cte.py +237 -0
- sqlalchemy/testing/suite/test_ddl.py +389 -0
- sqlalchemy/testing/suite/test_deprecations.py +153 -0
- sqlalchemy/testing/suite/test_dialect.py +776 -0
- sqlalchemy/testing/suite/test_insert.py +630 -0
- sqlalchemy/testing/suite/test_reflection.py +3557 -0
- sqlalchemy/testing/suite/test_results.py +504 -0
- sqlalchemy/testing/suite/test_rowcount.py +258 -0
- sqlalchemy/testing/suite/test_select.py +2010 -0
- sqlalchemy/testing/suite/test_sequence.py +317 -0
- sqlalchemy/testing/suite/test_types.py +2147 -0
- sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
- sqlalchemy/testing/suite/test_update_delete.py +139 -0
- sqlalchemy/testing/util.py +535 -0
- sqlalchemy/testing/warnings.py +52 -0
- sqlalchemy/types.py +74 -0
- sqlalchemy/util/__init__.py +162 -0
- sqlalchemy/util/_collections.py +712 -0
- sqlalchemy/util/_concurrency_py3k.py +288 -0
- sqlalchemy/util/_has_cy.py +40 -0
- sqlalchemy/util/_py_collections.py +541 -0
- sqlalchemy/util/compat.py +421 -0
- sqlalchemy/util/concurrency.py +110 -0
- sqlalchemy/util/deprecations.py +401 -0
- sqlalchemy/util/langhelpers.py +2203 -0
- sqlalchemy/util/preloaded.py +150 -0
- sqlalchemy/util/queue.py +322 -0
- sqlalchemy/util/tool_support.py +201 -0
- sqlalchemy/util/topological.py +120 -0
- sqlalchemy/util/typing.py +734 -0
- sqlalchemy-2.0.47.dist-info/METADATA +243 -0
- sqlalchemy-2.0.47.dist-info/RECORD +274 -0
- sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
- sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
- sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# dialects/mysql/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
|
+
import contextlib
|
|
9
|
+
|
|
10
|
+
from ... import event
|
|
11
|
+
from ... import exc
|
|
12
|
+
from ...testing.provision import allow_stale_update_impl
|
|
13
|
+
from ...testing.provision import configure_follower
|
|
14
|
+
from ...testing.provision import create_db
|
|
15
|
+
from ...testing.provision import delete_from_all_tables
|
|
16
|
+
from ...testing.provision import drop_db
|
|
17
|
+
from ...testing.provision import generate_driver_url
|
|
18
|
+
from ...testing.provision import temp_table_keyword_args
|
|
19
|
+
from ...testing.provision import upsert
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@generate_driver_url.for_db("mysql", "mariadb")
|
|
23
|
+
def generate_driver_url(url, driver, query_str):
|
|
24
|
+
backend = url.get_backend_name()
|
|
25
|
+
|
|
26
|
+
# NOTE: at the moment, tests are running mariadbconnector
|
|
27
|
+
# against both mariadb and mysql backends. if we want this to be
|
|
28
|
+
# limited, do the decision making here to reject a "mysql+mariadbconnector"
|
|
29
|
+
# URL. Optionally also re-enable the module level
|
|
30
|
+
# MySQLDialect_mariadbconnector.is_mysql flag as well, which must include
|
|
31
|
+
# a unit and/or functional test.
|
|
32
|
+
|
|
33
|
+
# all the Jenkins tests have been running mysqlclient Python library
|
|
34
|
+
# built against mariadb client drivers for years against all MySQL /
|
|
35
|
+
# MariaDB versions going back to MySQL 5.6, currently they can talk
|
|
36
|
+
# to MySQL databases without problems.
|
|
37
|
+
|
|
38
|
+
if backend == "mysql":
|
|
39
|
+
dialect_cls = url.get_dialect()
|
|
40
|
+
if dialect_cls._is_mariadb_from_url(url):
|
|
41
|
+
backend = "mariadb"
|
|
42
|
+
|
|
43
|
+
new_url = url.set(
|
|
44
|
+
drivername="%s+%s" % (backend, driver)
|
|
45
|
+
).update_query_string(query_str)
|
|
46
|
+
|
|
47
|
+
if driver == "mariadbconnector":
|
|
48
|
+
new_url = new_url.difference_update_query(["charset"])
|
|
49
|
+
elif driver == "mysqlconnector":
|
|
50
|
+
new_url = new_url.update_query_pairs(
|
|
51
|
+
[("collation", "utf8mb4_general_ci")]
|
|
52
|
+
)
|
|
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("mysql", "mariadb")
|
|
63
|
+
def _mysql_create_db(cfg, eng, ident):
|
|
64
|
+
with eng.begin() as conn:
|
|
65
|
+
try:
|
|
66
|
+
_mysql_drop_db(cfg, conn, ident)
|
|
67
|
+
except Exception:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
with eng.begin() as conn:
|
|
71
|
+
conn.exec_driver_sql(
|
|
72
|
+
"CREATE DATABASE %s CHARACTER SET utf8mb4" % ident
|
|
73
|
+
)
|
|
74
|
+
conn.exec_driver_sql(
|
|
75
|
+
"CREATE DATABASE %s_test_schema CHARACTER SET utf8mb4" % ident
|
|
76
|
+
)
|
|
77
|
+
conn.exec_driver_sql(
|
|
78
|
+
"CREATE DATABASE %s_test_schema_2 CHARACTER SET utf8mb4" % ident
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@configure_follower.for_db("mysql", "mariadb")
|
|
83
|
+
def _mysql_configure_follower(config, ident):
|
|
84
|
+
config.test_schema = "%s_test_schema" % ident
|
|
85
|
+
config.test_schema_2 = "%s_test_schema_2" % ident
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@drop_db.for_db("mysql", "mariadb")
|
|
89
|
+
def _mysql_drop_db(cfg, eng, ident):
|
|
90
|
+
with eng.begin() as conn:
|
|
91
|
+
conn.exec_driver_sql("DROP DATABASE %s_test_schema" % ident)
|
|
92
|
+
conn.exec_driver_sql("DROP DATABASE %s_test_schema_2" % ident)
|
|
93
|
+
conn.exec_driver_sql("DROP DATABASE %s" % ident)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@temp_table_keyword_args.for_db("mysql", "mariadb")
|
|
97
|
+
def _mysql_temp_table_keyword_args(cfg, eng):
|
|
98
|
+
return {"prefixes": ["TEMPORARY"]}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@upsert.for_db("mariadb")
|
|
102
|
+
def _upsert(
|
|
103
|
+
cfg,
|
|
104
|
+
table,
|
|
105
|
+
returning,
|
|
106
|
+
*,
|
|
107
|
+
set_lambda=None,
|
|
108
|
+
sort_by_parameter_order=False,
|
|
109
|
+
index_elements=None,
|
|
110
|
+
):
|
|
111
|
+
from sqlalchemy.dialects.mysql import insert
|
|
112
|
+
|
|
113
|
+
stmt = insert(table)
|
|
114
|
+
|
|
115
|
+
if set_lambda:
|
|
116
|
+
stmt = stmt.on_duplicate_key_update(**set_lambda(stmt.inserted))
|
|
117
|
+
else:
|
|
118
|
+
pk1 = table.primary_key.c[0]
|
|
119
|
+
stmt = stmt.on_duplicate_key_update({pk1.key: pk1})
|
|
120
|
+
|
|
121
|
+
stmt = stmt.returning(
|
|
122
|
+
*returning, sort_by_parameter_order=sort_by_parameter_order
|
|
123
|
+
)
|
|
124
|
+
return stmt
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@delete_from_all_tables.for_db("mysql", "mariadb")
|
|
128
|
+
def _delete_from_all_tables(connection, cfg, metadata):
|
|
129
|
+
connection.exec_driver_sql("SET foreign_key_checks = 0")
|
|
130
|
+
try:
|
|
131
|
+
delete_from_all_tables.call_original(connection, cfg, metadata)
|
|
132
|
+
finally:
|
|
133
|
+
connection.exec_driver_sql("SET foreign_key_checks = 1")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@allow_stale_update_impl.for_db("mariadb")
|
|
137
|
+
def _allow_stale_update_impl(cfg):
|
|
138
|
+
@contextlib.contextmanager
|
|
139
|
+
def go():
|
|
140
|
+
@event.listens_for(cfg.db, "engine_connect")
|
|
141
|
+
def turn_off_snapshot_isolation(conn):
|
|
142
|
+
conn.exec_driver_sql("SET innodb_snapshot_isolation = 'OFF'")
|
|
143
|
+
conn.rollback()
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
yield
|
|
147
|
+
finally:
|
|
148
|
+
event.remove(cfg.db, "engine_connect", turn_off_snapshot_isolation)
|
|
149
|
+
|
|
150
|
+
# dispose the pool; quick way to just have those reset
|
|
151
|
+
cfg.db.dispose()
|
|
152
|
+
|
|
153
|
+
return go()
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# dialects/mysql/pymysql.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
|
+
|
|
8
|
+
r"""
|
|
9
|
+
|
|
10
|
+
.. dialect:: mysql+pymysql
|
|
11
|
+
:name: PyMySQL
|
|
12
|
+
:dbapi: pymysql
|
|
13
|
+
:connectstring: mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
|
|
14
|
+
:url: https://pymysql.readthedocs.io/
|
|
15
|
+
|
|
16
|
+
Unicode
|
|
17
|
+
-------
|
|
18
|
+
|
|
19
|
+
Please see :ref:`mysql_unicode` for current recommendations on unicode
|
|
20
|
+
handling.
|
|
21
|
+
|
|
22
|
+
.. _pymysql_ssl:
|
|
23
|
+
|
|
24
|
+
SSL Connections
|
|
25
|
+
------------------
|
|
26
|
+
|
|
27
|
+
The PyMySQL DBAPI accepts the same SSL arguments as that of MySQLdb,
|
|
28
|
+
described at :ref:`mysqldb_ssl`. See that section for additional examples.
|
|
29
|
+
|
|
30
|
+
If the server uses an automatically-generated certificate that is self-signed
|
|
31
|
+
or does not match the host name (as seen from the client), it may also be
|
|
32
|
+
necessary to indicate ``ssl_check_hostname=false`` in PyMySQL::
|
|
33
|
+
|
|
34
|
+
connection_uri = (
|
|
35
|
+
"mysql+pymysql://scott:tiger@192.168.0.134/test"
|
|
36
|
+
"?ssl_ca=/home/gord/client-ssl/ca.pem"
|
|
37
|
+
"&ssl_cert=/home/gord/client-ssl/client-cert.pem"
|
|
38
|
+
"&ssl_key=/home/gord/client-ssl/client-key.pem"
|
|
39
|
+
"&ssl_check_hostname=false"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
MySQL-Python Compatibility
|
|
43
|
+
--------------------------
|
|
44
|
+
|
|
45
|
+
The pymysql DBAPI is a pure Python port of the MySQL-python (MySQLdb) driver,
|
|
46
|
+
and targets 100% compatibility. Most behavioral notes for MySQL-python apply
|
|
47
|
+
to the pymysql driver as well.
|
|
48
|
+
|
|
49
|
+
""" # noqa
|
|
50
|
+
from __future__ import annotations
|
|
51
|
+
|
|
52
|
+
from typing import Any
|
|
53
|
+
from typing import Dict
|
|
54
|
+
from typing import Optional
|
|
55
|
+
from typing import TYPE_CHECKING
|
|
56
|
+
from typing import Union
|
|
57
|
+
|
|
58
|
+
from .mysqldb import MySQLDialect_mysqldb
|
|
59
|
+
from ...util import langhelpers
|
|
60
|
+
from ...util.typing import Literal
|
|
61
|
+
|
|
62
|
+
if TYPE_CHECKING:
|
|
63
|
+
|
|
64
|
+
from ...engine.interfaces import ConnectArgsType
|
|
65
|
+
from ...engine.interfaces import DBAPIConnection
|
|
66
|
+
from ...engine.interfaces import DBAPICursor
|
|
67
|
+
from ...engine.interfaces import DBAPIModule
|
|
68
|
+
from ...engine.interfaces import PoolProxiedConnection
|
|
69
|
+
from ...engine.url import URL
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class MySQLDialect_pymysql(MySQLDialect_mysqldb):
|
|
73
|
+
driver = "pymysql"
|
|
74
|
+
supports_statement_cache = True
|
|
75
|
+
|
|
76
|
+
description_encoding = None
|
|
77
|
+
|
|
78
|
+
@langhelpers.memoized_property
|
|
79
|
+
def supports_server_side_cursors(self) -> bool:
|
|
80
|
+
try:
|
|
81
|
+
cursors = __import__("pymysql.cursors").cursors
|
|
82
|
+
self._sscursor = cursors.SSCursor
|
|
83
|
+
return True
|
|
84
|
+
except (ImportError, AttributeError):
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def import_dbapi(cls) -> DBAPIModule:
|
|
89
|
+
return __import__("pymysql")
|
|
90
|
+
|
|
91
|
+
@langhelpers.memoized_property
|
|
92
|
+
def _send_false_to_ping(self) -> bool:
|
|
93
|
+
"""determine if pymysql has deprecated, changed the default of,
|
|
94
|
+
or removed the 'reconnect' argument of connection.ping().
|
|
95
|
+
|
|
96
|
+
See #10492 and
|
|
97
|
+
https://github.com/PyMySQL/mysqlclient/discussions/651#discussioncomment-7308971
|
|
98
|
+
for background.
|
|
99
|
+
|
|
100
|
+
""" # noqa: E501
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
Connection = __import__(
|
|
104
|
+
"pymysql.connections"
|
|
105
|
+
).connections.Connection
|
|
106
|
+
except (ImportError, AttributeError):
|
|
107
|
+
return True
|
|
108
|
+
else:
|
|
109
|
+
insp = langhelpers.get_callable_argspec(Connection.ping)
|
|
110
|
+
try:
|
|
111
|
+
reconnect_arg = insp.args[1]
|
|
112
|
+
except IndexError:
|
|
113
|
+
return False
|
|
114
|
+
else:
|
|
115
|
+
return reconnect_arg == "reconnect" and (
|
|
116
|
+
not insp.defaults or insp.defaults[0] is not False
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def do_ping(self, dbapi_connection: DBAPIConnection) -> Literal[True]:
|
|
120
|
+
if self._send_false_to_ping:
|
|
121
|
+
dbapi_connection.ping(False)
|
|
122
|
+
else:
|
|
123
|
+
dbapi_connection.ping()
|
|
124
|
+
|
|
125
|
+
return True
|
|
126
|
+
|
|
127
|
+
def create_connect_args(
|
|
128
|
+
self, url: URL, _translate_args: Optional[Dict[str, Any]] = None
|
|
129
|
+
) -> ConnectArgsType:
|
|
130
|
+
if _translate_args is None:
|
|
131
|
+
_translate_args = dict(username="user")
|
|
132
|
+
return super().create_connect_args(
|
|
133
|
+
url, _translate_args=_translate_args
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def is_disconnect(
|
|
137
|
+
self,
|
|
138
|
+
e: DBAPIModule.Error,
|
|
139
|
+
connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
|
|
140
|
+
cursor: Optional[DBAPICursor],
|
|
141
|
+
) -> bool:
|
|
142
|
+
if super().is_disconnect(e, connection, cursor):
|
|
143
|
+
return True
|
|
144
|
+
elif isinstance(e, self.loaded_dbapi.Error):
|
|
145
|
+
str_e = str(e).lower()
|
|
146
|
+
return (
|
|
147
|
+
"already closed" in str_e or "connection was killed" in str_e
|
|
148
|
+
)
|
|
149
|
+
else:
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
def _extract_error_code(self, exception: BaseException) -> Any:
|
|
153
|
+
if isinstance(exception.args[0], Exception):
|
|
154
|
+
exception = exception.args[0]
|
|
155
|
+
return exception.args[0]
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
dialect = MySQLDialect_pymysql
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# dialects/mysql/pyodbc.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
|
+
|
|
8
|
+
|
|
9
|
+
r"""
|
|
10
|
+
|
|
11
|
+
.. dialect:: mysql+pyodbc
|
|
12
|
+
:name: PyODBC
|
|
13
|
+
:dbapi: pyodbc
|
|
14
|
+
:connectstring: mysql+pyodbc://<username>:<password>@<dsnname>
|
|
15
|
+
:url: https://pypi.org/project/pyodbc/
|
|
16
|
+
|
|
17
|
+
.. note::
|
|
18
|
+
|
|
19
|
+
The PyODBC for MySQL dialect is **not tested as part of
|
|
20
|
+
SQLAlchemy's continuous integration**.
|
|
21
|
+
The recommended MySQL dialects are mysqlclient and PyMySQL.
|
|
22
|
+
However, if you want to use the mysql+pyodbc dialect and require
|
|
23
|
+
full support for ``utf8mb4`` characters (including supplementary
|
|
24
|
+
characters like emoji) be sure to use a current release of
|
|
25
|
+
MySQL Connector/ODBC and specify the "ANSI" (**not** "Unicode")
|
|
26
|
+
version of the driver in your DSN or connection string.
|
|
27
|
+
|
|
28
|
+
Pass through exact pyodbc connection string::
|
|
29
|
+
|
|
30
|
+
import urllib
|
|
31
|
+
|
|
32
|
+
connection_string = (
|
|
33
|
+
"DRIVER=MySQL ODBC 8.0 ANSI Driver;"
|
|
34
|
+
"SERVER=localhost;"
|
|
35
|
+
"PORT=3307;"
|
|
36
|
+
"DATABASE=mydb;"
|
|
37
|
+
"UID=root;"
|
|
38
|
+
"PWD=(whatever);"
|
|
39
|
+
"charset=utf8mb4;"
|
|
40
|
+
)
|
|
41
|
+
params = urllib.parse.quote_plus(connection_string)
|
|
42
|
+
connection_uri = "mysql+pyodbc:///?odbc_connect=%s" % params
|
|
43
|
+
|
|
44
|
+
""" # noqa
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
|
|
47
|
+
import datetime
|
|
48
|
+
import re
|
|
49
|
+
from typing import Any
|
|
50
|
+
from typing import Callable
|
|
51
|
+
from typing import Optional
|
|
52
|
+
from typing import Tuple
|
|
53
|
+
from typing import TYPE_CHECKING
|
|
54
|
+
from typing import Union
|
|
55
|
+
|
|
56
|
+
from .base import MySQLDialect
|
|
57
|
+
from .base import MySQLExecutionContext
|
|
58
|
+
from .types import TIME
|
|
59
|
+
from ... import exc
|
|
60
|
+
from ... import util
|
|
61
|
+
from ...connectors.pyodbc import PyODBCConnector
|
|
62
|
+
from ...sql.sqltypes import Time
|
|
63
|
+
|
|
64
|
+
if TYPE_CHECKING:
|
|
65
|
+
from ...engine import Connection
|
|
66
|
+
from ...engine.interfaces import DBAPIConnection
|
|
67
|
+
from ...engine.interfaces import Dialect
|
|
68
|
+
from ...sql.type_api import _ResultProcessorType
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class _pyodbcTIME(TIME):
|
|
72
|
+
def result_processor(
|
|
73
|
+
self, dialect: Dialect, coltype: object
|
|
74
|
+
) -> _ResultProcessorType[datetime.time]:
|
|
75
|
+
def process(value: Any) -> Union[datetime.time, None]:
|
|
76
|
+
# pyodbc returns a datetime.time object; no need to convert
|
|
77
|
+
return value # type: ignore[no-any-return]
|
|
78
|
+
|
|
79
|
+
return process
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class MySQLExecutionContext_pyodbc(MySQLExecutionContext):
|
|
83
|
+
def get_lastrowid(self) -> int:
|
|
84
|
+
cursor = self.create_cursor()
|
|
85
|
+
cursor.execute("SELECT LAST_INSERT_ID()")
|
|
86
|
+
lastrowid = cursor.fetchone()[0] # type: ignore[index]
|
|
87
|
+
cursor.close()
|
|
88
|
+
return lastrowid # type: ignore[no-any-return]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class MySQLDialect_pyodbc(PyODBCConnector, MySQLDialect):
|
|
92
|
+
supports_statement_cache = True
|
|
93
|
+
colspecs = util.update_copy(MySQLDialect.colspecs, {Time: _pyodbcTIME})
|
|
94
|
+
supports_unicode_statements = True
|
|
95
|
+
execution_ctx_cls = MySQLExecutionContext_pyodbc
|
|
96
|
+
|
|
97
|
+
pyodbc_driver_name = "MySQL"
|
|
98
|
+
|
|
99
|
+
def _detect_charset(self, connection: Connection) -> str:
|
|
100
|
+
"""Sniff out the character set in use for connection results."""
|
|
101
|
+
|
|
102
|
+
# Prefer 'character_set_results' for the current connection over the
|
|
103
|
+
# value in the driver. SET NAMES or individual variable SETs will
|
|
104
|
+
# change the charset without updating the driver's view of the world.
|
|
105
|
+
#
|
|
106
|
+
# If it's decided that issuing that sort of SQL leaves you SOL, then
|
|
107
|
+
# this can prefer the driver value.
|
|
108
|
+
|
|
109
|
+
# set this to None as _fetch_setting attempts to use it (None is OK)
|
|
110
|
+
self._connection_charset = None
|
|
111
|
+
try:
|
|
112
|
+
value = self._fetch_setting(connection, "character_set_client")
|
|
113
|
+
if value:
|
|
114
|
+
return value
|
|
115
|
+
except exc.DBAPIError:
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
util.warn(
|
|
119
|
+
"Could not detect the connection character set. "
|
|
120
|
+
"Assuming latin1."
|
|
121
|
+
)
|
|
122
|
+
return "latin1"
|
|
123
|
+
|
|
124
|
+
def _get_server_version_info(
|
|
125
|
+
self, connection: Connection
|
|
126
|
+
) -> Tuple[int, ...]:
|
|
127
|
+
return MySQLDialect._get_server_version_info(self, connection)
|
|
128
|
+
|
|
129
|
+
def _extract_error_code(self, exception: BaseException) -> Optional[int]:
|
|
130
|
+
m = re.compile(r"\((\d+)\)").search(str(exception.args))
|
|
131
|
+
if m is None:
|
|
132
|
+
return None
|
|
133
|
+
c: Optional[str] = m.group(1)
|
|
134
|
+
if c:
|
|
135
|
+
return int(c)
|
|
136
|
+
else:
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
def on_connect(self) -> Callable[[DBAPIConnection], None]:
|
|
140
|
+
super_ = super().on_connect()
|
|
141
|
+
|
|
142
|
+
def on_connect(conn: DBAPIConnection) -> None:
|
|
143
|
+
if super_ is not None:
|
|
144
|
+
super_(conn)
|
|
145
|
+
|
|
146
|
+
# declare Unicode encoding for pyodbc as per
|
|
147
|
+
# https://github.com/mkleehammer/pyodbc/wiki/Unicode
|
|
148
|
+
pyodbc_SQL_CHAR = 1 # pyodbc.SQL_CHAR
|
|
149
|
+
pyodbc_SQL_WCHAR = -8 # pyodbc.SQL_WCHAR
|
|
150
|
+
conn.setdecoding(pyodbc_SQL_CHAR, encoding="utf-8")
|
|
151
|
+
conn.setdecoding(pyodbc_SQL_WCHAR, encoding="utf-8")
|
|
152
|
+
conn.setencoding(encoding="utf-8")
|
|
153
|
+
|
|
154
|
+
return on_connect
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
dialect = MySQLDialect_pyodbc
|