Single Item support for Association Proxy

Issue #2810 resolved
Former user created an issue

(original reporter: jonathan) Originally posted in the mailing list ( https://groups.google.com/forum/#!topic/sqlalchemy/Yehg9PIMQ_E )

Related to existing ticket "Scalar Support for Association Proxy" http://www.sqlalchemy.org/trac/ticket/2751

An AttributeError is raised when an association proxy exists for an relationship where uselist=False, there is no intermediary object, and the objects association proxy is accessed.

This behavior makes it impossible to even check for an association proxy. The expected behavior would be to return None, signifying there is no currently proxied attribute.

given this example:

    class Person:
         # orm relationships are preceded by (o)ne or (l)ist  
         o_Person2Address_ActiveShipping = sa.orm.relationship( "Person2Address", 
               primaryjoin="""and_( Person2Address.person_id==Person.id , Person2Address.role_id=='active-shipping' )""", 
               uselist=False )         
        active_shipping_address = association_proxy('o_Person2Address_ActiveShipping', 'address')

    class Person2Address:
        address = sa.orm.relationship("Address", primaryjoin="Person2Address.address_id==Address.id")

    class Address:
       pass

this works perfect when i have a Person2Address and address . I'd imagine it works fine if the proxy is for an empty list too.

the problem is when o_Person2Address_ActiveShipping is an empty item (from the uselist=False argument).

   jim = dbSession.query( Person )
   active_shipping = jim.o_Person2Address_ActiveShipping 
   type(active_shipping)
   >> None

   # this will raise an error
   if jim.active_shipping_address :

      # this will raise an error too
       if jim.active_shipping_address and jim.active_shipping_address.address :

          print jim.active_shipping_address

that raises an error on the .active_shipping_address

File "/Users/jvanasco/webserver/environments/project-2.7.5/lib/python2.7/site-packages/sqlalchemy/ext/associationproxy.py", line 241, in __get__
    return self._scalar_get(getattr(obj, self.target_collection))
AttributeError: 'NoneType' object has no attribute 'media_asset'

i think a simple fix could be something like this ( line 240, sqlalchemy/ext/associationproxy.py )

        if self.scalar:
-            if not getattr(obj, self.target_collection)
-            return self._scalar_get(getattr(obj, self.target_collection))
        else:

        if self.scalar:
+            proxied = getattr(obj, self.target_collection)
+            if not proxied :
+                return None
+            return self._scalar_get(proxied)
      else:

On the list, another person has reported encountering this and monkeypatching with the same fix since 0.5.x

Comments (10)

  1. Mike Bayer repo owner

    and there we have it

    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)
        bs = relationship("B", uselist=False)
    
        bname = association_proxy("bs", "name")
    
    class B(Base):
        __tablename__ = 'b'
    
        id = Column(Integer, primary_key=True)
        a_id = Column(Integer, ForeignKey('a.id'))
        name = Column(String)
    
    a1 = A()
    assert a1.bname is None
    
  2. Former user Account Deleted

    (original author: jonathan) sorry about that.

    here's my example, reformatted into a testcase, + your test case in a single example.

    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)
        name = Column(String)
    
        a2b_single = relationship("A2B", uselist=False)
        a2b_list = relationship("A2B", uselist=True)
    
        b_single = association_proxy("a2b_single", "b")
        b_list = association_proxy("a2b_list", "b")
    
        a2b_name = association_proxy("a2b_single", "name")
    
    class A2B(Base):
        __tablename__ = 'a2b'
    
        id = Column(Integer, primary_key=True)
        id_a = Column(Integer, ForeignKey('a.id'))
        id_b = Column(Integer, ForeignKey('b.id'))
        name = Column(String)
    
        a = relationship("A", primaryjoin="A2B.id_a==A.id")
        b = relationship("B", primaryjoin="A2B.id_b==B.id")
    
    
    class B(Base):
        __tablename__ = 'b'
    
        id = Column(Integer, primary_key=True)
        name = Column(String)
    
    a1 = A()
    assert a1.a2b_name is None
    assert a1.b_single is None
    assert a1.b_list == []
    
  3. Mike Bayer repo owner

    the tests here seem to make some old assumptions and also aren't handling the "fail" case correctly (i.e. fail() inside an open try: except: will never "fail"). so all those broken fails need to be changed to assert_raises() at least.

  4. Former user Account Deleted

    (original author: jonathan) if you can point to a file to fix, and a file to use as a styleguide, i'll block out an hour or two this afternoon.

  5. Mike Bayer repo owner
    • Fixed a regression in association proxy caused by 🎫2810 which caused a user-provided "getter" to no longer receive values of None when fetching scalar values from a target that is non-present. The check for None introduced by this change is now moved into the default getter, so a user-provided getter will also again receive values of None. re: #2810

    → <<cset b6428219c663>>

  6. Log in to comment