On 4/21/15 9:31 AM, Steven Winfield wrote:
Hi,
It seems like configuration is attempted for all new mappers,
globally, whenever a query is done. So if library A and B both use
sqlalchemy, and A imports B before A's mappers can be properly
initialised (e.g. there is a relationship("ClassnameAsString") call
somewhere that can't be resolved yet), and B does something to trigger
mapper configuration, then it will fail.
This occurs even if A and B make separate calls to declarative_base(),
even with explicitly different metadata and bound engines.
no there's not, and the short answer is that libraries shouldn't be
triggering mapper configuration (and definitely not doing ORM queries)
at import time, and/or the imports of A and B should be organized such
that B imports fully before A starts doing things. Either these
libraries have inter-dependencies, in which case this implies mapper
configuration should be across all of the mappings in both, or they
don't, in which case the imports of A and B should not be from within
each other.
An enhancement that would limit configuration to groups of mappings is a
feasible proposal but we don't have that right now. Wouldn't be that
easy to do without adding a performance penalty since the check for "new
mappers" would have to be limited to some categorization, meaning
lookups in critical sections.
Here's a boiled-down version of the problem that I've been playing
with, which shows that the relationship between Parent and Child is
configured when a query on Test is done - even though it may be part
of a different library and in a different database:
|
from sqlalchemy import Column, Integer, Text, ForeignKey, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
import traceback
Base1 = declarative_base()
class Test(Base1):
__tablename__ = "test"
id = Column(Integer, primary_key=True)
Base2 = declarative_base()
class Parent(Base2):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
def deferred_parent():
traceback.print_stack()
return Parent
class Child(Base2):
__tablename__ = "child"
id_parent = Column(Integer, ForeignKey(Parent.id), primary_key=True)
name = Column(Text, primary_key=True)
parent = relationship(deferred_parent)
engine = create_engine('sqlite://')
Session = sessionmaker(bind=engine)
session = Session()
try:
session.query(Test).all()
except:
pass
|
...the important bit of the traceback being:
File
"R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\session.py",
line 1165, in query
return self._query_cls(entities, self, **kwargs)
File
"R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\query.py",
line 108, in __init__
self._set_entities(entities)
File
"R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\query.py",
line 118, in _set_entities
self._set_entity_selectables(self._entities)
File
"R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\query.py",
line 151, in _set_entity_selectables
ent.setup_entity(*d[entity])
File
"R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\query.py",
line 2997, in setup_entity
self._with_polymorphic = ext_info.with_polymorphic_mappers
File
"R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\util\langhelpers.py",
line 726, in __get__
obj.__dict__[self.__name__] = result = self.fget(obj)
File
"R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\mapper.py",
line 1871, in _with_polymorphic_mappers
configure_mappers()
File
"R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\mapper.py",
line 2583, in configure_mappers
mapper._post_configure_properties()
File
"R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\mapper.py",
line 1688, in _post_configure_properties
prop.init()
File
"R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\interfaces.py",
line 144, in init
self.do_init()
File
"R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\relationships.py",
line 1549, in do_init
self._process_dependent_arguments()
File
"R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\relationships.py",
line 1605, in _process_dependent_arguments
self.target = self.mapper.mapped_table
File
"R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\util\langhelpers.py",
line 726, in __get__
obj.__dict__[self.__name__] = result = self.fget(obj)
File
"R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\relationships.py",
line 1522, in mapper
argument = self.argument()
File "user!winfis!sqlalchemy!query_triggers_relationship_config.py",
line 19, in deferred_parent
traceback.print_stack()
Is there some method that I've missed of delaying mapper
configuration? Aren't the only mappers than need to be set up those
that share metadata with entities in the query, or any metadata bound
to the engine that will be used?
Perhaps configure_mappers() could take an optional metadata/engine and
only set up mappers that are related to this?
As you can see, I'm doing this with 0.9.7 but looking at the 1.0.0
code I think I'd have the same problem.
If it helps, (and you're not already bored) here's our use-case:
We have one library that implements a PEP302 import hook, which
fetches python code from a database and compiles it. This is managed
by sqlalchemy.
Some of the code in the database also use sqlalchemy and define other
sets of ORM-mapped classes, completely unrelated to the first set, and
which relate to tables inreside in completely different databases.
If a query needs to be executed to fetch and compile some code while
another set of classes are not ready to have their mappers initialised
then exceptions are raised.
Thanks,
Steve.
--
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+unsubscr...@googlegroups.com
<mailto:sqlalchemy+unsubscr...@googlegroups.com>.
To post to this group, send email to sqlalchemy@googlegroups.com
<mailto:sqlalchemy@googlegroups.com>.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.
--
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+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.