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,571 @@
|
|
|
1
|
+
# orm/clsregistry.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
|
+
"""Routines to handle the string class registry used by declarative.
|
|
9
|
+
|
|
10
|
+
This system allows specification of classes and expressions used in
|
|
11
|
+
:func:`_orm.relationship` using strings.
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
from typing import Any
|
|
19
|
+
from typing import Callable
|
|
20
|
+
from typing import cast
|
|
21
|
+
from typing import Dict
|
|
22
|
+
from typing import Generator
|
|
23
|
+
from typing import Iterable
|
|
24
|
+
from typing import List
|
|
25
|
+
from typing import Mapping
|
|
26
|
+
from typing import MutableMapping
|
|
27
|
+
from typing import NoReturn
|
|
28
|
+
from typing import Optional
|
|
29
|
+
from typing import Set
|
|
30
|
+
from typing import Tuple
|
|
31
|
+
from typing import Type
|
|
32
|
+
from typing import TYPE_CHECKING
|
|
33
|
+
from typing import TypeVar
|
|
34
|
+
from typing import Union
|
|
35
|
+
import weakref
|
|
36
|
+
|
|
37
|
+
from . import attributes
|
|
38
|
+
from . import interfaces
|
|
39
|
+
from .descriptor_props import SynonymProperty
|
|
40
|
+
from .properties import ColumnProperty
|
|
41
|
+
from .util import class_mapper
|
|
42
|
+
from .. import exc
|
|
43
|
+
from .. import inspection
|
|
44
|
+
from .. import util
|
|
45
|
+
from ..sql.schema import _get_table_key
|
|
46
|
+
from ..util.typing import CallableReference
|
|
47
|
+
|
|
48
|
+
if TYPE_CHECKING:
|
|
49
|
+
from .relationships import RelationshipProperty
|
|
50
|
+
from ..sql.schema import MetaData
|
|
51
|
+
from ..sql.schema import Table
|
|
52
|
+
|
|
53
|
+
_T = TypeVar("_T", bound=Any)
|
|
54
|
+
|
|
55
|
+
_ClsRegistryType = MutableMapping[str, Union[type, "ClsRegistryToken"]]
|
|
56
|
+
|
|
57
|
+
# strong references to registries which we place in
|
|
58
|
+
# the _decl_class_registry, which is usually weak referencing.
|
|
59
|
+
# the internal registries here link to classes with weakrefs and remove
|
|
60
|
+
# themselves when all references to contained classes are removed.
|
|
61
|
+
_registries: Set[ClsRegistryToken] = set()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def add_class(
|
|
65
|
+
classname: str, cls: Type[_T], decl_class_registry: _ClsRegistryType
|
|
66
|
+
) -> None:
|
|
67
|
+
"""Add a class to the _decl_class_registry associated with the
|
|
68
|
+
given declarative class.
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
if classname in decl_class_registry:
|
|
72
|
+
# class already exists.
|
|
73
|
+
existing = decl_class_registry[classname]
|
|
74
|
+
if not isinstance(existing, _MultipleClassMarker):
|
|
75
|
+
decl_class_registry[classname] = _MultipleClassMarker(
|
|
76
|
+
[cls, cast("Type[Any]", existing)]
|
|
77
|
+
)
|
|
78
|
+
else:
|
|
79
|
+
decl_class_registry[classname] = cls
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
root_module = cast(
|
|
83
|
+
_ModuleMarker, decl_class_registry["_sa_module_registry"]
|
|
84
|
+
)
|
|
85
|
+
except KeyError:
|
|
86
|
+
decl_class_registry["_sa_module_registry"] = root_module = (
|
|
87
|
+
_ModuleMarker("_sa_module_registry", None)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
tokens = cls.__module__.split(".")
|
|
91
|
+
|
|
92
|
+
# build up a tree like this:
|
|
93
|
+
# modulename: myapp.snacks.nuts
|
|
94
|
+
#
|
|
95
|
+
# myapp->snack->nuts->(classes)
|
|
96
|
+
# snack->nuts->(classes)
|
|
97
|
+
# nuts->(classes)
|
|
98
|
+
#
|
|
99
|
+
# this allows partial token paths to be used.
|
|
100
|
+
while tokens:
|
|
101
|
+
token = tokens.pop(0)
|
|
102
|
+
module = root_module.get_module(token)
|
|
103
|
+
for token in tokens:
|
|
104
|
+
module = module.get_module(token)
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
module.add_class(classname, cls)
|
|
108
|
+
except AttributeError as ae:
|
|
109
|
+
if not isinstance(module, _ModuleMarker):
|
|
110
|
+
raise exc.InvalidRequestError(
|
|
111
|
+
f'name "{classname}" matches both a '
|
|
112
|
+
"class name and a module name"
|
|
113
|
+
) from ae
|
|
114
|
+
else:
|
|
115
|
+
raise
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def remove_class(
|
|
119
|
+
classname: str, cls: Type[Any], decl_class_registry: _ClsRegistryType
|
|
120
|
+
) -> None:
|
|
121
|
+
if classname in decl_class_registry:
|
|
122
|
+
existing = decl_class_registry[classname]
|
|
123
|
+
if isinstance(existing, _MultipleClassMarker):
|
|
124
|
+
existing.remove_item(cls)
|
|
125
|
+
else:
|
|
126
|
+
del decl_class_registry[classname]
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
root_module = cast(
|
|
130
|
+
_ModuleMarker, decl_class_registry["_sa_module_registry"]
|
|
131
|
+
)
|
|
132
|
+
except KeyError:
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
tokens = cls.__module__.split(".")
|
|
136
|
+
|
|
137
|
+
while tokens:
|
|
138
|
+
token = tokens.pop(0)
|
|
139
|
+
module = root_module.get_module(token)
|
|
140
|
+
for token in tokens:
|
|
141
|
+
module = module.get_module(token)
|
|
142
|
+
try:
|
|
143
|
+
module.remove_class(classname, cls)
|
|
144
|
+
except AttributeError:
|
|
145
|
+
if not isinstance(module, _ModuleMarker):
|
|
146
|
+
pass
|
|
147
|
+
else:
|
|
148
|
+
raise
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _key_is_empty(
|
|
152
|
+
key: str,
|
|
153
|
+
decl_class_registry: _ClsRegistryType,
|
|
154
|
+
test: Callable[[Any], bool],
|
|
155
|
+
) -> bool:
|
|
156
|
+
"""test if a key is empty of a certain object.
|
|
157
|
+
|
|
158
|
+
used for unit tests against the registry to see if garbage collection
|
|
159
|
+
is working.
|
|
160
|
+
|
|
161
|
+
"test" is a callable that will be passed an object should return True
|
|
162
|
+
if the given object is the one we were looking for.
|
|
163
|
+
|
|
164
|
+
We can't pass the actual object itself b.c. this is for testing garbage
|
|
165
|
+
collection; the caller will have to have removed references to the
|
|
166
|
+
object itself.
|
|
167
|
+
|
|
168
|
+
"""
|
|
169
|
+
if key not in decl_class_registry:
|
|
170
|
+
return True
|
|
171
|
+
|
|
172
|
+
thing = decl_class_registry[key]
|
|
173
|
+
if isinstance(thing, _MultipleClassMarker):
|
|
174
|
+
for sub_thing in thing.contents:
|
|
175
|
+
if test(sub_thing):
|
|
176
|
+
return False
|
|
177
|
+
else:
|
|
178
|
+
raise NotImplementedError("unknown codepath")
|
|
179
|
+
else:
|
|
180
|
+
return not test(thing)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class ClsRegistryToken:
|
|
184
|
+
"""an object that can be in the registry._class_registry as a value."""
|
|
185
|
+
|
|
186
|
+
__slots__ = ()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class _MultipleClassMarker(ClsRegistryToken):
|
|
190
|
+
"""refers to multiple classes of the same name
|
|
191
|
+
within _decl_class_registry.
|
|
192
|
+
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
__slots__ = "on_remove", "contents", "__weakref__"
|
|
196
|
+
|
|
197
|
+
contents: Set[weakref.ref[Type[Any]]]
|
|
198
|
+
on_remove: CallableReference[Optional[Callable[[], None]]]
|
|
199
|
+
|
|
200
|
+
def __init__(
|
|
201
|
+
self,
|
|
202
|
+
classes: Iterable[Type[Any]],
|
|
203
|
+
on_remove: Optional[Callable[[], None]] = None,
|
|
204
|
+
):
|
|
205
|
+
self.on_remove = on_remove
|
|
206
|
+
self.contents = {
|
|
207
|
+
weakref.ref(item, self._remove_item) for item in classes
|
|
208
|
+
}
|
|
209
|
+
_registries.add(self)
|
|
210
|
+
|
|
211
|
+
def remove_item(self, cls: Type[Any]) -> None:
|
|
212
|
+
self._remove_item(weakref.ref(cls))
|
|
213
|
+
|
|
214
|
+
def __iter__(self) -> Generator[Optional[Type[Any]], None, None]:
|
|
215
|
+
return (ref() for ref in self.contents)
|
|
216
|
+
|
|
217
|
+
def attempt_get(self, path: List[str], key: str) -> Type[Any]:
|
|
218
|
+
if len(self.contents) > 1:
|
|
219
|
+
raise exc.InvalidRequestError(
|
|
220
|
+
'Multiple classes found for path "%s" '
|
|
221
|
+
"in the registry of this declarative "
|
|
222
|
+
"base. Please use a fully module-qualified path."
|
|
223
|
+
% (".".join(path + [key]))
|
|
224
|
+
)
|
|
225
|
+
else:
|
|
226
|
+
ref = list(self.contents)[0]
|
|
227
|
+
cls = ref()
|
|
228
|
+
if cls is None:
|
|
229
|
+
raise NameError(key)
|
|
230
|
+
return cls
|
|
231
|
+
|
|
232
|
+
def _remove_item(self, ref: weakref.ref[Type[Any]]) -> None:
|
|
233
|
+
self.contents.discard(ref)
|
|
234
|
+
if not self.contents:
|
|
235
|
+
_registries.discard(self)
|
|
236
|
+
if self.on_remove:
|
|
237
|
+
self.on_remove()
|
|
238
|
+
|
|
239
|
+
def add_item(self, item: Type[Any]) -> None:
|
|
240
|
+
# protect against class registration race condition against
|
|
241
|
+
# asynchronous garbage collection calling _remove_item,
|
|
242
|
+
# [ticket:3208] and [ticket:10782]
|
|
243
|
+
modules = {
|
|
244
|
+
cls.__module__
|
|
245
|
+
for cls in [ref() for ref in list(self.contents)]
|
|
246
|
+
if cls is not None
|
|
247
|
+
}
|
|
248
|
+
if item.__module__ in modules:
|
|
249
|
+
util.warn(
|
|
250
|
+
"This declarative base already contains a class with the "
|
|
251
|
+
"same class name and module name as %s.%s, and will "
|
|
252
|
+
"be replaced in the string-lookup table."
|
|
253
|
+
% (item.__module__, item.__name__)
|
|
254
|
+
)
|
|
255
|
+
self.contents.add(weakref.ref(item, self._remove_item))
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class _ModuleMarker(ClsRegistryToken):
|
|
259
|
+
"""Refers to a module name within
|
|
260
|
+
_decl_class_registry.
|
|
261
|
+
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
__slots__ = "parent", "name", "contents", "mod_ns", "path", "__weakref__"
|
|
265
|
+
|
|
266
|
+
parent: Optional[_ModuleMarker]
|
|
267
|
+
contents: Dict[str, Union[_ModuleMarker, _MultipleClassMarker]]
|
|
268
|
+
mod_ns: _ModNS
|
|
269
|
+
path: List[str]
|
|
270
|
+
|
|
271
|
+
def __init__(self, name: str, parent: Optional[_ModuleMarker]):
|
|
272
|
+
self.parent = parent
|
|
273
|
+
self.name = name
|
|
274
|
+
self.contents = {}
|
|
275
|
+
self.mod_ns = _ModNS(self)
|
|
276
|
+
if self.parent:
|
|
277
|
+
self.path = self.parent.path + [self.name]
|
|
278
|
+
else:
|
|
279
|
+
self.path = []
|
|
280
|
+
_registries.add(self)
|
|
281
|
+
|
|
282
|
+
def __contains__(self, name: str) -> bool:
|
|
283
|
+
return name in self.contents
|
|
284
|
+
|
|
285
|
+
def __getitem__(self, name: str) -> ClsRegistryToken:
|
|
286
|
+
return self.contents[name]
|
|
287
|
+
|
|
288
|
+
def _remove_item(self, name: str) -> None:
|
|
289
|
+
self.contents.pop(name, None)
|
|
290
|
+
if not self.contents:
|
|
291
|
+
if self.parent is not None:
|
|
292
|
+
self.parent._remove_item(self.name)
|
|
293
|
+
_registries.discard(self)
|
|
294
|
+
|
|
295
|
+
def resolve_attr(self, key: str) -> Union[_ModNS, Type[Any]]:
|
|
296
|
+
return self.mod_ns.__getattr__(key)
|
|
297
|
+
|
|
298
|
+
def get_module(self, name: str) -> _ModuleMarker:
|
|
299
|
+
if name not in self.contents:
|
|
300
|
+
marker = _ModuleMarker(name, self)
|
|
301
|
+
self.contents[name] = marker
|
|
302
|
+
else:
|
|
303
|
+
marker = cast(_ModuleMarker, self.contents[name])
|
|
304
|
+
return marker
|
|
305
|
+
|
|
306
|
+
def add_class(self, name: str, cls: Type[Any]) -> None:
|
|
307
|
+
if name in self.contents:
|
|
308
|
+
existing = cast(_MultipleClassMarker, self.contents[name])
|
|
309
|
+
try:
|
|
310
|
+
existing.add_item(cls)
|
|
311
|
+
except AttributeError as ae:
|
|
312
|
+
if not isinstance(existing, _MultipleClassMarker):
|
|
313
|
+
raise exc.InvalidRequestError(
|
|
314
|
+
f'name "{name}" matches both a '
|
|
315
|
+
"class name and a module name"
|
|
316
|
+
) from ae
|
|
317
|
+
else:
|
|
318
|
+
raise
|
|
319
|
+
else:
|
|
320
|
+
self.contents[name] = _MultipleClassMarker(
|
|
321
|
+
[cls], on_remove=lambda: self._remove_item(name)
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
def remove_class(self, name: str, cls: Type[Any]) -> None:
|
|
325
|
+
if name in self.contents:
|
|
326
|
+
existing = cast(_MultipleClassMarker, self.contents[name])
|
|
327
|
+
existing.remove_item(cls)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class _ModNS:
|
|
331
|
+
__slots__ = ("__parent",)
|
|
332
|
+
|
|
333
|
+
__parent: _ModuleMarker
|
|
334
|
+
|
|
335
|
+
def __init__(self, parent: _ModuleMarker):
|
|
336
|
+
self.__parent = parent
|
|
337
|
+
|
|
338
|
+
def __getattr__(self, key: str) -> Union[_ModNS, Type[Any]]:
|
|
339
|
+
try:
|
|
340
|
+
value = self.__parent.contents[key]
|
|
341
|
+
except KeyError:
|
|
342
|
+
pass
|
|
343
|
+
else:
|
|
344
|
+
if value is not None:
|
|
345
|
+
if isinstance(value, _ModuleMarker):
|
|
346
|
+
return value.mod_ns
|
|
347
|
+
else:
|
|
348
|
+
assert isinstance(value, _MultipleClassMarker)
|
|
349
|
+
return value.attempt_get(self.__parent.path, key)
|
|
350
|
+
raise NameError(
|
|
351
|
+
"Module %r has no mapped classes "
|
|
352
|
+
"registered under the name %r" % (self.__parent.name, key)
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class _GetColumns:
|
|
357
|
+
__slots__ = ("cls",)
|
|
358
|
+
|
|
359
|
+
cls: Type[Any]
|
|
360
|
+
|
|
361
|
+
def __init__(self, cls: Type[Any]):
|
|
362
|
+
self.cls = cls
|
|
363
|
+
|
|
364
|
+
def __getattr__(self, key: str) -> Any:
|
|
365
|
+
mp = class_mapper(self.cls, configure=False)
|
|
366
|
+
if mp:
|
|
367
|
+
if key not in mp.all_orm_descriptors:
|
|
368
|
+
raise AttributeError(
|
|
369
|
+
"Class %r does not have a mapped column named %r"
|
|
370
|
+
% (self.cls, key)
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
desc = mp.all_orm_descriptors[key]
|
|
374
|
+
if desc.extension_type is interfaces.NotExtension.NOT_EXTENSION:
|
|
375
|
+
assert isinstance(desc, attributes.QueryableAttribute)
|
|
376
|
+
prop = desc.property
|
|
377
|
+
if isinstance(prop, SynonymProperty):
|
|
378
|
+
key = prop.name
|
|
379
|
+
elif not isinstance(prop, ColumnProperty):
|
|
380
|
+
raise exc.InvalidRequestError(
|
|
381
|
+
"Property %r is not an instance of"
|
|
382
|
+
" ColumnProperty (i.e. does not correspond"
|
|
383
|
+
" directly to a Column)." % key
|
|
384
|
+
)
|
|
385
|
+
return getattr(self.cls, key)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
inspection._inspects(_GetColumns)(
|
|
389
|
+
lambda target: inspection.inspect(target.cls)
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class _GetTable:
|
|
394
|
+
__slots__ = "key", "metadata"
|
|
395
|
+
|
|
396
|
+
key: str
|
|
397
|
+
metadata: MetaData
|
|
398
|
+
|
|
399
|
+
def __init__(self, key: str, metadata: MetaData):
|
|
400
|
+
self.key = key
|
|
401
|
+
self.metadata = metadata
|
|
402
|
+
|
|
403
|
+
def __getattr__(self, key: str) -> Table:
|
|
404
|
+
return self.metadata.tables[_get_table_key(key, self.key)]
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _determine_container(key: str, value: Any) -> _GetColumns:
|
|
408
|
+
if isinstance(value, _MultipleClassMarker):
|
|
409
|
+
value = value.attempt_get([], key)
|
|
410
|
+
return _GetColumns(value)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
class _class_resolver:
|
|
414
|
+
__slots__ = (
|
|
415
|
+
"cls",
|
|
416
|
+
"prop",
|
|
417
|
+
"arg",
|
|
418
|
+
"fallback",
|
|
419
|
+
"_dict",
|
|
420
|
+
"_resolvers",
|
|
421
|
+
"favor_tables",
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
cls: Type[Any]
|
|
425
|
+
prop: RelationshipProperty[Any]
|
|
426
|
+
fallback: Mapping[str, Any]
|
|
427
|
+
arg: str
|
|
428
|
+
favor_tables: bool
|
|
429
|
+
_resolvers: Tuple[Callable[[str], Any], ...]
|
|
430
|
+
|
|
431
|
+
def __init__(
|
|
432
|
+
self,
|
|
433
|
+
cls: Type[Any],
|
|
434
|
+
prop: RelationshipProperty[Any],
|
|
435
|
+
fallback: Mapping[str, Any],
|
|
436
|
+
arg: str,
|
|
437
|
+
favor_tables: bool = False,
|
|
438
|
+
):
|
|
439
|
+
self.cls = cls
|
|
440
|
+
self.prop = prop
|
|
441
|
+
self.arg = arg
|
|
442
|
+
self.fallback = fallback
|
|
443
|
+
self._dict = util.PopulateDict(self._access_cls)
|
|
444
|
+
self._resolvers = ()
|
|
445
|
+
self.favor_tables = favor_tables
|
|
446
|
+
|
|
447
|
+
def _access_cls(self, key: str) -> Any:
|
|
448
|
+
cls = self.cls
|
|
449
|
+
|
|
450
|
+
manager = attributes.manager_of_class(cls)
|
|
451
|
+
decl_base = manager.registry
|
|
452
|
+
assert decl_base is not None
|
|
453
|
+
decl_class_registry = decl_base._class_registry
|
|
454
|
+
metadata = decl_base.metadata
|
|
455
|
+
|
|
456
|
+
if self.favor_tables:
|
|
457
|
+
if key in metadata.tables:
|
|
458
|
+
return metadata.tables[key]
|
|
459
|
+
elif key in metadata._schemas:
|
|
460
|
+
return _GetTable(key, getattr(cls, "metadata", metadata))
|
|
461
|
+
|
|
462
|
+
if key in decl_class_registry:
|
|
463
|
+
return _determine_container(key, decl_class_registry[key])
|
|
464
|
+
|
|
465
|
+
if not self.favor_tables:
|
|
466
|
+
if key in metadata.tables:
|
|
467
|
+
return metadata.tables[key]
|
|
468
|
+
elif key in metadata._schemas:
|
|
469
|
+
return _GetTable(key, getattr(cls, "metadata", metadata))
|
|
470
|
+
|
|
471
|
+
if "_sa_module_registry" in decl_class_registry and key in cast(
|
|
472
|
+
_ModuleMarker, decl_class_registry["_sa_module_registry"]
|
|
473
|
+
):
|
|
474
|
+
registry = cast(
|
|
475
|
+
_ModuleMarker, decl_class_registry["_sa_module_registry"]
|
|
476
|
+
)
|
|
477
|
+
return registry.resolve_attr(key)
|
|
478
|
+
elif self._resolvers:
|
|
479
|
+
for resolv in self._resolvers:
|
|
480
|
+
value = resolv(key)
|
|
481
|
+
if value is not None:
|
|
482
|
+
return value
|
|
483
|
+
|
|
484
|
+
return self.fallback[key]
|
|
485
|
+
|
|
486
|
+
def _raise_for_name(self, name: str, err: Exception) -> NoReturn:
|
|
487
|
+
generic_match = re.match(r"(.+)\[(.+)\]", name)
|
|
488
|
+
|
|
489
|
+
if generic_match:
|
|
490
|
+
clsarg = generic_match.group(2).strip("'")
|
|
491
|
+
raise exc.InvalidRequestError(
|
|
492
|
+
f"When initializing mapper {self.prop.parent}, "
|
|
493
|
+
f'expression "relationship({self.arg!r})" seems to be '
|
|
494
|
+
"using a generic class as the argument to relationship(); "
|
|
495
|
+
"please state the generic argument "
|
|
496
|
+
"using an annotation, e.g. "
|
|
497
|
+
f'"{self.prop.key}: Mapped[{generic_match.group(1)}'
|
|
498
|
+
f"['{clsarg}']] = relationship()\""
|
|
499
|
+
) from err
|
|
500
|
+
else:
|
|
501
|
+
raise exc.InvalidRequestError(
|
|
502
|
+
"When initializing mapper %s, expression %r failed to "
|
|
503
|
+
"locate a name (%r). If this is a class name, consider "
|
|
504
|
+
"adding this relationship() to the %r class after "
|
|
505
|
+
"both dependent classes have been defined."
|
|
506
|
+
% (self.prop.parent, self.arg, name, self.cls)
|
|
507
|
+
) from err
|
|
508
|
+
|
|
509
|
+
def _resolve_name(self) -> Union[Table, Type[Any], _ModNS]:
|
|
510
|
+
name = self.arg
|
|
511
|
+
d = self._dict
|
|
512
|
+
rval = None
|
|
513
|
+
try:
|
|
514
|
+
for token in name.split("."):
|
|
515
|
+
if rval is None:
|
|
516
|
+
rval = d[token]
|
|
517
|
+
else:
|
|
518
|
+
rval = getattr(rval, token)
|
|
519
|
+
except KeyError as err:
|
|
520
|
+
self._raise_for_name(name, err)
|
|
521
|
+
except NameError as n:
|
|
522
|
+
self._raise_for_name(n.args[0], n)
|
|
523
|
+
else:
|
|
524
|
+
if isinstance(rval, _GetColumns):
|
|
525
|
+
return rval.cls
|
|
526
|
+
else:
|
|
527
|
+
if TYPE_CHECKING:
|
|
528
|
+
assert isinstance(rval, (type, Table, _ModNS))
|
|
529
|
+
return rval
|
|
530
|
+
|
|
531
|
+
def __call__(self) -> Any:
|
|
532
|
+
try:
|
|
533
|
+
x = eval(self.arg, globals(), self._dict)
|
|
534
|
+
|
|
535
|
+
if isinstance(x, _GetColumns):
|
|
536
|
+
return x.cls
|
|
537
|
+
else:
|
|
538
|
+
return x
|
|
539
|
+
except NameError as n:
|
|
540
|
+
self._raise_for_name(n.args[0], n)
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
_fallback_dict: Mapping[str, Any] = None # type: ignore
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def _resolver(cls: Type[Any], prop: RelationshipProperty[Any]) -> Tuple[
|
|
547
|
+
Callable[[str], Callable[[], Union[Type[Any], Table, _ModNS]]],
|
|
548
|
+
Callable[[str, bool], _class_resolver],
|
|
549
|
+
]:
|
|
550
|
+
global _fallback_dict
|
|
551
|
+
|
|
552
|
+
if _fallback_dict is None:
|
|
553
|
+
import sqlalchemy
|
|
554
|
+
from . import foreign
|
|
555
|
+
from . import remote
|
|
556
|
+
|
|
557
|
+
_fallback_dict = util.immutabledict(sqlalchemy.__dict__).union(
|
|
558
|
+
{"foreign": foreign, "remote": remote}
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
def resolve_arg(arg: str, favor_tables: bool = False) -> _class_resolver:
|
|
562
|
+
return _class_resolver(
|
|
563
|
+
cls, prop, _fallback_dict, arg, favor_tables=favor_tables
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
def resolve_name(
|
|
567
|
+
arg: str,
|
|
568
|
+
) -> Callable[[], Union[Type[Any], Table, _ModNS]]:
|
|
569
|
+
return _class_resolver(cls, prop, _fallback_dict, arg)._resolve_name
|
|
570
|
+
|
|
571
|
+
return resolve_name, resolve_arg
|