How to define metaclass for a class that extends from sqlalchemy declarative base ?

382 views
Skip to first unread message

Ven Karri

unread,
Jul 3, 2013, 12:44:32 AM7/3/13
to sqlal...@googlegroups.com
I use: Python 2.6 and sqlalchemy 0.6.1

This is what I am trying to do:

from sqlalchemy.types import (
   Integer,
   String,
   Boolean
)
from sqlalchemy.ext.declarative import declarative_base

    Base = declarative_base()

class SampleMeta(type):
        def __new__(cls, name, bases, attrs):
            attrs.update({   'id': Column('Id', Integer, primary_key=True),
                        'name': Column('Name', String),
                        'description': Column('Description', String),
                        'is_active': Column('IsActive', Boolean)
                    })
            return super(SampleMeta, cls).__new__(cls, name, bases, attrs)

    class Sample(Base):
        __tablename__ = 'Sample'
        __table_args__ = {'useexisting': True}
        __metaclass__ = SampleMeta

        def __init__(self, id, name, description, is_active):
            self.id = id
            self.name = name
            self.description = description
            self.is_active = is_active

        def __repr__(self):
            return "<(%d, '%s', '%s', %r)>" % (self.id, self.name, self.description, self.isactive)

And the error I am getting is this:

    TypeError: Error when calling the metaclass bases
        metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

Now, if I do the same thing above by using

    class Sample(object)

instead of

    class Sample(Base)

it works absolutely fine.

I need to update the attributes of the class dynamically. So, I will be using dynamic attribute and column names. And I need the above piece code to work in order to be able to get there.

**Please help** 

Michael Bayer

unread,
Jul 3, 2013, 11:11:39 AM7/3/13
to sqlal...@googlegroups.com
your metaclass must derive from the DeclarativeMeta class.

Also, I disagree that you need this metaclass,  what you're trying to do is very easy using mixins, which are supported in version 0.6: http://docs.sqlalchemy.org/en/rel_0_6/orm/extensions/declarative.html#mixing-in-columns




--
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+...@googlegroups.com.
To post to this group, send email to sqlal...@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Praveen

unread,
Aug 26, 2013, 4:35:01 PM8/26/13
to sqlal...@googlegroups.com
The problem with using Mixins is that you need to know the definition of columns already for creating the mixin class. What I am trying to do is more like get the definition dynamically on the fly.Take a look at this:

def get_properties(tablename, map):
    table_inspector = reflection.Inspector.from_engine(DB_ENGINE.connect())
    table = Table(tablename, metadata)
    table_inspector.reflecttable(table, None)
    columns = []
    for child in table.get_children():
        if isinstance(child, Column):
            column = list(child.base_columns)[0]
            column.table = None
            columns.append(column)
    return dict([(map[column.key],  column) for column in columns])

class CustomDeclarativeMeta(DeclarativeMeta):
    def __new__(cls, name, bases, attrs):
        attrs.update(get_properties(attrs.get('__tablename__'), attrs.get('__map__')))
        return super(CustomDeclarativeMeta, cls).__new__(cls, name, bases, attrs)

# Base = declarative_base(metaclass=CustomDeclarativeMeta)
Base = declarative_base()

class Enum_SampleBase):
    __tablename__ = 'Enum_Sample'
    __table_args__ = {'useexisting': True}
    __metaclass__ = CustomDeclarativeMeta
    __map__ = {'Id': 'id', 'Name': 'name', 'Description': 'description', 'IsActive': 'is_active'}

    def __init__(self, id, name, description, is_active):
        self.id = id
        self.name = name
        self.description = description
        self.is_active = is_active

    def __repr__(self):
        return "<(%d, '%s', '%s', %r)>" % (self.id, self.name, self.description, self.isactive)

Unfortunately, this isn't working. I want to declare column types by getting them from a table that's already created in the database.



--
You received this message because you are subscribed to a topic in the Google Groups "sqlalchemy" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/sqlalchemy/37M-1Qf8HO8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to sqlalchemy+...@googlegroups.com.

