need 0.6_beta2-compat declarative meta

2 views
Skip to first unread message

Daniel Robbins

unread,
Mar 27, 2010, 3:04:13 AM3/27/10
to sqlal...@googlegroups.com
Hi All,

In 0.6_beta2, the following code is not properly adding a primary key Column via DeclarativeMeta which calls my PrimaryKey() function:

def PrimaryKey(seqprefix):
return Column(Integer, Sequence(seqprefix, optional=True), primary_key=True)

class ClassDefaults(DeclarativeMeta):
def __init__(cls,classname, bases, dict_):
seqprefix = getattr(cls,'__tablename__',None)
dict_['id'] = PrimaryKey(seqprefix=seqprefix)
return DeclarativeMeta.__init__(cls, classname, bases, dict_)

Base = declarative_base(metaclass=ClassDefaults)

class Location(Base):
__tablename__ = 'location'
parent_id = Column(Integer, ForeignKey('location.id'))
parent = relation('Location', backref=backref('children'), remote_side='location.c.id')
name = UniqueString(25)
desc = Column(String(80))

SQLAlchemy 0.6_beta2 complains on table initialization:

File "/usr/lib64/python2.6/site-packages/sqlalchemy/orm/mapper.py", line 444, in _configure_pks
"key columns for mapped table '%s'" % (self, self.mapped_table.description))
sqlalchemy.exc.ArgumentError: Mapper Mapper|Location|location could not assemble any primary key columns for mapped table 'location'

This worked under 0.6_beta1 (and likely earlier versions of SQLAlchemy).

Can someone send me some code similar to above that works with 0.6_beta2, or is this a bug in beta2?

Thanks,

Daniel

avdd

unread,
Mar 27, 2010, 4:31:31 AM3/27/10
to sqlalchemy
In a metaclass's __init__, the attributes have already been placed on
the class, so mutating the attributes dict has no effect.

Try setting the id attribute directly:

self.id = PrimaryKey(...)

Chris Withers

unread,
Mar 27, 2010, 7:58:49 AM3/27/10
to sqlal...@googlegroups.com
avdd wrote:
> In a metaclass's __init__, the attributes have already been placed on
> the class, so mutating the attributes dict has no effect.

Spot on. SA fudged this prior to 0.6beta so you could get away with
shoving stuff in dict_, you now can't...

>> def PrimaryKey(seqprefix):
>> return Column(Integer, Sequence(seqprefix, optional=True), primary_key=True)
>>
>> class ClassDefaults(DeclarativeMeta):
>> def __init__(cls,classname, bases, dict_):
>> seqprefix = getattr(cls,'__tablename__',None)

When are you expecting cls not to have a tablename?
Using tabs for intentation is evil.

>> dict_['id'] = PrimaryKey(seqprefix=seqprefix)

Why not just have:

cls.id = Column(Integer, Sequence(cls.__tablename__, optional=True),
primary_key=True)

?

You might also benefit from reading:

http://www.sqlalchemy.org/docs/reference/ext/declarative.html#mix-in-classes

...I don't think they'll help here 'cos you're computing based on
__tablename__.

Of course, nowadays, I tend to have tablename computed in a mix-in that
does all my common stuff:

class BaseMixin(object):
__table_args__ = {'mysql_engine':'InnoDB'}
@classproperty
def __tablename__(cls):
return cls.__name__.lower()
id = Column(Integer,primary_key=True)

cheers,

Chris

--
Simplistix - Content Management, Batch Processing & Python Consulting
- http://www.simplistix.co.uk

Daniel Robbins

unread,
Mar 27, 2010, 2:36:49 PM3/27/10
to sqlal...@googlegroups.com
On Mar 27, 2010, at 5:58 AM, Chris Withers wrote:

avdd wrote:
In a metaclass's __init__, the attributes have already been placed on
the class, so mutating the attributes dict has no effect.

Spot on. SA fudged this prior to 0.6beta so you could get away with shoving stuff in dict_, you now can't...

OK. Simply assigning something to cls.id now works. Here is my current production code:

class ClassDefaults(DeclarativeMeta):
  def __init__(cls,classname, bases, dict_):
    if not ( dict_.has_key('__mapper_args__') and dict_['__mapper_args__'].has_key('polymorphic_identity') ):
      seqprefix = getattr(cls,'__tablename__',None)
      cls.id = PrimaryKey(seqprefix=seqprefix)
      return DeclarativeMeta.__init__(cls, classname, bases, dict_)

However, this new approach is incompatible with 0.6_beta1 (and earlier, I assume.)

class ClassDefaults(DeclarativeMeta):
       def __init__(cls,classname, bases, dict_):
               seqprefix = getattr(cls,'__tablename__',None)

When are you expecting cls not to have a tablename?

The line "Base = declarative_base(metaclass=ClassDefaults)" requires this. ext/declarative.py (at least in 0.6_beta1+) has a "return metaclass(name, bases, class_dict)" on line 764 which causes the constructor to be called, prior to assignment of a __tablename__. If I change my line above to "seqprefix = cls.__tablename__", I get a traceback.

Using tabs for intentation is evil.

I view choice of indentation as an issue of personal preference rather than one that has larger moral and religious implications. I have always preferred tabs over spaces for indent as I find them much easier to work with.

   cls.id = Column(Integer, Sequence(cls.__tablename__, optional=True),
                   primary_key=True)

This is related to the possibility that the __tablename__ can be undefined. When seqprefix is None, my PrimaryKey method will still return a primary key, but it will have a unique sequence name based on a global, incrementing integer.

I do welcome any improvements to SQLAlchemy that may make this particular usage case less complicated, but currently it appears that all my little tricks are required.


http://www.sqlalchemy.org/docs/reference/ext/declarative.html#mix-in-classes

...I don't think they'll help here 'cos you're computing based on __tablename__.

Right. Mix-ins look wonderful but they don't work for all cases.

Of course, nowadays, I tend to have tablename computed in a mix-in that does all my common stuff:

class BaseMixin(object):
 __table_args__ = {'mysql_engine':'InnoDB'}
 @classproperty
 def __tablename__(cls):
   return cls.__name__.lower()
 id = Column(Integer,primary_key=True)

I'm wondering if this would work for my purposes then:

class BaseMixin(object):
  @classproperty
  def __tablename__(cls):
    return cls.__name__
  id = Column(Integer, Sequence(cls.__name__, Optional=True), primary_key=True)

class Foo(Base,BaseMixin):
  # will I get an "id" + sequence from the BaseMixin?
  __name__ = "foo"
  foo = Column(String(80), nullable=False)

Haven't tried it yet. :)

-Daniel
Reply all
Reply to author
Forward
0 new messages