Custom metaclass to create hybrid properties in SQLAlchemy

青春壹個敷衍的年華 提交于 2019-12-06 11:05:56

问题


I want to create a custom interface on top of SQLAlchemy so that some pre-defined hybrid properties are supported transparently.

Specifically, I want to create a class SpecialColumn and a metaclass so that when a user adds SpecialColumn as an attribute of a class, my custom metaclass replaces that attribute with two SQLAlchemy Columns and adds a hybrid property that gets and sets those two columns as a tuple. Here's my approach so far:

First, I defined my special column type:

class SpecialColumn(object):
     pass

Then, I defined a metaclass inheriting from DeclarativeMeta which scans the class for instances of SpecialColumn and replaces them with two Columns and a hybrid property (defined as a closure):

class MyDeclarativeMeta(DeclarativeMeta):

     def __new__(cls, name, bases, attrs):
          for name, col in attrs.items():
              if isinstance(col, SpecialColumn):
                  # Replacing the column
                  del attrs[name]
                  col1_name = '_{0}_1'.format(name)
                  col2_name = '_{0}_2'.format(name)
                  attrs[col1_name] = Column(...)
                  attrs[col2_name] = Column(...)
                  # Adding the hybrid property
                  def getter(self):
                      return (getattr(self, col1_name), getattr(self, col2_name))
                  attrs[name] = hybrid_property(getter)

And finally I constructed an instance of declarative_base with it, and let the user define classes with the new base:

MyBase = declarative_base(metaclass=MyDeclarativeMeta)

class MyClass(MyBase):
    col1 = SpecialColumn()
    col2 = Column(...)

Now for my questions: Firstly, is my approach correct? Secondly, how can I use the metaclass to add the setter? Would it be correct to do:

def setter(self, (v1, v2)):
    setattr(self, col1_name, v1)
    setattr(self, col2_name, v2)

And then simply do attrs[name].setter(setter) ?


回答1:


There's no need to use metaclasses for a SQLAlchemy mapped class as we supply plenty of events to add features to classes as they are created and/or mapped. mapper_configured might be good here, which if you're on 0.8 you can apply to MyBase directly:

@event.listens_for(MyBase, 'mapper_configured')
def get_special_columns(mapper, cls):
    for attrname in dir(cls):
        val = getattr(cls, attrname)
        if isinstance(val, SpecialColumn):
             name1, name2 = "_%s_1" % attrname, "_%s_2" % attrname
             setattr(cls, name1, Column(...))
             setattr(cls, name2, Column(...))

             @hybrid_property
             def myhybrid(self):
                 return getattr(self, name1), getattr(self, name2)

             @myhybrid.setter
             def myhybrid(self, value):
                 setattr(self, name1, value[0])
                 setattr(self, name2, value[1])

             setattr(cls, attrname, myhybrid)

note that setattr() is the best way to go here, simple and to the point.



来源:https://stackoverflow.com/questions/14078052/custom-metaclass-to-create-hybrid-properties-in-sqlalchemy

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!