Michael Bayer wrote:
Suppose you had a joined table setup, and you placed a Constraint in the
__table_args__ of the base class.   You certainly don't want that feeding
into the table of the subclass.  So like the case with __mapper_args__,
unconditional inheritance of the attribute is a bad idea.

So again, and at this point you should just consider this research towards
an actual feature being added to SQLAlchemy, you need to roll this into
the DeclarativeMixin recipe at
http://www.sqlalchemy.org/trac/wiki/UsageRecipes/DeclarativeMixins .

To this end, please find attached the mixable.py I'm working and it's unit tests.

All the tests bar test_mapper_args_composite pass, and that currently blows up with a rather bizarre:

Traceback (most recent call last):
  File "test_mixable.py", line 177, in test_mapper_args_composite
    class MyModel(Base,MyMixin1,MyMixin2):
  File "mixable.py", line 27, in __init__
    return DeclarativeMeta.__init__(cls, classname, bases, dict_)
File "/usr/lib/python2.5/site-packages/SQLAlchemy-0.5.8-py2.5.egg/sqlalchemy/ext/declarative.py", line 561, in __init__
    _as_declarative(cls, classname, dict_)
File "/usr/lib/python2.5/site-packages/SQLAlchemy-0.5.8-py2.5.egg/sqlalchemy/ext/declarative.py", line 554, in _as_declarative cls.__mapper__ = mapper_cls(cls, table, properties=our_stuff, **mapper_args) File "/usr/lib/python2.5/site-packages/SQLAlchemy-0.5.8-py2.5.egg/sqlalchemy/orm/__init__.py", line 751, in mapper
    return Mapper(class_, local_table, *args, **params)
File "/usr/lib/python2.5/site-packages/SQLAlchemy-0.5.8-py2.5.egg/sqlalchemy/orm/mapper.py", line 198, in __init__
    self._configure_properties()
File "/usr/lib/python2.5/site-packages/SQLAlchemy-0.5.8-py2.5.egg/sqlalchemy/orm/mapper.py", line 514, in _configure_properties
    if self._should_exclude(col.key, col.key, local=False):
File "/usr/lib/python2.5/site-packages/SQLAlchemy-0.5.8-py2.5.egg/sqlalchemy/orm/mapper.py", line 985, in _should_exclude
    if getattr(self.class_, assigned_name, None)\
TypeError: Error when calling the metaclass bases
    getattr(): attribute name must be string

Any idea what's causing that? If I move the type_ columns the the __mapper_args__ to MyModel, the tests passes...

(also note evilness required because declarative gets __table_args__ from dict_ rather than the cls, where it should ;-) )

cheers,

Chris
import unittest

from mixable import declarative_base
from decorators import classproperty
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.schema import Column
from sqlalchemy.types import Integer, String


