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 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