]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
begin implementing inspection system for #2208
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 3 Apr 2012 22:53:39 +0000 (18:53 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 3 Apr 2012 22:53:39 +0000 (18:53 -0400)
13 files changed:
lib/sqlalchemy/__init__.py
lib/sqlalchemy/engine/reflection.py
lib/sqlalchemy/inspection.py [new file with mode: 0644]
lib/sqlalchemy/orm/instrumentation.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/state.py
lib/sqlalchemy/orm/util.py
test/base/test_inspect.py [new file with mode: 0644]
test/engine/test_reflection.py
test/orm/_fixtures.py
test/orm/test_inspect.py [new file with mode: 0644]
test/orm/test_mapper.py
test/orm/test_query.py

index 4e00437ea9ee0c56be4581db4100ea5419bd938a..ee7bf4c6f9bd9eddcb9004fc7fbf67dc55a3eff5 100644 (file)
@@ -4,7 +4,7 @@
 # This module is part of SQLAlchemy and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
 
-import inspect
+import inspect as _inspect
 import sys
 
 import sqlalchemy.exc as exceptions
@@ -111,15 +111,17 @@ from sqlalchemy.schema import (
     UniqueConstraint,
     )
 
+from sqlalchemy.inspection import inspect
+
 from sqlalchemy.engine import create_engine, engine_from_config
 
 
 __all__ = sorted(name for name, obj in locals().items()
-                 if not (name.startswith('_') or inspect.ismodule(obj)))
+                 if not (name.startswith('_') or _inspect.ismodule(obj)))
 
 __version__ = '0.7.7'
 
-del inspect, sys
+del _inspect, sys
 
 from sqlalchemy import util as _sa_util
 _sa_util.importlater.resolve_all()
index 71d97e65f884713a190be65faedef3d6aa6bc12c..b2a5a02eff9877e4864b496acadf2f1b5817de16 100644 (file)
@@ -30,7 +30,8 @@ from sqlalchemy import util
 from sqlalchemy.util import topological
 from sqlalchemy.types import TypeEngine
 from sqlalchemy import schema as sa_schema
-
+from sqlalchemy import inspection
+from sqlalchemy.engine.base import Connectable
 
 @util.decorator
 def cache(fn, self, con, *args, **kw):
@@ -118,6 +119,10 @@ class Inspector(object):
             return bind.dialect.inspector(bind)
         return Inspector(bind)
 
