Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SQLAlchemy accesses attribute table of custom ColumnElement #4142

Closed
sqlalchemy-bot opened this issue Dec 6, 2017 · 11 comments
Closed

SQLAlchemy accesses attribute table of custom ColumnElement #4142

sqlalchemy-bot opened this issue Dec 6, 2017 · 11 comments
Labels
bug Something isn't working sql
Milestone

Comments

@sqlalchemy-bot
Copy link
Collaborator

Migrated issue, originally created by Dmytro Starosud (@dima-starosud)

This is using sqlalchemy 1.2.0b3.

Please considering following code snippet. It fails with an error (shown in code). However there is a workaround to just add table = None to my_literal, but I suppose this may lead to other problems.

        Base = declarative_base()

        class my_literal(expression.ColumnElement):
            type = Unicode
            # table = None  # oO

        @compiles(my_literal)
        def default_my_literal(element, compiler, **kw):
            return "'my literal'"

        class _A(Base):
            __tablename__ = 'a_table'
            id = Column(Integer, primary_key=True)
            ml = column_property(my_literal())
            __mapper_args__ = {'polymorphic_on': ml}

        class _B(Base):
            __tablename__ = 'b_table'
            id = Column(Integer, primary_key=True)
            a_id = Column(Integer, ForeignKey(_A.id))
            ml = column_property(my_literal())
            __mapper_args__ = {'polymorphic_on': ml}

        class _C(Base):
            __tablename__ = 'c_table'
            id = Column(Integer, primary_key=True)
            b_id = Column(Integer, ForeignKey(_B.id))
            ml = column_property(my_literal())
            __mapper_args__ = {'polymorphic_on': ml}

        class A(_A):
            __mapper_args__ = {'polymorphic_identity': 'my literal'}

        class B(_B):
            __mapper_args__ = {'polymorphic_identity': 'my literal'}

        class C(_C):
            __mapper_args__ = {'polymorphic_identity': 'my literal'}

        B.a = relationship(A, backref='bs')
        C.b = relationship(B, backref='cs')
        A.cs = association_proxy('bs', 'cs')

        Base.metadata.create_all(bind=session.connection())

        a = A(bs=[B(cs=[C(), C()]), B(cs=[C(), C()])])
        session.add(a)
        session.flush()
        session.expire_all()
        assert a.cs  # AttributeError: Neither 'my_literal' object
                     # nor 'Comparator' object has an attribute 'table'

Stack trace.

/home/vagrant/venv/lib/python3.5/site-packages/sqlalchemy/ext/associationproxy.py:264: in __get__
    proxy = self._new(_lazy_collection(obj, self.target_collection))
/home/vagrant/venv/lib/python3.5/site-packages/sqlalchemy/ext/associationproxy.py:309: in _new
    self.collection_class = util.duck_type_collection(lazy_collection())
/home/vagrant/venv/lib/python3.5/site-packages/sqlalchemy/ext/associationproxy.py:499: in __call__
    return getattr(obj, self.target)
/home/vagrant/venv/lib/python3.5/site-packages/sqlalchemy/orm/attributes.py:242: in __get__
    return self.impl.get(instance_state(instance), dict_)
/home/vagrant/venv/lib/python3.5/site-packages/sqlalchemy/orm/attributes.py:603: in get
    value = self.callable_(state, passive)
/home/vagrant/venv/lib/python3.5/site-packages/sqlalchemy/orm/strategies.py:623: in _load_for_state
    return self._emit_lazyload(session, state, ident_key, passive)
<string>:1: in <lambda>
    ???
/home/vagrant/venv/lib/python3.5/site-packages/sqlalchemy/orm/strategies.py:733: in _emit_lazyload
    lazy_clause, params = self._generate_lazy_clause(state, passive)
/home/vagrant/venv/lib/python3.5/site-packages/sqlalchemy/orm/strategies.py:556: in _generate_lazy_clause
    state, dict_, ident, passive)
/home/vagrant/venv/lib/python3.5/site-packages/sqlalchemy/orm/mapper.py:2609: in _get_state_attr_by_column
    return state.manager[prop.key].impl.get(state, dict_, passive=passive)
