Single Item support for Association Proxy
(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)
-
repo owner -
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
-
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 == []
-
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.
-
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.
-
repo owner Oh, it took me like 10 minutes, sorry :). the fails are corrected in 99732dd29bd69a4a3808b d64ab47d7693e27ecebec201a 8f8522b4d1433475920674e272 back to 0.7.
-
repo owner -
repo owner - changed status to resolved
-
repo owner - removed milestone
Removing milestone: 0.9.0 (automated comment)
-
repo owner - Fixed a regression in association proxy caused by
2810
which caused a user-provided "getter" to no longer receive values ofNone
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>>
- Fixed a regression in association proxy caused by
- Log in to comment
I guess I'm creating the test case here, huh.