python - Inheriting AbstractConcreteBase multiple times [was: Adding an Auto-ID field to a model base class via mixin] -
i have created modelbase
class application of mine based on sqlalchemy.
to save me tedious work of having type out id = column(integer, primary_key=true)
provided attribute default id on modelbase
class.
base = declarative_base() class modelbase(abstractconcretebase, base): id = column(integer, primary_key=true) def __init__(self, *args, **kwargs): """ constructor """ # nifty magic, keyword cleaning on kwargs, might have data # coming json input super(modelbase,self).__init__(*args, **kwargs) def do_sth_else(self): """ shared fundamental logic, json (de)serialization """
i should't have done this, because classes have id integer field. turns out, want use composite keys on models, still want have default of model class having id
primary key. decided write mixin class , provide different modelbase classes.
base = declarative_base() class idmixin(object): id = column(integer, primary_key=true) class abstractmodelbase(object): def __init__(self, json_data='', *args, **kwargs): """ same constructor """ super(abstractmodelbase,self).__init__(*args, **kwargs) def do_sth_else(self): """ same shared fundamental logic """ class modelbase(abstractconcretebase, base, idmixin, abstractmodelbase): """ new model base class """ class noidmodelbase(abstractconcretebase, base, abstractmodelbase): """ new model base class """
however, instantiating class keyword dictionary gave me painful stack trace:
$ ./manage.py test # no, not django ;) # test 1 of 30 =============== traceback (most recent call last): file "./manage.py", line 40, in <module> execute_command(sys.argv) file "./manage.py", line 36, in execute_command cmd(argv[2:]) file "./management/test.py", line 362, in handle dbtestrunner(verbosity=args.verbosity).run(tests) file "./management/test.py", line 172, in run setupdb(t) file "./management/test.py", line 134, in setupdb instance = model_class(**d) ### instantiation occurs here ### file "<string>", line 2, in __init__ file "/path/to/code/refactoring/.env/lib/python2.7/site-packages/sqlalchemy/orm/instrumentation.py", line 310, in _new_state_if_none state = self._state_constructor(instance, self) file "/path/to/code/refactoring/.env/lib/python2.7/site-packages/sqlalchemy/util/langhelpers.py", line 582, in __get__ obj.__dict__[self.__name__] = result = self.fget(obj) file "/path/to/code/refactoring/.env/lib/python2.7/site-packages/sqlalchemy/orm/instrumentation.py", line 145, in _state_constructor self.dispatch.first_init(self, self.class_) file "/path/to/code/refactoring/.env/lib/python2.7/site-packages/sqlalchemy/event.py", line 409, in __call__ fn(*args, **kw) file "/path/to/code/refactoring/.env/lib/python2.7/site-packages/sqlalchemy/orm/mapper.py", line 2197, in _event_on_first_init configure_mappers() file "/path/to/code/refactoring/.env/lib/python2.7/site-packages/sqlalchemy/orm/mapper.py", line 2123, in configure_mappers _call_configured.dispatch.after_configured() file "/path/to/code/refactoring/.env/lib/python2.7/site-packages/sqlalchemy/event.py", line 372, in __call__ fn(*args, **kw) file "/path/to/code/refactoring/.env/lib/python2.7/site-packages/sqlalchemy/orm/events.py", line 489, in wrap wrapped_fn(*arg, **kw) file "/path/to/code/refactoring/.env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line 51, in go cls.__declare_last__() file "/path/to/code/refactoring/.env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/api.py", line 347, in __declare_last__ cls.__mapper__ = m = mapper(cls, pjoin, polymorphic_on=pjoin.c.type) file "/path/to/code/refactoring/.env/lib/python2.7/site-packages/sqlalchemy/util/_collections.py", line 172, in __getattr__ raise attributeerror(key) attributeerror: type
the constructor of model class called in method:
def setupdb(test): test.db_engine = create_engine('sqlite:///:memory:', convert_unicode=true) session_factory.configure(bind=test.db_engine) db_session = scoped_session(session_factory) modelbase.metadata.create_all(test.db_engine) test.client = client(app, response) if (hasattr(test, "fixtures")): # load fixtures json fixtures = test.fixtures if isinstance(fixtures, basestring): fixtures = (fixtures, ) elif isinstance(fixtures, collections.iterable): pass else: raise exception( "fixtures attribute needs string or iterable of strings") fixture in fixtures: try: open(fixture, 'r') f: fixture = json.loads(f.read()) # apply fixture database entry in fixture: model = entry["model"] # import module containing model class model_module = importlib.import_module( model[:model.rfind(".")]) # model class model_class = getattr( model_module, model[model.rfind(".") + 1:]) # create instance of model class # each entry in "data" data = entry["data"] d in data: instance = model_class(**d) instance.save() except ioerror e: print "could not load fixture!\n" print e sys.exit(1)
you have deeper problem notice traceback (though it's related). column
needs called once each actual column in database, if 4 tables have id
columns, need equal number of column
objects created.
fortunately, that's exact problem solved declared_attr
. can arrange declarative_base
set common functionality across subclasses, cls
argument. combining both:
from sqlalchemy.ext.declarative import declarative_base sqlalchemy.ext.declarative import declared_attr class modelbase(object): def __init__(self, json_data='', *args, **kwargs): """ same constructor """ super(modelbase,self).__init__(*args, **kwargs) def do_sth_else(self): """ same shared fundamental logic """ base = declarative_base(cls=modelbase) class idmixin(object): @declared_attr def id(cls): return column(integer, primary_key=true) class modelbase(base, idmixin): """ new model base class """ class noidmodelbase(base): """ new model base class """
Comments
Post a Comment