class Test(unittest.TestCase):

    def setUp(self):
        self.engine = create_engine("sqlite://")
        self.Session = sessionmaker(
            bind=self.engine,
            autoflush=True,
            autocommit=False
            )

    def test_simple(self):

        Base = declarative_base()

        class MyMixin:
            __mixin__ = True
            id =  Column(Integer, primary_key=True)

            def foo(self):
                return 'bar'+str(self.id)

        class MyModel(Base,MyMixin):
            __tablename__='test'
            name = Column(String(1000), nullable=False, index=True)

        Base.metadata.create_all(self.engine)
        
        session = self.Session()
        session.add(MyModel(name='testing'))
        session.commit()

        session = self.Session()
        obj = session.query(MyModel).one()
        self.assertEqual(obj.id,1)
        self.assertEqual(obj.name,'testing')
        self.assertEqual(obj.foo(),'bar1')
        
        
    def test_hierarchical_bases(self):

        Base = declarative_base()

        class MyMixinParent:
            __mixin__ = True
            id =  Column(Integer, primary_key=True)

            def foo(self):
                return 'bar'+str(self.id)

        class MyMixin(MyMixinParent):
            baz = Column(String(1000), nullable=False, index=True)

        class MyModel(Base,MyMixin):
            __tablename__='test'
            name = Column(String(1000), nullable=False, index=True)

        Base.metadata.create_all(self.engine)
        
        session = self.Session()
        session.add(MyModel(name='testing',baz='fu'))
        session.commit()

        session = self.Session()
        obj = session.query(MyModel).one()
        self.assertEqual(obj.id,1)
        self.assertEqual(obj.name,'testing')
        self.assertEqual(obj.foo(),'bar1')
        self.assertEqual(obj.baz,'fu')
        
    def test_table_args_inherited(self):
        
        Base = declarative_base()

        class MyMixin:
            __mixin__ = True
            __table_args__ = {'mysql_engine':'InnoDB'}             

        class MyModel(Base,MyMixin):
            __tablename__='test'
            id =  Column(Integer, primary_key=True)

        self.assertEqual(MyModel.__table__.kwargs,{'mysql_engine': 'InnoDB'})
    
    def test_table_args_overridden(self):
        
        Base = declarative_base()

        class MyMixin:
            __mixin__ = True
            __table_args__ = {'mysql_engine':'Foo'}             

        class MyModel(Base,MyMixin):
            __tablename__='test'
            __table_args__ = {'mysql_engine':'InnoDB'}             
            id =  Column(Integer, primary_key=True)

        self.assertEqual(MyModel.__table__.kwargs,{'mysql_engine': 'InnoDB'})

    def test_table_args_composite(self):
        Base = declarative_base()

        class MyMixin1:
            __mixin__ = True
            __table_args__ = {'info':{'baz':'bob'}} 

        class MyMixin2:
            __mixin__ = True
            __table_args__ = {'info':{'foo':'bar'}}

        class MyModel(Base,MyMixin1,MyMixin2):
            __tablename__='test'

            @classproperty
            def __table_args__(self):
                info = {}
                args = dict(info=info)
                info.update(MyMixin1.__table_args__['info'])
                info.update(MyMixin2.__table_args__['info'])
                return args
                
            id =  Column(Integer, primary_key=True)

        self.assertEqual(MyModel.__table__.info,{
                'foo': 'bar',
                'baz': 'bob',
                })
    
    def test_mapper_args_inherited(self):
        
        Base = declarative_base()

        class MyMixin:
            __mapper_args__=dict(always_refresh=True)

        class MyModel(Base,MyMixin):
            __tablename__='test'
            id =  Column(Integer, primary_key=True)

        self.assertEqual(MyModel.__mapper__.always_refresh,True)
    
    
    def test_mapper_args_overridden(self):
        
        Base = declarative_base()

        class MyMixin:
            __mapper_args__=dict(always_refresh=True)

        class MyModel(Base,MyMixin):
            __tablename__='test'
            __mapper_args__=dict(always_refresh=False)
            id =  Column(Integer, primary_key=True)

        self.assertEqual(MyModel.__mapper__.always_refresh,False)

    def test_mapper_args_composite(self):
        Base = declarative_base()

        class MyMixin1:
            type_ = Column(String(50))
            __mapper_args__=dict(polymorphic_on=type_)
            __mixin__ = True

        class MyMixin2:
            __mapper_args__=dict(always_refresh=True)

        class MyModel(Base,MyMixin1,MyMixin2):
            __tablename__='test'

            #...@classproperty
            #def __table_args__(self):
            #info = {}
            #    args = dict(info=info)
            #    info.update(MyMixin1.__table_args__['info'])
            #    info.update(MyMixin2.__table_args__['info'])
            #    return args
            id =  Column(Integer, primary_key=True)
 
        # because comparing a column returns an expression!
        self.assertEqual(
            repr(MyModel.__mapper__.polymorphic_on),
            "Column('type_', String(length=50, convert_unicode=False, "
            "assert_unicode=None), table=<test>)"
            )
        
        self.assertEqual(MyModel.__mapper__.always_refresh,False)
    
        
if __name__ == "__main__":
    unittest.main()
from functools import partial
from sqlalchemy.ext.declarative import declarative_base as sa_declarative_base
from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.schema import Column

# Originally from 
http://www.sqlalchemy.org/trac/wiki/UsageRecipes/DeclarativeMixins

class MixinMeta(DeclarativeMeta):
    def __init__(cls, classname, bases, dict_):
        for base in bases:
            names = dir(base)
            if "__mixin__" in names:
                to_mix = []
                for name in names:
                    obj = getattr(base,name)
                    if isinstance(obj, Column):
                        to_mix.append((name,obj.copy()))
                    elif name=='__table_args__' and name not in cls.__dict__:
                        setattr(cls,name,obj)
                dict_.update(to_mix)
                
        # work around declarative evilness
        # ..with more evilness :-(
        if '__table_args__' in cls.__dict__:
            cls.__table_args__=cls.__table_args__
            
        return DeclarativeMeta.__init__(cls, classname, bases, dict_)

declarative_base = partial(
    sa_declarative_base,
    metaclass=MixinMeta
    )
-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To post to this group, send email to sqlalch...@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.

Reply via email to