def replicate_relation(source, target, attr, target_attr, cache=None): if attr.property.cascade.delete_orphan: process_scalar = replicate_no_merge process_list = replicate_filter else: process_scalar = reflect process_list = reflect_filter value = getattr(source, attr.key) target_attr_model = target_attr.property.mapper.class_ if attr.property.uselist: adapter = collection_adapter(value) if adapter: # XXX The magic passes below are adapted from logic in # CollectionAttributeImpl.set() method without proper # understanding. The `elif` branch isn't even coverered by tests. if hasattr(value, '_sa_iterator'): value = value._sa_iterator() elif duck_type_collection(value) is dict: value = value.values() reflection = process_list(value, target_attr_model, cache=cache) impl = instance_state(target).get_impl(attr.key) impl.set(instance_state(target), instance_dict(target), reflection, # XXX We either have to convert reflection back to original # collection type or use this private parameter. _adapt=False) else: reflection = process_scalar(value, target_attr_model, cache=cache) setattr(target, attr.key, reflection) if (reflection is None and attr.property.direction is MANYTOONE and any(col.primary_key and not col.nullable for col in attr.property.local_columns)): raise _PrimaryKeyIsNull()
def test_no_orphan(self): """test that a lazily loaded child object is not marked as an orphan""" users, Address, addresses, User = ( self.tables.users, self.classes.Address, self.tables.addresses, self.classes.User, ) mapper( User, users, properties={ "addresses": relationship( Address, cascade="all,delete-orphan", lazy="select" ) }, ) mapper(Address, addresses) sess = create_session() user = sess.query(User).get(7) assert getattr(User, "addresses").hasparent( attributes.instance_state(user.addresses[0]), optimistic=True ) assert not sa.orm.class_mapper(Address)._is_orphan( attributes.instance_state(user.addresses[0]) )
def test_lazytrackparent(self): """test that the "hasparent" flag works properly when lazy loaders and backrefs are used""" class Post(object):pass class Blog(object):pass attributes.register_class(Post) attributes.register_class(Blog) # set up instrumented attributes with backrefs attributes.register_attribute(Post, 'blog', uselist=False, extension=attributes.GenericBackrefExtension('posts'), trackparent=True, useobject=True) attributes.register_attribute(Blog, 'posts', uselist=True, extension=attributes.GenericBackrefExtension('blog'), trackparent=True, useobject=True) # create objects as if they'd been freshly loaded from the database (without history) b = Blog() p1 = Post() attributes.instance_state(b).set_callable('posts', lambda:[p1]) attributes.instance_state(p1).set_callable('blog', lambda:b) p1, attributes.instance_state(b).commit_all() # no orphans (called before the lazy loaders fire off) assert attributes.has_parent(Blog, p1, 'posts', optimistic=True) assert attributes.has_parent(Post, b, 'blog', optimistic=True) # assert connections assert p1.blog is b assert p1 in b.posts # manual connections b2 = Blog() p2 = Post() b2.posts.append(p2) assert attributes.has_parent(Blog, p2, 'posts') assert attributes.has_parent(Post, b2, 'blog')
def test_basic(self): for base in (object, MyBaseClass, MyClass): class User(base): pass register_class(User) attributes.register_attribute( User, 'user_id', uselist=False, useobject=False) attributes.register_attribute( User, 'user_name', uselist=False, useobject=False) attributes.register_attribute( User, 'email_address', uselist=False, useobject=False) u = User() u.user_id = 7 u.user_name = 'john' u.email_address = '*****@*****.**' eq_(u.user_id, 7) eq_(u.user_name, "john") eq_(u.email_address, "*****@*****.**") attributes.instance_state(u)._commit_all( attributes.instance_dict(u)) eq_(u.user_id, 7) eq_(u.user_name, "john") eq_(u.email_address, "*****@*****.**") u.user_name = 'heythere' u.email_address = '*****@*****.**' eq_(u.user_id, 7) eq_(u.user_name, "heythere") eq_(u.email_address, "*****@*****.**")
def test_lazy_backref_collections(self): class Foo(_base.BasicEntity): pass class Bar(_base.BasicEntity): pass lazy_load = [] def lazyload(instance): def load(): return lazy_load return load attributes.register_class(Foo) attributes.register_class(Bar) attributes.register_attribute(Foo, 'bars', uselist=True, extension=attributes.GenericBackrefExtension('foo'), trackparent=True, callable_=lazyload, useobject=True) attributes.register_attribute(Bar, 'foo', uselist=False, extension=attributes.GenericBackrefExtension('bars'), trackparent=True, useobject=True) bar1, bar2, bar3, bar4 = [Bar(id=1), Bar(id=2), Bar(id=3), Bar(id=4)] lazy_load = [bar1, bar2, bar3] f = Foo() bar4 = Bar() bar4.foo = f eq_(attributes.get_history(attributes.instance_state(f), 'bars'), ([bar4], [bar1, bar2, bar3], [])) lazy_load = None f = Foo() bar4 = Bar() bar4.foo = f eq_(attributes.get_history(attributes.instance_state(f), 'bars'), ([bar4], [], [])) lazy_load = [bar1, bar2, bar3] attributes.instance_state(f).expire_attributes(['bars']) eq_(attributes.get_history(attributes.instance_state(f), 'bars'), ((), [bar1, bar2, bar3], ()))
def replicate_relation(source, target, attr, target_attr, cache=None): if attr.property.cascade.delete_orphan: process_scalar = replicate_no_merge process_list = replicate_filter else: process_scalar = reflect process_list = reflect_filter value = getattr(source, attr.key) target_attr_model = target_attr.property.mapper.class_ if attr.property.uselist: adapter = collection_adapter(value) if adapter: # Convert any collection to flat iterable value = adapter.adapt_like_to_iterable(value) reflection = process_list(value, target_attr_model, cache=cache) impl = instance_state(target).get_impl(attr.key) # Set any collection value from flat list impl._set_iterable(instance_state(target), instance_dict(target), reflection) else: reflection = process_scalar(value, target_attr_model, cache=cache) setattr(target, attr.key, reflection) if (reflection is None and attr.property.direction is MANYTOONE and any(col.primary_key and not col.nullable for col in attr.property.local_columns)): raise _PrimaryKeyIsNull()
def test_lazyhistory(self): """tests that history functions work with lazy-loading attributes""" class Foo(_base.BasicEntity): pass class Bar(_base.BasicEntity): pass attributes.register_class(Foo) attributes.register_class(Bar) bar1, bar2, bar3, bar4 = [Bar(id=1), Bar(id=2), Bar(id=3), Bar(id=4)] def func1(): return "this is func 1" def func2(): return [bar1, bar2, bar3] attributes.register_attribute(Foo, 'col1', uselist=False, callable_=lambda o:func1, useobject=True) attributes.register_attribute(Foo, 'col2', uselist=True, callable_=lambda o:func2, useobject=True) attributes.register_attribute(Bar, 'id', uselist=False, useobject=True) x = Foo() attributes.instance_state(x).commit_all() x.col2.append(bar4) eq_(attributes.get_history(attributes.instance_state(x), 'col2'), ([bar4], [bar1, bar2, bar3], []))
def preprocess_dependencies(self, task, deplist, uowcommit, delete=False): if self.post_update: return if delete: if self.cascade.delete or self.cascade.delete_orphan: for state in deplist: history = uowcommit.get_attribute_history(state, self.key, passive=self.passive_deletes) if history: if self.cascade.delete_orphan: todelete = history.sum() else: todelete = history.non_deleted() for child in todelete: if child is None: continue uowcommit.register_object(child, isdelete=True) for c, m in self.mapper.cascade_iterator('delete', child): uowcommit.register_object( attributes.instance_state(c), isdelete=True) else: for state in deplist: uowcommit.register_object(state) if self.cascade.delete_orphan: history = uowcommit.get_attribute_history(state, self.key, passive=self.passive_deletes) if history: for child in history.deleted: if self.hasparent(child) is False: uowcommit.register_object(child, isdelete=True) for c, m in self.mapper.cascade_iterator('delete', child): uowcommit.register_object( attributes.instance_state(c), isdelete=True)
def test_state_noload_to_lazy(self): """Behavioral test to verify the current activity of loader callables.""" users, Address, addresses, User = ( self.tables.users, self.classes.Address, self.tables.addresses, self.classes.User, ) mapper(User, users, properties={"addresses": relationship(Address, lazy="noload")}) mapper(Address, addresses) sess = create_session() u1 = sess.query(User).options(lazyload(User.addresses)).first() assert isinstance(attributes.instance_state(u1).callables["addresses"], strategies.LoadLazyAttribute) # expire, it stays sess.expire(u1) assert isinstance(attributes.instance_state(u1).callables["addresses"], strategies.LoadLazyAttribute) # load over it. callable goes away. sess.query(User).first() assert "addresses" not in attributes.instance_state(u1).callables sess.expunge_all() u1 = sess.query(User).options(lazyload(User.addresses)).first() sess.expire(u1, ["addresses"]) assert isinstance(attributes.instance_state(u1).callables["addresses"], strategies.LoadLazyAttribute) # load the attr, goes away u1.addresses assert "addresses" not in attributes.instance_state(u1).callables
def test_state_deferred_to_col(self): """Behavioral test to verify the current activity of loader callables.""" users, User = self.tables.users, self.classes.User mapper(User, users, properties={"name": deferred(users.c.name)}) sess = create_session() u1 = sess.query(User).options(undefer(User.name)).first() assert "name" not in attributes.instance_state(u1).callables # mass expire, the attribute was loaded, # the attribute gets the callable sess.expire(u1) assert isinstance(attributes.instance_state(u1).callables["name"], state.InstanceState) # load it, callable is gone u1.name assert "name" not in attributes.instance_state(u1).callables # mass expire, attribute was loaded but then deleted, # the callable goes away - the state wants to flip # it back to its "deferred" loader. sess.expunge_all() u1 = sess.query(User).options(undefer(User.name)).first() del u1.name sess.expire(u1) assert "name" not in attributes.instance_state(u1).callables # single attribute expire, the attribute gets the callable sess.expunge_all() u1 = sess.query(User).options(undefer(User.name)).first() sess.expire(u1, ["name"]) assert isinstance(attributes.instance_state(u1).callables["name"], state.InstanceState)
def set_(state, newvalue, oldvalue, initiator): # process "save_update" cascade rules for when an instance # is attached to another instance if oldvalue is newvalue: return newvalue sess = session._state_session(state) if sess: prop = state.manager.mapper._props[key] if newvalue is not None: newvalue_state = attributes.instance_state(newvalue) if prop.cascade.save_update and \ (prop.cascade_backrefs or key == initiator.key) and \ not sess._contains_state(newvalue_state): sess._save_or_update_state(newvalue_state) if oldvalue is not None and \ oldvalue is not attributes.PASSIVE_NO_RESULT and \ prop.cascade.delete_orphan: # possible to reach here with attributes.NEVER_SET ? oldvalue_state = attributes.instance_state(oldvalue) if oldvalue_state in sess._new and \ prop.mapper._is_orphan(oldvalue_state): sess.expunge(oldvalue) return newvalue
def test_option_state(self): """test that the merged takes on the MapperOption characteristics of that which is merged. """ users, User = self.tables.users, self.classes.User class Option(MapperOption): propagate_to_loaders = True opt1, opt2 = Option(), Option() sess = sessionmaker()() umapper = mapper(User, users) sess.add_all([ User(id=1, name='u1'), User(id=2, name='u2'), ]) sess.commit() sess2 = sessionmaker()() s2_users = sess2.query(User).options(opt2).all() # test 1. no options are replaced by merge options sess = sessionmaker()() s1_users = sess.query(User).all() for u in s1_users: ustate = attributes.instance_state(u) eq_(ustate.load_path, ()) eq_(ustate.load_options, set()) for u in s2_users: sess.merge(u) for u in s1_users: ustate = attributes.instance_state(u) eq_(ustate.load_path.path, (umapper, )) eq_(ustate.load_options, set([opt2])) # test 2. present options are replaced by merge options sess = sessionmaker()() s1_users = sess.query(User).options(opt1).all() for u in s1_users: ustate = attributes.instance_state(u) eq_(ustate.load_path.path, (umapper, )) eq_(ustate.load_options, set([opt1])) for u in s2_users: sess.merge(u) for u in s1_users: ustate = attributes.instance_state(u) eq_(ustate.load_path.path, (umapper, )) eq_(ustate.load_options, set([opt2]))
def test_deferred(self): for base in (object, MyBaseClass, MyClass): class Foo(base): pass data = {'a': 'this is a', 'b': 12} def loader(state, keys): for k in keys: state.dict[k] = data[k] return attributes.ATTR_WAS_SET manager = register_class(Foo) manager.deferred_scalar_loader = loader attributes.register_attribute( Foo, 'a', uselist=False, useobject=False) attributes.register_attribute( Foo, 'b', uselist=False, useobject=False) if base is object: assert Foo not in \ instrumentation._instrumentation_factory._state_finders else: assert Foo in \ instrumentation._instrumentation_factory._state_finders f = Foo() attributes.instance_state(f)._expire( attributes.instance_dict(f), set()) eq_(f.a, "this is a") eq_(f.b, 12) f.a = "this is some new a" attributes.instance_state(f)._expire( attributes.instance_dict(f), set()) eq_(f.a, "this is a") eq_(f.b, 12) attributes.instance_state(f)._expire( attributes.instance_dict(f), set()) f.a = "this is another new a" eq_(f.a, "this is another new a") eq_(f.b, 12) attributes.instance_state(f)._expire( attributes.instance_dict(f), set()) eq_(f.a, "this is a") eq_(f.b, 12) del f.a eq_(f.a, None) eq_(f.b, 12) attributes.instance_state(f)._commit_all( attributes.instance_dict(f)) eq_(f.a, None) eq_(f.b, 12)
def test_commit_removes_pending(self): global lazy_load lazy_load = (p1, ) = [Post("post 1"), ] called[0] = 0 b = Blog("blog 1") p1.blog = b attributes.instance_state(b).commit_all() attributes.instance_state(p1).commit_all() assert b.posts == [Post("post 1")]
def test_expire_cascade(self): mapper(User, users, properties={"addresses": relation(Address, cascade="all, refresh-expire")}) mapper(Address, addresses) s = create_session() u = s.query(User).get(8) assert u.addresses[0].email_address == "*****@*****.**" u.addresses[0].email_address = "someotheraddress" s.expire(u) u.name print attributes.instance_state(u).dict assert u.addresses[0].email_address == "*****@*****.**"
def test_no_orphan(self): """test that a lazily loaded child object is not marked as an orphan""" mapper(User, users, properties={ 'addresses':relation(Address, cascade="all,delete-orphan", lazy=True) }) mapper(Address, addresses) sess = create_session() user = sess.query(User).get(7) assert getattr(User, 'addresses').hasparent(attributes.instance_state(user.addresses[0]), optimistic=True) assert not sa.orm.class_mapper(Address)._is_orphan(attributes.instance_state(user.addresses[0]))
def test_multi_level_no_base(self): pjoin = polymorphic_union({ 'manager': managers_table, 'engineer': engineers_table, 'hacker': hackers_table }, 'type', 'pjoin') pjoin2 = polymorphic_union({ 'engineer': engineers_table, 'hacker': hackers_table }, 'type', 'pjoin2') employee_mapper = mapper(Employee, pjoin, polymorphic_on=pjoin.c.type) manager_mapper = mapper(Manager, managers_table, inherits=employee_mapper, concrete=True, polymorphic_identity='manager') engineer_mapper = mapper(Engineer, engineers_table, with_polymorphic=('*', pjoin2), polymorphic_on=pjoin2.c.type, inherits=employee_mapper, concrete=True, polymorphic_identity='engineer') hacker_mapper = mapper(Hacker, hackers_table, inherits=engineer_mapper, concrete=True, polymorphic_identity='hacker') session = create_session() tom = Manager('Tom', 'knows how to manage things') jerry = Engineer('Jerry', 'knows how to program') hacker = Hacker('Kurt', 'Badass', 'knows how to hack') session.add_all((tom, jerry, hacker)) session.flush() # ensure "readonly" on save logic didn't pollute the expired_attributes # collection assert 'nickname' not in attributes.instance_state(jerry).expired_attributes assert 'name' not in attributes.instance_state(jerry).expired_attributes assert 'name' not in attributes.instance_state(hacker).expired_attributes assert 'nickname' not in attributes.instance_state(hacker).expired_attributes def go(): self.assertEquals(jerry.name, "Jerry") self.assertEquals(hacker.nickname, "Badass") self.assert_sql_count(testing.db, go, 0) session.expunge_all() assert repr(session.query(Employee).filter(Employee.name=='Tom').one()) == "Manager Tom knows how to manage things" assert repr(session.query(Manager).filter(Manager.name=='Tom').one()) == "Manager Tom knows how to manage things" assert set([repr(x) for x in session.query(Employee).all()]) == set(["Engineer Jerry knows how to program", "Manager Tom knows how to manage things", "Hacker Kurt 'Badass' knows how to hack"]) assert set([repr(x) for x in session.query(Manager).all()]) == set(["Manager Tom knows how to manage things"]) assert set([repr(x) for x in session.query(Engineer).all()]) == set(["Engineer Jerry knows how to program", "Hacker Kurt 'Badass' knows how to hack"]) assert set([repr(x) for x in session.query(Hacker).all()]) == set(["Hacker Kurt 'Badass' knows how to hack"])
def clear_modified(self): ''' Unconditionally clear all modified state from the attibutes on this instance. ''' # Uses this method: http://www.sqlalchemy.org/docs/orm/internals.html?highlight=commit_all#sqlalchemy.orm.state.InstanceState.commit_all if int(sa_min_ver) > 7: # the methods became private in SA 8, need to check if # this is a problem return instance_state(self)._commit_all({}) else: return instance_state(self).commit_all({})
def identity_equal(a, b): if a is b: return True if a is None or b is None: return False try: state_a = attributes.instance_state(a) state_b = attributes.instance_state(b) except exc.NO_STATE: return False if state_a.key is None or state_b.key is None: return False return state_a.key == state_b.key
def _fixture(self): A, B = self.classes.A, self.classes.B session = create_session() uowcommit = self._get_test_uow(session) a_mapper = class_mapper(A) b_mapper= class_mapper(B) self.a1 = a1 = A() self.b1 = b1 = B() uowcommit = self._get_test_uow(session) return uowcommit,\ attributes.instance_state(a1),\ attributes.instance_state(b1),\ a_mapper, b_mapper
def test_partial_expire(self): orders, Order = self.tables.orders, self.classes.Order mapper(Order, orders) sess = create_session() o = sess.query(Order).get(3) sess.expire(o, attribute_names=["description"]) assert "id" in o.__dict__ assert "description" not in o.__dict__ assert attributes.instance_state(o).dict["isopen"] == 1 orders.update(orders.c.id == 3).execute(description="order 3 modified") def go(): assert o.description == "order 3 modified" self.assert_sql_count(testing.db, go, 1) assert attributes.instance_state(o).dict["description"] == "order 3 modified" o.isopen = 5 sess.expire(o, attribute_names=["description"]) assert "id" in o.__dict__ assert "description" not in o.__dict__ assert o.__dict__["isopen"] == 5 assert attributes.instance_state(o).committed_state["isopen"] == 1 def go(): assert o.description == "order 3 modified" self.assert_sql_count(testing.db, go, 1) assert o.__dict__["isopen"] == 5 assert attributes.instance_state(o).dict["description"] == "order 3 modified" assert attributes.instance_state(o).committed_state["isopen"] == 1 sess.flush() sess.expire(o, attribute_names=["id", "isopen", "description"]) assert "id" not in o.__dict__ assert "isopen" not in o.__dict__ assert "description" not in o.__dict__ def go(): assert o.description == "order 3 modified" assert o.id == 3 assert o.isopen == 5 self.assert_sql_count(testing.db, go, 1)
def value_as_iterable(self, dict_, key, passive=PASSIVE_OFF): """Return a list of tuples (state, obj) for the given key. returns an empty list if the value is None/empty/PASSIVE_NO_RESULT """ impl = self.manager[key].impl x = impl.get(self, dict_, passive=passive) if x is PASSIVE_NO_RESULT or x is None: return [] elif hasattr(impl, "get_collection"): return [(attributes.instance_state(o), o) for o in impl.get_collection(self, dict_, x, passive=passive)] else: return [(attributes.instance_state(x), x)]
def add(self, state): if state.key in self: if attributes.instance_state(dict.__getitem__(self, state.key)) is not state: raise AssertionError("A conflicting state is already present in the identity map for key %r" % (state.key, )) else: dict.__setitem__(self, state.key, state.obj()) self._manage_incoming_state(state)
def test_basic(self): class A(_fixtures.Base): pass class B(_fixtures.Base): pass mapper(A, table_a, properties={ 'bs':relation(B, cascade="all, delete-orphan") }) mapper(B, table_b) a1 = A(name='a1', bs=[B(name='b1'), B(name='b2'), B(name='b3')]) sess = create_session() sess.add(a1) sess.flush() sess.expunge_all() eq_(sess.query(A).get(a1.id), A(name='a1', bs=[B(name='b1'), B(name='b2'), B(name='b3')])) a1 = sess.query(A).get(a1.id) assert not class_mapper(B)._is_orphan( attributes.instance_state(a1.bs[0])) a1.bs[0].foo='b2modified' a1.bs[1].foo='b3modified' sess.flush() sess.expunge_all() eq_(sess.query(A).get(a1.id), A(name='a1', bs=[B(name='b1'), B(name='b2'), B(name='b3')]))
def test_gced_delete_on_rollback(self): User, users = self.classes.User, self.tables.users s = self.session() u1 = User(name='ed') s.add(u1) s.commit() s.delete(u1) u1_state = attributes.instance_state(u1) assert u1_state in s.identity_map.all_states() assert u1_state in s._deleted s.flush() assert u1_state not in s.identity_map.all_states() assert u1_state not in s._deleted del u1 gc_collect() assert u1_state.obj() is None s.rollback() # new in 1.1, not in identity map if the object was # gc'ed and we restore snapshot; we've changed update_impl # to just skip this object assert u1_state not in s.identity_map.all_states() # in any version, the state is replaced by the query # because the identity map would switch it u1 = s.query(User).filter_by(name='ed').one() assert u1_state not in s.identity_map.all_states() assert s.scalar(users.count()) == 1 s.delete(u1) s.flush() assert s.scalar(users.count()) == 0 s.commit()
def discard(self, state): obj = dict.get(self, state.key, None) if obj is not None: st = attributes.instance_state(obj) if st is state: dict.pop(self, state.key, None) self._manage_removed_state(state)
def test_gced_delete_on_rollback(self): s = self.session() u1 = User(name='ed') s.add(u1) s.commit() s.delete(u1) u1_state = attributes.instance_state(u1) assert u1_state in s.identity_map.all_states() assert u1_state in s._deleted s.flush() assert u1_state not in s.identity_map.all_states() assert u1_state not in s._deleted del u1 gc.collect() assert u1_state.obj() is None s.rollback() assert u1_state in s.identity_map.all_states() u1 = s.query(User).filter_by(name='ed').one() assert u1_state not in s.identity_map.all_states() assert s.scalar(users.count()) == 1 s.delete(u1) s.flush() assert s.scalar(users.count()) == 0 s.commit()
def is_modified(self): '''Check if SQLAlchemy believes this instance is modified.''' # TODO: Using this internal flag, is this a private internal # behavior? Is there a better, public-api way of getting this info? # alternatively is session.is_modified(self) although will this fail # if the instance isn't part of the session? return instance_state(self).modified
def test_m2o_lazy_loader_on_transient(self): for loadonpending in (False, True): for attach in (False, True): for autoflush in (False, True): for manualflush in (False, True): for enable_relationship_rel in (False, True): Child.parent.property.load_on_pending = loadonpending sess.autoflush = autoflush c2 = Child() if attach: state = instance_state(c2) state.session_id = sess.hash_key if enable_relationship_rel: sess.enable_relationship_loading(c2) c2.parent_id = p2.id if manualflush: sess.flush() if (loadonpending and attach) or enable_relationship_rel: assert c2.parent is p2 else: assert c2.parent is None sess.rollback()
def presort_saves(self, uowcommit, states): if not self.passive_updates: # if no passive updates, load history on # each collection where parent has changed PK, # so that prop_has_changes() returns True for state in states: if self._pks_changed(uowcommit, state): history = uowcommit.get_attribute_history( state, self.key, False) if not self.cascade.delete_orphan: return # check for child items removed from the collection # if delete_orphan check is turned on. for state in states: history = uowcommit.get_attribute_history( state, self.key, passive=True) if history: for child in history.deleted: if self.hasparent(child) is False: uowcommit.register_object(child, isdelete=True) for c, m in self.mapper.cascade_iterator( 'delete', child): uowcommit.register_object( attributes.instance_state(c), isdelete=True)
def create_version(obj, session, deleted=False): obj_mapper = object_mapper(obj) history_mapper = obj.__history_mapper__ history_cls = history_mapper.class_ obj_state = attributes.instance_state(obj) attr = {} obj_changed = False for om, hm in zip(obj_mapper.iterate_to_root(), history_mapper.iterate_to_root()): if hm.single: continue for hist_col in hm.local_table.c: if _is_versioning_col(hist_col): continue obj_col = om.local_table.c[hist_col.key] # get the value of the # attribute based on the MapperProperty related to the # mapped column. this will allow usage of MapperProperties # that have a different keyname than that of the mapped column. try: prop = obj_mapper.get_property_by_column(obj_col) except UnmappedColumnError: # in the case of single table inheritance, there may be # columns on the mapped table intended for the subclass only. # the "unmapped" status of the subclass column on the # base class is a feature of the declarative module. continue # expired object attributes and also deferred cols might not # be in the dict. force it to load no matter what by # using getattr(). if prop.key not in obj_state.dict: getattr(obj, prop.key) a, u, d = attributes.get_history(obj, prop.key) if d: attr[hist_col.key] = d[0] obj_changed = True elif u: attr[hist_col.key] = u[0] else: # if the attribute had no value. attr[hist_col.key] = a[0] obj_changed = True if not obj_changed: # not changed, but we have relationships. OK # check those too for prop in obj_mapper.iterate_properties: if isinstance(prop, RelationshipProperty) and \ attributes.get_history( obj, prop.key, passive=attributes.PASSIVE_NO_INITIALIZE).has_changes(): for p in prop.local_columns: if p.foreign_keys: obj_changed = True break if obj_changed is True: break if not obj_changed and not deleted: return attr['version'] = obj.version hist = history_cls() for key, value in attr.items(): setattr(hist, key, value) session.add(hist) obj.version += 1
def keyfunc(value): state = instance_state(value) m = _state_mapper(state) return tuple(m._get_state_attr_by_column(state, c) for c in mapping_spec)
def test_multi_level_no_base(self): pjoin = polymorphic_union( { 'manager': managers_table, 'engineer': engineers_table, 'hacker': hackers_table }, 'type', 'pjoin') pjoin2 = polymorphic_union( { 'engineer': engineers_table, 'hacker': hackers_table }, 'type', 'pjoin2') employee_mapper = mapper(Employee, pjoin, polymorphic_on=pjoin.c.type) manager_mapper = mapper(Manager, managers_table, inherits=employee_mapper, concrete=True, polymorphic_identity='manager') engineer_mapper = mapper(Engineer, engineers_table, with_polymorphic=('*', pjoin2), polymorphic_on=pjoin2.c.type, inherits=employee_mapper, concrete=True, polymorphic_identity='engineer') hacker_mapper = mapper(Hacker, hackers_table, inherits=engineer_mapper, concrete=True, polymorphic_identity='hacker') session = create_session() tom = Manager('Tom', 'knows how to manage things') assert_raises_message( AttributeError, "does not implement attribute .?'type' at the instance level.", setattr, tom, "type", "sometype") jerry = Engineer('Jerry', 'knows how to program') hacker = Hacker('Kurt', 'Badass', 'knows how to hack') assert_raises_message( AttributeError, "does not implement attribute .?'type' at the instance level.", setattr, hacker, "type", "sometype") session.add_all((tom, jerry, hacker)) session.flush() # ensure "readonly" on save logic didn't pollute the # expired_attributes collection assert 'nickname' \ not in attributes.instance_state(jerry).expired_attributes assert 'name' \ not in attributes.instance_state(jerry).expired_attributes assert 'name' \ not in attributes.instance_state(hacker).expired_attributes assert 'nickname' \ not in attributes.instance_state(hacker).expired_attributes def go(): eq_(jerry.name, 'Jerry') eq_(hacker.nickname, 'Badass') self.assert_sql_count(testing.db, go, 0) session.expunge_all() assert repr(session.query(Employee).filter(Employee.name == 'Tom') .one()) \ == 'Manager Tom knows how to manage things' assert repr(session.query(Manager) .filter(Manager.name == 'Tom').one()) \ == 'Manager Tom knows how to manage things' assert set([repr(x) for x in session.query(Employee).all()]) \ == set(['Engineer Jerry knows how to program', 'Manager Tom knows how to manage things', "Hacker Kurt 'Badass' knows how to hack"]) assert set([repr(x) for x in session.query(Manager).all()]) \ == set(['Manager Tom knows how to manage things']) assert set([repr(x) for x in session.query(Engineer).all()]) \ == set(['Engineer Jerry knows how to program', "Hacker Kurt 'Badass' knows how to hack"]) assert set([repr(x) for x in session.query(Hacker).all()]) \ == set(["Hacker Kurt 'Badass' knows how to hack"])
def child_relationships(obj): '''Get instances with child relationships to this obj.''' for related_obj, mapper, state, data in obj.__mapper__.cascade_iterator( "save-update", instance_state(obj)): yield related_obj
def _organize_states_for_save(base_mapper, states, uowtransaction): """Make an initial pass across a set of states for INSERT or UPDATE. This includes splitting out into distinct lists for each, calling before_insert/before_update, obtaining key information for each state including its dictionary, mapper, the connection to use for the execution per state, and the identity flag. """ states_to_insert = [] states_to_update = [] for state, dict_, mapper, connection in _connections_for_states( base_mapper, uowtransaction, states): has_identity = bool(state.key) instance_key = state.key or mapper._identity_key_from_state(state) row_switch = None # call before_XXX extensions if not has_identity: mapper.dispatch.before_insert(mapper, connection, state) else: mapper.dispatch.before_update(mapper, connection, state) # detect if we have a "pending" instance (i.e. has # no instance_key attached to it), and another instance # with the same identity key already exists as persistent. # convert to an UPDATE if so. if not has_identity and \ instance_key in uowtransaction.session.identity_map: instance = \ uowtransaction.session.identity_map[instance_key] existing = attributes.instance_state(instance) if not uowtransaction.is_deleted(existing): raise orm_exc.FlushError( "New instance %s with identity key %s conflicts " "with persistent instance %s" % (state_str(state), instance_key, state_str(existing))) base_mapper._log_debug( "detected row switch for identity %s. " "will update %s, remove %s from " "transaction", instance_key, state_str(state), state_str(existing)) # remove the "delete" flag from the existing element uowtransaction.remove_state_actions(existing) row_switch = existing if not has_identity and not row_switch: states_to_insert.append( (state, dict_, mapper, connection, has_identity, instance_key, row_switch) ) else: states_to_update.append( (state, dict_, mapper, connection, has_identity, instance_key, row_switch) ) return states_to_insert, states_to_update
def keyfunc(value): state = instance_state(value) m = _state_mapper(state) return m._get_state_attr_by_column(state, cols[0])
def all_states(self): return [attributes.instance_state(o) for o in self.itervalues()]
def contains_state(self, state): return state.key in self and attributes.instance_state( self[state.key]) is state
def remove(self, state): if attributes.instance_state(dict.pop(self, state.key)) is not state: raise AssertionError( "State %s is not present in this identity map" % state) self._manage_removed_state(state)
def remove_key(self, key): state = attributes.instance_state(dict.__getitem__(self, key)) self.remove(state)
def get_all_pending(self, state, dict_): c = self._get_collection_history(state, True) return [(attributes.instance_state(x), x) for x in c.added_items + c.unchanged_items + c.deleted_items]
def append(self, item): self.attr.append(attributes.instance_state(self.instance), attributes.instance_dict(self.instance), item, None)
def has_identity(object): state = attributes.instance_state(object) return _state_has_identity(state)
def test_get_or_create_report_returns_new_report_if_none_exists(self): submission = Submission.newest() report = submission.get_or_create_report() self.assertFalse(instance_state(report).persistent)
def test_history(self): for base in (object, MyBaseClass, MyClass): class Foo(base): pass class Bar(base): pass attributes.register_class(Foo) attributes.register_class(Bar) attributes.register_attribute(Foo, "name", uselist=False, useobject=False) attributes.register_attribute(Foo, "bars", uselist=True, trackparent=True, useobject=True) attributes.register_attribute(Bar, "name", uselist=False, useobject=False) f1 = Foo() f1.name = 'f1' eq_(attributes.get_state_history(attributes.instance_state(f1), 'name'), (['f1'], (), ())) b1 = Bar() b1.name = 'b1' f1.bars.append(b1) eq_(attributes.get_state_history(attributes.instance_state(f1), 'bars'), ([b1], [], [])) attributes.instance_state(f1).commit_all(attributes.instance_dict(f1)) attributes.instance_state(b1).commit_all(attributes.instance_dict(b1)) eq_(attributes.get_state_history(attributes.instance_state(f1), 'name'), ((), ['f1'], ())) eq_(attributes.get_state_history(attributes.instance_state(f1), 'bars'), ((), [b1], ())) f1.name = 'f1mod' b2 = Bar() b2.name = 'b2' f1.bars.append(b2) eq_(attributes.get_state_history(attributes.instance_state(f1), 'name'), (['f1mod'], (), ['f1'])) eq_(attributes.get_state_history(attributes.instance_state(f1), 'bars'), ([b2], [b1], [])) f1.bars.remove(b1) eq_(attributes.get_state_history(attributes.instance_state(f1), 'bars'), ([b2], [], [b1]))
def remove(self, item): self.attr.remove(attributes.instance_state(self.instance), attributes.instance_dict(self.instance), item, None)
def test_multi_level_no_base(self): pjoin = polymorphic_union( { "manager": managers_table, "engineer": engineers_table, "hacker": hackers_table, }, "type", "pjoin", ) pjoin2 = polymorphic_union( { "engineer": engineers_table, "hacker": hackers_table }, "type", "pjoin2", ) employee_mapper = mapper(Employee, pjoin, polymorphic_on=pjoin.c.type) mapper( Manager, managers_table, inherits=employee_mapper, concrete=True, polymorphic_identity="manager", ) engineer_mapper = mapper( Engineer, engineers_table, with_polymorphic=("*", pjoin2), polymorphic_on=pjoin2.c.type, inherits=employee_mapper, concrete=True, polymorphic_identity="engineer", ) mapper( Hacker, hackers_table, inherits=engineer_mapper, concrete=True, polymorphic_identity="hacker", ) session = create_session() tom = Manager("Tom", "knows how to manage things") assert_raises_message( AttributeError, "does not implement attribute .?'type' at the instance level.", setattr, tom, "type", "sometype", ) jerry = Engineer("Jerry", "knows how to program") hacker = Hacker("Kurt", "Badass", "knows how to hack") assert_raises_message( AttributeError, "does not implement attribute .?'type' at the instance level.", setattr, hacker, "type", "sometype", ) session.add_all((tom, jerry, hacker)) session.flush() # ensure "readonly" on save logic didn't pollute the # expired_attributes collection assert ("nickname" not in attributes.instance_state(jerry).expired_attributes) assert ("name" not in attributes.instance_state(jerry).expired_attributes) assert ("name" not in attributes.instance_state(hacker).expired_attributes) assert ("nickname" not in attributes.instance_state(hacker).expired_attributes) def go(): eq_(jerry.name, "Jerry") eq_(hacker.nickname, "Badass") self.assert_sql_count(testing.db, go, 0) session.expunge_all() assert (repr( session.query(Employee).filter(Employee.name == "Tom").one()) == "Manager Tom knows how to manage things") assert (repr( session.query(Manager).filter(Manager.name == "Tom").one()) == "Manager Tom knows how to manage things") assert set([repr(x) for x in session.query(Employee).all()]) == set([ "Engineer Jerry knows how to program", "Manager Tom knows how to manage things", "Hacker Kurt 'Badass' knows how to hack", ]) assert set([repr(x) for x in session.query(Manager).all() ]) == set(["Manager Tom knows how to manage things"]) assert set([repr(x) for x in session.query(Engineer).all()]) == set([ "Engineer Jerry knows how to program", "Hacker Kurt 'Badass' knows how to hack", ]) assert set([repr(x) for x in session.query(Hacker).all() ]) == set(["Hacker Kurt 'Badass' knows how to hack"])
def test_instance_state(self): User = self.classes.User u1 = User() insp = inspect(u1) is_(insp, instance_state(u1))
def init_instance(self, mapper, class_, oldinit, instance, args, kwargs): session = self.scoped_session() state = attributes.instance_state(instance) session._save_impl(state) return EXT_CONTINUE
def instance_str(instance): """Return a string describing an instance.""" return state_str(attributes.instance_state(instance))