diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-08-17 17:47:19 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-08-18 13:47:14 -0400 |
commit | d0abdbe247dc82db51aa0af3d31b051042a8ba87 (patch) | |
tree | 53d2b906fdccc76378f68104d0b8786ab0aa884d /lib/sqlalchemy/ext | |
parent | a47d76ca25275344345b208def5f72292e8687b4 (diff) | |
download | sqlalchemy-d0abdbe247dc82db51aa0af3d31b051042a8ba87.tar.gz |
improve abstractconcretebase
try to limit the attributes on the base and set up wpoly
etc so that things still work the way we want.
It seems like I've tried this in the past before so not sure
if this is actually working or if there are problems. it needs
a little more strictness on how you set up your base since
attributes are no longer implicit. So, it seems like perhaps
the new behavior should be on a flag or something like
"strict_attributes=True", something like that, so that nothing
breaks for existing users and we can slowly deal with the new
way being a little bit less worse than the old way.
Fixes: #8403
Change-Id: Ic9652d9a0b024d649807aaf3505e67173e7dc3b9
Diffstat (limited to 'lib/sqlalchemy/ext')
-rw-r--r-- | lib/sqlalchemy/ext/declarative/extensions.py | 60 |
1 files changed, 47 insertions, 13 deletions
diff --git a/lib/sqlalchemy/ext/declarative/extensions.py b/lib/sqlalchemy/ext/declarative/extensions.py index 29eb76e82..596379bac 100644 --- a/lib/sqlalchemy/ext/declarative/extensions.py +++ b/lib/sqlalchemy/ext/declarative/extensions.py @@ -123,14 +123,18 @@ class AbstractConcreteBase(ConcreteBase): :class:`.AbstractConcreteBase` will use the :func:`.polymorphic_union` function automatically, against all tables mapped as a subclass to this class. The function is called via the - ``__declare_last__()`` function, which is essentially - a hook for the :meth:`.after_configured` event. - - :class:`.AbstractConcreteBase` does produce a mapped class - for the base class, however it is not persisted to any table; it - is instead mapped directly to the "polymorphic" selectable directly - and is only used for selecting. Compare to :class:`.ConcreteBase`, - which does create a persisted table for the base class. + ``__declare_first__()`` function, which is essentially + a hook for the :meth:`.before_configured` event. + + :class:`.AbstractConcreteBase` applies :class:`_orm.Mapper` for its + immediately inheriting class, as would occur for any other + declarative mapped class. However, the :class:`_orm.Mapper` is not + mapped to any particular :class:`.Table` object. Instead, it's + mapped directly to the "polymorphic" selectable produced by + :func:`.polymorphic_union`, and performs no persistence operations on its + own. Compare to :class:`.ConcreteBase`, which maps its + immediately inheriting class to an actual + :class:`.Table` that stores rows directly. .. note:: @@ -182,17 +186,21 @@ class AbstractConcreteBase(ConcreteBase): or an ``__abstract__`` base class. Once classes are configured and mappings are produced, it then gets mapped itself, but after all of its descendants. This is a very unique system of mapping - not found in any other SQLAlchemy system. + not found in any other SQLAlchemy API feature. Using this approach, we can specify columns and properties that will take place on mapped subclasses, in the way that we normally do as in :ref:`declarative_mixins`:: + from sqlalchemy.ext.declarative import AbstractConcreteBase + class Company(Base): __tablename__ = 'company' id = Column(Integer, primary_key=True) class Employee(AbstractConcreteBase, Base): + strict_attrs = True + employee_id = Column(Integer, primary_key=True) @declared_attr @@ -223,6 +231,13 @@ class AbstractConcreteBase(ConcreteBase): select(Employee).filter(Employee.company.has(id=5)) ) + :param strict_attrs: when specified on the base class, "strict" attribute + mode is enabled which attempts to limit ORM mapped attributes on the + base class to only those that are immediately present, while still + preserving "polymorphic" loading behavior. + + .. versionadded:: 2.0 + .. seealso:: :class:`.ConcreteBase` @@ -271,27 +286,46 @@ class AbstractConcreteBase(ConcreteBase): # In that case, ensure we update the properties entry # to the correct column from the pjoin target table. declared_cols = set(to_map.declared_columns) + declared_col_keys = {c.key for c in declared_cols} for k, v in list(to_map.properties.items()): if v in declared_cols: to_map.properties[k] = pjoin.c[v.key] + declared_col_keys.remove(v.key) to_map.local_table = pjoin + strict_attrs = cls.__dict__.get("strict_attrs", False) + m_args = to_map.mapper_args_fn or dict def mapper_args(): args = m_args() args["polymorphic_on"] = pjoin.c[discriminator_name] + if strict_attrs: + args["include_properties"] = ( + set(pjoin.primary_key) + | declared_col_keys + | {discriminator_name} + ) + args["with_polymorphic"] = ("*", pjoin) return args to_map.mapper_args_fn = mapper_args - m = to_map.map() + to_map.map() - for scls in cls.__subclasses__(): + stack = [cls] + while stack: + scls = stack.pop(0) + stack.extend(scls.__subclasses__()) sm = _mapper_or_none(scls) - if sm and sm.concrete and cls in scls.__bases__: - sm._set_concrete_base(m) + if sm and sm.concrete and sm.inherits is None: + for sup_ in scls.__mro__[1:]: + sup_sm = _mapper_or_none(sup_) + if sup_sm: + + sm._set_concrete_base(sup_sm) + break @classmethod def _sa_raise_deferred_config(cls): |