diff options
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r-- | lib/sqlalchemy/orm/events.py | 46 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 45 |
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: |