On Oct 14, 2010, at 11:53 AM, Julien Demoor wrote: > Thanks again for your help. > > I had tried using TypeDecorator without success. Now I tested further and > found the problem is more narrow in scope than I thought. If I create a > table MyTable that contains a column MyArray, with MyArray a TypeDecorator > subclass that converts lists to tuples, insert a row, then do > session.query(MyTable).get(row_id), it works fine and I get a tuple for my > array. (The code is at the bottom of this message) > > If I do session.query(MyTable).from_statement('SELECT ... FROM > my_table;').first(), then MyArray.process_result_value() is not called and > the returned instance's array attribute is a list rather than a tuple. In > fact, ARRAY's result processor is not used either in that case. I added some > print statements to ResultMetaData.__init__ to try to understand why : with > a regular query, the column type is MyArray; with a query that uses > from_statement(), the column type is NullType. > > From there I'm lost. Is there a way to force Query() to a apply a column > type with from_statement()?
oh, right, with from_statement() SQLA knows nothing about the types - and in fact in that case you are getting psycopg2's returned array directly. For that you can use the text() construct: query.from_statement(text("select * from ...", typemap={'your_array_column':MyArrayType})) > > CODE : > > import os > from sqlalchemy import create_engine, Table, Integer, MetaData, Column > from sqlalchemy.orm import create_session, mapper > from sqlalchemy.dialects.postgresql.base import ARRAY > > sa_engine = create_engine(os.environ['TEST_DSN']) > session = create_session(sa_engine, autoflush=True, expire_on_commit=True, > autocommit=False) > > from sqlalchemy import types > class MyArray(types.TypeDecorator): > impl = ARRAY > > def process_bind_param(self, value, engine): > return value > > def process_result_value(self, value, engine): > print 'process_result_value() called' > if value is None: > return None > else: > return tuple(value) > def copy(self): > return MyArray(self.impl.item_type, self.impl.mutable) > > metadata = MetaData(bind=sa_engine) > foo = Table('foo', metadata, > Column('bar', Integer, primary_key=True), > Column('my_array', MyArray(Integer, mutable=False)) > ) > class Foo(object): > pass > mapper(Foo, foo) > > foo_obj = session.query(Foo).from_statement("SELECT 1 AS foo_bar, > '{1,2,3}'::integer[] AS foo_my_array;").first() > print foo_obj.my_array # A list > > foo.drop(checkfirst=True) > foo.create() > foo_obj = Foo() > foo_obj.bar = -1 > foo_obj.my_array = [-1, -2] > session.add(foo_obj) > session.flush() > session.expunge_all() > del foo_obj > > foo_obj = session.query(Foo).get(-1) > print foo_obj.my_array # A tuple > > session.expunge_all() > del foo_obj > > foo_obj = session.query(Foo).from_statement("SELECT * FROM foo WHERE > bar=-1;").first() > print foo_obj.my_array # A list > > > -----Message d'origine----- > De : sqlalchemy@googlegroups.com [mailto:sqlalch...@googlegroups.com] De la > part de Michael Bayer > Envoyé : jeudi 14 octobre 2010 15:52 > À : sqlalchemy@googlegroups.com > Objet : Re: [sqlalchemy] Cannot retrieve PostgreSQL array column with > session.query() : TypeError: unhashable type: 'list' > > > On Oct 14, 2010, at 4:38 AM, Julien Demoor wrote: > >> Thanks for your reply. >> >> What you propose would work for me, but in the meantime I need a > workaround. > > If you need that exact pattern to work, build a TypeDecorator around ARRAY > and have it return a tuple around the result. > > TypeDecorator is described at: > http://www.sqlalchemy.org/docs/core/types.html?highlight=typedecorator#sqlal > chemy.types.TypeDecorator > > The SQLAlchemy type is always involved in between where psycopg2 returns > data and where unique_list() is called. > > As far as changing Query, the unique_list() is what makes it such that if > you load a Parent object with many Child objects in a joined-load > collection, you get just one Parent and not the same Parent for as many > Child objects as are in the result set. > > > >> I thought of serializing the arrays in SQL, then converting back to tuples >> in my code, but the objects contained in the arrays are quite complex to >> parse (decimals, datetims...). So I tried patching the ARRAY class to > return >> tuples when mutable=False, and that had no effect. If I understand >> correctly, by the time unique_list() is called, ARRAY hasn't been involved >> yet, and the database's array is converted to a Python list by the driver >> (psycopg2 in my case). >> >> The workaround I've found is to make the following change, in >> sqlalchemy.orm.query.Query.instances : >> >> if filtered: >> #if single_entity: >> # filter = lambda x: util.unique_list(x, util.IdentitySet) >> #else: >> # filter = util.unique_list >> filter = lambda x: util.unique_list(x, util.IdentitySet) >> >> Should I expect negative side-effects from this? >> >> -----Message d'origine----- >> De : sqlalchemy@googlegroups.com [mailto:sqlalch...@googlegroups.com] De > la >> part de Michael Bayer >> Envoyé : mercredi 13 octobre 2010 23:37 >> À : sqlalchemy@googlegroups.com >> Objet : Re: [sqlalchemy] Cannot retrieve PostgreSQL array column with >> session.query() : TypeError: unhashable type: 'list' >> >> The Query runs the result through unique_list() anytime there are mapped >> entities in the columns list. The ARRAY result, returning a Python list >> [], isn't hashable, so thats that. >> >> If you only queried for columns, it wouldn't be running through >> unique_list(). >> >> I suppose we'd modify ARRAY to return tuples if it's "mutable" flag isn't >> set. that could only be in 0.7, though. Let me know if that works for >> you, we'll add a ticket (hard for me to say since I never use the ARRAY >> type). >> >> >> >> On Oct 13, 2010, at 2:22 PM, Julien Demoor wrote: >> >>> Hello, >>> >>> The problem I'm seeing is illustrated by the code below. I tried a >>> workaround using TypeDecorator with process_result_value returning a >>> tuple rather than a list, to no avail. >>> >>> Any help will be greatly appreciated. >>> >>> Regards. >>> >>> Traceback : >>> >>> Traceback (most recent call last): >>> File "satest2.py", line 23, in <module> >>> session.query(Foo, 'col').from_statement("SELECT 55 AS foo_bar, >>> '{1,2,3}'::integer[] AS col;").first() >>> File "/home/user/programs/env/lib/python2.6/site-packages/sqlalchemy/ >>> orm/query.py", line 1494, in first >>> ret = list(self)[0:1] >>> File "/home/user/programs/env/lib/python2.6/site-packages/sqlalchemy/ >>> orm/query.py", line 1682, in instances >>> rows = filter(rows) >>> File "/home/jdemoor/programs/km/lib/python2.6/site-packages/ >>> sqlalchemy/util.py", line 1193, in unique_list >>> return [x for x in seq if x not in seen and not seen.add(x)] >>> TypeError: unhashable type: 'list' >>> >>> Full code : >>> >>> import os >>> from sqlalchemy import create_engine, Table, Integer, MetaData, Column >>> from sqlalchemy.orm import create_session, mapper >>> >>> sa_engine = create_engine(os.environ['TEST_DSN']) >>> session = create_session(sa_engine, autoflush=True, >>> expire_on_commit=True, autocommit=False) >>> >>> metadata = MetaData() >>> foo = Table('foo', metadata, Column('bar', Integer, primary_key=True)) >>> class Foo(object): >>> pass >>> mapper(Foo, foo) >>> >>> # This works >>> assert session.query('col').from_statement("SELECT 'abc' AS >>> col;").first() == ('abc',) >>> assert session.query('col').from_statement("SELECT >>> '{1,2,3}'::integer[] AS col;").first() == ([1,2,3],) >>> assert session.query('col1', 'col2').from_statement("SELECT >>> '{1,2,3}'::integer[] AS col1, 'abc' AS col2;").first() == ([1,2,3], >>> 'abc') >>> foo_obj = session.query(Foo).from_statement("SELECT 1 AS >>> foo_bar;").first() >>> assert foo_obj.bar == 1 >>> >>> try: >>> # This fails >>> session.query(Foo, 'col').from_statement("SELECT 55 AS foo_bar, >>> '{1,2,3}'::integer[] AS col;").first() >>> except TypeError, e: >>> print e >>> >>> from sqlalchemy.dialects.postgresql.base import ARRAY >>> col = Column('col', ARRAY(Integer, mutable=False)) >>> try: >>> # This fails too >>> session.query(Foo, col).from_statement("SELECT 55 AS foo_bar, >>> '{1,2,3}'::integer[] AS col;").first() >>> except TypeError, e: >>> print e >>> >>> -- >>> You received this message because you are subscribed to the Google Groups >> "sqlalchemy" group. >>> To post to this group, send email to sqlalch...@googlegroups.com. >>> To unsubscribe from this group, send email to >> sqlalchemy+unsubscr...@googlegroups.com. >>> For more options, visit this group at >> http://groups.google.com/group/sqlalchemy?hl=en. >>> >> >> -- >> You received this message because you are subscribed to the Google Groups >> "sqlalchemy" group. >> To post to this group, send email to sqlalch...@googlegroups.com. >> To unsubscribe from this group, send email to >> sqlalchemy+unsubscr...@googlegroups.com. >> For more options, visit this group at >> http://groups.google.com/group/sqlalchemy?hl=en. >> >> >> -- >> You received this message because you are subscribed to the Google Groups > "sqlalchemy" group. >> To post to this group, send email to sqlalch...@googlegroups.com. >> To unsubscribe from this group, send email to > sqlalchemy+unsubscr...@googlegroups.com. >> For more options, visit this group at > http://groups.google.com/group/sqlalchemy?hl=en. >> > > -- > You received this message because you are subscribed to the Google Groups > "sqlalchemy" group. > To post to this group, send email to sqlalch...@googlegroups.com. > To unsubscribe from this group, send email to > sqlalchemy+unsubscr...@googlegroups.com. > For more options, visit this group at > http://groups.google.com/group/sqlalchemy?hl=en. > > > -- > You received this message because you are subscribed to the Google Groups > "sqlalchemy" group. > To post to this group, send email to sqlalch...@googlegroups.com. > To unsubscribe from this group, send email to > sqlalchemy+unsubscr...@googlegroups.com. > For more options, visit this group at > http://groups.google.com/group/sqlalchemy?hl=en. > -- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to sqlalch...@googlegroups.com. To unsubscribe from this group, send email to sqlalchemy+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en.