+    @inspection._inspects(Connectable)
+    def _insp(bind):
+        return Inspector.from_engine(bind)
+
     @property
     def default_schema_name(self):
         """Return the default schema name presented by the dialect
diff --git a/lib/sqlalchemy/inspection.py b/lib/sqlalchemy/inspection.py
new file mode 100644 (file)
index 0000000..9ce52be
--- /dev/null
@@ -0,0 +1,44 @@
+# sqlalchemy/inspect.py
+# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
+"""Base inspect API.
+
+:func:`.inspect` provides access to a contextual object
+regarding a subject.   
+
+Various subsections of SQLAlchemy,
+such as the :class:`.Inspector`, :class:`.Mapper`, and
+others register themselves with the "inspection registry" here
+so that they may return a context object given a certain kind
+of argument.
+"""
+
+from sqlalchemy import util
+_registrars = util.defaultdict(list)
+
+def inspect(subject):
+    type_ = type(subject)
+    for cls in type_.__mro__:
+        if cls in _registrars:
+            reg = _registrars[cls]
+            break
+    else:
+        raise exc.InvalidRequestError(
+                "No inspection system is "
+                "available for object of type %s" % 
+                type_)
+    return reg(subject)
+
+def _inspects(*types):
+    def decorate(fn_or_cls):
+        for type_ in types:
+            if type_ in _registrars:
+                raise AssertionError(
+                            "Type %s is already "
+                            "registered" % type_)
+            _registrars[type_] = fn_or_cls
+        return fn_or_cls
+    return decorate
index af9ef7841ac3c325f2f0a8621d2f3f4bacdb401a..1012af67a9d880c6408cd5bfea0c0d708fc71df3 100644 (file)
@@ -23,7 +23,7 @@ An example of full customization is in /examples/custom_attributes.
 
 from sqlalchemy.orm import exc, collections, events
 from operator import attrgetter, itemgetter
-from sqlalchemy import event, util
+from sqlalchemy import event, util, inspection
 import weakref
 from sqlalchemy.orm import state, attributes
 
index e96b7549a9da892f99d658133f3bd1c3b87e3a66..4fa8dfe24fec9cb72f5fca000ff4b2ac1e11d95f 100644 (file)
@@ -33,6 +33,7 @@ from sqlalchemy.orm.util import _INSTRUMENTOR, _class_to_mapper, \
 import sys
 sessionlib = util.importlater("sqlalchemy.orm", "session")
 properties = util.importlater("sqlalchemy.orm", "properties")
+descriptor_props = util.importlater("sqlalchemy.orm", "descriptor_props")
 
 __all__ = (
     'Mapper',
@@ -1392,12 +1393,35 @@ class Mapper(object):
                         continue
                 yield c
 
-    @property
+    @util.memoized_property
     def properties(self):
-        raise NotImplementedError(
-                    "Public collection of MapperProperty objects is "
-                    "provided by the get_property() and iterate_properties "
-                    "accessors.")
+        if _new_mappers:
+            configure_mappers()
+        return util.ImmutableProperties(self._props)
+
+    @_memoized_configured_property
+    def synonyms(self):
+        return self._filter_properties(descriptor_props.SynonymProperty)
+
+    @_memoized_configured_property
+    def column_attrs(self):
+        return self._filter_properties(properties.ColumnProperty)
+
+    @_memoized_configured_property
+    def relationships(self):
+        return self._filter_properties(properties.RelationshipProperty)
+
+    @_memoized_configured_property
+    def composites(self):
+        return self._filter_properties(descriptor_props.CompositeProperty)
+
+    def _filter_properties(self, type_):
+        if _new_mappers:
+            configure_mappers()
+        return dict(
+            (k, v) for k, v in self._props.iteritems()
+            if isinstance(v, type_)
+        )
 
     @_memoized_configured_property
     def _get_clause(self):
index 4803ecdc3d14cab77ece3046cb683bbad9548830..156686bc7397a36f3130005663f8f65a610ba900 100644 (file)
@@ -54,6 +54,10 @@ class InstanceState(object):
     def pending(self):
         return {}
 
+    @util.memoized_property
+    def mapper(self):
+        return self.manager.mapper
+
     @property
     def has_identity(self):
         return bool(self.key)
index 0c5f203a72c84283638606699209e8150ea08e38..8d334ce17e4b796e0dac049cf459f39213b9b966 100644 (file)
@@ -5,7 +5,7 @@
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
 
 
-from sqlalchemy import sql, util, event, exc as sa_exc
+from sqlalchemy import sql, util, event, exc as sa_exc, inspection
 from sqlalchemy.sql import expression, util as sql_util, operators
 from sqlalchemy.orm.interfaces import MapperExtension, EXT_CONTINUE,\
                                 PropComparator, MapperProperty
@@ -616,15 +616,34 @@ def object_mapper(instance):
 
     Raises UnmappedInstanceError if no mapping is configured.
 
+    This function is available via the inspection system as::
+    
+        inspect(instance).mapper
+
+    """
+    return object_state(instance).mapper
+
+@inspection._inspects(object)
+def object_state(instance):
+    """Given an object, return the primary Mapper associated with the object
+    instance.
+
+    Raises UnmappedInstanceError if no mapping is configured.
+
+    This function is available via the inspection system as::
+    
+        inspect(instance)
+
     """
     try:
-        state = attributes.instance_state(instance)
-        return state.manager.mapper
+        return attributes.instance_state(instance)
     except exc.UnmappedClassError:
         raise exc.UnmappedInstanceError(instance)
     except exc.NO_STATE:
         raise exc.UnmappedInstanceError(instance)
 
+
+@inspection._inspects(type)
 def class_mapper(class_, compile=True):
     """Given a class, return the primary :class:`.Mapper` associated 
     with the key.
@@ -633,6 +652,10 @@ def class_mapper(class_, compile=True):
     on the given class, or :class:`.ArgumentError` if a non-class
     object is passed.
 
+    This function is available via the inspection system as::
+    
+        inspect(some_mapped_class)
+    
     """
 
     try:
