AttributeError: can't set attribute after updating to 1.0.4 from 0.9.4

2,694 views
Skip to first unread message

Antoine Leclair

unread,
May 15, 2015, 9:56:16 AM5/15/15
to sqlal...@googlegroups.com
Hi all,

I recently tried to update SQLAlchemy from 0.9.4 to 1.0.4 in a client's project. However, I run into an error that is not there with 0.9.4.

It happens for this type of query:

    q = DBSession.query(User) \
                 .filter(User.profile_id.in_(profile_ids)) \
                 .join(Profile)
    q = q.outerjoin(
        CouplePhoto,
        and_(
            User.profile_id==CouplePhoto.profile_id,
            CouplePhoto.avatar==True
        )
    )
    q = q.with_entities(User.profile_id, User.given_name, User.zipcode,
                        CouplePhoto.filename, User.gender, Profile.sex)
    users_data = q.all()
    for u in users_data:
        if u.filename:
            # the next line causes the error in 1.0.4
            u.filename = 'something'

In 0.9.4, the type of "u" was "KeyedTuple" and in 1.0.4, it's "result". The error is "AttributeError: can't set attribute".

The goal of this assignation is to recompute the filename according to the original filename (to show a thumb instead of original). It is not saved in the database.

Now, I know this is not necessarily good design and I would not have done it that way myself. But the code base is probably full of this type of thing, mixed with business logic, and trying to fix them is clearly not doable in the short term.

And we have to update SQLAlchemy, because some circular reference issue was fixed when using Alembic somewhere between 0.9.4 and 1.0.4.

Is there anything I should be aware of to help with this?

Thanks in advance.

Mike Bayer

unread,
May 15, 2015, 11:21:17 AM5/15/15
to sqlal...@googlegroups.com


On 5/15/15 9:56 AM, Antoine Leclair wrote:
Hi all,

I recently tried to update SQLAlchemy from 0.9.4 to 1.0.4 in a client's project. However, I run into an error that is not there with 0.9.4.

It happens for this type of query:

    q = DBSession.query(User) \
                 .filter(User.profile_id.in_(profile_ids)) \
                 .join(Profile)
    q = q.outerjoin(
        CouplePhoto,
        and_(
            User.profile_id==CouplePhoto.profile_id,
            CouplePhoto.avatar==True
        )
    )
    q = q.with_entities(User.profile_id, User.given_name, User.zipcode,
                        CouplePhoto.filename, User.gender, Profile.sex)
    users_data = q.all()
    for u in users_data:
        if u.filename:
            # the next line causes the error in 1.0.4
            u.filename = 'something'

In 0.9.4, the type of "u" was "KeyedTuple" and in 1.0.4, it's "result". The error is "AttributeError: can't set attribute".

The goal of this assignation is to recompute the filename according to the original filename (to show a thumb instead of original). It is not saved in the database.

Now, I know this is not necessarily good design and I would not have done it that way myself. But the code base is probably full of this type of thing, mixed with business logic, and trying to fix them is clearly not doable in the short term.
let's look at the behavior of Python's own "namedtuple":

>>> import collections
>>> my_tuple = collections.namedtuple('x', ['a', 'b'])
>>> rec = my_tuple(1, 2)

as we can see, assigning to a tuple is not allowed:

>>> rec.a = 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>

AttributeError: can't set attribute

additionally, the tuple doesn't allow any other attributes to be set either; this is actually not working in 1.0.4's version of the tuple but we just fixed this for 1.0.5:

>>> rec.c = 7
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'x' object has no attribute 'c'

So unfortunately your code is doing something that never should have been allowed.



And we have to update SQLAlchemy, because some circular reference issue was fixed when using Alembic somewhere between 0.9.4 and 1.0.4.
SQLAlchemy's releases are parallel, where we support multiple X.Y series at the same time for a short period.  In this case, the 0.9 series is separate and is in maintenance mode.  The reason these parallel releases exist is exactly so that existing applications can take all the time they need, in some cases years, to upgrade their applications to the newer major version (see the bottom of http://www.sqlalchemy.org/download.html to get a feel for this).   So there is no reason for you to be using the 1.0 series, stay with the 0.9 series (currently at 0.9.9) until you have the opportunity to upgrade your development tree to the 1.0 series.    I'm not familiar with the bug you refer to but critical Python-level bugs are typically backported and I'm sure that if this is a documented bug fixed since 0.9.4, it's in the 0.9 series.

Antoine Leclair

unread,
May 15, 2015, 11:26:54 AM5/15/15
to sqlal...@googlegroups.com
Thanks, I was not aware of the parallel versions. We will try the latest 0.9.

And  I do agree that it should never have worked in the first place, it's just that the client's code base is full of that and it would be hard to try to remove them all at once.

Thanks for the fast reply, very appreciated.

Antoine 

Pankaj Gupta

