]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- merge attribute flag overhaul for [ticket:2358]
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 23 Apr 2012 15:45:06 +0000 (11:45 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 23 Apr 2012 15:45:06 +0000 (11:45 -0400)
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/orm/dynamic.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/orm/state.py
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/orm/unitofwork.py
lib/sqlalchemy/util/langhelpers.py
test/base/test_utils.py
test/orm/inheritance/test_basic.py
test/orm/test_attributes.py

index 3b4f18b310750ce384045ef6af1f076661363ad7..ec0b84a60d8f523766f0a3b65db22fa385a2a464 100644 (file)
@@ -22,60 +22,77 @@ from sqlalchemy.orm import interfaces, collections, events, exc as orm_exc
 
 mapperutil = util.importlater("sqlalchemy.orm", "util")
 
-PASSIVE_NO_RESULT = util.symbol('PASSIVE_NO_RESULT')
-ATTR_WAS_SET = util.symbol('ATTR_WAS_SET')
-ATTR_EMPTY = util.symbol('ATTR_EMPTY')
-NO_VALUE = util.symbol('NO_VALUE')
-NEVER_SET = util.symbol('NEVER_SET')
-
-PASSIVE_RETURN_NEVER_SET = util.symbol('PASSIVE_RETURN_NEVER_SET',
-"""Symbol indicating that loader callables can be 
-fired off, but if no callable is applicable and no value is
-present, the attribute should remain non-initialized.
-NEVER_SET is returned in this case.
+PASSIVE_NO_RESULT = util.symbol('PASSIVE_NO_RESULT',
+"""Symbol returned by a loader callable or other attribute/history
+retrieval operation when a value could not be determined, based
+on loader callable flags.
+"""
+)
+
+ATTR_WAS_SET = util.symbol('ATTR_WAS_SET',
+"""Symbol returned by a loader callable to indicate the
+retrieved value, or values, were assigned to their attributes
+on the target object.
 """)
 
-PASSIVE_NO_INITIALIZE = util.symbol('PASSIVE_NO_INITIALIZE',
-"""Symbol indicating that loader callables should
-   not be fired off, and a non-initialized attribute 
-   should remain that way.
+ATTR_EMPTY = util.symbol('ATTR_EMPTY',
+"""Symbol used internally to indicate an attribute had no callable.
 """)
 
-PASSIVE_NO_FETCH = util.symbol('PASSIVE_NO_FETCH',
-"""Symbol indicating that loader callables should not emit SQL, 
-   but a value can be fetched from the current session.
-   
-   Non-initialized attributes should be initialized to an empty value.
+NO_VALUE = util.symbol('NO_VALUE',
+"""Symbol which may be placed as the 'previous' value of an attribute,
+indicating no value was loaded for an attribute when it was modified,
+and flags indicated we were not to load it.
+"""
+)
 
-""")
+NEVER_SET = util.symbol('NEVER_SET',
+"""Symbol which may be placed as the 'previous' value of an attribute
+indicating that the attribute had not been assigned to previously.
+"""
+)
 
-PASSIVE_NO_FETCH_RELATED = util.symbol('PASSIVE_NO_FETCH_RELATED',
-"""Symbol indicating that loader callables should not emit SQL for
-   loading a related object, but can refresh the attributes of the local
-   instance in order to locate a related object in the current session.
-   
-   Non-initialized attributes should be initialized to an empty value.
-   
-   The unit of work uses this mode to check if history is present
-   on many-to-one attributes with minimal SQL emitted.
+CALLABLES_OK = util.symbol("CALLABLES_OK",
+"""Loader callables can be fired off if a value
+is not present.""", canonical=1
+)
 
-""")
+SQL_OK = util.symbol("SQL_OK",
+"""Loader callables can emit SQL at least on scalar value
+attributes.""", canonical=2)
 
-PASSIVE_ONLY_PERSISTENT = util.symbol('PASSIVE_ONLY_PERSISTENT',
-"""Symbol indicating that loader callables should only fire off for
-   parent objects which are persistent (i.e., have a database
-   identity).
+RELATED_OBJECT_OK = util.symbol("RELATED_OBJECT_OK",
+"""callables can use SQL to load related objects as well
+as scalar value attributes.
+""", canonical=4
+)
 
-   Load operations for the "previous" value of an attribute make
-   use of this flag during change events.
+INIT_OK = util.symbol("INIT_OK",
+"""Attributes should be initialized with a blank
+value (None or an empty collection) upon get, if no other
+value can be obtained.
+""", canonical=8
+)
 
-""")
+NON_PERSISTENT_OK = util.symbol("NON_PERSISTENT_OK",
+"""callables can be emitted if the parent is not persistent.""", 
+canonical=16
+)
 
-PASSIVE_OFF = util.symbol('PASSIVE_OFF',
-"""Symbol indicating that loader callables should be executed
-   normally.
 
-""")
+# pre-packaged sets of flags used as inputs
+PASSIVE_OFF = RELATED_OBJECT_OK | \
+                NON_PERSISTENT_OK | \
+                INIT_OK | \
+                CALLABLES_OK | \
+                SQL_OK
+
+PASSIVE_RETURN_NEVER_SET = PASSIVE_OFF  ^ INIT_OK
+PASSIVE_NO_INITIALIZE = PASSIVE_RETURN_NEVER_SET ^ CALLABLES_OK
+PASSIVE_NO_FETCH = PASSIVE_OFF ^ SQL_OK
+PASSIVE_NO_FETCH_RELATED = PASSIVE_OFF ^ RELATED_OBJECT_OK
+PASSIVE_ONLY_PERSISTENT = PASSIVE_OFF ^ NON_PERSISTENT_OK
+
 
 
 class QueryableAttribute(interfaces.PropComparator):
@@ -443,7 +460,7 @@ class AttributeImpl(object):
             key = self.key
             if key not in state.committed_state or \
                 state.committed_state[key] is NEVER_SET:
-                if passive is PASSIVE_NO_INITIALIZE:
+                if not passive & CALLABLES_OK:
                     return PASSIVE_NO_RESULT
 
                 if key in state.callables:
@@ -468,7 +485,7 @@ class AttributeImpl(object):
                 elif value is not ATTR_EMPTY:
                     return self.set_committed_value(state, dict_, value)
 
-            if passive is PASSIVE_RETURN_NEVER_SET:
+            if not passive & INIT_OK:
                 return NEVER_SET
             else:
                 # Return a new, empty value
@@ -639,8 +656,8 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
         if self.key in dict_:
             return History.from_object_attribute(self, state, dict_[self.key])
         else:
-            if passive is PASSIVE_OFF:
-                passive = PASSIVE_RETURN_NEVER_SET
+            if passive & INIT_OK:
+                passive ^= INIT_OK
             current = self.get(state, dict_, passive=passive)
             if current is PASSIVE_NO_RESULT:
                 return HISTORY_BLANK
index edf05287055c55c8be4d495fa9600a114fc58a52..1a3e52a367df5b8ccafdc2e7000261677f5453e7 100644 (file)
@@ -57,7 +57,7 @@ class DynamicAttributeImpl(attributes.AttributeImpl):
             self.query_class = mixin_user_query(query_class)
 
     def get(self, state, dict_, passive=attributes.PASSIVE_OFF):
-        if passive is not attributes.PASSIVE_OFF:
+        if not passive & attributes.SQL_OK:
             return self._get_collection_history(state,
                     attributes.PASSIVE_NO_INITIALIZE).added_items
         else:
@@ -65,7 +65,7 @@ class DynamicAttributeImpl(attributes.AttributeImpl):
 
     def get_collection(self, state, dict_, user_data=None, 
                             passive=attributes.PASSIVE_NO_INITIALIZE):
-        if passive is not attributes.PASSIVE_OFF:
+        if not passive & attributes.SQL_OK:
             return self._get_collection_history(state,
                     passive).added_items
         else:
@@ -142,7 +142,7 @@ class DynamicAttributeImpl(attributes.AttributeImpl):
                                   c.deleted_items)
 
     def get_all_pending(self, state, dict_):
-        c = self._get_collection_history(state, True)
+        c = self._get_collection_history(state, attributes.PASSIVE_NO_INITIALIZE)
         return [
                 (attributes.instance_state(x), x) 
                 for x in 
@@ -155,7 +155,9 @@ class DynamicAttributeImpl(attributes.AttributeImpl):
         else:
             c = CollectionHistory(self, state)
 
-        if passive is attributes.PASSIVE_OFF:
+        # TODO: consider using a different flag here, possibly
+        # one local to dynamic
+        if passive & attributes.INIT_OK:
             return CollectionHistory(self, state, apply_to=c)
         else:
             return c
index 66d7f6eb4e3d2c10fce4b4e68453790630a59594..92467908532384f9cb923704b710e30c99a9197a 100644 (file)
@@ -764,7 +764,7 @@ class Query(object):
                 not mapper.always_refresh and \
                 self._lockmode is None:
 
-            instance = self._get_from_identity(self.session, key, False)
+            instance = self._get_from_identity(self.session, key, attributes.PASSIVE_OFF)
             if instance is not None:
                 # reject calls for id in identity map but class
                 # mismatch.
@@ -2426,10 +2426,10 @@ class Query(object):
 
             # expired - ensure it still exists
             if state.expired:
-                if passive is attributes.PASSIVE_NO_FETCH:
+                if not passive & attributes.SQL_OK:
                     # TODO: no coverage here
                     return attributes.PASSIVE_NO_RESULT
-                elif passive is attributes.PASSIVE_NO_FETCH_RELATED:
+                elif not passive & attributes.RELATED_OBJECT_OK:
                     # this mode is used within a flush and the instance's
                     # expired state will be checked soon enough, if necessary
                     return instance
index 4803ecdc3d14cab77ece3046cb683bbad9548830..30a08faba007ae0c455bfe3d2b80722662308901 100644 (file)
@@ -18,7 +18,7 @@ from sqlalchemy import util
 from sqlalchemy.orm import exc as orm_exc, attributes, interfaces,\
         util as orm_util
 from sqlalchemy.orm.attributes import PASSIVE_OFF, PASSIVE_NO_RESULT, \
-    PASSIVE_NO_FETCH, NEVER_SET, ATTR_WAS_SET, NO_VALUE
+    SQL_OK, NEVER_SET, ATTR_WAS_SET, NO_VALUE
 
 mapperlib = util.importlater("sqlalchemy.orm", "mapperlib")
 
@@ -276,7 +276,7 @@ class InstanceState(object):
 
         """
 
-        if passive is PASSIVE_NO_FETCH:
+        if not passive & SQL_OK:
             return PASSIVE_NO_RESULT
 
         toload = self.expired_attributes.\
index 2e09ccbbea96f3d53d7ffb3c449770b489a29d01..703fc0160671ac54917115aa5e558a2f268e4d7a 100644 (file)
@@ -213,7 +213,7 @@ class DeferredColumnLoader(LoaderStrategy):
         if not state.key:
             return attributes.ATTR_EMPTY
 
-        if passive is attributes.PASSIVE_NO_FETCH:
+        if not passive & attributes.SQL_OK:
             return attributes.PASSIVE_NO_RESULT
 
         localparent = state.manager.mapper
@@ -464,13 +464,10 @@ class LazyLoader(AbstractRelationshipLoader):
         ident_key = None
 
         if (
-                (passive is attributes.PASSIVE_NO_FETCH or \
-                    passive is attributes.PASSIVE_NO_FETCH_RELATED) and 
-                not self.use_get
-            ) or (
-                passive is attributes.PASSIVE_ONLY_PERSISTENT and 
-                pending
-            ):
+            (not passive & attributes.SQL_OK and not self.use_get)
+            or
+            (not passive & attributes.NON_PERSISTENT_OK and pending)
+        ):
             return attributes.PASSIVE_NO_RESULT
 
         session = sessionlib._state_session(state)
@@ -501,8 +498,8 @@ class LazyLoader(AbstractRelationshipLoader):
             instance = Query._get_from_identity(session, ident_key, passive)
             if instance is not None:
                 return instance
-            elif passive is attributes.PASSIVE_NO_FETCH or \
-                passive is attributes.PASSIVE_NO_FETCH_RELATED:
+            elif not passive & attributes.SQL_OK or \
+                not passive & attributes.RELATED_OBJECT_OK:
                 return attributes.PASSIVE_NO_RESULT
 
         return self._emit_lazyload(session, state, ident_key)
@@ -517,17 +514,12 @@ class LazyLoader(AbstractRelationshipLoader):
 
         dict_ = state.dict
 
-        if passive is attributes.PASSIVE_NO_FETCH_RELATED:
-            attr_passive = attributes.PASSIVE_OFF
-        else:
-            attr_passive = passive
-
         return [
             get_attr(
                     state,
                     dict_,
                     self._equated_columns[pk],
-                    passive=attr_passive)
+                    passive=passive)
             for pk in self.mapper.primary_key
         ]
 
index 8fc5f139d3718d864aad3c95f2bfac7a26dcec7e..3523e7d06ed0bbe39af27fcf76fe75bc9e18189d 100644 (file)
@@ -166,8 +166,9 @@ class UOWTransaction(object):
             history, state_history, cached_passive = self.attributes[hashkey]
             # if the cached lookup was "passive" and now 
             # we want non-passive, do a non-passive lookup and re-cache
-            if cached_passive is not attributes.PASSIVE_OFF \
-                and passive is attributes.PASSIVE_OFF:
+
+            if not cached_passive & attributes.SQL_OK \
+                and passive & attributes.SQL_OK:
                 impl = state.manager[key].impl
                 history = impl.get_history(state, state.dict, 
                                     attributes.PASSIVE_OFF)
index b6c89b11a05c7b5c321814a6185c351e2c1c1ec5..d266c966406524977687062bf8cdc3e8e3eac766 100644 (file)
@@ -783,15 +783,21 @@ class classproperty(property):
         return desc.fget(cls)
 
 
-class _symbol(object):
-    def __init__(self, name, doc=None):
+class _symbol(int):
+    def __new__(self, name, doc=None, canonical=None):
         """Construct a new named symbol."""
         assert isinstance(name, str)
-        self.name = name
+        if canonical is None:
+            canonical = hash(name)
+        v = int.__new__(_symbol, canonical)
+        v.name = name
         if doc:
-            self.__doc__ = doc
+            v.__doc__ = doc
+        return v
+
     def __reduce__(self):
-        return symbol, (self.name,)
+        return symbol, (self.name, "x", int(self))
+
     def __repr__(self):
         return "<symbol '%s>" % self.name
 
@@ -822,12 +828,12 @@ class symbol(object):
     symbols = {}
     _lock = threading.Lock()
 
-    def __new__(cls, name, doc=None):
+    def __new__(cls, name, doc=None, canonical=None):
         cls._lock.acquire()
         try:
             sym = cls.symbols.get(name)
             if sym is None:
-                cls.symbols[name] = sym = _symbol(name, doc)
+                cls.symbols[name] = sym = _symbol(name, doc, canonical)
             return sym
         finally:
             symbol._lock.release()
index 3a7ce07f27d7ee9e972d4e6e2e9a5ee4d3ca9bac..b65124c38d59c577a67388359296b003045f9965 100644 (file)
@@ -616,6 +616,27 @@ class SymbolTest(fixtures.TestBase):
             assert rt is sym1
             assert rt is sym2
 
+    def test_bitflags(self):
+        sym1 = util.symbol('sym1', canonical=1)
+        sym2 = util.symbol('sym2', canonical=2)
+
+        assert sym1 & sym1
+        assert not sym1 & sym2
+        assert not sym1 & sym1 & sym2
+
+    def test_composites(self):
+        sym1 = util.symbol('sym1', canonical=1)
+        sym2 = util.symbol('sym2', canonical=2)
+        sym3 = util.symbol('sym3', canonical=4)
+        sym4 = util.symbol('sym4', canonical=8)
+
+        assert sym1 & (sym2 | sym1 | sym4)
+        assert not sym1 & (sym2 | sym3)
+
+        assert not (sym1 | sym2) & (sym3 | sym4)
+        assert (sym1 | sym2) & (sym2 | sym4)
+
+
 class WeakIdentityMappingTest(fixtures.TestBase):
     class Data(object):
         pass
index 3785fcdaa46f8ff08c6c0c083e4c8201ec65c47e..271dd49597edc90156b0210f8f4a565fd4894f4d 100644 (file)
@@ -68,11 +68,9 @@ class O2MTest(fixtures.MappedTest):
         l = sess.query(Blub).all()
         result = ','.join([repr(l[0]), repr(l[1]),
                           repr(l[0].parent_foo), repr(l[1].parent_foo)])
-        print compare
-        print result
-        self.assert_(compare == result)
-        self.assert_(l[0].parent_foo.data == 'foo #1'
-                     and l[1].parent_foo.data == 'foo #1')
+        eq_(compare, result)
+        eq_(l[0].parent_foo.data, 'foo #1')
+        eq_(l[1].parent_foo.data, 'foo #1')
 
 class PolymorphicOnNotLocalTest(fixtures.MappedTest):
     @classmethod
index e94afdb1be6d2b509d01fea54558283b59dec12d..7ff939a138a7f58e704fe4cad9ae482f87e39634 100644 (file)
@@ -990,14 +990,6 @@ class GetNoValueTest(fixtures.ORMTest):
         )
         assert 'attr' not in dict_
 
-    def test_passive_no_result_empty(self):
-        attr, state, dict_ = self._fixture(None)
-        eq_(
-            attr.get(state, dict_, passive=attributes.PASSIVE_NO_RESULT),
-            None
-        )
-        assert 'attr' in dict_
-
     def test_off_empty(self):
         attr, state, dict_ = self._fixture(None)
         eq_(