from ..sql import and_
from ..schema import ForeignKeyConstraint
from ..orm import relationship, backref, interfaces
+from ..orm.mapper import _CONFIGURE_MUTEX
from .. import util
autoload_replace=False
)
- table_to_map_config = dict(
- (m.local_table, m)
- for m in _DeferredMapperConfig.
- classes_for_base(cls, sort=False)
- )
-
- many_to_many = []
+ _CONFIGURE_MUTEX.acquire()
+ try:
+ table_to_map_config = dict(
+ (m.local_table, m)
+ for m in _DeferredMapperConfig.
+ classes_for_base(cls, sort=False)
+ )
- for table in cls.metadata.tables.values():
- lcl_m2m, rem_m2m, m2m_const = _is_many_to_many(cls, table)
- if lcl_m2m is not None:
- many_to_many.append((lcl_m2m, rem_m2m, m2m_const, table))
- elif not table.primary_key:
- continue
- elif table not in table_to_map_config:
- mapped_cls = type(
- classname_for_table(cls, table.name, table),
- (cls, ),
- {"__table__": table}
- )
- map_config = _DeferredMapperConfig.config_for_cls(mapped_cls)
- cls.classes[map_config.cls.__name__] = mapped_cls
- table_to_map_config[table] = map_config
-
- for map_config in table_to_map_config.values():
- _relationships_for_fks(cls,
- map_config,
- table_to_map_config,
- collection_class,
- name_for_scalar_relationship,
- name_for_collection_relationship,
- generate_relationship)
-
- for lcl_m2m, rem_m2m, m2m_const, table in many_to_many:
- _m2m_relationship(cls, lcl_m2m, rem_m2m, m2m_const, table,
- table_to_map_config,
- collection_class,
- name_for_scalar_relationship,
- name_for_collection_relationship,
- generate_relationship)
-
- for map_config in _DeferredMapperConfig.classes_for_base(cls):
- map_config.map()
+ many_to_many = []
+
+ for table in cls.metadata.tables.values():
+ lcl_m2m, rem_m2m, m2m_const = _is_many_to_many(cls, table)
+ if lcl_m2m is not None:
+ many_to_many.append((lcl_m2m, rem_m2m, m2m_const, table))
+ elif not table.primary_key:
+ continue
+ elif table not in table_to_map_config:
+ mapped_cls = type(
+ classname_for_table(cls, table.name, table),
+ (cls, ),
+ {"__table__": table}
+ )
+ map_config = _DeferredMapperConfig.config_for_cls(
+ mapped_cls)
+ cls.classes[map_config.cls.__name__] = mapped_cls
+ table_to_map_config[table] = map_config
+
+ for map_config in table_to_map_config.values():
+ _relationships_for_fks(cls,
+ map_config,
+ table_to_map_config,
+ collection_class,
+ name_for_scalar_relationship,
+ name_for_collection_relationship,
+ generate_relationship)
+
+ for lcl_m2m, rem_m2m, m2m_const, table in many_to_many:
+ _m2m_relationship(cls, lcl_m2m, rem_m2m, m2m_const, table,
+ table_to_map_config,
+ collection_class,
+ name_for_scalar_relationship,
+ name_for_collection_relationship,
+ generate_relationship)
+
+ for map_config in _DeferredMapperConfig.classes_for_base(cls):
+ map_config.map()
+ finally:
+ _CONFIGURE_MUTEX.release()
_sa_decl_prepare = True
"""Indicate that the mapping of classes should be deferred.
from sqlalchemy.orm import relationship, interfaces, configure_mappers
from sqlalchemy.ext.automap import generate_relationship
from sqlalchemy.testing.mock import Mock, patch
-from sqlalchemy import String, Integer, ForeignKey
+from sqlalchemy import String, Integer, ForeignKey, MetaData
from sqlalchemy import testing
from sqlalchemy.testing.schema import Table, Column
+from sqlalchemy import create_engine
+import threading
+import time
+import random
+
class AutomapTest(fixtures.MappedTest):
@classmethod
Base.prepare(
engine=testing.db, reflect=True,
generate_relationship=_gen_relationship)
+
+
+class ConcurrentAutomapTest(fixtures.TestBase):
+ __only_on__ = 'sqlite'
+
+ def _make_tables(self, e):
+ m = MetaData()
+ for i in range(15):
+ Table(
+ 'table_%d' % i,
+ m,
+ Column('id', Integer, primary_key=True),
+ Column('data', String(50)),
+ Column(
+ 't_%d_id' % (i - 1),
+ ForeignKey('table_%d.id' % (i - 1))
+ ) if i > 4 else None
+ )
+ m.drop_all(e)
+ m.create_all(e)
+
+ def _automap(self, e):
+ Base = automap_base()
+
+ Base.prepare(e, reflect=True)
+
+ time.sleep(.01)
+ configure_mappers()
+
+ def _chaos(self):
+ e = create_engine("sqlite://")
+ try:
+ self._make_tables(e)
+ for i in range(2):
+ try:
+ self._automap(e)
+ except:
+ self._success = False
+ raise
+ time.sleep(random.random())
+ finally:
+ e.dispose()
+
+ def test_concurrent_automaps_w_configure(self):
+ self._success = True
+ threads = [threading.Thread(target=self._chaos) for i in range(30)]
+ for t in threads:
+ t.start()
+
+ for t in threads:
+ t.join()
+
+ assert self._success, "One or more threads failed"
\ No newline at end of file