ColumnClause pretends to be iterable and then isn't

Issue #2726 resolved
Former user created an issue

Updating to SQLAlchemy 0.8, I'm getting regressions because ColumnClause.__iter__ now exists (defined in ColumnOperators in 1e29a74bd62c6c872819388e430901c35f84f07c) only to raise an exception. This sucker-punches code that checks for iterableness either by hasattr(o, '__iter__') or isinstance(o, collections.Iterable) ... such as this VALUES compiler that supports tuples/lists/iterables on PostgreSQL, representing them as ARRAYs:

@compiles(values)
def compile_values(element, compiler, asfrom=False, **kw):
    v = u"VALUES %s" % ", ".join(
        u"(%s)" % u", ".join(
            u'ARRAY[%s](%s)' % ','.join(
                (u"'%s'" % subel.replace(u"'", u"''").replace(u"%", u"%%"))
                if isinstance(subel, unicode)
                else str(subel) if isinstance(subel, int)
                else compiler.process(subel)
                for subel in elem
            ) if hasattr(elem, '__iter__')
            and elem.__iter__.__func__ is not blocking_iter_func
            else (u"'%s'" % elem.replace(u"'", u"''").replace(u"%", u"%%"))
            if isinstance(elem, unicode)
            else u'NULL' if elem is None
            else compiler.process(elem) if isinstance(elem, ColumnElement)
            else repr(elem)
            for elem in tup)
        for tup in element.list 
    ) 
    if asfrom: 
        v = "(%s)" % v 
    return v

I get what the method is there for, but it satisfies the conventional ways of checking for iterableness, while it's actually there to assert ''non-''iterableness. That's not very neat.

I'm provisionally working around this as a special case:

from sqlalchemy.sql.operators import ColumnOperators
blocking_iter_func = ColumnOperators.__iter__.__func__

    ... if hasattr(elem, '__iter__') and elem.__iter__.__func__ is not blocking_iter_func

(and the wind carries a soft murmur of kittens screaming in the distance...)

Comments (5)

  1. Mike Bayer repo owner

    what are you going to do about strings in Python 3? they have __iter__() also. I'm not sure if checking for __iter__/Iterable and nothing else to detect lists/tuples/sets is really feasible due to that.

  2. Former user Account Deleted

    Hopefully nothing, for a while. :) (Our codebase is not a library, does not need to run on Python 3 until either (a) we want it to, or (b) libraries we want to use no longer support 2.7.x, neither of which seems imminent.)

    We're not only detecting lists/tuples/sets, it's also generators, and our own types with __iter__ defined. So just isinstance(o, (list, tuple, set)) or isinstance(o, collections.Sequence) wouldn't hack it. In these (fairly common) cases we're looking for anything iterable that isn't a string. Presumably we'll just end up saying that verbatim, i.e. isinstance(o, collections.Iterable) and not isinstance(o, basestring), which isn't much neater, but at least explicit.

  3. Mike Bayer repo owner

    there's not much option here except to take that whole thing out, and wait for the stackoverflow issues when people call list() on their custom columns...

  4. Log in to comment