Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/ext/declarative/extensions.py: 37%
104 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
1# ext/declarative/extensions.py
2# Copyright (C) 2005-2022 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"""Public API functions and helpers for declarative."""
10from ... import inspection
11from ... import util
12from ...orm import exc as orm_exc
13from ...orm import registry
14from ...orm import relationships
15from ...orm.base import _mapper_or_none
16from ...orm.clsregistry import _resolver
17from ...orm.decl_base import _DeferredMapperConfig
18from ...orm.util import polymorphic_union
19from ...schema import Table
20from ...util import OrderedDict
23@util.deprecated(
24 "2.0",
25 "the instrument_declarative function is deprecated "
26 "and will be removed in SQLAlhcemy 2.0. Please use "
27 ":meth:`_orm.registry.map_declaratively",
28)
29def instrument_declarative(cls, cls_registry, metadata):
30 """Given a class, configure the class declaratively,
31 using the given registry, which can be any dictionary, and
32 MetaData object.
34 """
35 registry(metadata=metadata, class_registry=cls_registry).map_declaratively(
36 cls
37 )
40class ConcreteBase(object):
41 """A helper class for 'concrete' declarative mappings.
43 :class:`.ConcreteBase` will use the :func:`.polymorphic_union`
44 function automatically, against all tables mapped as a subclass
45 to this class. The function is called via the
46 ``__declare_last__()`` function, which is essentially
47 a hook for the :meth:`.after_configured` event.
49 :class:`.ConcreteBase` produces a mapped
50 table for the class itself. Compare to :class:`.AbstractConcreteBase`,
51 which does not.
53 Example::
55 from sqlalchemy.ext.declarative import ConcreteBase
57 class Employee(ConcreteBase, Base):
58 __tablename__ = 'employee'
59 employee_id = Column(Integer, primary_key=True)
60 name = Column(String(50))
61 __mapper_args__ = {
62 'polymorphic_identity':'employee',
63 'concrete':True}
65 class Manager(Employee):
66 __tablename__ = 'manager'
67 employee_id = Column(Integer, primary_key=True)
68 name = Column(String(50))
69 manager_data = Column(String(40))
70 __mapper_args__ = {
71 'polymorphic_identity':'manager',
72 'concrete':True}
75 The name of the discriminator column used by :func:`.polymorphic_union`
76 defaults to the name ``type``. To suit the use case of a mapping where an
77 actual column in a mapped table is already named ``type``, the
78 discriminator name can be configured by setting the
79 ``_concrete_discriminator_name`` attribute::
81 class Employee(ConcreteBase, Base):
82 _concrete_discriminator_name = '_concrete_discriminator'
84 .. versionadded:: 1.3.19 Added the ``_concrete_discriminator_name``
85 attribute to :class:`_declarative.ConcreteBase` so that the
86 virtual discriminator column name can be customized.
88 .. versionchanged:: 1.4.2 The ``_concrete_discriminator_name`` attribute
89 need only be placed on the basemost class to take correct effect for
90 all subclasses. An explicit error message is now raised if the
91 mapped column names conflict with the discriminator name, whereas
92 in the 1.3.x series there would be some warnings and then a non-useful
93 query would be generated.
95 .. seealso::
97 :class:`.AbstractConcreteBase`
99 :ref:`concrete_inheritance`
102 """
104 @classmethod
105 def _create_polymorphic_union(cls, mappers, discriminator_name):
106 return polymorphic_union(
107 OrderedDict(
108 (mp.polymorphic_identity, mp.local_table) for mp in mappers
109 ),
110 discriminator_name,
111 "pjoin",
112 )
114 @classmethod
115 def __declare_first__(cls):
116 m = cls.__mapper__
117 if m.with_polymorphic:
118 return
120 discriminator_name = (
121 getattr(cls, "_concrete_discriminator_name", None) or "type"
122 )
124 mappers = list(m.self_and_descendants)
125 pjoin = cls._create_polymorphic_union(mappers, discriminator_name)
126 m._set_with_polymorphic(("*", pjoin))
127 m._set_polymorphic_on(pjoin.c[discriminator_name])
130class AbstractConcreteBase(ConcreteBase):
131 """A helper class for 'concrete' declarative mappings.
133 :class:`.AbstractConcreteBase` will use the :func:`.polymorphic_union`
134 function automatically, against all tables mapped as a subclass
135 to this class. The function is called via the
136 ``__declare_last__()`` function, which is essentially
137 a hook for the :meth:`.after_configured` event.
139 :class:`.AbstractConcreteBase` does produce a mapped class
140 for the base class, however it is not persisted to any table; it
141 is instead mapped directly to the "polymorphic" selectable directly
142 and is only used for selecting. Compare to :class:`.ConcreteBase`,
143 which does create a persisted table for the base class.
145 .. note::
147 The :class:`.AbstractConcreteBase` delays the mapper creation of the
148 base class until all the subclasses have been defined,
149 as it needs to create a mapping against a selectable that will include
150 all subclass tables. In order to achieve this, it waits for the
151 **mapper configuration event** to occur, at which point it scans
152 through all the configured subclasses and sets up a mapping that will
153 query against all subclasses at once.
155 While this event is normally invoked automatically, in the case of
156 :class:`.AbstractConcreteBase`, it may be necessary to invoke it
157 explicitly after **all** subclass mappings are defined, if the first
158 operation is to be a query against this base class. To do so, once all
159 the desired classes have been configured, the
160 :meth:`_orm.registry.configure` method on the :class:`_orm.registry`
161 in use can be invoked, which is available in relation to a particular
162 declarative base class::
164 Base.registry.configure()
166 Example::
168 from sqlalchemy.ext.declarative import AbstractConcreteBase
169 from sqlalchemy.orm import declarative_base
171 Base = declarative_base()
173 class Employee(AbstractConcreteBase, Base):
174 pass
176 class Manager(Employee):
177 __tablename__ = 'manager'
178 employee_id = Column(Integer, primary_key=True)
179 name = Column(String(50))
180 manager_data = Column(String(40))
182 __mapper_args__ = {
183 'polymorphic_identity':'manager',
184 'concrete':True
185 }
187 Base.registry.configure()
189 The abstract base class is handled by declarative in a special way;
190 at class configuration time, it behaves like a declarative mixin
191 or an ``__abstract__`` base class. Once classes are configured
192 and mappings are produced, it then gets mapped itself, but
193 after all of its descendants. This is a very unique system of mapping
194 not found in any other SQLAlchemy system.
196 Using this approach, we can specify columns and properties
197 that will take place on mapped subclasses, in the way that
198 we normally do as in :ref:`declarative_mixins`::
200 class Company(Base):
201 __tablename__ = 'company'
202 id = Column(Integer, primary_key=True)
204 class Employee(AbstractConcreteBase, Base):
205 employee_id = Column(Integer, primary_key=True)
207 @declared_attr
208 def company_id(cls):
209 return Column(ForeignKey('company.id'))
211 @declared_attr
212 def company(cls):
213 return relationship("Company")
215 class Manager(Employee):
216 __tablename__ = 'manager'
218 name = Column(String(50))
219 manager_data = Column(String(40))
221 __mapper_args__ = {
222 'polymorphic_identity':'manager',
223 'concrete':True
224 }
226 Base.registry.configure()
228 When we make use of our mappings however, both ``Manager`` and
229 ``Employee`` will have an independently usable ``.company`` attribute::
231 session.execute(
232 select(Employee).filter(Employee.company.has(id=5))
233 )
235 .. seealso::
237 :class:`.ConcreteBase`
239 :ref:`concrete_inheritance`
241 :ref:`abstract_concrete_base`
243 """
245 __no_table__ = True
247 @classmethod
248 def __declare_first__(cls):
249 cls._sa_decl_prepare_nocascade()
251 @classmethod
252 def _sa_decl_prepare_nocascade(cls):
253 if getattr(cls, "__mapper__", None):
254 return
256 to_map = _DeferredMapperConfig.config_for_cls(cls)
258 # can't rely on 'self_and_descendants' here
259 # since technically an immediate subclass
260 # might not be mapped, but a subclass
261 # may be.
262 mappers = []
263 stack = list(cls.__subclasses__())
264 while stack:
265 klass = stack.pop()
266 stack.extend(klass.__subclasses__())
267 mn = _mapper_or_none(klass)
268 if mn is not None:
269 mappers.append(mn)
271 discriminator_name = (
272 getattr(cls, "_concrete_discriminator_name", None) or "type"
273 )
274 pjoin = cls._create_polymorphic_union(mappers, discriminator_name)
276 # For columns that were declared on the class, these
277 # are normally ignored with the "__no_table__" mapping,
278 # unless they have a different attribute key vs. col name
279 # and are in the properties argument.
280 # In that case, ensure we update the properties entry
281 # to the correct column from the pjoin target table.
282 declared_cols = set(to_map.declared_columns)
283 for k, v in list(to_map.properties.items()):
284 if v in declared_cols:
285 to_map.properties[k] = pjoin.c[v.key]
287 to_map.local_table = pjoin
289 m_args = to_map.mapper_args_fn or dict
291 def mapper_args():
292 args = m_args()
293 args["polymorphic_on"] = pjoin.c[discriminator_name]
294 return args
296 to_map.mapper_args_fn = mapper_args
298 m = to_map.map()
300 for scls in cls.__subclasses__():
301 sm = _mapper_or_none(scls)
302 if sm and sm.concrete and cls in scls.__bases__:
303 sm._set_concrete_base(m)
305 @classmethod
306 def _sa_raise_deferred_config(cls):
307 raise orm_exc.UnmappedClassError(
308 cls,
309 msg="Class %s is a subclass of AbstractConcreteBase and "
310 "has a mapping pending until all subclasses are defined. "
311 "Call the sqlalchemy.orm.configure_mappers() function after "
312 "all subclasses have been defined to "
313 "complete the mapping of this class."
314 % orm_exc._safe_cls_name(cls),
315 )
318class DeferredReflection(object):
319 """A helper class for construction of mappings based on
320 a deferred reflection step.
322 Normally, declarative can be used with reflection by
323 setting a :class:`_schema.Table` object using autoload_with=engine
324 as the ``__table__`` attribute on a declarative class.
325 The caveat is that the :class:`_schema.Table` must be fully
326 reflected, or at the very least have a primary key column,
327 at the point at which a normal declarative mapping is
328 constructed, meaning the :class:`_engine.Engine` must be available
329 at class declaration time.
331 The :class:`.DeferredReflection` mixin moves the construction
332 of mappers to be at a later point, after a specific
333 method is called which first reflects all :class:`_schema.Table`
334 objects created so far. Classes can define it as such::
336 from sqlalchemy.ext.declarative import declarative_base
337 from sqlalchemy.ext.declarative import DeferredReflection
338 Base = declarative_base()
340 class MyClass(DeferredReflection, Base):
341 __tablename__ = 'mytable'
343 Above, ``MyClass`` is not yet mapped. After a series of
344 classes have been defined in the above fashion, all tables
345 can be reflected and mappings created using
346 :meth:`.prepare`::
348 engine = create_engine("someengine://...")
349 DeferredReflection.prepare(engine)
351 The :class:`.DeferredReflection` mixin can be applied to individual
352 classes, used as the base for the declarative base itself,
353 or used in a custom abstract class. Using an abstract base
354 allows that only a subset of classes to be prepared for a
355 particular prepare step, which is necessary for applications
356 that use more than one engine. For example, if an application
357 has two engines, you might use two bases, and prepare each
358 separately, e.g.::
360 class ReflectedOne(DeferredReflection, Base):
361 __abstract__ = True
363 class ReflectedTwo(DeferredReflection, Base):
364 __abstract__ = True
366 class MyClass(ReflectedOne):
367 __tablename__ = 'mytable'
369 class MyOtherClass(ReflectedOne):
370 __tablename__ = 'myothertable'
372 class YetAnotherClass(ReflectedTwo):
373 __tablename__ = 'yetanothertable'
375 # ... etc.
377 Above, the class hierarchies for ``ReflectedOne`` and
378 ``ReflectedTwo`` can be configured separately::
380 ReflectedOne.prepare(engine_one)
381 ReflectedTwo.prepare(engine_two)
383 .. seealso::
385 :ref:`orm_declarative_reflected_deferred_reflection` - in the
386 :ref:`orm_declarative_table_config_toplevel` section.
388 """
390 @classmethod
391 def prepare(cls, engine):
392 """Reflect all :class:`_schema.Table` objects for all current
393 :class:`.DeferredReflection` subclasses"""
395 to_map = _DeferredMapperConfig.classes_for_base(cls)
397 with inspection.inspect(engine)._inspection_context() as insp:
398 for thingy in to_map:
399 cls._sa_decl_prepare(thingy.local_table, insp)
400 thingy.map()
401 mapper = thingy.cls.__mapper__
402 metadata = mapper.class_.metadata
403 for rel in mapper._props.values():
404 if (
405 isinstance(rel, relationships.RelationshipProperty)
406 and rel.secondary is not None
407 ):
408 if isinstance(rel.secondary, Table):
409 cls._reflect_table(rel.secondary, insp)
410 elif isinstance(rel.secondary, str):
412 _, resolve_arg = _resolver(rel.parent.class_, rel)
414 rel.secondary = resolve_arg(rel.secondary)
415 rel.secondary._resolvers += (
416 cls._sa_deferred_table_resolver(
417 insp, metadata
418 ),
419 )
421 # controversy! do we resolve it here? or leave
422 # it deferred? I think doing it here is necessary
423 # so the connection does not leak.
424 rel.secondary = rel.secondary()
426 @classmethod
427 def _sa_deferred_table_resolver(cls, inspector, metadata):
428 def _resolve(key):
429 t1 = Table(key, metadata)
430 cls._reflect_table(t1, inspector)
431 return t1
433 return _resolve
435 @classmethod
436 def _sa_decl_prepare(cls, local_table, inspector):
437 # autoload Table, which is already
438 # present in the metadata. This
439 # will fill in db-loaded columns
440 # into the existing Table object.
441 if local_table is not None:
442 cls._reflect_table(local_table, inspector)
444 @classmethod
445 def _sa_raise_deferred_config(cls):
446 raise orm_exc.UnmappedClassError(
447 cls,
448 msg="Class %s is a subclass of DeferredReflection. "
449 "Mappings are not produced until the .prepare() "
450 "method is called on the class hierarchy."
451 % orm_exc._safe_cls_name(cls),
452 )
454 @classmethod
455 def _reflect_table(cls, table, inspector):
456 Table(
457 table.name,
458 table.metadata,
459 extend_existing=True,
460 autoload_replace=False,
461 autoload_with=inspector,
462 schema=table.schema,
463 )