summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2013-10-25 17:56:53 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2013-10-25 17:56:53 -0400
commit91ae63569df12654e0eae576938066a4079439aa (patch)
treef03f5155a9697eb2029af83e50f2c6a1afcfa57f
parentb0cd41dd50e9279b8f7823eadd001f04605990a4 (diff)
downloadsqlalchemy-91ae63569df12654e0eae576938066a4079439aa.tar.gz
- 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]
-rw-r--r--doc/build/changelog/changelog_09.rst12
-rw-r--r--doc/build/changelog/migration_09.rst43
-rw-r--r--doc/build/orm/extensions/associationproxy.rst1
-rw-r--r--lib/sqlalchemy/ext/associationproxy.py6
-rw-r--r--test/ext/test_associationproxy.py58
5 files changed, 108 insertions, 12 deletions
diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst
index d281397b4..4c9caf1f8 100644
--- a/doc/build/changelog/changelog_09.rst
+++ b/doc/build/changelog/changelog_09.rst
@@ -13,6 +13,18 @@
: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 3aaf9670b..936097328 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 90bb29ebf..9b25c4a68 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 fca2f0008..60875bcf0 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 d46b08f6d..3450eeb2f 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):