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.

Reply via email to