unread,
May 30, 2016, 4:57:33 AM5/30/16
to sqlalchemy
I'm facing exactly same issue. 

It worked in past very well (below 1.0) and was very useful to update attribute or add new attributes (temporarily), now i can neither update not add new attributes. 

I'm wondering if someone can help us to port to later release or give some guidance for us to make it work.

Thanks,
-Pankaj

Mike Bayer

unread,
May 30, 2016, 12:21:38 PM5/30/16
to sqlal...@googlegroups.com


On 05/30/2016 04:57 AM, 'Pankaj Gupta' via sqlalchemy wrote:
> I'm facing exactly same issue.
>
> It worked in past very well (below 1.0) and was very useful to update
> attribute or add new attributes (temporarily), now i can neither update
> not add new attributes.
>
> I'm wondering if someone can help us to port to later release or give
> some guidance for us to make it work.


The Query object is subclassable, here's a naive implementation of a
wrapper for that tuple:


from sqlalchemy.orm.query import Query


class WritableKeyedTuple(object):
def __init__(self, elem):
self._elem = elem

# TODO: this is slow, come up with a faster object
# that is still writable.
def __getattr__(self, key):
return getattr(self._elem, key)

def __repr__(self):
inner = [
(key, getattr(self._elem, key))
for key in self._elem.keys()
]
outer = [
(key, getattr(self, key))
for key in dir(self) if not key.startswith("_")
]
return "WritableKeyedTuple(%s)" % (
", ".join(
"%s=%s" % (key, value) for
(key, value) in inner + outer
)
)


class WritableTupleQuery(Query):
def __iter__(self):
it = super(WritableTupleQuery, self).__iter__()

cdesc = self.column_descriptions
if len(cdesc) > 1 or not isinstance(cdesc[0]['type'], type):
return (
WritableKeyedTuple(elem)
for elem in it
)
else:
return it

if __name__ == '__main__':

from sqlalchemy import create_engine, Column, Integer
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class A(Base):
__tablename__ = 'a'
id = Column(Integer, primary_key=True)
x = Column(Integer)
y = Column(Integer)

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)

Session = sessionmaker(bind=e, query_cls=WritableTupleQuery)

s1 = Session()
s1.add_all([A(x=1, y=2), A(x=3, y=4)])
s1.commit()

for im_an_entity in s1.query(A):
print im_an_entity

for im_a_tuple in s1.query(A.x, A.y):
im_a_tuple.supplementary = 'hi'
print im_a_tuple

for im_a_tuple in s1.query(A.x):
im_a_tuple.supplementary = 'hi'
print im_a_tuple

>
> Thanks,
> -Pankaj
>
> On Friday, May 15, 2015 at 7:26:16 PM UTC+5:30, Antoine Leclair wrote:
>
> Hi all,
>
> I recently tried to update SQLAlchemy from 0.9.4 to 1.0.4 in a
> client's project. However, I run into an error that is not there
> with 0.9.4.
>
> It happens for this type of query:
>
> q = DBSession.query(User) \
> .filter(User.profile_id.in_(profile_ids)) \
> .join(Profile)
> q = q.outerjoin(
> CouplePhoto,
> and_(
> User.profile_id==CouplePhoto.profile_id,
> CouplePhoto.avatar==True
> )
> )
> q = q.with_entities(User.profile_id, User.given_name, User.zipcode,
> CouplePhoto.filename, User.gender, Profile.sex)
> users_data = q.all()
> for u in users_data:
> if u.filename:
> *# the next line causes the error in 1.0.4*
> * u.filename = 'something'*
>
> In 0.9.4, the type of "u" was "KeyedTuple" and in 1.0.4, it's
> "result". The error is "AttributeError: can't set attribute".
>
> The goal of this assignation is to recompute the filename according
> to the original filename (to show a thumb instead of original). It
> is not saved in the database.
>
> Now, I know this is not necessarily good design and I would not have
> done it that way myself. But the code base is probably full of this
> type of thing, mixed with business logic, and trying to fix them is
> clearly not doable in the short term.
>
> And we have to update SQLAlchemy, because some circular reference
> issue was fixed when using Alembic somewhere between 0.9.4 and 1.0.4.
>
> Is there anything I should be aware of to help with this?
>
> Thanks in advance.
>
> --
> You received this message because you are subscribed to the Google
> Groups "sqlalchemy" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to sqlalchemy+...@googlegroups.com
> <mailto:sqlalchemy+...@googlegroups.com>.
> To post to this group, send email to sqlal...@googlegroups.com
> <mailto:sqlal...@googlegroups.com>.
> Visit this group at https://groups.google.com/group/sqlalchemy.
> For more options, visit https://groups.google.com/d/optout.

mike bayer

unread,
May 30, 2016, 12:27:10 PM5/30/16
to sqlal...@googlegroups.com
Reply all
Reply to author
Forward
0 new messages