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):
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:
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
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
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:
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:
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
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
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.
# 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
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")
"""
- if passive is PASSIVE_NO_FETCH:
+ if not passive & SQL_OK:
return PASSIVE_NO_RESULT
toload = self.expired_attributes.\
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
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)
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)
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
]
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)
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
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()
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
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
)
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_(