From: Mike Bayer Date: Fri, 25 Oct 2013 21:56:53 +0000 (-0400) Subject: - catch the metadata on ScalarTest.test_scalar_proxy, this has been leaving itself X-Git-Tag: rel_0_9_0b1~7 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=91ae63569df12654e0eae576938066a4079439aa;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - catch the metadata on ScalarTest.test_scalar_proxy, this has been leaving itself around for a long time - association proxy now returns None for proxied scalar that is also None, rather than raising AttributeError. [ticket:2810] --- diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst index d281397b41..4c9caf1f85 100644 --- a/doc/build/changelog/changelog_09.rst +++ b/doc/build/changelog/changelog_09.rst @@ -12,6 +12,18 @@ .. changelog:: :version: 0.9.0 + .. change:: + :tags: feature, orm + :tickets: 2810 + + The association proxy now returns ``None`` when fetching a scalar + attribute off of a scalar relationship, where the scalar relationship + itself points to ``None``, instead of raising an ``AttributeError``. + + .. seealso:: + + :ref:`migration_2810` + .. change:: :tags: feature, sql, postgresql, mysql :tickets: 2183 diff --git a/doc/build/changelog/migration_09.rst b/doc/build/changelog/migration_09.rst index 3aaf9670ba..936097328c 100644 --- a/doc/build/changelog/migration_09.rst +++ b/doc/build/changelog/migration_09.rst @@ -321,6 +321,49 @@ against ``b_value`` directly. :ticket:`2751` +.. _migration_2810: + +Association Proxy Missing Scalar returns None +--------------------------------------------- + +An association proxy from a scalar attribute to a scalar will now return +``None`` if the proxied object isn't present. This is consistent with the +fact that missing many-to-ones return None in SQLAlchemy, so should the +proxied value. E.g.:: + + from sqlalchemy import * + from sqlalchemy.orm import * + from sqlalchemy.ext.declarative import declarative_base + from sqlalchemy.ext.associationproxy import association_proxy + + Base = declarative_base() + + class A(Base): + __tablename__ = 'a' + + id = Column(Integer, primary_key=True) + b = relationship("B", uselist=False) + + bname = association_proxy("b", "name") + + class B(Base): + __tablename__ = 'b' + + id = Column(Integer, primary_key=True) + a_id = Column(Integer, ForeignKey('a.id')) + name = Column(String) + + a1 = A() + + # this is how m2o's always have worked + assert a1.b is None + + # but prior to 0.9, this would raise AttributeError, + # now returns None just like the proxied value. + assert a1.bname is None + +:ticket:`2810` + .. _migration_2850: A bindparam() construct with no type gets upgraded via copy when a type is available diff --git a/doc/build/orm/extensions/associationproxy.rst b/doc/build/orm/extensions/associationproxy.rst index 90bb29ebf8..9b25c4a685 100644 --- a/doc/build/orm/extensions/associationproxy.rst +++ b/doc/build/orm/extensions/associationproxy.rst @@ -15,6 +15,7 @@ the construction of sophisticated collections and dictionary views of virtually any geometry, persisted to the database using standard, transparently configured relational patterns. + Simplifying Scalar Collections ------------------------------ diff --git a/lib/sqlalchemy/ext/associationproxy.py b/lib/sqlalchemy/ext/associationproxy.py index fca2f0008f..60875bcf06 100644 --- a/lib/sqlalchemy/ext/associationproxy.py +++ b/lib/sqlalchemy/ext/associationproxy.py @@ -242,7 +242,11 @@ class AssociationProxy(interfaces._InspectionAttr): return self if self.scalar: - return self._scalar_get(getattr(obj, self.target_collection)) + target = getattr(obj, self.target_collection) + if target is not None: + return self._scalar_get(target) + else: + return None else: try: # If the owning instance is reborn (orm session resurrect, diff --git a/test/ext/test_associationproxy.py b/test/ext/test_associationproxy.py index d46b08f6db..3450eeb2f2 100644 --- a/test/ext/test_associationproxy.py +++ b/test/ext/test_associationproxy.py @@ -666,8 +666,9 @@ class ProxyFactoryTest(ListTest): class ScalarTest(fixtures.TestBase): + @testing.provide_metadata def test_scalar_proxy(self): - metadata = MetaData(testing.db) + metadata = self.metadata parents_table = Table('Parent', metadata, Column('id', Integer, primary_key=True, @@ -715,11 +716,8 @@ class ScalarTest(fixtures.TestBase): p = Parent('p') - # No child - assert_raises( - AttributeError, - getattr, p, "foo" - ) + eq_(p.child, None) + eq_(p.foo, None) p.child = Child(foo='a', bar='b', baz='c') @@ -740,11 +738,7 @@ class ScalarTest(fixtures.TestBase): p.child = None - # No child again - assert_raises( - AttributeError, - getattr, p, "foo" - ) + eq_(p.foo, None) # Bogus creator for this scalar type assert_raises( @@ -780,6 +774,48 @@ class ScalarTest(fixtures.TestBase): p2 = Parent('p2') p2.bar = 'quux' + @testing.provide_metadata + def test_empty_scalars(self): + metadata = self.metadata + + a = Table('a', metadata, + Column('id', Integer, primary_key=True), + Column('name', String(50)) + ) + a2b = Table('a2b', metadata, + Column('id', Integer, primary_key=True), + Column('id_a', Integer, ForeignKey('a.id')), + Column('id_b', Integer, ForeignKey('b.id')), + Column('name', String(50)) + ) + b = Table('b', metadata, + Column('id', Integer, primary_key=True), + Column('name', String(50)) + ) + class A(object): + a2b_name = association_proxy("a2b_single", "name") + b_single = association_proxy("a2b_single", "b") + + class A2B(object): + pass + + class B(object): + pass + + mapper(A, a, properties=dict( + a2b_single=relationship(A2B, uselist=False) + )) + + mapper(A2B, a2b, properties=dict( + b=relationship(B) + )) + mapper(B, b) + + a1 = A() + assert a1.a2b_name is None + assert a1.b_single is None + + class LazyLoadTest(fixtures.TestBase): def setup(self):