diff --git a/test/base/test_inspect.py b/test/base/test_inspect.py
new file mode 100644 (file)
index 0000000..b95b7d8
--- /dev/null
@@ -0,0 +1,62 @@
+"""test the inspection registry system."""
+
+from test.lib.testing import eq_, assert_raises
+from sqlalchemy import exc, util
+from sqlalchemy import inspection, inspect
+from test.lib import fixtures
+
+class TestFixture(object):
+    pass
+
+class TestEvents(fixtures.TestBase):
+    """Test class- and instance-level event registration."""
+
+    def tearDown(self):
+        for type_ in list(inspection._registrars):
+            if issubclass(type_, TestFixture):
+                del inspection._registrars[type_]
+
+    def test_def_insp(self):
+        class SomeFoo(TestFixture):
+            pass
+
+        @inspection._inspects(SomeFoo)
+        def insp_somefoo(subject):
+            return {"insp":subject}
+
+        somefoo = SomeFoo()
+        insp = inspect(somefoo)
+        assert insp["insp"] is somefoo
+
+    def test_class_insp(self):
+        class SomeFoo(TestFixture):
+            pass
+
+        @inspection._inspects(SomeFoo)
+        class SomeFooInspect(object):
+            def __init__(self, target):
+                self.target = target
+
+        somefoo = SomeFoo()
+        insp = inspect(somefoo)
+        assert isinstance(insp, SomeFooInspect)
+        assert insp.target is somefoo
+
+    def test_hierarchy_insp(self):
+        class SomeFoo(TestFixture):
+            pass
+
+        class SomeSubFoo(SomeFoo):
+            pass
+
+        @inspection._inspects(SomeFoo)
+        def insp_somefoo(subject):
+            return 1
+
+        @inspection._inspects(SomeSubFoo)
+        def insp_somesubfoo(subject):
+            return 2
+
+        somefoo = SomeFoo()
+        eq_(inspect(SomeFoo()), 1)
+        eq_(inspect(SomeSubFoo()), 2)
index f385a0fa23997f26e0d7387ab513b15225cf7364..e59849d98f77af184679f2d22d2a0df5d82e9b30 100644 (file)
@@ -1,8 +1,7 @@
 from test.lib.testing import eq_, assert_raises, assert_raises_message
 import StringIO, unicodedata
 from sqlalchemy import types as sql_types
-from sqlalchemy import schema, events, event
-from sqlalchemy.engine.reflection import Inspector
+from sqlalchemy import schema, events, event, inspect
 from sqlalchemy import MetaData, Integer, String
 from test.lib.schema import Table, Column
 import sqlalchemy as sa
@@ -10,8 +9,6 @@ from test.lib import ComparesTables, \
                             testing, engines, AssertsCompiledSQL
 from test.lib import fixtures
 
-create_inspector = Inspector.from_engine
-
 metadata, users = None, None
 
 class ReflectionTest(fixtures.TestBase, ComparesTables):
@@ -773,7 +770,7 @@ class ReflectionTest(fixtures.TestBase, ComparesTables):
     def test_inspector_conn_closing(self):
         m1 = MetaData()
         c = testing.db.connect()
-        i = Inspector.from_engine(testing.db)
+        i = inspect(testing.db)
         assert not c.closed
 
     @testing.provide_metadata
@@ -1050,7 +1047,7 @@ class UnicodeReflectionTest(fixtures.TestBase):
 
     @testing.requires.unicode_connections
     def test_get_names(self):
-        inspector = Inspector.from_engine(self.bind)
+        inspector = inspect(self.bind)
         names = dict(
             (tname, (cname, ixname)) for tname, cname, ixname in self.names
         )
@@ -1362,18 +1359,18 @@ class ComponentReflectionTest(fixtures.TestBase):
 
     @testing.requires.schemas
     def test_get_schema_names(self):
-        insp = Inspector(testing.db)
+        insp = inspect(testing.db)
 
         self.assert_('test_schema' in insp.get_schema_names())
 
     def test_dialect_initialize(self):
         engine = engines.testing_engine()
         assert not hasattr(engine.dialect, 'default_schema_name')
-        insp = Inspector(engine)
+        insp = inspect(engine)
         assert hasattr(engine.dialect, 'default_schema_name')
 
     def test_get_default_schema_name(self):
-        insp = Inspector(testing.db)
+        insp = inspect(testing.db)
         eq_(insp.default_schema_name, testing.db.dialect.default_schema_name)
 
     @testing.provide_metadata
