Michael Bayer wrote:
>> Michael Bayer wrote:
>>> The Column objects you declare within declarative become members of
>>> the underlying Table object, so its not as simple as just having
>>> those members present on a mixin - what would really be needed would
>>> be some sort of copying of each column object when declarative comes
>>> across them.
>> Indeed, how about a marker on "abstract" base classes like the Django ORM?
>
> im sorry, did you say you were feeling slightly sick earlier ? :)
An abstract marker on a base class to say "actually, this is just here
to provide information and methods, it's not table-mapped class" isn't
such a bad thing, is it? Especially given how horrible the resulting
code is...
> I noticed you cut out the part of my reply with the likely fix here, to
> provide a special class that is specifically for declarative mixins. It
> would only allow elements on it that make sense for copying to many
> subclasses.
I did indeed miss this, but I don't see how it's fundamentally different
from marking a base class as abstract ;-)
Anyway, here's the thought process that got us where we are (which feels
a bit icky to me), any ways to make things "nicer" would be very
gratefully received.
Okay, so, the naive approach that a normal python user might expect to
work would be:
Base = declarative_base()
class VersionedBase(Base):
id = Column(Integer, primary_key=True)
version = Column(Integer, nullable=False, index=True)
a_constant = ('some','tuple')
def aMethod(self):
do_some_stuff()
class Employee(VersionedBase):
__tablename__ = 'employee'
name = Column(String, nullable=False, index=True)
...but this results in:
sqlalchemy.exc.InvalidRequestError: Class <class
'__main__.VersionedBase'> does not have a __table__ or __tablename__
specified and does not inherit from an existing table-mapped class.
Fair enough, so why not use multi-table inheritance? Because we really
want those columns in each table:
- no overhead of joins when selecting a particular set of versions
- ability to bypass the orm layer more easilly if emergency debugging is
needed
Okay, so why not concrete or single table inheritance? Because we
*really* want one table per subclass but we don't ever want to select
from more than one table at once...
So, moving on, we tried:
Base = declarative_base()
class VersionedBase(object):
id = Column(Integer, primary_key=True)
version = Column(Integer, nullable=False, index=True)
a_constant = ('some','tuple')
def aMethod(self):
do_some_stuff(self.a_constant)
class Employee(Base, VersionedBase):
__tablename__ = 'employee'
name = Column(String, nullable=False, index=True)
...which gave a more cryptic error:
sqlalchemy.exc.ArgumentError: Mapper Mapper|Employee|employee could not
assemble any primary key columns for mapped table 'employee'.
I'm guessing this is because VersionedBase is basically ignored by the
declarative metaclass?
Okay, so metaclass time... First attempt failed:
def aMethod(self):
do_some_stuff(self.a_constant)
class VersionedMeta(DeclarativeMeta):
def __init__(cls, name, bases, dict_):
cls.id = Column(Integer, primary_key=True)
cls.version = Column(Integer, nullable=False, index=True)
cls.a_constant = ('some','tuple')
cls.aMethod = aMethod
return DeclarativeMeta.__init__(cls, name, bases, dict_)
...because DeclarativeMeta's __init__ ignores columns already set on cls
by the time it's called, which is fair enough, but means we end up with
a (working) metaclass method that's a bit ugly:
def aMethod(self):
do_some_stuff(self.a_constant)
class VersionedMeta(DeclarativeMeta):
def __init__(cls, name, bases, dict_):
dict_.update(dict(
id = Column(Integer, primary_key=True),
version = Column(Integer, nullable=False, index=True),
))
cls.a_constant = ('some','tuple')
cls.aMethod = aMethod
return DeclarativeMeta.__init__(cls, name, bases, dict_)
def versioned_declarative_base():
return declarative_base(metaclass=VersionedMeta)
Base = version_declarative_base()
class Employee(Base):
__tablename__ = 'employee'
name = Column(String, nullable=False, index=True)
Is it okay to have multiple declarative bases floating around like this?
Will they all end up using the same metadata collection or are we in for
problems down the line?
So anyway, on to your next suggestion: class decorator...
def aMethod(self):
do_some_stuff(self.a_constant)
def versioned(cls):
cls.id = Column(Integer, primary_key=True),
cls.version = Column(Integer, nullable=False, index=True),
cls.a_constant = ('some','tuple')
cls.aMethod = aMethod
@versioned
class Employee(Base):
__tablename__ = 'employee'
name = Column(String, nullable=False, index=True)
Much nicer! Except... we're on Python 2.5, so no class decorators. No
problems, thinks I, we'll just do it the old fashioned way:
class Employee(Base):
__tablename__ = 'employee'
name = Column(String, nullable=False, index=True)
Employee = versioned(Employee)
Uh oh:
sqlalchemy.exc.ArgumentError: Mapper Mapper|Employee|employee could not
assemble any primary key columns for mapped table 'employee'
...which makes me wonder if the class decorator would actually work at
all. Surely it'll only kick in after the DeclarativeMeta has already
done its thing and got upset?
Ideas welcome...
Chris
--
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/sqlalchemy?hl=en.