/home/vagrant/venv/lib/python3.5/site-packages/sqlalchemy/orm/attributes.py:598: in get
    value = state._load_expired(state, passive)
/home/vagrant/venv/lib/python3.5/site-packages/sqlalchemy/orm/state.py:594: in _load_expired
    self.manager.deferred_scalar_loader(self, toload)
/home/vagrant/venv/lib/python3.5/site-packages/sqlalchemy/orm/loading.py:792: in load_scalar_attributes
    statement = mapper._optimized_get_statement(state, attribute_names)
/home/vagrant/venv/lib/python3.5/site-packages/sqlalchemy/orm/mapper.py:2647: in _optimized_get_statement
    for key in attribute_names
/home/vagrant/venv/lib/python3.5/site-packages/sqlalchemy/orm/mapper.py:2648: in <listcomp>
    for c in props[key].columns]
/home/vagrant/venv/lib/python3.5/site-packages/sqlalchemy/sql/util.py:148: in find_tables
    visitors.traverse(clause, {'column_collections': False}, _visitors)
/home/vagrant/venv/lib/python3.5/site-packages/sqlalchemy/sql/visitors.py:269: in traverse
    return traverse_using(iterate(obj, opts), obj, visitors)
/home/vagrant/venv/lib/python3.5/site-packages/sqlalchemy/sql/visitors.py:260: in traverse_using
    meth(target)
/home/vagrant/venv/lib/python3.5/site-packages/sqlalchemy/sql/util.py:143: in visit_column
    tables.append(column.table)
@sqlalchemy-bot
Copy link
Collaborator Author

Michael Bayer (@zzzeek) wrote:

stack trace.....i have to adapt this to real code and run it to see b.c. not provided...

@sqlalchemy-bot
Copy link
Collaborator Author

Michael Bayer (@zzzeek) wrote:

anyway, the answer for now is, put table=None, there's no side effect from that.

@sqlalchemy-bot
Copy link
Collaborator Author

Michael Bayer (@zzzeek) wrote:

https://gerrit.sqlalchemy.org/#/c/zzzeek/sqlalchemy/+/610/ will test feasibility of a change to prevent user-defined columnelements from being assumed to be table-bound

@sqlalchemy-bot
Copy link
Collaborator Author

Changes by Michael Bayer (@zzzeek):

  • added labels: sql

@sqlalchemy-bot
Copy link
Collaborator Author

Changes by Michael Bayer (@zzzeek):

  • set milestone to "1.2"

@sqlalchemy-bot
Copy link
Collaborator Author

Dmytro Starosud (@dima-starosud) wrote:

stack trace

@zzzeek Sorry, will attach later.

@sqlalchemy-bot
Copy link
Collaborator Author

Changes by Dmytro Starosud (@dima-starosud):

  • edited description

@sqlalchemy-bot
Copy link
Collaborator Author

Dmytro Starosud (@dima-starosud) wrote:

stack trace

@zzzeek Done.

@sqlalchemy-bot
Copy link
Collaborator Author

Michael Bayer (@zzzeek) wrote:

Change visit name for ColumnElement

No SQLA built-in subclasses ColumnElement without specifying
an alternate visit_name, and user defined ColumnElement
subclasses should avoid being treated like ColumnClause,
e.g. where a Table is present.

Fixes: #4142
Change-Id: I15ed09ba8bdebae4cb0c7e5e5df3f59351477577

5e8396a

@sqlalchemy-bot
Copy link
Collaborator Author

Changes by Michael Bayer (@zzzeek):

  • changed status to closed

@sqlalchemy-bot
Copy link
Collaborator Author

Michael Bayer (@zzzeek) wrote:

my point was, put the stack trace when you first post the issue, so that I can identify the problem without actually having to generate a complete test and run it. just saves me five minutes, that's all.

@sqlalchemy-bot sqlalchemy-bot added bug Something isn't working sql labels Nov 27, 2018
@sqlalchemy-bot sqlalchemy-bot added this to the 1.2 milestone Nov 27, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working sql
Projects
None yet
Development

No branches or pull requests

1 participant