@@ -1384,7 +1381,7 @@ class ComponentReflectionTest(fixtures.TestBase):
         meta.create_all()
         _create_views(meta.bind, schema)
         try:
-            insp = Inspector(meta.bind)
+            insp = inspect(meta.bind)
             if table_type == 'view':
                 table_names = insp.get_view_names(schema)
                 table_names.sort()
@@ -1428,7 +1425,7 @@ class ComponentReflectionTest(fixtures.TestBase):
             _create_views(meta.bind, schema)
             table_names = ['users_v', 'email_addresses_v']
         try:
-            insp = Inspector(meta.bind)
+            insp = inspect(meta.bind)
             for table_name, table in zip(table_names, (users,
                     addresses)):
                 schema_name = schema
@@ -1490,7 +1487,7 @@ class ComponentReflectionTest(fixtures.TestBase):
         meta = self.metadata
         users, addresses, dingalings = createTables(meta, schema)
         meta.create_all()
-        insp = Inspector(meta.bind)
+        insp = inspect(meta.bind)
         users_pkeys = insp.get_primary_keys(users.name,
                                             schema=schema)
         eq_(users_pkeys,  ['user_id'])
@@ -1517,7 +1514,7 @@ class ComponentReflectionTest(fixtures.TestBase):
         meta = self.metadata
         users, addresses, dingalings = createTables(meta, schema)
         meta.create_all()