To post to this group, send email to sqlal...@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
Have a nice day !!!

Praveen

unread,
Aug 26, 2013, 4:37:12 PM8/26/13
to sqlal...@googlegroups.com
Sorry, it should've been:

class Enum_Sample(Base):

Typo.

Michael Bayer

unread,
Aug 26, 2013, 5:16:08 PM8/26/13
to sqlal...@googlegroups.com
On Aug 26, 2013, at 4:35 PM, Praveen <praveen...@gmail.com> wrote:

The problem with using Mixins is that you need to know the definition of columns already for creating the mixin class. What I am trying to do is more like get the definition dynamically on the fly.Take a look at this:

here's a simple way to add reflection events which intercept the Column and add the .key of your choice, and saves on code by making use of the existing DeferredReflection mixin instead of hand-coding it.  There's many more variants on this kind of thing as well.   DeferredReflection is using some not-yet-public APIs to do its work, but there are other ways of deferring the mapping as well (typically by overriding the __mapper__ attribute).   Basically your class creates itself an empty Table object, to which we apply events.  then DeferredReflection.prepare() fills in those Table objects with their columns using reflection.

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.declarative import DeferredReflection
from sqlalchemy import event

Base = declarative_base()

e = create_engine("sqlite://", echo=True)
e.execute("""
    create table sample (
        Id integer primary key,
        Name varchar,
        Description varchar,
        IsActive varchar
    )
""")

class MagicMappyThing(DeferredReflection):
    @classmethod
    def __declare_last__(cls):
        @event.listens_for(cls.__table__, "column_reflect")
        def new_col(inspector, table, column_info):
            column_info['key'] = cls.__map__.get(column_info['name'], column_info['name'])

class Sample(MagicMappyThing, Base):
    __tablename__ = 'sample'
    __map__ = {'Id': 'id', 'Name': 'name', 'Description': 'description', 'IsActive': 'is_active'}

    def __init__(self, id, name, description, is_active):
        self.id = id
        self.name = name
        self.description = description
        self.is_active = is_active

MagicMappyThing.prepare(e)

s = Session(e)

s.add(Sample(id=1, name='some name', description='foo', is_active='foo'))
s.commit()




signature.asc

Michael Bayer

unread,
Aug 26, 2013, 5:17:49 PM8/26/13
to sqlal...@googlegroups.com
On Aug 26, 2013, at 5:16 PM, Michael Bayer <mik...@zzzcomputing.com> wrote:


On Aug 26, 2013, at 4:35 PM, Praveen <praveen...@gmail.com> wrote:

The problem with using Mixins is that you need to know the definition of columns already for creating the mixin class. What I am trying to do is more like get the definition dynamically on the fly.Take a look at this:

here's a simple way to add reflection events which intercept the Column and add the .key of your choice, and saves on code by making use of the existing DeferredReflection mixin instead of hand-coding it.

except it doesnt work yet, give me 10 minutes.

signature.asc

Michael Bayer

unread,
Aug 26, 2013, 5:36:46 PM8/26/13
to sqlal...@googlegroups.com
OK here we are, had to switch approaches due to a bug with the column reflect event, to use the aforementioned __mapper_cls__ (had the name wrong), so I think you'll see this is a pretty open-ended way to control how something maps as you're given total access to mapper() here:

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.ext.declarative import DeferredReflection
from sqlalchemy import event

Base = declarative_base()

e = create_engine("sqlite://", echo=True)
e.execute("""
    create table sample (
        Id integer primary key,
        Name varchar,
        Description varchar,
        IsActive varchar
    )
""")

class MagicMappyThing(DeferredReflection):
    @declared_attr
    def __mapper_cls__(cls):
        def map_(cls, *arg, **kw):
            props = kw.setdefault("properties", {})
            for k, v in cls.__map__.items():
                props[v] = cls.__table__.c[k]
            return mapper(cls, *arg, **kw)
        return map_

