]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- catch the metadata on ScalarTest.test_scalar_proxy, this has been leaving itself
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 25 Oct 2013 21:56:53 +0000 (17:56 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 25 Oct 2013 21:56:53 +0000 (17:56 -0400)
around for a long time
- association proxy now returns None for proxied scalar that is also None, rather
than raising AttributeError. [ticket:2810]

doc/build/changelog/changelog_09.rst
doc/build/changelog/migration_09.rst
doc/build/orm/extensions/associationproxy.rst
lib/sqlalchemy/ext/associationproxy.py
test/ext/test_associationproxy.py

index d281397b41e4c3577a6b54b7526f147c27fa6ce2..4c9caf1f85023f5955d54dea56d81c4b805c3ee4 100644 (file)
 .. 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
index 3aaf9670ba9701d5d888ef2e2d45afa1c1d28d6d..936097328c51a80b80fb01b4a9205691039914b3 100644 (file)
@@ -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
index 90bb29ebf8760148a1df8182373ccd7ca2f8bc29..9b25c4a685eca95134dd21729f2f7cf84976e9ae 100644 (file)
@@ -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
 ------------------------------
 
index fca2f0008f56e55548251445737232760f9fa04e..60875bcf06122152bea334b79de50fcaa6322560 100644 (file)
@@ -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,
index d46b08f6dbfa3418e26cb4852399c6adb0b50aee..3450eeb2f2ce817b858f01797e830964c6d90a89 100644 (file)
@@ -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):