summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/orm/events.py46
-rw-r--r--lib/sqlalchemy/orm/mapper.py45
2 files changed, 71 insertions, 20 deletions
diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py
index 5635c76e2..5e8e9c0d9 100644
--- a/lib/sqlalchemy/orm/events.py
+++ b/lib/sqlalchemy/orm/events.py
@@ -911,7 +911,11 @@ class MapperEvents(event.Events[mapperlib.Mapper[Any]]):
before instrumentation is applied to the mapped class.
This event is the earliest phase of mapper construction.
- Most attributes of the mapper are not yet initialized.
+ Most attributes of the mapper are not yet initialized. To
+ receive an event within initial mapper construction where basic
+ state is available such as the :attr:`_orm.Mapper.attrs` collection,
+ the :meth:`_orm.MapperEvents.after_mapper_constructed` event may
+ be a better choice.
This listener can either be applied to the :class:`_orm.Mapper`
class overall, or to any un-mapped class which serves as a base
@@ -927,6 +931,44 @@ class MapperEvents(event.Events[mapperlib.Mapper[Any]]):
of this event.
:param class\_: the mapped class.
+ .. seealso::
+
+ :meth:`_orm.MapperEvents.after_mapper_constructed`
+
+ """
+
+ def after_mapper_constructed(
+ self, mapper: Mapper[_O], class_: Type[_O]
+ ) -> None:
+ """Receive a class and mapper when the :class:`_orm.Mapper` has been
+ fully constructed.
+
+ This event is called after the initial constructor for
+ :class:`_orm.Mapper` completes. This occurs after the
+ :meth:`_orm.MapperEvents.instrument_class` event and after the
+ :class:`_orm.Mapper` has done an initial pass of its arguments
+ to generate its collection of :class:`_orm.MapperProperty` objects,
+ which are accessible via the :meth:`_orm.Mapper.get_property`
+ method and the :attr:`_orm.Mapper.iterate_properties` attribute.
+
+ This event differs from the
+ :meth:`_orm.MapperEvents.before_mapper_configured` event in that it
+ is invoked within the constructor for :class:`_orm.Mapper`, rather
+ than within the :meth:`_orm.registry.configure` process. Currently,
+ this event is the only one which is appropriate for handlers that
+ wish to create additional mapped classes in response to the
+ construction of this :class:`_orm.Mapper`, which will be part of the
+ same configure step when :meth:`_orm.registry.configure` next runs.
+
+ .. versionadded:: 2.0.2
+
+ .. seealso::
+
+ :ref:`examples_versioning` - an example which illustrates the use
+ of the :meth:`_orm.MapperEvents.before_mapper_configured`
+ event to create new mappers to record change-audit histories on
+ objects.
+
"""
def before_mapper_configured(
@@ -938,7 +980,7 @@ class MapperEvents(event.Events[mapperlib.Mapper[Any]]):
the configure step, by returning the :attr:`.orm.interfaces.EXT_SKIP`
symbol which indicates to the :func:`.configure_mappers` call that this
particular mapper (or hierarchy of mappers, if ``propagate=True`` is
- used) should be skipped in the current configuration run. When one or
+ used) should be skipped in the current configuration run. When one or
more mappers are skipped, the he "new mappers" flag will remain set,
meaning the :func:`.configure_mappers` function will continue to be
called when mappers are used, to continue to try to configure all
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index bb7e470ff..c962a682a 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -861,6 +861,8 @@ class Mapper(
self._log("constructed")
self._expire_memoizations()
+ self.dispatch.after_mapper_constructed(self, self.class_)
+
def _prefer_eager_defaults(self, dialect, table):
if self.eager_defaults == "auto":
if not table.implicit_returning:
@@ -1686,7 +1688,6 @@ class Mapper(
# that's given. For other properties, set them up in _props now.
if self._init_properties:
for key, prop_arg in self._init_properties.items():
-
if not isinstance(prop_arg, MapperProperty):
possible_col_prop = self._make_prop_from_column(
key, prop_arg
@@ -1698,17 +1699,22 @@ class Mapper(
# Column that is local to the local Table, don't set it up
# in ._props yet, integrate it into the order given within
# the Table.
+
+ _map_as_property_now = True
if isinstance(possible_col_prop, properties.ColumnProperty):
- given_col = possible_col_prop.columns[0]
- if self.local_table.c.contains_column(given_col):
- explicit_col_props_by_key[key] = possible_col_prop
- explicit_col_props_by_column[given_col] = (
- key,
- possible_col_prop,
- )
- continue
+ for given_col in possible_col_prop.columns:
+ if self.local_table.c.contains_column(given_col):
+ _map_as_property_now = False
+ explicit_col_props_by_key[key] = possible_col_prop
+ explicit_col_props_by_column[given_col] = (
+ key,
+ possible_col_prop,
+ )
- self._configure_property(key, possible_col_prop, init=False)
+ if _map_as_property_now:
+ self._configure_property(
+ key, possible_col_prop, init=False
+ )
# step 2: pull properties from the inherited mapper. reconcile
# columns with those which are explicit above. for properties that
@@ -1728,10 +1734,12 @@ class Mapper(
incoming_prop=incoming_prop,
)
explicit_col_props_by_key[key] = new_prop
- explicit_col_props_by_column[incoming_prop.columns[0]] = (
- key,
- new_prop,
- )
+
+ for inc_col in incoming_prop.columns:
+ explicit_col_props_by_column[inc_col] = (
+ key,
+ new_prop,
+ )
elif key not in self._props:
self._adapt_inherited_property(key, inherited_prop, False)
@@ -1742,7 +1750,6 @@ class Mapper(
# reconciliation against inherited columns occurs here also.
for column in self.persist_selectable.columns:
-
if column in explicit_col_props_by_column:
# column was explicitly passed to properties; configure
# it now in the order in which it corresponds to the
@@ -2428,7 +2435,7 @@ class Mapper(
return key in self._props
def get_property(
- self, key: str, _configure_mappers: bool = True
+ self, key: str, _configure_mappers: bool = False
) -> MapperProperty[Any]:
"""return a MapperProperty associated with the given key."""
@@ -2439,7 +2446,9 @@ class Mapper(
return self._props[key]
except KeyError as err:
raise sa_exc.InvalidRequestError(
- "Mapper '%s' has no property '%s'" % (self, key)
+ f"Mapper '{self}' has no property '{key}'. If this property "
+ "was indicated from other mappers or configure events, ensure "
+ "registry.configure() has been called."
) from err
def get_property_by_column(
@@ -2454,7 +2463,6 @@ class Mapper(
def iterate_properties(self):
"""return an iterator of all MapperProperty objects."""
- self._check_configure()
return iter(self._props.values())
def _mappers_from_spec(
@@ -4080,6 +4088,7 @@ def _do_configure_registries(
for mapper in reg._mappers_to_configure():
run_configure = None
+
for fn in mapper.dispatch.before_mapper_configured:
run_configure = fn(mapper, mapper.class_)
if run_configure is EXT_SKIP: