From 083d44c082a13761910c761debd17efac75a71b3 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 7 Sep 2014 19:17:46 -0400 Subject: [PATCH] - remove some old cruft - prop.compare() isn't needed; replace with prop._with_parent() for relationships - update docs in orm/interfaces --- lib/sqlalchemy/orm/dynamic.py | 4 +- lib/sqlalchemy/orm/interfaces.py | 136 +++++++++++++++++----------- lib/sqlalchemy/orm/relationships.py | 27 ++---- lib/sqlalchemy/orm/util.py | 4 +- 4 files changed, 93 insertions(+), 78 deletions(-) diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index 51db1b107e..a4ccfe417f 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -221,10 +221,8 @@ class AppenderMixin(object): mapper = object_mapper(instance) prop = mapper._props[self.attr.key] - self._criterion = prop.compare( - operators.eq, + self._criterion = prop._with_parent( instance, - value_is_parent=True, alias_secondary=False) if self.attr.order_by: diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 47ee4c0761..ad2452c1b9 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -9,11 +9,12 @@ Contains various base classes used throughout the ORM. -Defines the now deprecated ORM extension classes as well -as ORM internals. +Defines some key base classes prominent within the internals, +as well as the now-deprecated ORM extension classes. Other than the deprecated extensions, this module and the -classes within should be considered mostly private. +classes within are mostly private, though some attributes +are exposed when inspecting mappings. """ @@ -67,9 +68,15 @@ class MapperProperty(_MappedAttribute, InspectionAttr): This collection is checked before the 'cascade_iterator' method is called. + The collection typically only applies to a RelationshipProperty. + """ is_property = True + """Part of the InspectionAttr interface; states this object is a + mapper property. + + """ def setup(self, context, entity, path, adapter, **kwargs): """Called by Query for the purposes of constructing a SQL statement. @@ -77,16 +84,15 @@ class MapperProperty(_MappedAttribute, InspectionAttr): Each MapperProperty associated with the target mapper processes the statement referenced by the query context, adding columns and/or criterion as appropriate. - """ - pass + """ def create_row_processor(self, context, path, mapper, result, adapter, populators): - """Return a 3-tuple consisting of three row processing functions. + """Produce row processing functions and append to the given + set of populators lists. """ - pass def cascade_iterator(self, type_, state, visited_instances=None, halt_on=None): @@ -98,16 +104,40 @@ class MapperProperty(_MappedAttribute, InspectionAttr): Note that the 'cascade' collection on this MapperProperty is checked first for the given type before cascade_iterator is called. - See PropertyLoader for the related instance implementation. + This method typically only applies to RelationshipProperty. + """ return iter(()) def set_parent(self, parent, init): + """Set the parent mapper that references this MapperProperty. + + This method is overridden by some subclasses to perform extra + setup when the mapper is first known. + + """ self.parent = parent - def instrument_class(self, mapper): # pragma: no-coverage - raise NotImplementedError() + def instrument_class(self, mapper): + """Hook called by the Mapper to the property to initiate + instrumentation of the class attribute managed by this + MapperProperty. + + The MapperProperty here will typically call out to the + attributes module to set up an InstrumentedAttribute. + + This step is the first of two steps to set up an InstrumentedAttribute, + and is called early in the mapper setup process. + + The second step is typically the init_class_attribute step, + called from StrategizedProperty via the post_instrument_class() + hook. This step assigns additional state to the InstrumentedAttribute + (specifically the "impl") which has been determined after the + MapperProperty has determined what kind of persistence + management it needs to do (e.g. scalar, object, collection, etc). + + """ _configure_started = False _configure_finished = False @@ -157,45 +187,28 @@ class MapperProperty(_MappedAttribute, InspectionAttr): """ - pass - def post_instrument_class(self, mapper): """Perform instrumentation adjustments that need to occur after init() has completed. - """ - pass + The given Mapper is the Mapper invoking the operation, which + may not be the same Mapper as self.parent in an inheritance + scenario; however, Mapper will always at least be a sub-mapper of + self.parent. - def is_primary(self): - """Return True if this ``MapperProperty``'s mapper is the - primary mapper for its class. + This method is typically used by StrategizedProperty, which delegates + it to LoaderStrategy.init_class_attribute() to perform final setup + on the class-bound InstrumentedAttribute. - This flag is used to indicate that the ``MapperProperty`` can - define attribute instrumentation for the class at the class - level (as opposed to the individual instance level). """ - return not self.parent.non_primary - def merge(self, session, source_state, source_dict, dest_state, dest_dict, load, _recursive): """Merge the attribute represented by this ``MapperProperty`` - from source to destination object""" - - pass - - def compare(self, operator, value, **kw): - """Return a compare operation for the columns represented by - this ``MapperProperty`` to the given value, which may be a - column value or an instance. 'operator' is an operator from - the operators module, or from sql.Comparator. + from source to destination object. - By default uses the PropComparator attached to this MapperProperty - under the attribute name "comparator". """ - return operator(self.comparator, value) - def __repr__(self): return '<%s at 0x%x; %s>' % ( self.__class__.__name__, @@ -203,8 +216,7 @@ class MapperProperty(_MappedAttribute, InspectionAttr): class PropComparator(operators.ColumnOperators): - """Defines boolean, comparison, and other operators for - :class:`.MapperProperty` objects. + """Defines SQL operators for :class:`.MapperProperty` objects. SQLAlchemy allows for operators to be redefined at both the Core and ORM level. :class:`.PropComparator` @@ -399,6 +411,13 @@ class StrategizedProperty(MapperProperty): strategies can be selected at Query time through the usage of ``StrategizedOption`` objects via the Query.options() method. + The mechanics of StrategizedProperty are used for every Query + invocation for every mapped attribute participating in that Query, + to determine first how the attribute will be rendered in SQL + and secondly how the attribute will retrieve a value from a result + row and apply it to a mapped object. The routines here are very + performance-critical. + """ strategy_wildcard_key = None @@ -460,7 +479,7 @@ class StrategizedProperty(MapperProperty): self.strategy = self._get_strategy_by_cls(self.strategy_class) def post_instrument_class(self, mapper): - if self.is_primary() and \ + if not self.parent.non_primary and \ not mapper.class_manager._attr_has_impl(self.key): self.strategy.init_class_attribute(mapper) @@ -493,18 +512,24 @@ class MapperOption(object): propagate_to_loaders = False """if True, indicate this option should be carried along - Query object generated by scalar or object lazy loaders. + to "secondary" Query objects produced during lazy loads + or refresh operations. + """ def process_query(self, query): - pass + """Apply a modification to the given :class:`.Query`.""" def process_query_conditionally(self, query): """same as process_query(), except that this option may not apply to the given query. - Used when secondary loaders resend existing options to a new - Query.""" + This is typically used during a lazy load or scalar refresh + operation to propagate options stated in the original Query to the + new Query being used for the load. It occurs for those options that + specify propagate_to_loaders=True. + + """ self.process_query(query) @@ -523,9 +548,9 @@ class LoaderStrategy(object): * it processes the ``QueryContext`` at statement construction time, where it can modify the SQL statement that is being produced. - Simple column attributes may add their represented column to the - list of selected columns, *eager loading* properties may add - ``LEFT OUTER JOIN`` clauses to the statement. + For example, simple column attributes will add their represented + column to the list of selected columns, a joined eager loader + may establish join clauses to add to the statement. * It produces "row processor" functions at result fetching time. These "row processor" functions populate a particular attribute @@ -543,17 +568,26 @@ class LoaderStrategy(object): pass def setup_query(self, context, entity, path, loadopt, adapter, **kwargs): - pass + """Establish column and other state for a given QueryContext. + + This method fulfills the contract specified by MapperProperty.setup(). + + StrategizedProperty delegates its setup() method + directly to this method. + + """ def create_row_processor(self, context, path, loadopt, mapper, result, adapter, populators): - """Return row processing functions which fulfill the contract - specified by MapperProperty.create_row_processor. + """Establish row processing functions for a given QueryContext. - StrategizedProperty delegates its create_row_processor method - directly to this method. """ + This method fulfills the contract specified by + MapperProperty.create_row_processor(). - pass + StrategizedProperty delegates its create_row_processor() method + directly to this method. + + """ def __str__(self): return str(self.parent_property) diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index 2bcb3f4a19..95ff21444f 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -1307,25 +1307,10 @@ class RelationshipProperty(StrategizedProperty): mapperlib.Mapper._configure_all() return self.prop - def compare(self, op, value, - value_is_parent=False, - alias_secondary=True): - if op == operators.eq: - if value is None: - if self.uselist: - return ~sql.exists([1], self.primaryjoin) - else: - return self._optimized_compare( - None, - value_is_parent=value_is_parent, - alias_secondary=alias_secondary) - else: - return self._optimized_compare( - value, - value_is_parent=value_is_parent, - alias_secondary=alias_secondary) - else: - return op(self.comparator, value) + def _with_parent(self, instance, alias_secondary=True): + assert instance is not None + return self._optimized_compare( + instance, value_is_parent=True, alias_secondary=alias_secondary) def _optimized_compare(self, value, value_is_parent=False, adapt_source=None, @@ -1633,7 +1618,7 @@ class RelationshipProperty(StrategizedProperty): """Test that this relationship is legal, warn about inheritance conflicts.""" - if not self.is_primary() and not mapperlib.class_mapper( + if self.parent.non_primary and not mapperlib.class_mapper( self.parent.class_, configure=False).has_property(self.key): raise sa_exc.ArgumentError( @@ -1719,7 +1704,7 @@ class RelationshipProperty(StrategizedProperty): """Interpret the 'backref' instruction to create a :func:`.relationship` complementary to this one.""" - if not self.is_primary(): + if self.parent.non_primary: return if self.backref is not None and not self.back_populates: if isinstance(self.backref, util.string_types): diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index 1bb6b571e9..734f9d5e6f 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -896,9 +896,7 @@ def with_parent(instance, prop): elif isinstance(prop, attributes.QueryableAttribute): prop = prop.property - return prop.compare(operators.eq, - instance, - value_is_parent=True) + return prop._with_parent(instance) def has_identity(object): -- 2.47.3