-        insp = Inspector(meta.bind)
+        insp = inspect(meta.bind)
         expected_schema = schema
         # users
         users_fkeys = insp.get_foreign_keys(users.name,
@@ -1561,7 +1558,7 @@ class ComponentReflectionTest(fixtures.TestBase):
         createIndexes(meta.bind, schema)
         # The database may decide to create indexes for foreign keys, etc.
         # so there may be more indexes than expected.
-        insp = Inspector(meta.bind)
+        insp = inspect(meta.bind)
         indexes = insp.get_indexes('users', schema=schema)
         expected_indexes = [
             {'unique': False,
@@ -1590,7 +1587,7 @@ class ComponentReflectionTest(fixtures.TestBase):
         view_name1 = 'users_v'
         view_name2 = 'email_addresses_v'
         try:
-            insp = Inspector(meta.bind)
+            insp = inspect(meta.bind)
             v1 = insp.get_view_definition(view_name1, schema=schema)
             self.assert_(v1)
             v2 = insp.get_view_definition(view_name2, schema=schema)
@@ -1613,7 +1610,7 @@ class ComponentReflectionTest(fixtures.TestBase):
         meta = self.metadata
         users, addresses, dingalings = createTables(meta, schema)
         meta.create_all()
-        insp = create_inspector(meta.bind)
+        insp = inspect(meta.bind)
         oid = insp.get_table_oid(table_name, schema)
         self.assert_(isinstance(oid, (int, long)))
 
index 5def54e3a4c4d481baf9b27d8f8270d293ed7d7a..7431a3a8363caf4f25db65c951762069af0bf6ba 100644 (file)
@@ -2,7 +2,8 @@ from sqlalchemy import MetaData, Integer, String, ForeignKey
 from sqlalchemy import util
 from test.lib.schema import Table
 from test.lib.schema import Column
-from sqlalchemy.orm import attributes
+from sqlalchemy.orm import attributes, mapper, relationship, \
+    backref, configure_mappers
 from test.lib import fixtures
 
 __all__ = ()
@@ -48,6 +49,48 @@ class FixtureTest(fixtures.MappedTest):
         class CompositePk(Base):
             pass
 
+    @classmethod
+    def _setup_stock_mapping(cls):
+        Node, composite_pk_table, users, Keyword, items, Dingaling, \
+            order_items, item_keywords, Item, User, dingalings, \
+            Address, keywords, CompositePk, nodes, Order, orders, \
+            addresses = cls.classes.Node, \
+            cls.tables.composite_pk_table, cls.tables.users, \
+            cls.classes.Keyword, cls.tables.items, \
+            cls.classes.Dingaling, cls.tables.order_items, \
+            cls.tables.item_keywords, cls.classes.Item, \
+            cls.classes.User, cls.tables.dingalings, \
+            cls.classes.Address, cls.tables.keywords, \
+            cls.classes.CompositePk, cls.tables.nodes, \
+            cls.classes.Order, cls.tables.orders, cls.tables.addresses
+
+        mapper(User, users, properties={
+            'addresses':relationship(Address, backref='user', order_by=addresses.c.id),
+            'orders':relationship(Order, backref='user', order_by=orders.c.id), # o2m, m2o
+        })
+        mapper(Address, addresses, properties={
+            'dingaling':relationship(Dingaling, uselist=False, backref="address")  #o2o
+        })
+        mapper(Dingaling, dingalings)
+        mapper(Order, orders, properties={
+            'items':relationship(Item, secondary=order_items, order_by=items.c.id),  #m2m
+            'address':relationship(Address),  # m2o
+        })
+        mapper(Item, items, properties={
+            'keywords':relationship(Keyword, secondary=item_keywords) #m2m
+        })
+        mapper(Keyword, keywords)
+
+        mapper(Node, nodes, properties={
+            'children':relationship(Node, 
+                backref=backref('parent', remote_side=[nodes.c.id])
+            )
+        })
+
+        mapper(CompositePk, composite_pk_table)
+
+        configure_mappers()
+
     @classmethod
     def define_tables(cls, metadata):
         Table('users', metadata,
diff --git a/test/orm/test_inspect.py b/test/orm/test_inspect.py
new file mode 100644 (file)
index 0000000..3c824d8
--- /dev/null
@@ -0,0 +1,194 @@
+"""test the inspection registry system."""
+
+from test.lib.testing import eq_, assert_raises
+from sqlalchemy import exc, util
+from sqlalchemy import inspect
+from test.orm import _fixtures
+from sqlalchemy.orm import class_mapper, synonym
+from sqlalchemy.orm.attributes import instance_state
+
+class TestORMInspection(_fixtures.FixtureTest):
+    @classmethod
+    def setup_mappers(cls):
+        cls._setup_stock_mapping()
+        inspect(cls.classes.User).add_property(
+            "name_syn",synonym("name")
+        )
+
+    def test_class_mapper(self):
+        User = self.classes.User
+
+        assert inspect(User) is class_mapper(User)
+
+    def test_instance_state(self):
+        User = self.classes.User
+        u1 = User()
+
+        assert inspect(u1) is instance_state(u1)
+
+    def test_synonyms(self):
+        User = self.classes.User
+        syn = inspect(User).synonyms
+
+        # TODO: some of the synonym debacle in 0.7
+        # has led User.name_syn.property to be the 
+        # ColumnProperty.  not sure if we want that
+        # implicit jump in there though, perhaps get Query/etc. to 
+        # call upon "effective_property" or something like that
+
+        eq_(inspect(User).synonyms, {
+            "name_syn":class_mapper(User).get_property("name_syn")
+        })
+
+    # TODO: test all these accessors...
+
+"""
+# column collection
+>>> b.columns
+[<id column>, <name column>]
+
+# its a ColumnCollection
+>>> b.columns.id
+<id column>
+
+# i.e. from mapper
+>>> b.primary_key
+(<id column>, )
+
+# i.e. from mapper
+>>> b.local_table
+<user table>
+
+# ColumnProperty
+>>> b.attr.id.columns
+[<id column>]
+
+# but perhaps we use a collection with some helpers
+>>> b.attr.id.columns.first
+<id column>
+
+# and a mapper?  its None since this is a column
+>>> b.attr.id.mapper
+None
+
+# attr is basically the _props
+>>> b.attr.keys()
+['id', 'name', 'name_syn', 'addresses']
+
+# b itself is likely just the mapper
+>>> b
+<User mapper>
+
+# get only column attributes
+>>> b.column_attrs
+[<id prop>, <name prop>]
+
+# its a namespace
+>>> b.column_attrs.id
+<id prop>
+
+# get only synonyms
+>>> b.synonyms
+[<name syn prop>]
+
+# get only relationships
+>>> b.relationships
+[<addresses prop>]
+
+# its a namespace
+>>> b.relationships.addresses
+<addresses prop>
+
+# point inspect() at a class level attribute,
+# basically returns ".property"
+>>> b = inspect(User.addresses)
+>>> b
+<addresses prop>
+
+# mapper
+>>> b.mapper
+<Address mapper>
+
+# None columns collection, just like columnprop has empty mapper
+>>> b.columns
+None
+
+# the parent
+>>> b.parent
+<User mapper>
+
+# __clause_element__()
+>>> b.expression
+User.id==Address.user_id
+
+>>> inspect(User.id).expression
+<id column with ORM annotations>
+
+
+# inspect works on instances !  
+>>> u1 = User(id=3, name='x')
+>>> b = inspect(u1)
+
+# what's b here ?  probably InstanceState
+>>> b
+<InstanceState>
+
+>>> b.attr.keys()
+['id', 'name', 'name_syn', 'addresses']
+
+# this is class level stuff - should this require b.mapper.columns ?
+>>> b.columns
+[<id column>, <name column>]
+
+# does this return '3'?  or an object?
+>>> b.attr.id
+<magic attribute inspect thing>
+
+# or does this ?
+>>> b.attr.id.value 
+3
+
+>>> b.attr.id.history
+<history object>
+
+>>> b.attr.id.history.unchanged
+3
+
+>>> b.attr.id.history.deleted
+None
+
+# lets assume the object is persistent
+>>> s = Session()
+>>> s.add(u1)
+>>> s.commit()
+
+# big one - the primary key identity !  always
+# works in query.get()
+>>> b.identity
+[3]
+
+# the mapper level key
+>>> b.identity_key
+(User, [3])
+
+>>> b.persistent
+True
+
+>>> b.transient
+False
+
+>>> b.deleted
+False
+
+>>> b.detached
+False
+
+>>> b.session
+<session>
+
+# the object.  this navigates obj()
+# of course, would be nice if it was b.obj...
+>>> b.object_
+<User instance u1>
+
+"""
index 1c5f29b71677bac3aeaf6f6bf521e1d57020024e..bec813f39db5404cae1cf1d80717dcb3d1e4e04b 100644 (file)
@@ -87,14 +87,6 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
 
 
 
-    def test_prop_accessor(self):
-        users, User = self.tables.users, self.classes.User
-
-        mapper(User, users)
-        assert_raises(NotImplementedError,
-                          getattr, sa.orm.class_mapper(User), 'properties')
-
-
     def test_friendly_attribute_str_on_uncompiled_boom(self):
         User, users = self.classes.User, self.tables.users
 
index bcc97681657382a55836512bc1b3a0ef153148b3..bd9f10d2172ebff68da84941ee5c23e8420e9b3e 100644 (file)
@@ -29,45 +29,7 @@ class QueryTest(_fixtures.FixtureTest):
 
     @classmethod
     def setup_mappers(cls):
-        Node, composite_pk_table, users, Keyword, items, Dingaling, \
-            order_items, item_keywords, Item, User, dingalings, \
-            Address, keywords, CompositePk, nodes, Order, orders, \
-            addresses = cls.classes.Node, \
-            cls.tables.composite_pk_table, cls.tables.users, \
-            cls.classes.Keyword, cls.tables.items, \
-            cls.classes.Dingaling, cls.tables.order_items, \
-            cls.tables.item_keywords, cls.classes.Item, \
-            cls.classes.User, cls.tables.dingalings, \
-            cls.classes.Address, cls.tables.keywords, \
-            cls.classes.CompositePk, cls.tables.nodes, \
-            cls.classes.Order, cls.tables.orders, cls.tables.addresses
-
-        mapper(User, users, properties={
-            'addresses':relationship(Address, backref='user', order_by=addresses.c.id),
-            'orders':relationship(Order, backref='user', order_by=orders.c.id), # o2m, m2o
-        })
-        mapper(Address, addresses, properties={
-            'dingaling':relationship(Dingaling, uselist=False, backref="address")  #o2o
-        })
-        mapper(Dingaling, dingalings)
-        mapper(Order, orders, properties={
-            'items':relationship(Item, secondary=order_items, order_by=items.c.id),  #m2m
-            'address':relationship(Address),  # m2o
-        })
-        mapper(Item, items, properties={
-            'keywords':relationship(Keyword, secondary=item_keywords) #m2m
-        })
-        mapper(Keyword, keywords)
-
-        mapper(Node, nodes, properties={
-            'children':relationship(Node, 
-                backref=backref('parent', remote_side=[nodes.c.id])
-            )
-        })
-
-        mapper(CompositePk, composite_pk_table)
-
-        configure_mappers()
+        cls._setup_stock_mapping()
 
 class MiscTest(QueryTest):
     run_create_tables = None