SQLAlchemy 2.0.47__cp313-cp313t-win_amd64.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-win_amd64.pyd +0 -0
- sqlalchemy/cyextension/collections.pyx +409 -0
- sqlalchemy/cyextension/immutabledict.cp313t-win_amd64.pyd +0 -0
- sqlalchemy/cyextension/immutabledict.pxd +8 -0
- sqlalchemy/cyextension/immutabledict.pyx +133 -0
- sqlalchemy/cyextension/processors.cp313t-win_amd64.pyd +0 -0
- sqlalchemy/cyextension/processors.pyx +68 -0
- sqlalchemy/cyextension/resultproxy.cp313t-win_amd64.pyd +0 -0
- sqlalchemy/cyextension/resultproxy.pyx +102 -0
- sqlalchemy/cyextension/util.cp313t-win_amd64.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,332 @@
|
|
|
1
|
+
# testing/fixtures/mypy.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 __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import inspect
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import re
|
|
15
|
+
import shutil
|
|
16
|
+
import sys
|
|
17
|
+
import tempfile
|
|
18
|
+
|
|
19
|
+
from .base import TestBase
|
|
20
|
+
from .. import config
|
|
21
|
+
from ..assertions import eq_
|
|
22
|
+
from ... import util
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@config.add_to_marker.mypy
|
|
26
|
+
class MypyTest(TestBase):
|
|
27
|
+
__requires__ = ("no_sqlalchemy2_stubs",)
|
|
28
|
+
|
|
29
|
+
@config.fixture(scope="function")
|
|
30
|
+
def per_func_cachedir(self):
|
|
31
|
+
yield from self._cachedir()
|
|
32
|
+
|
|
33
|
+
@config.fixture(scope="class")
|
|
34
|
+
def cachedir(self):
|
|
35
|
+
yield from self._cachedir()
|
|
36
|
+
|
|
37
|
+
def _cachedir(self):
|
|
38
|
+
# as of mypy 0.971 i think we need to keep mypy_path empty
|
|
39
|
+
mypy_path = ""
|
|
40
|
+
|
|
41
|
+
with tempfile.TemporaryDirectory() as cachedir:
|
|
42
|
+
with open(
|
|
43
|
+
Path(cachedir) / "sqla_mypy_config.cfg", "w"
|
|
44
|
+
) as config_file:
|
|
45
|
+
config_file.write(
|
|
46
|
+
f"""
|
|
47
|
+
[mypy]\n
|
|
48
|
+
plugins = sqlalchemy.ext.mypy.plugin\n
|
|
49
|
+
show_error_codes = True\n
|
|
50
|
+
{mypy_path}
|
|
51
|
+
disable_error_code = no-untyped-call
|
|
52
|
+
|
|
53
|
+
[mypy-sqlalchemy.*]
|
|
54
|
+
ignore_errors = True
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
)
|
|
58
|
+
with open(
|
|
59
|
+
Path(cachedir) / "plain_mypy_config.cfg", "w"
|
|
60
|
+
) as config_file:
|
|
61
|
+
config_file.write(
|
|
62
|
+
f"""
|
|
63
|
+
[mypy]\n
|
|
64
|
+
show_error_codes = True\n
|
|
65
|
+
{mypy_path}
|
|
66
|
+
disable_error_code = var-annotated,no-untyped-call
|
|
67
|
+
[mypy-sqlalchemy.*]
|
|
68
|
+
ignore_errors = True
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
)
|
|
72
|
+
yield cachedir
|
|
73
|
+
|
|
74
|
+
@config.fixture()
|
|
75
|
+
def mypy_runner(self, cachedir):
|
|
76
|
+
from mypy import api
|
|
77
|
+
|
|
78
|
+
def run(path, use_plugin=False, use_cachedir=None):
|
|
79
|
+
if use_cachedir is None:
|
|
80
|
+
use_cachedir = cachedir
|
|
81
|
+
args = [
|
|
82
|
+
"--strict",
|
|
83
|
+
"--raise-exceptions",
|
|
84
|
+
"--cache-dir",
|
|
85
|
+
use_cachedir,
|
|
86
|
+
"--config-file",
|
|
87
|
+
os.path.join(
|
|
88
|
+
use_cachedir,
|
|
89
|
+
(
|
|
90
|
+
"sqla_mypy_config.cfg"
|
|
91
|
+
if use_plugin
|
|
92
|
+
else "plain_mypy_config.cfg"
|
|
93
|
+
),
|
|
94
|
+
),
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
# mypy as of 0.990 is more aggressively blocking messaging
|
|
98
|
+
# for paths that are in sys.path, and as pytest puts currdir,
|
|
99
|
+
# test/ etc in sys.path, just copy the source file to the
|
|
100
|
+
# tempdir we are working in so that we don't have to try to
|
|
101
|
+
# manipulate sys.path and/or guess what mypy is doing
|
|
102
|
+
filename = os.path.basename(path)
|
|
103
|
+
test_program = os.path.join(use_cachedir, filename)
|
|
104
|
+
if path != test_program:
|
|
105
|
+
shutil.copyfile(path, test_program)
|
|
106
|
+
args.append(test_program)
|
|
107
|
+
|
|
108
|
+
# I set this locally but for the suite here needs to be
|
|
109
|
+
# disabled
|
|
110
|
+
os.environ.pop("MYPY_FORCE_COLOR", None)
|
|
111
|
+
|
|
112
|
+
stdout, stderr, exitcode = api.run(args)
|
|
113
|
+
return stdout, stderr, exitcode
|
|
114
|
+
|
|
115
|
+
return run
|
|
116
|
+
|
|
117
|
+
@config.fixture
|
|
118
|
+
def mypy_typecheck_file(self, mypy_runner):
|
|
119
|
+
def run(path, use_plugin=False):
|
|
120
|
+
expected_messages = self._collect_messages(path)
|
|
121
|
+
stdout, stderr, exitcode = mypy_runner(path, use_plugin=use_plugin)
|
|
122
|
+
self._check_output(
|
|
123
|
+
path, expected_messages, stdout, stderr, exitcode
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return run
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def file_combinations(dirname):
|
|
130
|
+
if os.path.isabs(dirname):
|
|
131
|
+
path = dirname
|
|
132
|
+
else:
|
|
133
|
+
caller_path = inspect.stack()[1].filename
|
|
134
|
+
path = os.path.join(os.path.dirname(caller_path), dirname)
|
|
135
|
+
files = list(Path(path).glob("**/*.py"))
|
|
136
|
+
|
|
137
|
+
for extra_dir in config.options.mypy_extra_test_paths:
|
|
138
|
+
if extra_dir and os.path.isdir(extra_dir):
|
|
139
|
+
files.extend((Path(extra_dir) / dirname).glob("**/*.py"))
|
|
140
|
+
return files
|
|
141
|
+
|
|
142
|
+
def _collect_messages(self, path):
|
|
143
|
+
from sqlalchemy.ext.mypy.util import mypy_14
|
|
144
|
+
|
|
145
|
+
expected_messages = []
|
|
146
|
+
expected_re = re.compile(
|
|
147
|
+
r"\s*# EXPECTED(_MYPY)?(_RE)?(_ROW)?(_TYPE)?: (.+)"
|
|
148
|
+
)
|
|
149
|
+
py_ver_re = re.compile(r"^#\s*PYTHON_VERSION\s?>=\s?(\d+\.\d+)")
|
|
150
|
+
with open(path) as file_:
|
|
151
|
+
current_assert_messages = []
|
|
152
|
+
for num, line in enumerate(file_, 1):
|
|
153
|
+
m = py_ver_re.match(line)
|
|
154
|
+
if m:
|
|
155
|
+
major, _, minor = m.group(1).partition(".")
|
|
156
|
+
if sys.version_info < (int(major), int(minor)):
|
|
157
|
+
config.skip_test(
|
|
158
|
+
"Requires python >= %s" % (m.group(1))
|
|
159
|
+
)
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
m = expected_re.match(line)
|
|
163
|
+
if m:
|
|
164
|
+
is_mypy = bool(m.group(1))
|
|
165
|
+
is_re = bool(m.group(2))
|
|
166
|
+
is_row = bool(m.group(3))
|
|
167
|
+
is_type = bool(m.group(4))
|
|
168
|
+
|
|
169
|
+
expected_msg = re.sub(r"# noqa[:]? ?.*", "", m.group(5))
|
|
170
|
+
if is_row:
|
|
171
|
+
expected_msg = re.sub(
|
|
172
|
+
r"Row\[([^\]]+)\]",
|
|
173
|
+
lambda m: f"tuple[{m.group(1)}, fallback=s"
|
|
174
|
+
f"qlalchemy.engine.row.{m.group(0)}]",
|
|
175
|
+
expected_msg,
|
|
176
|
+
)
|
|
177
|
+
# For some reason it does not use or syntax (|)
|
|
178
|
+
expected_msg = re.sub(
|
|
179
|
+
r"Optional\[(.*)\]",
|
|
180
|
+
lambda m: f"Union[{m.group(1)}, None]",
|
|
181
|
+
expected_msg,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if is_type:
|
|
185
|
+
if not is_re:
|
|
186
|
+
# the goal here is that we can cut-and-paste
|
|
187
|
+
# from vscode -> pylance into the
|
|
188
|
+
# EXPECTED_TYPE: line, then the test suite will
|
|
189
|
+
# validate that line against what mypy produces
|
|
190
|
+
expected_msg = re.sub(
|
|
191
|
+
r"([\[\]])",
|
|
192
|
+
lambda m: rf"\{m.group(0)}",
|
|
193
|
+
expected_msg,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# note making sure preceding text matches
|
|
197
|
+
# with a dot, so that an expect for "Select"
|
|
198
|
+
# does not match "TypedSelect"
|
|
199
|
+
expected_msg = re.sub(
|
|
200
|
+
r"([\w_]+)",
|
|
201
|
+
lambda m: rf"(?:.*\.)?{m.group(1)}\*?",
|
|
202
|
+
expected_msg,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
expected_msg = re.sub(
|
|
206
|
+
"List", "builtins.list", expected_msg
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
expected_msg = re.sub(
|
|
210
|
+
r"\b(int|str|float|bool)\b",
|
|
211
|
+
lambda m: rf"builtins.{m.group(0)}\*?",
|
|
212
|
+
expected_msg,
|
|
213
|
+
)
|
|
214
|
+
# expected_msg = re.sub(
|
|
215
|
+
# r"(Sequence|Tuple|List|Union)",
|
|
216
|
+
# lambda m: fr"typing.{m.group(0)}\*?",
|
|
217
|
+
# expected_msg,
|
|
218
|
+
# )
|
|
219
|
+
|
|
220
|
+
is_mypy = is_re = True
|
|
221
|
+
expected_msg = f'Revealed type is "{expected_msg}"'
|
|
222
|
+
|
|
223
|
+
if mypy_14 and util.py39:
|
|
224
|
+
# use_lowercase_names, py39 and above
|
|
225
|
+
# https://github.com/python/mypy/blob/304997bfb85200fb521ac727ee0ce3e6085e5278/mypy/options.py#L363 # noqa: E501
|
|
226
|
+
|
|
227
|
+
# skip first character which could be capitalized
|
|
228
|
+
# "List item x not found" type of message
|
|
229
|
+
expected_msg = expected_msg[0] + re.sub(
|
|
230
|
+
(
|
|
231
|
+
r"\b(List|Tuple|Dict|Set)\b"
|
|
232
|
+
if is_type
|
|
233
|
+
else r"\b(List|Tuple|Dict|Set|Type)\b"
|
|
234
|
+
),
|
|
235
|
+
lambda m: m.group(1).lower(),
|
|
236
|
+
expected_msg[1:],
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
if mypy_14 and util.py310:
|
|
240
|
+
# use_or_syntax, py310 and above
|
|
241
|
+
# https://github.com/python/mypy/blob/304997bfb85200fb521ac727ee0ce3e6085e5278/mypy/options.py#L368 # noqa: E501
|
|
242
|
+
expected_msg = re.sub(
|
|
243
|
+
r"Optional\[(.*?)\]",
|
|
244
|
+
lambda m: f"{m.group(1)} | None",
|
|
245
|
+
expected_msg,
|
|
246
|
+
)
|
|
247
|
+
current_assert_messages.append(
|
|
248
|
+
(is_mypy, is_re, expected_msg.strip())
|
|
249
|
+
)
|
|
250
|
+
elif current_assert_messages:
|
|
251
|
+
expected_messages.extend(
|
|
252
|
+
(num, is_mypy, is_re, expected_msg)
|
|
253
|
+
for (
|
|
254
|
+
is_mypy,
|
|
255
|
+
is_re,
|
|
256
|
+
expected_msg,
|
|
257
|
+
) in current_assert_messages
|
|
258
|
+
)
|
|
259
|
+
current_assert_messages[:] = []
|
|
260
|
+
|
|
261
|
+
return expected_messages
|
|
262
|
+
|
|
263
|
+
def _check_output(
|
|
264
|
+
self, path, expected_messages, stdout: str, stderr, exitcode
|
|
265
|
+
):
|
|
266
|
+
not_located = []
|
|
267
|
+
filename = os.path.basename(path)
|
|
268
|
+
if expected_messages:
|
|
269
|
+
# mypy 0.990 changed how return codes work, so don't assume a
|
|
270
|
+
# 1 or a 0 return code here, could be either depending on if
|
|
271
|
+
# errors were generated or not
|
|
272
|
+
|
|
273
|
+
output = []
|
|
274
|
+
|
|
275
|
+
raw_lines = stdout.split("\n")
|
|
276
|
+
while raw_lines:
|
|
277
|
+
e = raw_lines.pop(0)
|
|
278
|
+
if re.match(r".+\.py:\d+: error: .*", e):
|
|
279
|
+
output.append(("error", e))
|
|
280
|
+
elif re.match(
|
|
281
|
+
r".+\.py:\d+: note: +(?:Possible overload|def ).*", e
|
|
282
|
+
):
|
|
283
|
+
while raw_lines:
|
|
284
|
+
ol = raw_lines.pop(0)
|
|
285
|
+
if not re.match(r".+\.py:\d+: note: +def .*", ol):
|
|
286
|
+
raw_lines.insert(0, ol)
|
|
287
|
+
break
|
|
288
|
+
elif re.match(
|
|
289
|
+
r".+\.py:\d+: note: .*(?:perhaps|suggestion)", e, re.I
|
|
290
|
+
):
|
|
291
|
+
pass
|
|
292
|
+
elif re.match(r".+\.py:\d+: note: .*", e):
|
|
293
|
+
output.append(("note", e))
|
|
294
|
+
|
|
295
|
+
for num, is_mypy, is_re, msg in expected_messages:
|
|
296
|
+
msg = msg.replace("'", '"')
|
|
297
|
+
prefix = "[SQLAlchemy Mypy plugin] " if not is_mypy else ""
|
|
298
|
+
for idx, (typ, errmsg) in enumerate(output):
|
|
299
|
+
if is_re:
|
|
300
|
+
if re.match(
|
|
301
|
+
rf".*{filename}\:{num}\: {typ}\: {prefix}{msg}",
|
|
302
|
+
errmsg,
|
|
303
|
+
):
|
|
304
|
+
break
|
|
305
|
+
elif (
|
|
306
|
+
f"{filename}:{num}: {typ}: {prefix}{msg}"
|
|
307
|
+
in errmsg.replace("'", '"')
|
|
308
|
+
):
|
|
309
|
+
break
|
|
310
|
+
else:
|
|
311
|
+
not_located.append(msg)
|
|
312
|
+
continue
|
|
313
|
+
del output[idx]
|
|
314
|
+
|
|
315
|
+
if not_located:
|
|
316
|
+
missing = "\n".join(not_located)
|
|
317
|
+
print("Couldn't locate expected messages:", missing, sep="\n")
|
|
318
|
+
if output:
|
|
319
|
+
extra = "\n".join(msg for _, msg in output)
|
|
320
|
+
print("Remaining messages:", extra, sep="\n")
|
|
321
|
+
assert False, "expected messages not found, see stdout"
|
|
322
|
+
|
|
323
|
+
if output:
|
|
324
|
+
print(f"{len(output)} messages from mypy were not consumed:")
|
|
325
|
+
print("\n".join(msg for _, msg in output))
|
|
326
|
+
assert False, "errors and/or notes remain, see stdout"
|
|
327
|
+
|
|
328
|
+
else:
|
|
329
|
+
if exitcode != 0:
|
|
330
|
+
print(stdout, stderr, sep="\n")
|
|
331
|
+
|
|
332
|
+
eq_(exitcode, 0, msg=stdout)
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# testing/fixtures/orm.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
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import sqlalchemy as sa
|
|
13
|
+
from .base import TestBase
|
|
14
|
+
from .sql import TablesTest
|
|
15
|
+
from .. import assertions
|
|
16
|
+
from .. import config
|
|
17
|
+
from .. import schema
|
|
18
|
+
from ..entities import BasicEntity
|
|
19
|
+
from ..entities import ComparableEntity
|
|
20
|
+
from ..util import adict
|
|
21
|
+
from ... import orm
|
|
22
|
+
from ...orm import DeclarativeBase
|
|
23
|
+
from ...orm import events as orm_events
|
|
24
|
+
from ...orm import registry
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ORMTest(TestBase):
|
|
28
|
+
@config.fixture
|
|
29
|
+
def fixture_session(self):
|
|
30
|
+
return fixture_session()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class MappedTest(ORMTest, TablesTest, assertions.AssertsExecutionResults):
|
|
34
|
+
# 'once', 'each', None
|
|
35
|
+
run_setup_classes = "once"
|
|
36
|
+
|
|
37
|
+
# 'once', 'each', None
|
|
38
|
+
run_setup_mappers = "each"
|
|
39
|
+
|
|
40
|
+
classes: Any = None
|
|
41
|
+
|
|
42
|
+
@config.fixture(autouse=True, scope="class")
|
|
43
|
+
def _setup_tables_test_class(self):
|
|
44
|
+
cls = self.__class__
|
|
45
|
+
cls._init_class()
|
|
46
|
+
|
|
47
|
+
if cls.classes is None:
|
|
48
|
+
cls.classes = adict()
|
|
49
|
+
|
|
50
|
+
cls._setup_once_tables()
|
|
51
|
+
cls._setup_once_classes()
|
|
52
|
+
cls._setup_once_mappers()
|
|
53
|
+
cls._setup_once_inserts()
|
|
54
|
+
|
|
55
|
+
yield
|
|
56
|
+
|
|
57
|
+
cls._teardown_once_class()
|
|
58
|
+
cls._teardown_once_metadata_bind()
|
|
59
|
+
|
|
60
|
+
@config.fixture(autouse=True, scope="function")
|
|
61
|
+
def _setup_tables_test_instance(self):
|
|
62
|
+
self._setup_each_tables()
|
|
63
|
+
self._setup_each_classes()
|
|
64
|
+
self._setup_each_mappers()
|
|
65
|
+
self._setup_each_inserts()
|
|
66
|
+
|
|
67
|
+
yield
|
|
68
|
+
|
|
69
|
+
orm.session.close_all_sessions()
|
|
70
|
+
self._teardown_each_mappers()
|
|
71
|
+
self._teardown_each_classes()
|
|
72
|
+
self._teardown_each_tables()
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def _teardown_once_class(cls):
|
|
76
|
+
cls.classes.clear()
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def _setup_once_classes(cls):
|
|
80
|
+
if cls.run_setup_classes == "once":
|
|
81
|
+
cls._with_register_classes(cls.setup_classes)
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def _setup_once_mappers(cls):
|
|
85
|
+
if cls.run_setup_mappers == "once":
|
|
86
|
+
cls.mapper_registry, cls.mapper = cls._generate_registry()
|
|
87
|
+
cls._with_register_classes(cls.setup_mappers)
|
|
88
|
+
|
|
89
|
+
def _setup_each_mappers(self):
|
|
90
|
+
if self.run_setup_mappers != "once":
|
|
91
|
+
(
|
|
92
|
+
self.__class__.mapper_registry,
|
|
93
|
+
self.__class__.mapper,
|
|
94
|
+
) = self._generate_registry()
|
|
95
|
+
|
|
96
|
+
if self.run_setup_mappers == "each":
|
|
97
|
+
self._with_register_classes(self.setup_mappers)
|
|
98
|
+
|
|
99
|
+
def _setup_each_classes(self):
|
|
100
|
+
if self.run_setup_classes == "each":
|
|
101
|
+
self._with_register_classes(self.setup_classes)
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def _generate_registry(cls):
|
|
105
|
+
decl = registry(metadata=cls._tables_metadata)
|
|
106
|
+
return decl, decl.map_imperatively
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def _with_register_classes(cls, fn):
|
|
110
|
+
"""Run a setup method, framing the operation with a Base class
|
|
111
|
+
that will catch new subclasses to be established within
|
|
112
|
+
the "classes" registry.
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
cls_registry = cls.classes
|
|
116
|
+
|
|
117
|
+
class _Base:
|
|
118
|
+
def __init_subclass__(cls) -> None:
|
|
119
|
+
assert cls_registry is not None
|
|
120
|
+
cls_registry[cls.__name__] = cls
|
|
121
|
+
super().__init_subclass__()
|
|
122
|
+
|
|
123
|
+
class Basic(BasicEntity, _Base):
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
class Comparable(ComparableEntity, _Base):
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
cls.Basic = Basic
|
|
130
|
+
cls.Comparable = Comparable
|
|
131
|
+
fn()
|
|
132
|
+
|
|
133
|
+
def _teardown_each_mappers(self):
|
|
134
|
+
# some tests create mappers in the test bodies
|
|
135
|
+
# and will define setup_mappers as None -
|
|
136
|
+
# clear mappers in any case
|
|
137
|
+
if self.run_setup_mappers != "once":
|
|
138
|
+
orm.clear_mappers()
|
|
139
|
+
|
|
140
|
+
def _teardown_each_classes(self):
|
|
141
|
+
if self.run_setup_classes != "once":
|
|
142
|
+
self.classes.clear()
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
def setup_classes(cls):
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
def setup_mappers(cls):
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class DeclarativeMappedTest(MappedTest):
|
|
154
|
+
run_setup_classes = "once"
|
|
155
|
+
run_setup_mappers = "once"
|
|
156
|
+
|
|
157
|
+
@classmethod
|
|
158
|
+
def _setup_once_tables(cls):
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def _with_register_classes(cls, fn):
|
|
163
|
+
cls_registry = cls.classes
|
|
164
|
+
|
|
165
|
+
class _DeclBase(DeclarativeBase):
|
|
166
|
+
__table_cls__ = schema.Table
|
|
167
|
+
metadata = cls._tables_metadata
|
|
168
|
+
type_annotation_map = {
|
|
169
|
+
str: sa.String().with_variant(
|
|
170
|
+
sa.String(50), "mysql", "mariadb", "oracle"
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
def __init_subclass__(cls, **kw) -> None:
|
|
175
|
+
assert cls_registry is not None
|
|
176
|
+
cls_registry[cls.__name__] = cls
|
|
177
|
+
super().__init_subclass__(**kw)
|
|
178
|
+
|
|
179
|
+
cls.DeclarativeBasic = _DeclBase
|
|
180
|
+
|
|
181
|
+
# sets up cls.Basic which is helpful for things like composite
|
|
182
|
+
# classes
|
|
183
|
+
super()._with_register_classes(fn)
|
|
184
|
+
|
|
185
|
+
if cls._tables_metadata.tables and cls.run_create_tables:
|
|
186
|
+
cls._tables_metadata.create_all(config.db)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class RemoveORMEventsGlobally:
|
|
190
|
+
@config.fixture(autouse=True)
|
|
191
|
+
def _remove_listeners(self):
|
|
192
|
+
yield
|
|
193
|
+
orm_events.MapperEvents._clear()
|
|
194
|
+
orm_events.InstanceEvents._clear()
|
|
195
|
+
orm_events.SessionEvents._clear()
|
|
196
|
+
orm_events.InstrumentationEvents._clear()
|
|
197
|
+
orm_events.QueryEvents._clear()
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
_fixture_sessions = set()
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def fixture_session(**kw):
|
|
204
|
+
kw.setdefault("autoflush", True)
|
|
205
|
+
kw.setdefault("expire_on_commit", True)
|
|
206
|
+
|
|
207
|
+
bind = kw.pop("bind", config.db)
|
|
208
|
+
|
|
209
|
+
sess = orm.Session(bind, **kw)
|
|
210
|
+
_fixture_sessions.add(sess)
|
|
211
|
+
return sess
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def close_all_sessions():
|
|
215
|
+
# will close all still-referenced sessions
|
|
216
|
+
orm.close_all_sessions()
|
|
217
|
+
_fixture_sessions.clear()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def stop_test_class_inside_fixtures(cls):
|
|
221
|
+
close_all_sessions()
|
|
222
|
+
orm.clear_mappers()
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def after_test():
|
|
226
|
+
if _fixture_sessions:
|
|
227
|
+
close_all_sessions()
|