SQLAlchemy 2.0.45__cp314-cp314-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.cp314-win_amd64.pyd +0 -0
- sqlalchemy/cyextension/collections.pyx +409 -0
- sqlalchemy/cyextension/immutabledict.cp314-win_amd64.pyd +0 -0
- sqlalchemy/cyextension/immutabledict.pxd +8 -0
- sqlalchemy/cyextension/immutabledict.pyx +133 -0
- sqlalchemy/cyextension/processors.cp314-win_amd64.pyd +0 -0
- sqlalchemy/cyextension/processors.pyx +68 -0
- sqlalchemy/cyextension/resultproxy.cp314-win_amd64.pyd +0 -0
- sqlalchemy/cyextension/resultproxy.pyx +102 -0
- sqlalchemy/cyextension/util.cp314-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 +4089 -0
- sqlalchemy/dialects/mssql/information_schema.py +285 -0
- sqlalchemy/dialects/mssql/json.py +129 -0
- sqlalchemy/dialects/mssql/provision.py +168 -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 +3933 -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 +73 -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 +147 -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 +5328 -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 +395 -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 +175 -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 +446 -0
- sqlalchemy/dialects/sqlite/base.py +3045 -0
- sqlalchemy/dialects/sqlite/dml.py +263 -0
- sqlalchemy/dialects/sqlite/json.py +92 -0
- sqlalchemy/dialects/sqlite/provision.py +223 -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 +3382 -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 +2387 -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 +1516 -0
- sqlalchemy/pool/events.py +372 -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 +481 -0
- sqlalchemy/sql/annotation.py +587 -0
- sqlalchemy/sql/base.py +2219 -0
- sqlalchemy/sql/cache_key.py +1057 -0
- sqlalchemy/sql/coercions.py +1404 -0
- sqlalchemy/sql/compiler.py +8034 -0
- sqlalchemy/sql/crud.py +1744 -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 +2139 -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 +478 -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 +366 -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 +327 -0
- sqlalchemy/testing/provision.py +597 -0
- sqlalchemy/testing/requirements.py +1940 -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 +717 -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 +316 -0
- sqlalchemy/util/concurrency.py +110 -0
- sqlalchemy/util/deprecations.py +401 -0
- sqlalchemy/util/langhelpers.py +2306 -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.45.dist-info/METADATA +243 -0
- sqlalchemy-2.0.45.dist-info/RECORD +274 -0
- sqlalchemy-2.0.45.dist-info/WHEEL +5 -0
- sqlalchemy-2.0.45.dist-info/licenses/LICENSE +19 -0
- sqlalchemy-2.0.45.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
# testing/util.py
|
|
2
|
+
# Copyright (C) 2005-2025 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
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections import deque
|
|
13
|
+
from collections import namedtuple
|
|
14
|
+
import contextlib
|
|
15
|
+
import decimal
|
|
16
|
+
import gc
|
|
17
|
+
from itertools import chain
|
|
18
|
+
import pickle
|
|
19
|
+
import random
|
|
20
|
+
import sys
|
|
21
|
+
from sys import getsizeof
|
|
22
|
+
import time
|
|
23
|
+
import types
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
from . import config
|
|
27
|
+
from . import mock
|
|
28
|
+
from .. import inspect
|
|
29
|
+
from ..engine import Connection
|
|
30
|
+
from ..schema import Column
|
|
31
|
+
from ..schema import DropConstraint
|
|
32
|
+
from ..schema import DropTable
|
|
33
|
+
from ..schema import ForeignKeyConstraint
|
|
34
|
+
from ..schema import MetaData
|
|
35
|
+
from ..schema import Table
|
|
36
|
+
from ..sql import schema
|
|
37
|
+
from ..sql.sqltypes import Integer
|
|
38
|
+
from ..util import decorator
|
|
39
|
+
from ..util import defaultdict
|
|
40
|
+
from ..util import has_refcount_gc
|
|
41
|
+
from ..util import inspect_getfullargspec
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if not has_refcount_gc:
|
|
45
|
+
|
|
46
|
+
def non_refcount_gc_collect(*args):
|
|
47
|
+
gc.collect()
|
|
48
|
+
gc.collect()
|
|
49
|
+
|
|
50
|
+
gc_collect = lazy_gc = non_refcount_gc_collect
|
|
51
|
+
else:
|
|
52
|
+
# assume CPython - straight gc.collect, lazy_gc() is a pass
|
|
53
|
+
gc_collect = gc.collect
|
|
54
|
+
|
|
55
|
+
def lazy_gc():
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def picklers():
|
|
60
|
+
nt = namedtuple("picklers", ["loads", "dumps"])
|
|
61
|
+
|
|
62
|
+
for protocol in range(-2, pickle.HIGHEST_PROTOCOL + 1):
|
|
63
|
+
yield nt(pickle.loads, lambda d: pickle.dumps(d, protocol))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def random_choices(population, k=1):
|
|
67
|
+
return random.choices(population, k=k)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def round_decimal(value, prec):
|
|
71
|
+
if isinstance(value, float):
|
|
72
|
+
return round(value, prec)
|
|
73
|
+
|
|
74
|
+
# can also use shift() here but that is 2.6 only
|
|
75
|
+
return (value * decimal.Decimal("1" + "0" * prec)).to_integral(
|
|
76
|
+
decimal.ROUND_FLOOR
|
|
77
|
+
) / pow(10, prec)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class RandomSet(set):
|
|
81
|
+
def __iter__(self):
|
|
82
|
+
l = list(set.__iter__(self))
|
|
83
|
+
random.shuffle(l)
|
|
84
|
+
return iter(l)
|
|
85
|
+
|
|
86
|
+
def pop(self):
|
|
87
|
+
index = random.randint(0, len(self) - 1)
|
|
88
|
+
item = list(set.__iter__(self))[index]
|
|
89
|
+
self.remove(item)
|
|
90
|
+
return item
|
|
91
|
+
|
|
92
|
+
def union(self, other):
|
|
93
|
+
return RandomSet(set.union(self, other))
|
|
94
|
+
|
|
95
|
+
def difference(self, other):
|
|
96
|
+
return RandomSet(set.difference(self, other))
|
|
97
|
+
|
|
98
|
+
def intersection(self, other):
|
|
99
|
+
return RandomSet(set.intersection(self, other))
|
|
100
|
+
|
|
101
|
+
def copy(self):
|
|
102
|
+
return RandomSet(self)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def conforms_partial_ordering(tuples, sorted_elements):
|
|
106
|
+
"""True if the given sorting conforms to the given partial ordering."""
|
|
107
|
+
|
|
108
|
+
deps = defaultdict(set)
|
|
109
|
+
for parent, child in tuples:
|
|
110
|
+
deps[parent].add(child)
|
|
111
|
+
for i, node in enumerate(sorted_elements):
|
|
112
|
+
for n in sorted_elements[i:]:
|
|
113
|
+
if node in deps[n]:
|
|
114
|
+
return False
|
|
115
|
+
else:
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def all_partial_orderings(tuples, elements):
|
|
120
|
+
edges = defaultdict(set)
|
|
121
|
+
for parent, child in tuples:
|
|
122
|
+
edges[child].add(parent)
|
|
123
|
+
|
|
124
|
+
def _all_orderings(elements):
|
|
125
|
+
if len(elements) == 1:
|
|
126
|
+
yield list(elements)
|
|
127
|
+
else:
|
|
128
|
+
for elem in elements:
|
|
129
|
+
subset = set(elements).difference([elem])
|
|
130
|
+
if not subset.intersection(edges[elem]):
|
|
131
|
+
for sub_ordering in _all_orderings(subset):
|
|
132
|
+
yield [elem] + sub_ordering
|
|
133
|
+
|
|
134
|
+
return iter(_all_orderings(elements))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def function_named(fn, name):
|
|
138
|
+
"""Return a function with a given __name__.
|
|
139
|
+
|
|
140
|
+
Will assign to __name__ and return the original function if possible on
|
|
141
|
+
the Python implementation, otherwise a new function will be constructed.
|
|
142
|
+
|
|
143
|
+
This function should be phased out as much as possible
|
|
144
|
+
in favor of @decorator. Tests that "generate" many named tests
|
|
145
|
+
should be modernized.
|
|
146
|
+
|
|
147
|
+
"""
|
|
148
|
+
try:
|
|
149
|
+
fn.__name__ = name
|
|
150
|
+
except TypeError:
|
|
151
|
+
fn = types.FunctionType(
|
|
152
|
+
fn.__code__, fn.__globals__, name, fn.__defaults__, fn.__closure__
|
|
153
|
+
)
|
|
154
|
+
return fn
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def run_as_contextmanager(ctx, fn, *arg, **kw):
|
|
158
|
+
"""Run the given function under the given contextmanager,
|
|
159
|
+
simulating the behavior of 'with' to support older
|
|
160
|
+
Python versions.
|
|
161
|
+
|
|
162
|
+
This is not necessary anymore as we have placed 2.6
|
|
163
|
+
as minimum Python version, however some tests are still using
|
|
164
|
+
this structure.
|
|
165
|
+
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
obj = ctx.__enter__()
|
|
169
|
+
try:
|
|
170
|
+
result = fn(obj, *arg, **kw)
|
|
171
|
+
ctx.__exit__(None, None, None)
|
|
172
|
+
return result
|
|
173
|
+
except:
|
|
174
|
+
exc_info = sys.exc_info()
|
|
175
|
+
raise_ = ctx.__exit__(*exc_info)
|
|
176
|
+
if not raise_:
|
|
177
|
+
raise
|
|
178
|
+
else:
|
|
179
|
+
return raise_
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def rowset(results):
|
|
183
|
+
"""Converts the results of sql execution into a plain set of column tuples.
|
|
184
|
+
|
|
185
|
+
Useful for asserting the results of an unordered query.
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
return {tuple(row) for row in results}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def fail(msg):
|
|
192
|
+
assert False, msg
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@decorator
|
|
196
|
+
def provide_metadata(fn, *args, **kw):
|
|
197
|
+
"""Provide bound MetaData for a single test, dropping afterwards.
|
|
198
|
+
|
|
199
|
+
Legacy; use the "metadata" pytest fixture.
|
|
200
|
+
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
from . import fixtures
|
|
204
|
+
|
|
205
|
+
metadata = schema.MetaData()
|
|
206
|
+
self = args[0]
|
|
207
|
+
prev_meta = getattr(self, "metadata", None)
|
|
208
|
+
self.metadata = metadata
|
|
209
|
+
try:
|
|
210
|
+
return fn(*args, **kw)
|
|
211
|
+
finally:
|
|
212
|
+
# close out some things that get in the way of dropping tables.
|
|
213
|
+
# when using the "metadata" fixture, there is a set ordering
|
|
214
|
+
# of things that makes sure things are cleaned up in order, however
|
|
215
|
+
# the simple "decorator" nature of this legacy function means
|
|
216
|
+
# we have to hardcode some of that cleanup ahead of time.
|
|
217
|
+
|
|
218
|
+
# close ORM sessions
|
|
219
|
+
fixtures.close_all_sessions()
|
|
220
|
+
|
|
221
|
+
# integrate with the "connection" fixture as there are many
|
|
222
|
+
# tests where it is used along with provide_metadata
|
|
223
|
+
cfc = fixtures.base._connection_fixture_connection
|
|
224
|
+
if cfc:
|
|
225
|
+
# TODO: this warning can be used to find all the places
|
|
226
|
+
# this is used with connection fixture
|
|
227
|
+
# warn("mixing legacy provide metadata with connection fixture")
|
|
228
|
+
drop_all_tables_from_metadata(metadata, cfc)
|
|
229
|
+
# as the provide_metadata fixture is often used with "testing.db",
|
|
230
|
+
# when we do the drop we have to commit the transaction so that
|
|
231
|
+
# the DB is actually updated as the CREATE would have been
|
|
232
|
+
# committed
|
|
233
|
+
cfc.get_transaction().commit()
|
|
234
|
+
else:
|
|
235
|
+
drop_all_tables_from_metadata(metadata, config.db)
|
|
236
|
+
self.metadata = prev_meta
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def flag_combinations(*combinations):
|
|
240
|
+
"""A facade around @testing.combinations() oriented towards boolean
|
|
241
|
+
keyword-based arguments.
|
|
242
|
+
|
|
243
|
+
Basically generates a nice looking identifier based on the keywords
|
|
244
|
+
and also sets up the argument names.
|
|
245
|
+
|
|
246
|
+
E.g.::
|
|
247
|
+
|
|
248
|
+
@testing.flag_combinations(
|
|
249
|
+
dict(lazy=False, passive=False),
|
|
250
|
+
dict(lazy=True, passive=False),
|
|
251
|
+
dict(lazy=False, passive=True),
|
|
252
|
+
dict(lazy=False, passive=True, raiseload=True),
|
|
253
|
+
)
|
|
254
|
+
def test_fn(lazy, passive, raiseload): ...
|
|
255
|
+
|
|
256
|
+
would result in::
|
|
257
|
+
|
|
258
|
+
@testing.combinations(
|
|
259
|
+
("", False, False, False),
|
|
260
|
+
("lazy", True, False, False),
|
|
261
|
+
("lazy_passive", True, True, False),
|
|
262
|
+
("lazy_passive", True, True, True),
|
|
263
|
+
id_="iaaa",
|
|
264
|
+
argnames="lazy,passive,raiseload",
|
|
265
|
+
)
|
|
266
|
+
def test_fn(lazy, passive, raiseload): ...
|
|
267
|
+
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
keys = set()
|
|
271
|
+
|
|
272
|
+
for d in combinations:
|
|
273
|
+
keys.update(d)
|
|
274
|
+
|
|
275
|
+
keys = sorted(keys)
|
|
276
|
+
|
|
277
|
+
return config.combinations(
|
|
278
|
+
*[
|
|
279
|
+
("_".join(k for k in keys if d.get(k, False)),)
|
|
280
|
+
+ tuple(d.get(k, False) for k in keys)
|
|
281
|
+
for d in combinations
|
|
282
|
+
],
|
|
283
|
+
id_="i" + ("a" * len(keys)),
|
|
284
|
+
argnames=",".join(keys),
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def lambda_combinations(lambda_arg_sets, **kw):
|
|
289
|
+
args = inspect_getfullargspec(lambda_arg_sets)
|
|
290
|
+
|
|
291
|
+
arg_sets = lambda_arg_sets(*[mock.Mock() for arg in args[0]])
|
|
292
|
+
|
|
293
|
+
def create_fixture(pos):
|
|
294
|
+
def fixture(**kw):
|
|
295
|
+
return lambda_arg_sets(**kw)[pos]
|
|
296
|
+
|
|
297
|
+
fixture.__name__ = "fixture_%3.3d" % pos
|
|
298
|
+
return fixture
|
|
299
|
+
|
|
300
|
+
return config.combinations(
|
|
301
|
+
*[(create_fixture(i),) for i in range(len(arg_sets))], **kw
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def resolve_lambda(__fn, **kw):
|
|
306
|
+
"""Given a no-arg lambda and a namespace, return a new lambda that
|
|
307
|
+
has all the values filled in.
|
|
308
|
+
|
|
309
|
+
This is used so that we can have module-level fixtures that
|
|
310
|
+
refer to instance-level variables using lambdas.
|
|
311
|
+
|
|
312
|
+
"""
|
|
313
|
+
|
|
314
|
+
pos_args = inspect_getfullargspec(__fn)[0]
|
|
315
|
+
pass_pos_args = {arg: kw.pop(arg) for arg in pos_args}
|
|
316
|
+
glb = dict(__fn.__globals__)
|
|
317
|
+
glb.update(kw)
|
|
318
|
+
new_fn = types.FunctionType(__fn.__code__, glb)
|
|
319
|
+
return new_fn(**pass_pos_args)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def metadata_fixture(ddl="function"):
|
|
323
|
+
"""Provide MetaData for a pytest fixture."""
|
|
324
|
+
|
|
325
|
+
def decorate(fn):
|
|
326
|
+
def run_ddl(self):
|
|
327
|
+
metadata = self.metadata = schema.MetaData()
|
|
328
|
+
try:
|
|
329
|
+
result = fn(self, metadata)
|
|
330
|
+
metadata.create_all(config.db)
|
|
331
|
+
# TODO:
|
|
332
|
+
# somehow get a per-function dml erase fixture here
|
|
333
|
+
yield result
|
|
334
|
+
finally:
|
|
335
|
+
metadata.drop_all(config.db)
|
|
336
|
+
|
|
337
|
+
return config.fixture(scope=ddl)(run_ddl)
|
|
338
|
+
|
|
339
|
+
return decorate
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def force_drop_names(*names):
|
|
343
|
+
"""Force the given table names to be dropped after test complete,
|
|
344
|
+
isolating for foreign key cycles
|
|
345
|
+
|
|
346
|
+
"""
|
|
347
|
+
|
|
348
|
+
@decorator
|
|
349
|
+
def go(fn, *args, **kw):
|
|
350
|
+
try:
|
|
351
|
+
return fn(*args, **kw)
|
|
352
|
+
finally:
|
|
353
|
+
drop_all_tables(config.db, inspect(config.db), include_names=names)
|
|
354
|
+
|
|
355
|
+
return go
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class adict(dict):
|
|
359
|
+
"""Dict keys available as attributes. Shadows."""
|
|
360
|
+
|
|
361
|
+
def __getattribute__(self, key):
|
|
362
|
+
try:
|
|
363
|
+
return self[key]
|
|
364
|
+
except KeyError:
|
|
365
|
+
return dict.__getattribute__(self, key)
|
|
366
|
+
|
|
367
|
+
def __call__(self, *keys):
|
|
368
|
+
return tuple([self[key] for key in keys])
|
|
369
|
+
|
|
370
|
+
get_all = __call__
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def drop_all_tables_from_metadata(metadata, engine_or_connection):
|
|
374
|
+
from . import engines
|
|
375
|
+
|
|
376
|
+
def go(connection):
|
|
377
|
+
engines.testing_reaper.prepare_for_drop_tables(connection)
|
|
378
|
+
|
|
379
|
+
if not connection.dialect.supports_alter:
|
|
380
|
+
from . import assertions
|
|
381
|
+
|
|
382
|
+
with assertions.expect_warnings(
|
|
383
|
+
"Can't sort tables", assert_=False
|
|
384
|
+
):
|
|
385
|
+
metadata.drop_all(connection)
|
|
386
|
+
else:
|
|
387
|
+
metadata.drop_all(connection)
|
|
388
|
+
|
|
389
|
+
if not isinstance(engine_or_connection, Connection):
|
|
390
|
+
with engine_or_connection.begin() as connection:
|
|
391
|
+
go(connection)
|
|
392
|
+
else:
|
|
393
|
+
go(engine_or_connection)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def drop_all_tables(
|
|
397
|
+
engine,
|
|
398
|
+
inspector,
|
|
399
|
+
schema=None,
|
|
400
|
+
consider_schemas=(None,),
|
|
401
|
+
include_names=None,
|
|
402
|
+
):
|
|
403
|
+
if include_names is not None:
|
|
404
|
+
include_names = set(include_names)
|
|
405
|
+
|
|
406
|
+
if schema is not None:
|
|
407
|
+
assert consider_schemas == (
|
|
408
|
+
None,
|
|
409
|
+
), "consider_schemas and schema are mutually exclusive"
|
|
410
|
+
consider_schemas = (schema,)
|
|
411
|
+
|
|
412
|
+
with engine.begin() as conn:
|
|
413
|
+
for table_key, fkcs in reversed(
|
|
414
|
+
inspector.sort_tables_on_foreign_key_dependency(
|
|
415
|
+
consider_schemas=consider_schemas
|
|
416
|
+
)
|
|
417
|
+
):
|
|
418
|
+
if table_key:
|
|
419
|
+
if (
|
|
420
|
+
include_names is not None
|
|
421
|
+
and table_key[1] not in include_names
|
|
422
|
+
):
|
|
423
|
+
continue
|
|
424
|
+
conn.execute(
|
|
425
|
+
DropTable(
|
|
426
|
+
Table(table_key[1], MetaData(), schema=table_key[0])
|
|
427
|
+
)
|
|
428
|
+
)
|
|
429
|
+
elif fkcs:
|
|
430
|
+
if not engine.dialect.supports_alter:
|
|
431
|
+
continue
|
|
432
|
+
for t_key, fkc in fkcs:
|
|
433
|
+
if (
|
|
434
|
+
include_names is not None
|
|
435
|
+
and t_key[1] not in include_names
|
|
436
|
+
):
|
|
437
|
+
continue
|
|
438
|
+
tb = Table(
|
|
439
|
+
t_key[1],
|
|
440
|
+
MetaData(),
|
|
441
|
+
Column("x", Integer),
|
|
442
|
+
Column("y", Integer),
|
|
443
|
+
schema=t_key[0],
|
|
444
|
+
)
|
|
445
|
+
conn.execute(
|
|
446
|
+
DropConstraint(
|
|
447
|
+
ForeignKeyConstraint([tb.c.x], [tb.c.y], name=fkc)
|
|
448
|
+
)
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def teardown_events(event_cls):
|
|
453
|
+
@decorator
|
|
454
|
+
def decorate(fn, *arg, **kw):
|
|
455
|
+
try:
|
|
456
|
+
return fn(*arg, **kw)
|
|
457
|
+
finally:
|
|
458
|
+
event_cls._clear()
|
|
459
|
+
|
|
460
|
+
return decorate
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def total_size(o):
|
|
464
|
+
"""Returns the approximate memory footprint an object and all of its
|
|
465
|
+
contents.
|
|
466
|
+
|
|
467
|
+
source: https://code.activestate.com/recipes/577504/
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
"""
|
|
471
|
+
|
|
472
|
+
def dict_handler(d):
|
|
473
|
+
return chain.from_iterable(d.items())
|
|
474
|
+
|
|
475
|
+
all_handlers = {
|
|
476
|
+
tuple: iter,
|
|
477
|
+
list: iter,
|
|
478
|
+
deque: iter,
|
|
479
|
+
dict: dict_handler,
|
|
480
|
+
set: iter,
|
|
481
|
+
frozenset: iter,
|
|
482
|
+
}
|
|
483
|
+
seen = set() # track which object id's have already been seen
|
|
484
|
+
default_size = getsizeof(0) # estimate sizeof object without __sizeof__
|
|
485
|
+
|
|
486
|
+
def sizeof(o):
|
|
487
|
+
if id(o) in seen: # do not double count the same object
|
|
488
|
+
return 0
|
|
489
|
+
seen.add(id(o))
|
|
490
|
+
s = getsizeof(o, default_size)
|
|
491
|
+
|
|
492
|
+
for typ, handler in all_handlers.items():
|
|
493
|
+
if isinstance(o, typ):
|
|
494
|
+
s += sum(map(sizeof, handler(o)))
|
|
495
|
+
break
|
|
496
|
+
return s
|
|
497
|
+
|
|
498
|
+
return sizeof(o)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def count_cache_key_tuples(tup):
|
|
502
|
+
"""given a cache key tuple, counts how many instances of actual
|
|
503
|
+
tuples are found.
|
|
504
|
+
|
|
505
|
+
used to alert large jumps in cache key complexity.
|
|
506
|
+
|
|
507
|
+
"""
|
|
508
|
+
stack = [tup]
|
|
509
|
+
|
|
510
|
+
sentinel = object()
|
|
511
|
+
num_elements = 0
|
|
512
|
+
|
|
513
|
+
while stack:
|
|
514
|
+
elem = stack.pop(0)
|
|
515
|
+
if elem is sentinel:
|
|
516
|
+
num_elements += 1
|
|
517
|
+
elif isinstance(elem, tuple):
|
|
518
|
+
if elem:
|
|
519
|
+
stack = list(elem) + [sentinel] + stack
|
|
520
|
+
return num_elements
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
@contextlib.contextmanager
|
|
524
|
+
def skip_if_timeout(seconds: float, cleanup: Any = None):
|
|
525
|
+
|
|
526
|
+
now = time.time()
|
|
527
|
+
yield
|
|
528
|
+
sec = time.time() - now
|
|
529
|
+
if sec > seconds:
|
|
530
|
+
try:
|
|
531
|
+
cleanup()
|
|
532
|
+
finally:
|
|
533
|
+
config.skip_test(
|
|
534
|
+
f"test took too long ({sec:.4f} seconds > {seconds})"
|
|
535
|
+
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# testing/warnings.py
|
|
2
|
+
# Copyright (C) 2005-2025 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 warnings
|
|
12
|
+
|
|
13
|
+
from . import assertions
|
|
14
|
+
from .. import exc
|
|
15
|
+
from .. import exc as sa_exc
|
|
16
|
+
from ..exc import SATestSuiteWarning
|
|
17
|
+
from ..util.langhelpers import _warnings_warn
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def warn_test_suite(message):
|
|
21
|
+
_warnings_warn(message, category=SATestSuiteWarning)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def setup_filters():
|
|
25
|
+
"""hook for setting up warnings filters.
|
|
26
|
+
|
|
27
|
+
SQLAlchemy-specific classes must only be here and not in pytest config,
|
|
28
|
+
as we need to delay importing SQLAlchemy until conftest.py has been
|
|
29
|
+
processed.
|
|
30
|
+
|
|
31
|
+
NOTE: filters on subclasses of DeprecationWarning or
|
|
32
|
+
PendingDeprecationWarning have no effect if added here, since pytest
|
|
33
|
+
will add at each test the following filters
|
|
34
|
+
``always::PendingDeprecationWarning`` and ``always::DeprecationWarning``
|
|
35
|
+
that will take precedence over any added here.
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
warnings.filterwarnings("error", category=exc.SAWarning)
|
|
39
|
+
warnings.filterwarnings("always", category=exc.SATestSuiteWarning)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def assert_warnings(fn, warning_msgs, regex=False):
|
|
43
|
+
"""Assert that each of the given warnings are emitted by fn.
|
|
44
|
+
|
|
45
|
+
Deprecated. Please use assertions.expect_warnings().
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
with assertions._expect_warnings(
|
|
50
|
+
sa_exc.SAWarning, warning_msgs, regex=regex
|
|
51
|
+
):
|
|
52
|
+
return fn()
|
sqlalchemy/types.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# types.py
|
|
2
|
+
# Copyright (C) 2005-2025 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
|
+
"""Compatibility namespace for sqlalchemy.sql.types."""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from .sql.sqltypes import _Binary as _Binary
|
|
14
|
+
from .sql.sqltypes import ARRAY as ARRAY
|
|
15
|
+
from .sql.sqltypes import BIGINT as BIGINT
|
|
16
|
+
from .sql.sqltypes import BigInteger as BigInteger
|
|
17
|
+
from .sql.sqltypes import BINARY as BINARY
|
|
18
|
+
from .sql.sqltypes import BLOB as BLOB
|
|
19
|
+
from .sql.sqltypes import BOOLEAN as BOOLEAN
|
|
20
|
+
from .sql.sqltypes import Boolean as Boolean
|
|
21
|
+
from .sql.sqltypes import CHAR as CHAR
|
|
22
|
+
from .sql.sqltypes import CLOB as CLOB
|
|
23
|
+
from .sql.sqltypes import Concatenable as Concatenable
|
|
24
|
+
from .sql.sqltypes import DATE as DATE
|
|
25
|
+
from .sql.sqltypes import Date as Date
|
|
26
|
+
from .sql.sqltypes import DATETIME as DATETIME
|
|
27
|
+
from .sql.sqltypes import DateTime as DateTime
|
|
28
|
+
from .sql.sqltypes import DECIMAL as DECIMAL
|
|
29
|
+
from .sql.sqltypes import DOUBLE as DOUBLE
|
|
30
|
+
from .sql.sqltypes import Double as Double
|
|
31
|
+
from .sql.sqltypes import DOUBLE_PRECISION as DOUBLE_PRECISION
|
|
32
|
+
from .sql.sqltypes import Enum as Enum
|
|
33
|
+
from .sql.sqltypes import FLOAT as FLOAT
|
|
34
|
+
from .sql.sqltypes import Float as Float
|
|
35
|
+
from .sql.sqltypes import Indexable as Indexable
|
|
36
|
+
from .sql.sqltypes import INT as INT
|
|
37
|
+
from .sql.sqltypes import INTEGER as INTEGER
|
|
38
|
+
from .sql.sqltypes import Integer as Integer
|
|
39
|
+
from .sql.sqltypes import Interval as Interval
|
|
40
|
+
from .sql.sqltypes import JSON as JSON
|
|
41
|
+
from .sql.sqltypes import LargeBinary as LargeBinary
|
|
42
|
+
from .sql.sqltypes import MatchType as MatchType
|
|
43
|
+
from .sql.sqltypes import NCHAR as NCHAR
|
|
44
|
+
from .sql.sqltypes import NULLTYPE as NULLTYPE
|
|
45
|
+
from .sql.sqltypes import NullType as NullType
|
|
46
|
+
from .sql.sqltypes import NUMERIC as NUMERIC
|
|
47
|
+
from .sql.sqltypes import Numeric as Numeric
|
|
48
|
+
from .sql.sqltypes import NVARCHAR as NVARCHAR
|
|
49
|
+
from .sql.sqltypes import PickleType as PickleType
|
|
50
|
+
from .sql.sqltypes import REAL as REAL
|
|
51
|
+
from .sql.sqltypes import SchemaType as SchemaType
|
|
52
|
+
from .sql.sqltypes import SMALLINT as SMALLINT
|
|
53
|
+
from .sql.sqltypes import SmallInteger as SmallInteger
|
|
54
|
+
from .sql.sqltypes import String as String
|
|
55
|
+
from .sql.sqltypes import STRINGTYPE as STRINGTYPE
|
|
56
|
+
from .sql.sqltypes import TEXT as TEXT
|
|
57
|
+
from .sql.sqltypes import Text as Text
|
|
58
|
+
from .sql.sqltypes import TIME as TIME
|
|
59
|
+
from .sql.sqltypes import Time as Time
|
|
60
|
+
from .sql.sqltypes import TIMESTAMP as TIMESTAMP
|
|
61
|
+
from .sql.sqltypes import TupleType as TupleType
|
|
62
|
+
from .sql.sqltypes import Unicode as Unicode
|
|
63
|
+
from .sql.sqltypes import UnicodeText as UnicodeText
|
|
64
|
+
from .sql.sqltypes import UUID as UUID
|
|
65
|
+
from .sql.sqltypes import Uuid as Uuid
|
|
66
|
+
from .sql.sqltypes import VARBINARY as VARBINARY
|
|
67
|
+
from .sql.sqltypes import VARCHAR as VARCHAR
|
|
68
|
+
from .sql.type_api import adapt_type as adapt_type
|
|
69
|
+
from .sql.type_api import ExternalType as ExternalType
|
|
70
|
+
from .sql.type_api import to_instance as to_instance
|
|
71
|
+
from .sql.type_api import TypeDecorator as TypeDecorator
|
|
72
|
+
from .sql.type_api import TypeEngine as TypeEngine
|
|
73
|
+
from .sql.type_api import UserDefinedType as UserDefinedType
|
|
74
|
+
from .sql.type_api import Variant as Variant
|