On Thu, Oct 31, 2019, at 2:34 PM, Cory Virok wrote:
> Actually, I think I can use your lambda example and just scope the import to
> the function, like so:
>
>
> def _d_secondary():
> return join(B, D, B.d_id == D.id).join(C, C.d_id == D.id)
>
> class A(Base):
> __tablename__ = 'a'
>
> id = Column(Integer, primary_key=True)
> b_id = Column(ForeignKey('b.id'))
>
> d = relationship("D",
> secondary=_d_secondary,
> #secondary="join(B, D, B.d_id == D.id)."
> # "join(C, C.d_id == D.id)",
> primaryjoin="and_(A.b_id == B.id, A.id == C.a_id)",
> secondaryjoin="D.id == B.d_id",
> uselist=True
> )
>
> That seems to be a decent workaround for the problem.
>
> If you did want to provide the "join(...)" string secondary relationship
> functionality to the deferred mappings, I did some investigation and found
> that the reason it doesn't work with the deferred configuration is because
> the `self.fallback[key]` method for determining how to resolve the secondary
> is never used. This might be fixable if you add the fallback resolver to
> rel.secondary._resolvers alongside cls._sa_deferred_table_resolver(...)n
> sqlalchemy.schema.ext.declarative/api.py#prepare()
>
> @classmethod
> def prepare(cls, engine):
> *"""Reflect all :class:`.Table` objects for all current
** :class:`.DeferredReflection` subclasses"""
**
** *to_map = _DeferredMapperConfig.classes_for_base(cls)
> for thingy in to_map:
> cls._sa_decl_prepare(thingy.local_table, engine)
> thingy.map()
> mapper = thingy.cls.__mapper__
> metadata = mapper.class_.metadata
> for rel in mapper._props.values():
> if (
> isinstance(rel, properties.RelationshipProperty)
> and rel.secondary is not None
> ):
> if isinstance(rel.secondary, Table):
> cls._reflect_table(rel.secondary, engine)
> elif isinstance(rel.secondary, _class_resolver):
> rel.secondary._resolvers += (
> cls._sa_deferred_table_resolver(engine, metadata),
> )
>
> # ************* Add fallback resolver here ***************
>
> But I'm not familiar enough with the code to know if that's the right place
> for it.
I didn't really look to see why it happens, can you post an issue with your
proposed patch ? this is not code I deal with very often.
>
> - Cory
>
>
> On Thursday, October 31, 2019 at 10:10:36 AM UTC-7, Cory Virok wrote:
>> Hello Mike and thank you for the fast reply!
>>
>> The primary reason I'm using the string form of the join conditions is
>> because I'm unable to easily import the module that the relationship refers
>> to, due to circular dependencies in a large codebase. Do you know of any way
>> to specify the same secondary condition that doesn't require importing the
>> module that the condition refers to?
>>
>> As for the previous versions, thanks for testing that. The code that I'm
>> upgrading was using the DeclarativeReflectedBase class from the examples. So
>> something else must have caused the string form of the relationship to
>> break.
>>
>> Thanks,
>> - Cory
>>
>> On Wednesday, October 30, 2019 at 6:49:50 PM UTC-7, Mike Bayer wrote:
>>> it's clear that the deferredreflection sees the word "join" as the name of
>>> a table to reflect from the "secondary" argument. Also, your test
>>> reproduces the error in all versions of SQLAlchemy, I can't find any
>>> version that has DeferredReflection which does not behave identically; 1.2,
>>> 1.1, 1.0, 0.9, they all do the same thing. So if you saw something break
>>> between 1.2 and 1.3, it's something else, this happens every time.
>>>
>>> The test case can be made to work by using a lambda instead of a string,
>>> which means SQLAlchemy can do less guessing as to what the name of the
>>> "secondary" table might be (in this case nothing):
>>>
>>> from sqlalchemy import and_, join
>>>
>>> # ...
>>>
>>> class ...
>>>
>>> d = relationship(
>>> "D",
>>> secondary=lambda: join(B, D, B.d_id == D.id).join(C, C.d_id == D.id),
>>> primaryjoin=lambda: and_(A.b_id == B.id, A.id == C.a_id),
>>> secondaryjoin=lambda: D.id == B.d_id,
>>> uselist=True,
>>> )
>>>
>>>
>>>
>>> On Wed, Oct 30, 2019, at 6:35 PM, Cory Virok wrote:
>>>> I recently upgraded from SQLAlchemy 1.2 to 1.3 and one of the
>>>> relationships I have no longer works. I'm trying to reflect an existing
>>>> schema and provide a base class that provides some helper methods to all
>>>> of my ORM classes. I'm also trying to defer reflection to after the ORM
>>>> classes are prepared.
>>>>
>>>> The error I'm seeing is:
>>>>
>>>> File "scratch.py", line 98, in <module>
>>>> a = session.query(A)
>>>> File "lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1544,
>>>> in query
>>>> return self._query_cls(entities, self, **kwargs)
>>>> File "lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 168, in
>>>> __init__
>>>> self._set_entities(entities)
>>>> File "lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 200, in
>>>> _set_entities
>>>> self._set_entity_selectables(self._entities)
>>>> File "lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 231, in
>>>> _set_entity_selectables
>>>> ent.setup_entity(*d[entity])
>>>> File "lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 4121, in
>>>> setup_entity
>>>> self._with_polymorphic = ext_info.with_polymorphic_mappers
>>>> File "lib/python2.7/site-packages/sqlalchemy/util/langhelpers.py", line
>>>> 855, in __get__
>>>> obj.__dict__[self.__name__] = result = self.fget(obj)
>>>> File "lib/python2.7/site-packages/sqlalchemy/orm/mapper.py", line 2135, in
>>>> _with_polymorphic_mappers
>>>> configure_mappers()
>>>> File "lib/python2.7/site-packages/sqlalchemy/orm/mapper.py", line 3248, in
>>>> configure_mappers
>>>> mapper._post_configure_properties()
>>>> File "lib/python2.7/site-packages/sqlalchemy/orm/mapper.py", line 1947, in
>>>> _post_configure_properties
>>>> prop.init()
>>>> File "lib/python2.7/site-packages/sqlalchemy/orm/interfaces.py", line 196,
>>>> in init
>>>> self.do_init()
>>>> File "lib/python2.7/site-packages/sqlalchemy/orm/relationships.py", line
>>>> 1860, in do_init
>>>> self._process_dependent_arguments()
>>>> File "lib/python2.7/site-packages/sqlalchemy/orm/relationships.py", line
>>>> 1889, in _process_dependent_arguments
>>>> setattr(self, attr, attr_value())
>>>> File
>>>> "lib/python2.7/site-packages/sqlalchemy/ext/declarative/clsregistry.py",
>>>> line 294, in __call__
>>>> x = eval(self.arg, globals(), self._dict)
>>>> File "<string>", line 1, in <module>
>>>> File "lib/python2.7/site-packages/sqlalchemy/util/_collections.py", line
>>>> 734, in __missing__
>>>> self[key] = val = self.creator(key)
>>>> File
>>>> "lib/python2.7/site-packages/sqlalchemy/ext/declarative/clsregistry.py",
>>>> line 286, in _access_cls
>>>> value = resolv(key)
>>>> File "lib/python2.7/site-packages/sqlalchemy/ext/declarative/api.py", line
>>>> 778, in _resolve
>>>> cls._reflect_table(t1, engine)
>>>> File "lib/python2.7/site-packages/sqlalchemy/ext/declarative/api.py", line
>>>> 811, in _reflect_table
>>>> schema=table.schema,
>>>> File "<string>", line 2, in __new__
>>>> File "lib/python2.7/site-packages/sqlalchemy/util/deprecations.py", line
>>>> 128, in warned
>>>> return fn(*args, **kwargs)
>>>> File "lib/python2.7/site-packages/sqlalchemy/sql/schema.py", line 492, in
>>>> __new__
>>>> table._init_existing(*args, **kw)
>>>> File "lib/python2.7/site-packages/sqlalchemy/sql/schema.py", line 708, in
>>>> _init_existing
>>>> _extend_on=_extend_on,
>>>> File "lib/python2.7/site-packages/sqlalchemy/sql/schema.py", line 619, in
>>>> _autoload
>>>> _extend_on=_extend_on,
>>>> File "lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 2160,
>>>> in run_callable
>>>> return conn.run_callable(callable_, *args, **kwargs)
>>>> File "lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1612,
>>>> in run_callable
>>>> return callable_(self, *args, **kwargs)
>>>> File "lib/python2.7/site-packages/sqlalchemy/engine/default.py", line 459,
>>>> in reflecttable
>>>> table, include_columns, exclude_columns, resolve_fks, **opts
>>>> File "lib/python2.7/site-packages/sqlalchemy/engine/reflection.py", line
>>>> 629, in reflecttable
>>>> table_name, schema, **table.dialect_kwargs
>>>> File "lib/python2.7/site-packages/sqlalchemy/engine/reflection.py", line
>>>> 314, in get_table_options
>>>> self.bind, table_name, schema, info_cache=self.info_cache, **kw
>>>> File "<string>", line 2, in get_table_options
>>>> File "lib/python2.7/site-packages/sqlalchemy/engine/reflection.py", line
>>>> 56, in cache
>>>> ret = fn(self, con, *args, **kw)
>>>> File "lib/python2.7/site-packages/sqlalchemy/dialects/mysql/base.py", line
>>>> 2499, in get_table_options
>>>> connection, table_name, schema, **kw
>>>> File "lib/python2.7/site-packages/sqlalchemy/dialects/mysql/base.py", line
>>>> 2745, in _parsed_state_or_create
>>>> info_cache=kw.get("info_cache", None),
>>>> File "<string>", line 2, in _setup_parser
>>>> File "lib/python2.7/site-packages/sqlalchemy/engine/reflection.py", line
>>>> 56, in cache
>>>> ret = fn(self, con, *args, **kw)
>>>> File "lib/python2.7/site-packages/sqlalchemy/dialects/mysql/base.py", line
>>>> 2773, in _setup_parser
>>>> connection, None, charset, full_name=full_name
>>>> File "lib/python2.7/site-packages/sqlalchemy/dialects/mysql/base.py", line
>>>> 2876, in _show_create_table
>>>> raise exc.NoSuchTableError(full_name)
>>>> sqlalchemy.exc.NoSuchTableError: `join`
>>>>
>>>>
>>>> Here's a repro based on the example from the docs
>>>> (https://docs.sqlalchemy.org/en/13/orm/join_conditions.html?highlight=secondary%20composite#composite-secondary-joins)
>>>>
>>>> from sqlalchemy import Column, Integer, ForeignKey, create_engine
>>>> from sqlalchemy.ext.declarative import declarative_base, DeferredReflection
>>>> from sqlalchemy.orm import relationship, Session
>>>>
>>>> _Base = declarative_base()
>>>>
>>>>
>>>> class Base(_Base, DeferredReflection):
>>>> __abstract__ = True
>>>>
>>>>
>>>> class A(Base):
>>>> __tablename__ = 'a'
>>>>
>>>> id = Column(Integer, primary_key=True)
>>>> b_id = Column(ForeignKey('b.id'))
>>>>
>>>> d = relationship("D",
>>>> secondary="join(B, D, B.d_id == D.id)."
>>>> "join(C, C.d_id == D.id)",
>>>> primaryjoin="and_(A.b_id == B.id, A.id == C.a_id)",
>>>> secondaryjoin="D.id == B.d_id",
>>>> uselist=True
>>>> )
>>>>
>>>>
>>>> class B(Base):
>>>> __tablename__ = 'b'
>>>>
>>>> id = Column(Integer, primary_key=True)
>>>> d_id = Column(ForeignKey('d.id'))
>>>>
>>>>
>>>> class C(Base):
>>>> __tablename__ = 'c'
>>>>
>>>> id = Column(Integer, primary_key=True)
>>>> a_id = Column(ForeignKey('a.id'))
>>>> d_id = Column(ForeignKey('d.id'))
>>>>
>>>>
>>>> class D(Base):
>>>> __tablename__ = 'd'
>>>>
>>>> id = Column(Integer, primary_key=True)
>>>>
>>>>
>>>> engine =
>>>> create_engine("mysql+mysqldb://[email protected]:3306/testing?charset=utf8mb4&binary_prefix=true",
>>>> echo=True)
>>>> engine.execute("""
>>>> DROP DATABASE testing;
>>>> CREATE DATABASE testing;
>>>> USE testing;
>>>>
>>>> CREATE TABLE d (
>>>> id int unsigned auto_increment,
>>>> primary key (id)
>>>> ) engine=innodb;
>>>>
>>>> CREATE TABLE b (
>>>> id int unsigned auto_increment,
>>>> d_id int unsigned default null,
>>>> primary key (id),
>>>> foreign key (d_id) references `d` (id)
>>>> ) engine=innodb;
>>>>
>>>> CREATE TABLE a (
>>>> id int unsigned auto_increment,
>>>> b_id int unsigned default null,
>>>> primary key (id),
>>>> foreign key (b_id) references `b` (id)
>>>> ) engine=innodb;
>>>>
>>>> CREATE TABLE c (
>>>> id int unsigned auto_increment,
>>>> a_id int unsigned default null,
>>>> d_id int unsigned default null,
>>>> primary key (id),
>>>> foreign key (a_id) references `a` (id),
>>>> foreign key (d_id) references `d` (id)
>>>> ) engine=innodb;
>>>>
>>>> INSERT INTO d (id) values (1), (2), (3);
>>>> INSERT INTO b (d_id) values (1), (1), (2);
>>>> INSERT INTO a (b_id) values (2), (3), (1);
>>>> INSERT INTO c (a_id, d_id) values (1, 1), (1, 2), (2, 3);
>>>> """)
>>>>
>>>> Base.metadata.reflect(bind=engine)
>>>>
>>>> A.prepare(engine)
>>>> B.prepare(engine)
>>>> C.prepare(engine)
>>>> D.prepare(engine)
>>>>
>>>> engine =
>>>> create_engine("mysql+mysqldb://[email protected]:3306/testing?charset=utf8mb4&binary_prefix=true",
>>>> echo=True)
>>>> session = Session(engine)
>>>>
>>>> a = session.query(A)
>>>> for _a in a:
>>>> print(a)
>>>> if _a.d:
>>>> print(list(_a.d))
>>>>
>>>> I've verified that the error only happens when I defer reflection. I.e. I
>>>> remove the DefferedReflection base class from _Base and I comment out the
>>>> A.prepare(engine)... lines.
>>>>
>>>> Has anyone else run into this?
>>>>
>>>> Thanks!
>>>>
>>>> --
>>>> SQLAlchemy -
>>>> The Python SQL Toolkit and Object Relational Mapper
>>>>
>>>> http://www.sqlalchemy.org/
>>>>
>>>> To post example code, please provide an MCVE: Minimal, Complete, and
>>>> Verifiable Example. See http://stackoverflow.com/help/mcve for a full
>>>> description.
>>>> ---
>>>> 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 [email protected].
>>>> To view this discussion on the web visit
>>>> https://groups.google.com/d/msgid/sqlalchemy/b6e3e875-3d17-49c0-b5f6-70d7215ea3f0%40googlegroups.com
>>>>
>>>> <https://groups.google.com/d/msgid/sqlalchemy/b6e3e875-3d17-49c0-b5f6-70d7215ea3f0%40googlegroups.com?utm_medium=email&utm_source=footer>.
>>>
>
> --
> SQLAlchemy -
> The Python SQL Toolkit and Object Relational Mapper
>
> http://www.sqlalchemy.org/
>
> To post example code, please provide an MCVE: Minimal, Complete, and
> Verifiable Example. See http://stackoverflow.com/help/mcve for a full
> description.
> ---
> 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 [email protected].
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/sqlalchemy/04c1b097-afc0-481a-a040-dde4ae2d54b1%40googlegroups.com
>
> <https://groups.google.com/d/msgid/sqlalchemy/04c1b097-afc0-481a-a040-dde4ae2d54b1%40googlegroups.com?utm_medium=email&utm_source=footer>.
--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
http://www.sqlalchemy.org/
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable
Example. See http://stackoverflow.com/help/mcve for a full description.
---
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 [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/sqlalchemy/a30ac818-32ca-429d-a4f1-245c74147089%40www.fastmail.com.