SQLAlchemy 2.0.47__cp313-cp313t-win32.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlalchemy/__init__.py +283 -0
- sqlalchemy/connectors/__init__.py +18 -0
- sqlalchemy/connectors/aioodbc.py +184 -0
- sqlalchemy/connectors/asyncio.py +429 -0
- sqlalchemy/connectors/pyodbc.py +250 -0
- sqlalchemy/cyextension/__init__.py +6 -0
- sqlalchemy/cyextension/collections.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/collections.pyx +409 -0
- sqlalchemy/cyextension/immutabledict.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/immutabledict.pxd +8 -0
- sqlalchemy/cyextension/immutabledict.pyx +133 -0
- sqlalchemy/cyextension/processors.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/processors.pyx +68 -0
- sqlalchemy/cyextension/resultproxy.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/resultproxy.pyx +102 -0
- sqlalchemy/cyextension/util.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/util.pyx +90 -0
- sqlalchemy/dialects/__init__.py +62 -0
- sqlalchemy/dialects/_typing.py +30 -0
- sqlalchemy/dialects/mssql/__init__.py +88 -0
- sqlalchemy/dialects/mssql/aioodbc.py +63 -0
- sqlalchemy/dialects/mssql/base.py +4093 -0
- sqlalchemy/dialects/mssql/information_schema.py +285 -0
- sqlalchemy/dialects/mssql/json.py +129 -0
- sqlalchemy/dialects/mssql/provision.py +185 -0
- sqlalchemy/dialects/mssql/pymssql.py +126 -0
- sqlalchemy/dialects/mssql/pyodbc.py +760 -0
- sqlalchemy/dialects/mysql/__init__.py +104 -0
- sqlalchemy/dialects/mysql/aiomysql.py +250 -0
- sqlalchemy/dialects/mysql/asyncmy.py +231 -0
- sqlalchemy/dialects/mysql/base.py +3949 -0
- sqlalchemy/dialects/mysql/cymysql.py +106 -0
- sqlalchemy/dialects/mysql/dml.py +225 -0
- sqlalchemy/dialects/mysql/enumerated.py +282 -0
- sqlalchemy/dialects/mysql/expression.py +146 -0
- sqlalchemy/dialects/mysql/json.py +91 -0
- sqlalchemy/dialects/mysql/mariadb.py +72 -0
- sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
- sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
- sqlalchemy/dialects/mysql/mysqldb.py +314 -0
- sqlalchemy/dialects/mysql/provision.py +153 -0
- sqlalchemy/dialects/mysql/pymysql.py +158 -0
- sqlalchemy/dialects/mysql/pyodbc.py +157 -0
- sqlalchemy/dialects/mysql/reflection.py +727 -0
- sqlalchemy/dialects/mysql/reserved_words.py +570 -0
- sqlalchemy/dialects/mysql/types.py +835 -0
- sqlalchemy/dialects/oracle/__init__.py +81 -0
- sqlalchemy/dialects/oracle/base.py +3802 -0
- sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
- sqlalchemy/dialects/oracle/dictionary.py +507 -0
- sqlalchemy/dialects/oracle/oracledb.py +941 -0
- sqlalchemy/dialects/oracle/provision.py +297 -0
- sqlalchemy/dialects/oracle/types.py +316 -0
- sqlalchemy/dialects/oracle/vector.py +365 -0
- sqlalchemy/dialects/postgresql/__init__.py +167 -0
- sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
- sqlalchemy/dialects/postgresql/array.py +519 -0
- sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
- sqlalchemy/dialects/postgresql/base.py +5378 -0
- sqlalchemy/dialects/postgresql/dml.py +339 -0
- sqlalchemy/dialects/postgresql/ext.py +540 -0
- sqlalchemy/dialects/postgresql/hstore.py +406 -0
- sqlalchemy/dialects/postgresql/json.py +404 -0
- sqlalchemy/dialects/postgresql/named_types.py +524 -0
- sqlalchemy/dialects/postgresql/operators.py +129 -0
- sqlalchemy/dialects/postgresql/pg8000.py +669 -0
- sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
- sqlalchemy/dialects/postgresql/provision.py +183 -0
- sqlalchemy/dialects/postgresql/psycopg.py +862 -0
- sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
- sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
- sqlalchemy/dialects/postgresql/ranges.py +1031 -0
- sqlalchemy/dialects/postgresql/types.py +313 -0
- sqlalchemy/dialects/sqlite/__init__.py +57 -0
- sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
- sqlalchemy/dialects/sqlite/base.py +3056 -0
- sqlalchemy/dialects/sqlite/dml.py +263 -0
- sqlalchemy/dialects/sqlite/json.py +92 -0
- sqlalchemy/dialects/sqlite/provision.py +229 -0
- sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
- sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
- sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
- sqlalchemy/engine/__init__.py +62 -0
- sqlalchemy/engine/_py_processors.py +136 -0
- sqlalchemy/engine/_py_row.py +128 -0
- sqlalchemy/engine/_py_util.py +74 -0
- sqlalchemy/engine/base.py +3390 -0
- sqlalchemy/engine/characteristics.py +155 -0
- sqlalchemy/engine/create.py +893 -0
- sqlalchemy/engine/cursor.py +2298 -0
- sqlalchemy/engine/default.py +2394 -0
- sqlalchemy/engine/events.py +965 -0
- sqlalchemy/engine/interfaces.py +3471 -0
- sqlalchemy/engine/mock.py +134 -0
- sqlalchemy/engine/processors.py +61 -0
- sqlalchemy/engine/reflection.py +2102 -0
- sqlalchemy/engine/result.py +2399 -0
- sqlalchemy/engine/row.py +400 -0
- sqlalchemy/engine/strategies.py +16 -0
- sqlalchemy/engine/url.py +924 -0
- sqlalchemy/engine/util.py +167 -0
- sqlalchemy/event/__init__.py +26 -0
- sqlalchemy/event/api.py +220 -0
- sqlalchemy/event/attr.py +676 -0
- sqlalchemy/event/base.py +472 -0
- sqlalchemy/event/legacy.py +258 -0
- sqlalchemy/event/registry.py +390 -0
- sqlalchemy/events.py +17 -0
- sqlalchemy/exc.py +832 -0
- sqlalchemy/ext/__init__.py +11 -0
- sqlalchemy/ext/associationproxy.py +2027 -0
- sqlalchemy/ext/asyncio/__init__.py +25 -0
- sqlalchemy/ext/asyncio/base.py +281 -0
- sqlalchemy/ext/asyncio/engine.py +1471 -0
- sqlalchemy/ext/asyncio/exc.py +21 -0
- sqlalchemy/ext/asyncio/result.py +965 -0
- sqlalchemy/ext/asyncio/scoping.py +1599 -0
- sqlalchemy/ext/asyncio/session.py +1947 -0
- sqlalchemy/ext/automap.py +1701 -0
- sqlalchemy/ext/baked.py +570 -0
- sqlalchemy/ext/compiler.py +600 -0
- sqlalchemy/ext/declarative/__init__.py +65 -0
- sqlalchemy/ext/declarative/extensions.py +564 -0
- sqlalchemy/ext/horizontal_shard.py +478 -0
- sqlalchemy/ext/hybrid.py +1535 -0
- sqlalchemy/ext/indexable.py +364 -0
- sqlalchemy/ext/instrumentation.py +450 -0
- sqlalchemy/ext/mutable.py +1085 -0
- sqlalchemy/ext/mypy/__init__.py +6 -0
- sqlalchemy/ext/mypy/apply.py +324 -0
- sqlalchemy/ext/mypy/decl_class.py +515 -0
- sqlalchemy/ext/mypy/infer.py +590 -0
- sqlalchemy/ext/mypy/names.py +335 -0
- sqlalchemy/ext/mypy/plugin.py +303 -0
- sqlalchemy/ext/mypy/util.py +357 -0
- sqlalchemy/ext/orderinglist.py +439 -0
- sqlalchemy/ext/serializer.py +185 -0
- sqlalchemy/future/__init__.py +16 -0
- sqlalchemy/future/engine.py +15 -0
- sqlalchemy/inspection.py +174 -0
- sqlalchemy/log.py +288 -0
- sqlalchemy/orm/__init__.py +171 -0
- sqlalchemy/orm/_orm_constructors.py +2661 -0
- sqlalchemy/orm/_typing.py +179 -0
- sqlalchemy/orm/attributes.py +2845 -0
- sqlalchemy/orm/base.py +971 -0
- sqlalchemy/orm/bulk_persistence.py +2135 -0
- sqlalchemy/orm/clsregistry.py +571 -0
- sqlalchemy/orm/collections.py +1627 -0
- sqlalchemy/orm/context.py +3334 -0
- sqlalchemy/orm/decl_api.py +2004 -0
- sqlalchemy/orm/decl_base.py +2192 -0
- sqlalchemy/orm/dependency.py +1302 -0
- sqlalchemy/orm/descriptor_props.py +1092 -0
- sqlalchemy/orm/dynamic.py +300 -0
- sqlalchemy/orm/evaluator.py +379 -0
- sqlalchemy/orm/events.py +3252 -0
- sqlalchemy/orm/exc.py +237 -0
- sqlalchemy/orm/identity.py +302 -0
- sqlalchemy/orm/instrumentation.py +754 -0
- sqlalchemy/orm/interfaces.py +1496 -0
- sqlalchemy/orm/loading.py +1686 -0
- sqlalchemy/orm/mapped_collection.py +557 -0
- sqlalchemy/orm/mapper.py +4444 -0
- sqlalchemy/orm/path_registry.py +809 -0
- sqlalchemy/orm/persistence.py +1788 -0
- sqlalchemy/orm/properties.py +935 -0
- sqlalchemy/orm/query.py +3459 -0
- sqlalchemy/orm/relationships.py +3508 -0
- sqlalchemy/orm/scoping.py +2148 -0
- sqlalchemy/orm/session.py +5280 -0
- sqlalchemy/orm/state.py +1168 -0
- sqlalchemy/orm/state_changes.py +196 -0
- sqlalchemy/orm/strategies.py +3470 -0
- sqlalchemy/orm/strategy_options.py +2568 -0
- sqlalchemy/orm/sync.py +164 -0
- sqlalchemy/orm/unitofwork.py +796 -0
- sqlalchemy/orm/util.py +2403 -0
- sqlalchemy/orm/writeonly.py +674 -0
- sqlalchemy/pool/__init__.py +44 -0
- sqlalchemy/pool/base.py +1524 -0
- sqlalchemy/pool/events.py +375 -0
- sqlalchemy/pool/impl.py +588 -0
- sqlalchemy/py.typed +0 -0
- sqlalchemy/schema.py +69 -0
- sqlalchemy/sql/__init__.py +145 -0
- sqlalchemy/sql/_dml_constructors.py +132 -0
- sqlalchemy/sql/_elements_constructors.py +1872 -0
- sqlalchemy/sql/_orm_types.py +20 -0
- sqlalchemy/sql/_py_util.py +75 -0
- sqlalchemy/sql/_selectable_constructors.py +763 -0
- sqlalchemy/sql/_typing.py +482 -0
- sqlalchemy/sql/annotation.py +587 -0
- sqlalchemy/sql/base.py +2293 -0
- sqlalchemy/sql/cache_key.py +1057 -0
- sqlalchemy/sql/coercions.py +1404 -0
- sqlalchemy/sql/compiler.py +8081 -0
- sqlalchemy/sql/crud.py +1752 -0
- sqlalchemy/sql/ddl.py +1444 -0
- sqlalchemy/sql/default_comparator.py +551 -0
- sqlalchemy/sql/dml.py +1850 -0
- sqlalchemy/sql/elements.py +5589 -0
- sqlalchemy/sql/events.py +458 -0
- sqlalchemy/sql/expression.py +159 -0
- sqlalchemy/sql/functions.py +2158 -0
- sqlalchemy/sql/lambdas.py +1442 -0
- sqlalchemy/sql/naming.py +209 -0
- sqlalchemy/sql/operators.py +2623 -0
- sqlalchemy/sql/roles.py +323 -0
- sqlalchemy/sql/schema.py +6222 -0
- sqlalchemy/sql/selectable.py +7265 -0
- sqlalchemy/sql/sqltypes.py +3930 -0
- sqlalchemy/sql/traversals.py +1024 -0
- sqlalchemy/sql/type_api.py +2368 -0
- sqlalchemy/sql/util.py +1485 -0
- sqlalchemy/sql/visitors.py +1164 -0
- sqlalchemy/testing/__init__.py +96 -0
- sqlalchemy/testing/assertions.py +994 -0
- sqlalchemy/testing/assertsql.py +520 -0
- sqlalchemy/testing/asyncio.py +135 -0
- sqlalchemy/testing/config.py +434 -0
- sqlalchemy/testing/engines.py +483 -0
- sqlalchemy/testing/entities.py +117 -0
- sqlalchemy/testing/exclusions.py +476 -0
- sqlalchemy/testing/fixtures/__init__.py +28 -0
- sqlalchemy/testing/fixtures/base.py +384 -0
- sqlalchemy/testing/fixtures/mypy.py +332 -0
- sqlalchemy/testing/fixtures/orm.py +227 -0
- sqlalchemy/testing/fixtures/sql.py +482 -0
- sqlalchemy/testing/pickleable.py +155 -0
- sqlalchemy/testing/plugin/__init__.py +6 -0
- sqlalchemy/testing/plugin/bootstrap.py +51 -0
- sqlalchemy/testing/plugin/plugin_base.py +828 -0
- sqlalchemy/testing/plugin/pytestplugin.py +892 -0
- sqlalchemy/testing/profiling.py +329 -0
- sqlalchemy/testing/provision.py +603 -0
- sqlalchemy/testing/requirements.py +1945 -0
- sqlalchemy/testing/schema.py +198 -0
- sqlalchemy/testing/suite/__init__.py +19 -0
- sqlalchemy/testing/suite/test_cte.py +237 -0
- sqlalchemy/testing/suite/test_ddl.py +389 -0
- sqlalchemy/testing/suite/test_deprecations.py +153 -0
- sqlalchemy/testing/suite/test_dialect.py +776 -0
- sqlalchemy/testing/suite/test_insert.py +630 -0
- sqlalchemy/testing/suite/test_reflection.py +3557 -0
- sqlalchemy/testing/suite/test_results.py +504 -0
- sqlalchemy/testing/suite/test_rowcount.py +258 -0
- sqlalchemy/testing/suite/test_select.py +2010 -0
- sqlalchemy/testing/suite/test_sequence.py +317 -0
- sqlalchemy/testing/suite/test_types.py +2147 -0
- sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
- sqlalchemy/testing/suite/test_update_delete.py +139 -0
- sqlalchemy/testing/util.py +535 -0
- sqlalchemy/testing/warnings.py +52 -0
- sqlalchemy/types.py +74 -0
- sqlalchemy/util/__init__.py +162 -0
- sqlalchemy/util/_collections.py +712 -0
- sqlalchemy/util/_concurrency_py3k.py +288 -0
- sqlalchemy/util/_has_cy.py +40 -0
- sqlalchemy/util/_py_collections.py +541 -0
- sqlalchemy/util/compat.py +421 -0
- sqlalchemy/util/concurrency.py +110 -0
- sqlalchemy/util/deprecations.py +401 -0
- sqlalchemy/util/langhelpers.py +2203 -0
- sqlalchemy/util/preloaded.py +150 -0
- sqlalchemy/util/queue.py +322 -0
- sqlalchemy/util/tool_support.py +201 -0
- sqlalchemy/util/topological.py +120 -0
- sqlalchemy/util/typing.py +734 -0
- sqlalchemy-2.0.47.dist-info/METADATA +243 -0
- sqlalchemy-2.0.47.dist-info/RECORD +274 -0
- sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
- sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
- sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
# ext/declarative/extensions.py
|
|
2
|
+
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
|
|
3
|
+
# <see AUTHORS file>
|
|
4
|
+
#
|
|
5
|
+
# This module is part of SQLAlchemy and is released under
|
|
6
|
+
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
# mypy: ignore-errors
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
"""Public API functions and helpers for declarative."""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import collections
|
|
14
|
+
import contextlib
|
|
15
|
+
from typing import Any
|
|
16
|
+
from typing import Callable
|
|
17
|
+
from typing import TYPE_CHECKING
|
|
18
|
+
from typing import Union
|
|
19
|
+
|
|
20
|
+
from ... import exc as sa_exc
|
|
21
|
+
from ...engine import Connection
|
|
22
|
+
from ...engine import Engine
|
|
23
|
+
from ...orm import exc as orm_exc
|
|
24
|
+
from ...orm import relationships
|
|
25
|
+
from ...orm.base import _mapper_or_none
|
|
26
|
+
from ...orm.clsregistry import _resolver
|
|
27
|
+
from ...orm.decl_base import _DeferredMapperConfig
|
|
28
|
+
from ...orm.util import polymorphic_union
|
|
29
|
+
from ...schema import Table
|
|
30
|
+
from ...util import OrderedDict
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from ...sql.schema import MetaData
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ConcreteBase:
|
|
37
|
+
"""A helper class for 'concrete' declarative mappings.
|
|
38
|
+
|
|
39
|
+
:class:`.ConcreteBase` will use the :func:`.polymorphic_union`
|
|
40
|
+
function automatically, against all tables mapped as a subclass
|
|
41
|
+
to this class. The function is called via the
|
|
42
|
+
``__declare_last__()`` function, which is essentially
|
|
43
|
+
a hook for the :meth:`.after_configured` event.
|
|
44
|
+
|
|
45
|
+
:class:`.ConcreteBase` produces a mapped
|
|
46
|
+
table for the class itself. Compare to :class:`.AbstractConcreteBase`,
|
|
47
|
+
which does not.
|
|
48
|
+
|
|
49
|
+
Example::
|
|
50
|
+
|
|
51
|
+
from sqlalchemy.ext.declarative import ConcreteBase
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Employee(ConcreteBase, Base):
|
|
55
|
+
__tablename__ = "employee"
|
|
56
|
+
employee_id = Column(Integer, primary_key=True)
|
|
57
|
+
name = Column(String(50))
|
|
58
|
+
__mapper_args__ = {
|
|
59
|
+
"polymorphic_identity": "employee",
|
|
60
|
+
"concrete": True,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Manager(Employee):
|
|
65
|
+
__tablename__ = "manager"
|
|
66
|
+
employee_id = Column(Integer, primary_key=True)
|
|
67
|
+
name = Column(String(50))
|
|
68
|
+
manager_data = Column(String(40))
|
|
69
|
+
__mapper_args__ = {
|
|
70
|
+
"polymorphic_identity": "manager",
|
|
71
|
+
"concrete": True,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
The name of the discriminator column used by :func:`.polymorphic_union`
|
|
75
|
+
defaults to the name ``type``. To suit the use case of a mapping where an
|
|
76
|
+
actual column in a mapped table is already named ``type``, the
|
|
77
|
+
discriminator name can be configured by setting the
|
|
78
|
+
``_concrete_discriminator_name`` attribute::
|
|
79
|
+
|
|
80
|
+
class Employee(ConcreteBase, Base):
|
|
81
|
+
_concrete_discriminator_name = "_concrete_discriminator"
|
|
82
|
+
|
|
83
|
+
.. versionadded:: 1.3.19 Added the ``_concrete_discriminator_name``
|
|
84
|
+
attribute to :class:`_declarative.ConcreteBase` so that the
|
|
85
|
+
virtual discriminator column name can be customized.
|
|
86
|
+
|
|
87
|
+
.. versionchanged:: 1.4.2 The ``_concrete_discriminator_name`` attribute
|
|
88
|
+
need only be placed on the basemost class to take correct effect for
|
|
89
|
+
all subclasses. An explicit error message is now raised if the
|
|
90
|
+
mapped column names conflict with the discriminator name, whereas
|
|
91
|
+
in the 1.3.x series there would be some warnings and then a non-useful
|
|
92
|
+
query would be generated.
|
|
93
|
+
|
|
94
|
+
.. seealso::
|
|
95
|
+
|
|
96
|
+
:class:`.AbstractConcreteBase`
|
|
97
|
+
|
|
98
|
+
:ref:`concrete_inheritance`
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def _create_polymorphic_union(cls, mappers, discriminator_name):
|
|
105
|
+
return polymorphic_union(
|
|
106
|
+
OrderedDict(
|
|
107
|
+
(mp.polymorphic_identity, mp.local_table) for mp in mappers
|
|
108
|
+
),
|
|
109
|
+
discriminator_name,
|
|
110
|
+
"pjoin",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def __declare_first__(cls):
|
|
115
|
+
m = cls.__mapper__
|
|
116
|
+
if m.with_polymorphic:
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
discriminator_name = (
|
|
120
|
+
getattr(cls, "_concrete_discriminator_name", None) or "type"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
mappers = list(m.self_and_descendants)
|
|
124
|
+
pjoin = cls._create_polymorphic_union(mappers, discriminator_name)
|
|
125
|
+
m._set_with_polymorphic(("*", pjoin))
|
|
126
|
+
m._set_polymorphic_on(pjoin.c[discriminator_name])
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class AbstractConcreteBase(ConcreteBase):
|
|
130
|
+
"""A helper class for 'concrete' declarative mappings.
|
|
131
|
+
|
|
132
|
+
:class:`.AbstractConcreteBase` will use the :func:`.polymorphic_union`
|
|
133
|
+
function automatically, against all tables mapped as a subclass
|
|
134
|
+
to this class. The function is called via the
|
|
135
|
+
``__declare_first__()`` function, which is essentially
|
|
136
|
+
a hook for the :meth:`.before_configured` event.
|
|
137
|
+
|
|
138
|
+
:class:`.AbstractConcreteBase` applies :class:`_orm.Mapper` for its
|
|
139
|
+
immediately inheriting class, as would occur for any other
|
|
140
|
+
declarative mapped class. However, the :class:`_orm.Mapper` is not
|
|
141
|
+
mapped to any particular :class:`.Table` object. Instead, it's
|
|
142
|
+
mapped directly to the "polymorphic" selectable produced by
|
|
143
|
+
:func:`.polymorphic_union`, and performs no persistence operations on its
|
|
144
|
+
own. Compare to :class:`.ConcreteBase`, which maps its
|
|
145
|
+
immediately inheriting class to an actual
|
|
146
|
+
:class:`.Table` that stores rows directly.
|
|
147
|
+
|
|
148
|
+
.. note::
|
|
149
|
+
|
|
150
|
+
The :class:`.AbstractConcreteBase` delays the mapper creation of the
|
|
151
|
+
base class until all the subclasses have been defined,
|
|
152
|
+
as it needs to create a mapping against a selectable that will include
|
|
153
|
+
all subclass tables. In order to achieve this, it waits for the
|
|
154
|
+
**mapper configuration event** to occur, at which point it scans
|
|
155
|
+
through all the configured subclasses and sets up a mapping that will
|
|
156
|
+
query against all subclasses at once.
|
|
157
|
+
|
|
158
|
+
While this event is normally invoked automatically, in the case of
|
|
159
|
+
:class:`.AbstractConcreteBase`, it may be necessary to invoke it
|
|
160
|
+
explicitly after **all** subclass mappings are defined, if the first
|
|
161
|
+
operation is to be a query against this base class. To do so, once all
|
|
162
|
+
the desired classes have been configured, the
|
|
163
|
+
:meth:`_orm.registry.configure` method on the :class:`_orm.registry`
|
|
164
|
+
in use can be invoked, which is available in relation to a particular
|
|
165
|
+
declarative base class::
|
|
166
|
+
|
|
167
|
+
Base.registry.configure()
|
|
168
|
+
|
|
169
|
+
Example::
|
|
170
|
+
|
|
171
|
+
from sqlalchemy.orm import DeclarativeBase
|
|
172
|
+
from sqlalchemy.ext.declarative import AbstractConcreteBase
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class Base(DeclarativeBase):
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class Employee(AbstractConcreteBase, Base):
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class Manager(Employee):
|
|
184
|
+
__tablename__ = "manager"
|
|
185
|
+
employee_id = Column(Integer, primary_key=True)
|
|
186
|
+
name = Column(String(50))
|
|
187
|
+
manager_data = Column(String(40))
|
|
188
|
+
|
|
189
|
+
__mapper_args__ = {
|
|
190
|
+
"polymorphic_identity": "manager",
|
|
191
|
+
"concrete": True,
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
Base.registry.configure()
|
|
196
|
+
|
|
197
|
+
The abstract base class is handled by declarative in a special way;
|
|
198
|
+
at class configuration time, it behaves like a declarative mixin
|
|
199
|
+
or an ``__abstract__`` base class. Once classes are configured
|
|
200
|
+
and mappings are produced, it then gets mapped itself, but
|
|
201
|
+
after all of its descendants. This is a very unique system of mapping
|
|
202
|
+
not found in any other SQLAlchemy API feature.
|
|
203
|
+
|
|
204
|
+
Using this approach, we can specify columns and properties
|
|
205
|
+
that will take place on mapped subclasses, in the way that
|
|
206
|
+
we normally do as in :ref:`declarative_mixins`::
|
|
207
|
+
|
|
208
|
+
from sqlalchemy.ext.declarative import AbstractConcreteBase
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class Company(Base):
|
|
212
|
+
__tablename__ = "company"
|
|
213
|
+
id = Column(Integer, primary_key=True)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class Employee(AbstractConcreteBase, Base):
|
|
217
|
+
strict_attrs = True
|
|
218
|
+
|
|
219
|
+
employee_id = Column(Integer, primary_key=True)
|
|
220
|
+
|
|
221
|
+
@declared_attr
|
|
222
|
+
def company_id(cls):
|
|
223
|
+
return Column(ForeignKey("company.id"))
|
|
224
|
+
|
|
225
|
+
@declared_attr
|
|
226
|
+
def company(cls):
|
|
227
|
+
return relationship("Company")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class Manager(Employee):
|
|
231
|
+
__tablename__ = "manager"
|
|
232
|
+
|
|
233
|
+
name = Column(String(50))
|
|
234
|
+
manager_data = Column(String(40))
|
|
235
|
+
|
|
236
|
+
__mapper_args__ = {
|
|
237
|
+
"polymorphic_identity": "manager",
|
|
238
|
+
"concrete": True,
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
Base.registry.configure()
|
|
243
|
+
|
|
244
|
+
When we make use of our mappings however, both ``Manager`` and
|
|
245
|
+
``Employee`` will have an independently usable ``.company`` attribute::
|
|
246
|
+
|
|
247
|
+
session.execute(select(Employee).filter(Employee.company.has(id=5)))
|
|
248
|
+
|
|
249
|
+
:param strict_attrs: when specified on the base class, "strict" attribute
|
|
250
|
+
mode is enabled which attempts to limit ORM mapped attributes on the
|
|
251
|
+
base class to only those that are immediately present, while still
|
|
252
|
+
preserving "polymorphic" loading behavior.
|
|
253
|
+
|
|
254
|
+
.. versionadded:: 2.0
|
|
255
|
+
|
|
256
|
+
.. seealso::
|
|
257
|
+
|
|
258
|
+
:class:`.ConcreteBase`
|
|
259
|
+
|
|
260
|
+
:ref:`concrete_inheritance`
|
|
261
|
+
|
|
262
|
+
:ref:`abstract_concrete_base`
|
|
263
|
+
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
__no_table__ = True
|
|
267
|
+
|
|
268
|
+
@classmethod
|
|
269
|
+
def __declare_first__(cls):
|
|
270
|
+
cls._sa_decl_prepare_nocascade()
|
|
271
|
+
|
|
272
|
+
@classmethod
|
|
273
|
+
def _sa_decl_prepare_nocascade(cls):
|
|
274
|
+
if getattr(cls, "__mapper__", None):
|
|
275
|
+
return
|
|
276
|
+
|
|
277
|
+
to_map = _DeferredMapperConfig.config_for_cls(cls)
|
|
278
|
+
|
|
279
|
+
# can't rely on 'self_and_descendants' here
|
|
280
|
+
# since technically an immediate subclass
|
|
281
|
+
# might not be mapped, but a subclass
|
|
282
|
+
# may be.
|
|
283
|
+
mappers = []
|
|
284
|
+
stack = list(cls.__subclasses__())
|
|
285
|
+
while stack:
|
|
286
|
+
klass = stack.pop()
|
|
287
|
+
stack.extend(klass.__subclasses__())
|
|
288
|
+
mn = _mapper_or_none(klass)
|
|
289
|
+
if mn is not None:
|
|
290
|
+
mappers.append(mn)
|
|
291
|
+
|
|
292
|
+
discriminator_name = (
|
|
293
|
+
getattr(cls, "_concrete_discriminator_name", None) or "type"
|
|
294
|
+
)
|
|
295
|
+
pjoin = cls._create_polymorphic_union(mappers, discriminator_name)
|
|
296
|
+
|
|
297
|
+
# For columns that were declared on the class, these
|
|
298
|
+
# are normally ignored with the "__no_table__" mapping,
|
|
299
|
+
# unless they have a different attribute key vs. col name
|
|
300
|
+
# and are in the properties argument.
|
|
301
|
+
# In that case, ensure we update the properties entry
|
|
302
|
+
# to the correct column from the pjoin target table.
|
|
303
|
+
declared_cols = set(to_map.declared_columns)
|
|
304
|
+
declared_col_keys = {c.key for c in declared_cols}
|
|
305
|
+
for k, v in list(to_map.properties.items()):
|
|
306
|
+
if v in declared_cols:
|
|
307
|
+
to_map.properties[k] = pjoin.c[v.key]
|
|
308
|
+
declared_col_keys.remove(v.key)
|
|
309
|
+
|
|
310
|
+
to_map.local_table = pjoin
|
|
311
|
+
|
|
312
|
+
strict_attrs = cls.__dict__.get("strict_attrs", False)
|
|
313
|
+
|
|
314
|
+
m_args = to_map.mapper_args_fn or dict
|
|
315
|
+
|
|
316
|
+
def mapper_args():
|
|
317
|
+
args = m_args()
|
|
318
|
+
args["polymorphic_on"] = pjoin.c[discriminator_name]
|
|
319
|
+
args["polymorphic_abstract"] = True
|
|
320
|
+
if strict_attrs:
|
|
321
|
+
args["include_properties"] = (
|
|
322
|
+
set(pjoin.primary_key)
|
|
323
|
+
| declared_col_keys
|
|
324
|
+
| {discriminator_name}
|
|
325
|
+
)
|
|
326
|
+
args["with_polymorphic"] = ("*", pjoin)
|
|
327
|
+
return args
|
|
328
|
+
|
|
329
|
+
to_map.mapper_args_fn = mapper_args
|
|
330
|
+
|
|
331
|
+
to_map.map()
|
|
332
|
+
|
|
333
|
+
stack = [cls]
|
|
334
|
+
while stack:
|
|
335
|
+
scls = stack.pop(0)
|
|
336
|
+
stack.extend(scls.__subclasses__())
|
|
337
|
+
sm = _mapper_or_none(scls)
|
|
338
|
+
if sm and sm.concrete and sm.inherits is None:
|
|
339
|
+
for sup_ in scls.__mro__[1:]:
|
|
340
|
+
sup_sm = _mapper_or_none(sup_)
|
|
341
|
+
if sup_sm:
|
|
342
|
+
sm._set_concrete_base(sup_sm)
|
|
343
|
+
break
|
|
344
|
+
|
|
345
|
+
@classmethod
|
|
346
|
+
def _sa_raise_deferred_config(cls):
|
|
347
|
+
raise orm_exc.UnmappedClassError(
|
|
348
|
+
cls,
|
|
349
|
+
msg="Class %s is a subclass of AbstractConcreteBase and "
|
|
350
|
+
"has a mapping pending until all subclasses are defined. "
|
|
351
|
+
"Call the sqlalchemy.orm.configure_mappers() function after "
|
|
352
|
+
"all subclasses have been defined to "
|
|
353
|
+
"complete the mapping of this class."
|
|
354
|
+
% orm_exc._safe_cls_name(cls),
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class DeferredReflection:
|
|
359
|
+
"""A helper class for construction of mappings based on
|
|
360
|
+
a deferred reflection step.
|
|
361
|
+
|
|
362
|
+
Normally, declarative can be used with reflection by
|
|
363
|
+
setting a :class:`_schema.Table` object using autoload_with=engine
|
|
364
|
+
as the ``__table__`` attribute on a declarative class.
|
|
365
|
+
The caveat is that the :class:`_schema.Table` must be fully
|
|
366
|
+
reflected, or at the very least have a primary key column,
|
|
367
|
+
at the point at which a normal declarative mapping is
|
|
368
|
+
constructed, meaning the :class:`_engine.Engine` must be available
|
|
369
|
+
at class declaration time.
|
|
370
|
+
|
|
371
|
+
The :class:`.DeferredReflection` mixin moves the construction
|
|
372
|
+
of mappers to be at a later point, after a specific
|
|
373
|
+
method is called which first reflects all :class:`_schema.Table`
|
|
374
|
+
objects created so far. Classes can define it as such::
|
|
375
|
+
|
|
376
|
+
from sqlalchemy.ext.declarative import declarative_base
|
|
377
|
+
from sqlalchemy.ext.declarative import DeferredReflection
|
|
378
|
+
|
|
379
|
+
Base = declarative_base()
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
class MyClass(DeferredReflection, Base):
|
|
383
|
+
__tablename__ = "mytable"
|
|
384
|
+
|
|
385
|
+
Above, ``MyClass`` is not yet mapped. After a series of
|
|
386
|
+
classes have been defined in the above fashion, all tables
|
|
387
|
+
can be reflected and mappings created using
|
|
388
|
+
:meth:`.prepare`::
|
|
389
|
+
|
|
390
|
+
engine = create_engine("someengine://...")
|
|
391
|
+
DeferredReflection.prepare(engine)
|
|
392
|
+
|
|
393
|
+
The :class:`.DeferredReflection` mixin can be applied to individual
|
|
394
|
+
classes, used as the base for the declarative base itself,
|
|
395
|
+
or used in a custom abstract class. Using an abstract base
|
|
396
|
+
allows that only a subset of classes to be prepared for a
|
|
397
|
+
particular prepare step, which is necessary for applications
|
|
398
|
+
that use more than one engine. For example, if an application
|
|
399
|
+
has two engines, you might use two bases, and prepare each
|
|
400
|
+
separately, e.g.::
|
|
401
|
+
|
|
402
|
+
class ReflectedOne(DeferredReflection, Base):
|
|
403
|
+
__abstract__ = True
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
class ReflectedTwo(DeferredReflection, Base):
|
|
407
|
+
__abstract__ = True
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
class MyClass(ReflectedOne):
|
|
411
|
+
__tablename__ = "mytable"
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class MyOtherClass(ReflectedOne):
|
|
415
|
+
__tablename__ = "myothertable"
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
class YetAnotherClass(ReflectedTwo):
|
|
419
|
+
__tablename__ = "yetanothertable"
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
# ... etc.
|
|
423
|
+
|
|
424
|
+
Above, the class hierarchies for ``ReflectedOne`` and
|
|
425
|
+
``ReflectedTwo`` can be configured separately::
|
|
426
|
+
|
|
427
|
+
ReflectedOne.prepare(engine_one)
|
|
428
|
+
ReflectedTwo.prepare(engine_two)
|
|
429
|
+
|
|
430
|
+
.. seealso::
|
|
431
|
+
|
|
432
|
+
:ref:`orm_declarative_reflected_deferred_reflection` - in the
|
|
433
|
+
:ref:`orm_declarative_table_config_toplevel` section.
|
|
434
|
+
|
|
435
|
+
"""
|
|
436
|
+
|
|
437
|
+
@classmethod
|
|
438
|
+
def prepare(
|
|
439
|
+
cls, bind: Union[Engine, Connection], **reflect_kw: Any
|
|
440
|
+
) -> None:
|
|
441
|
+
r"""Reflect all :class:`_schema.Table` objects for all current
|
|
442
|
+
:class:`.DeferredReflection` subclasses
|
|
443
|
+
|
|
444
|
+
:param bind: :class:`_engine.Engine` or :class:`_engine.Connection`
|
|
445
|
+
instance
|
|
446
|
+
|
|
447
|
+
..versionchanged:: 2.0.16 a :class:`_engine.Connection` is also
|
|
448
|
+
accepted.
|
|
449
|
+
|
|
450
|
+
:param \**reflect_kw: additional keyword arguments passed to
|
|
451
|
+
:meth:`_schema.MetaData.reflect`, such as
|
|
452
|
+
:paramref:`_schema.MetaData.reflect.views`.
|
|
453
|
+
|
|
454
|
+
.. versionadded:: 2.0.16
|
|
455
|
+
|
|
456
|
+
"""
|
|
457
|
+
|
|
458
|
+
to_map = _DeferredMapperConfig.classes_for_base(cls)
|
|
459
|
+
|
|
460
|
+
metadata_to_table = collections.defaultdict(set)
|
|
461
|
+
|
|
462
|
+
# first collect the primary __table__ for each class into a
|
|
463
|
+
# collection of metadata/schemaname -> table names
|
|
464
|
+
for thingy in to_map:
|
|
465
|
+
if thingy.local_table is not None:
|
|
466
|
+
metadata_to_table[
|
|
467
|
+
(thingy.local_table.metadata, thingy.local_table.schema)
|
|
468
|
+
].add(thingy.local_table.name)
|
|
469
|
+
|
|
470
|
+
# then reflect all those tables into their metadatas
|
|
471
|
+
|
|
472
|
+
if isinstance(bind, Connection):
|
|
473
|
+
conn = bind
|
|
474
|
+
ctx = contextlib.nullcontext(enter_result=conn)
|
|
475
|
+
elif isinstance(bind, Engine):
|
|
476
|
+
ctx = bind.connect()
|
|
477
|
+
else:
|
|
478
|
+
raise sa_exc.ArgumentError(
|
|
479
|
+
f"Expected Engine or Connection, got {bind!r}"
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
with ctx as conn:
|
|
483
|
+
for (metadata, schema), table_names in metadata_to_table.items():
|
|
484
|
+
metadata.reflect(
|
|
485
|
+
conn,
|
|
486
|
+
only=table_names,
|
|
487
|
+
schema=schema,
|
|
488
|
+
extend_existing=True,
|
|
489
|
+
autoload_replace=False,
|
|
490
|
+
**reflect_kw,
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
metadata_to_table.clear()
|
|
494
|
+
|
|
495
|
+
# .map() each class, then go through relationships and look
|
|
496
|
+
# for secondary
|
|
497
|
+
for thingy in to_map:
|
|
498
|
+
thingy.map()
|
|
499
|
+
|
|
500
|
+
mapper = thingy.cls.__mapper__
|
|
501
|
+
metadata = mapper.class_.metadata
|
|
502
|
+
|
|
503
|
+
for rel in mapper._props.values():
|
|
504
|
+
if (
|
|
505
|
+
isinstance(rel, relationships.RelationshipProperty)
|
|
506
|
+
and rel._init_args.secondary._is_populated()
|
|
507
|
+
):
|
|
508
|
+
secondary_arg = rel._init_args.secondary
|
|
509
|
+
|
|
510
|
+
if isinstance(secondary_arg.argument, Table):
|
|
511
|
+
secondary_table = secondary_arg.argument
|
|
512
|
+
metadata_to_table[
|
|
513
|
+
(
|
|
514
|
+
secondary_table.metadata,
|
|
515
|
+
secondary_table.schema,
|
|
516
|
+
)
|
|
517
|
+
].add(secondary_table.name)
|
|
518
|
+
elif isinstance(secondary_arg.argument, str):
|
|
519
|
+
_, resolve_arg = _resolver(rel.parent.class_, rel)
|
|
520
|
+
|
|
521
|
+
resolver = resolve_arg(
|
|
522
|
+
secondary_arg.argument, True
|
|
523
|
+
)
|
|
524
|
+
metadata_to_table[
|
|
525
|
+
(metadata, thingy.local_table.schema)
|
|
526
|
+
].add(secondary_arg.argument)
|
|
527
|
+
|
|
528
|
+
resolver._resolvers += (
|
|
529
|
+
cls._sa_deferred_table_resolver(metadata),
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
secondary_arg.argument = resolver()
|
|
533
|
+
|
|
534
|
+
for (metadata, schema), table_names in metadata_to_table.items():
|
|
535
|
+
metadata.reflect(
|
|
536
|
+
conn,
|
|
537
|
+
only=table_names,
|
|
538
|
+
schema=schema,
|
|
539
|
+
extend_existing=True,
|
|
540
|
+
autoload_replace=False,
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
@classmethod
|
|
544
|
+
def _sa_deferred_table_resolver(
|
|
545
|
+
cls, metadata: MetaData
|
|
546
|
+
) -> Callable[[str], Table]:
|
|
547
|
+
def _resolve(key: str) -> Table:
|
|
548
|
+
# reflection has already occurred so this Table would have
|
|
549
|
+
# its contents already
|
|
550
|
+
return Table(key, metadata)
|
|
551
|
+
|
|
552
|
+
return _resolve
|
|
553
|
+
|
|
554
|
+
_sa_decl_prepare = True
|
|
555
|
+
|
|
556
|
+
@classmethod
|
|
557
|
+
def _sa_raise_deferred_config(cls):
|
|
558
|
+
raise orm_exc.UnmappedClassError(
|
|
559
|
+
cls,
|
|
560
|
+
msg="Class %s is a subclass of DeferredReflection. "
|
|
561
|
+
"Mappings are not produced until the .prepare() "
|
|
562
|
+
"method is called on the class hierarchy."
|
|
563
|
+
% orm_exc._safe_cls_name(cls),
|
|
564
|
+
)
|