class Sample(MagicMappyThing, Base):
    __tablename__ = 'sample'
    __map__ = {'Id': 'id', 'Name': 'name', 'Description': 'description', 'IsActive': 'is_active'}

    def __init__(self, id, name, description, is_active):
        self.id = id
        self.name = name
        self.description = description
        self.is_active = is_active

MagicMappyThing.prepare(e)

s = Session(e)

s.add(Sample(id=1, name='some name', description='foo', is_active='foo'))
s.commit()



signature.asc

Praveen

unread,
Aug 26, 2013, 5:38:26 PM8/26/13
to sqlal...@googlegroups.com
Does this work in sqlalchemy 0.6.1 ?

Praveen

unread,
Aug 26, 2013, 5:41:54 PM8/26/13
to sqlal...@googlegroups.com
I am getting ImportError for the following:

from sqlalchemy.ext.declarative import DeferredReflection
from sqlalchemy import event

I use sqlalchemy 0.6.1. Is there any way I can make it work in 0.6.1 ?

Michael Bayer

unread,
Aug 26, 2013, 5:41:46 PM8/26/13
to sqlal...@googlegroups.com
you'd need to hand-roll the deferred reflection part, there's an example in 0.7 called "declarative_reflection" but it might require features that aren't in 0.6.

I'd not be looking to add any kind of slick/magic systems to an 0.6 app, 0.6 is very early in the curve for declarative techniques.  upgrade it first, otherwise stick with the hacky approaches you have.



signature.asc

Praveen

unread,
Aug 26, 2013, 5:46:38 PM8/26/13
to sqlal...@googlegroups.com
Could you please point me to the link where I can find the example ?

Praveen

unread,
Aug 26, 2013, 5:49:30 PM8/26/13
to sqlal...@googlegroups.com
nvm... i found it.

Praveen

unread,
Aug 26, 2013, 5:52:03 PM8/26/13
to sqlal...@googlegroups.com
It works only in 0.7 like you said. I can't find any way to crack this situation in 0.6. :-(

Praveen

unread,
Aug 26, 2013, 7:32:18 PM8/26/13
to sqlal...@googlegroups.com
I tried your example in sqlalchemy 0.6 by manually plugging in api.py library (attached) that I got from here

I get this error:

File "path\to\sample_orm.py", line 33, in map_
    props[v] = cls.__table__.c[k]
  File "path\to\lib\python2.6\sqlalchemy\util.py", line 731, in __getitem__

KeyError: 'Description'


Here's my code:

from sqlalchemy.orm import mapper
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from .api import DeferredReflection, declared_attr --> this is coming from attached api.py file

Base = declarative_base()

e = create_engine("sqlite://", echo=True)
e.execute("""
    create table Sample (
        Id integer primary key,
        Name varchar,
        Description varchar,
        IsActive varchar
    )
""")

class MagicMappyThing(DeferredReflection):
    @declared_attr
    def __mapper_cls__(cls):
        def map_(cls, *arg, **kw):
            props = kw.setdefault("properties", {})
            for k, v in cls.__map__.items():
                props[v] = cls.__table__.c[k]
            return mapper(cls, *arg, **kw)
        return map_

class Sample(MagicMappyThing, Base):
    __tablename__ = 'Sample'
    __map__ = {'Id': 'id', 'Name': 'name', 'Description': 'description', 'IsActive': 'is_active'}

    def __init__(self, id, name, description, is_active):
        self.id = id
        self.name = name
        self.description = description
        self.is_active = is_active

MagicMappyThing.prepare(e)

# s = Session(e)

# s.add(Sample(id=1, name='some name', description='foo', is_active='foo'))
# s.commit()
api.py

Michael Bayer

unread,
Aug 26, 2013, 7:42:57 PM8/26/13
to sqlal...@googlegroups.com
On Aug 26, 2013, at 7:32 PM, Praveen <praveen...@gmail.com> wrote:

I tried your example in sqlalchemy 0.6 by manually plugging in api.py library (attached) that I got from here


its not going to work.  you need to upgrade.  



signature.asc
Reply all
Reply to author
Forward
0 new messages