我有一个使用SQLAlchemy的现有工作Flask应用程序。这个应用程序中的几个模型/表具有存储原始HTML的列,并且我想在列的setter中注入一个函数,以便传入的原始html得到“清理”。我想在模型中这样做,所以我不需要通过表单或路径代码来“清理这些数据”。Flask + SQLAlchemy - 用于修改列设置器的自定义元类(动态hybrid_property)
我现在已经做到这一点,像这样:
from application import db, clean_the_data
from sqlalchemy.ext.hybrid import hybrid_property
class Example(db.Model):
__tablename__ = 'example'
normal_column = db.Column(db.Integer,
primary_key=True,
autoincrement=True)
_html_column = db.Column('html_column', db.Text,
nullable=False)
@hybrid_property
def html_column(self):
return self._html_column
@html_column.setter
def html_column(self, value):
self._html_column = clean_the_data(value)
这就像一个魅力 - 除了模型定义的_html_column名称是从未见过的,清洁函数被调用,并清理数据使用。万岁。
我当然可以在那里停下来,只是吃了不好处理的列,但为什么当你可以混淆metaclasses?
注意:以下所有假设“应用”是主要的瓶模块,并且它有两个孩子:“DB” - 在SQLAlchemy的手柄和“clean_the_data”功能来清理输入HTML。
因此,我着手尝试创建一个新的基类Model类,该类可以在创建类时发现需要清理的列,并且自动处理周围的东西,以便代替上述代码,可以执行类似这样的:
from application import db
class Example(db.Model):
__tablename__ = 'example'
__html_columns__ = ['html_column'] # Our oh-so-subtle hint
normal_column = db.Column(db.Integer,
primary_key=True,
autoincrement=True)
html_column = db.Column(db.Text,
nullable=False)
当然,挂羊头卖狗肉的使用元类的事情与SQLAlchemy的和瓶幕后的组合使这个比直截了当以下(也是为什么近匹配问题“自定义元类来创建混合性质在SQLAlchemy中“并不完全有帮助 - Flask也是如此)。我几乎在应用/型号以下/ __ init__.py变得有:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
# Yes, I'm importing _X stuff...I tried other ways to avoid this
# but to no avail
from flask_sqlalchemy import (Model as BaseModel,
_BoundDeclarativeMeta,
_QueryProperty)
from application import db, clean_the_data
class _HTMLBoundDeclarativeMeta(_BoundDeclarativeMeta):
def __new__(cls, name, bases, d):
# Move any fields named in __html_columns__ to a
# _field/field pair with a hybrid_property
if '__html_columns__' in d:
for field in d['__html_columns__']:
if field not in d:
continue
hidden = '_' + field
fget = lambda self: getattr(self, hidden)
fset = lambda self, value: setattr(self, hidden,
clean_the_data(value))
d[hidden] = d[field] # clobber...
d[hidden].name = field # So we don't have to explicitly
# name the column. Should probably
# force a quote on the name too
d[field] = hybrid_property(fget, fset)
del d['__html_columns__'] # Not needed any more
return _BoundDeclarativeMeta.__new__(cls, name, bases, d)
# The following copied from how flask_sqlalchemy creates it's Model
Model = declarative_base(cls=BaseModel, name='Model',
metaclass=_HTMLBoundDeclarativeMeta)
Model.query = _QueryProperty(db)
# Need to replace the original Model in flask_sqlalchemy, otherwise it
# uses the old one, while you use the new one, and tables aren't
# shared between them
db.Model = Model
一旦这样设置,你的模型类可以看起来像:
from application import db
from application.models import Model
class Example(Model): # Or db.Model really, since it's been replaced
__tablename__ = 'example'
__html_columns__ = ['html_column'] # Our oh-so-subtle hint
normal_column = db.Column(db.Integer,
primary_key=True,
autoincrement=True)
html_column = db.Column(db.Text,
nullable=False)
这几乎作品,没有错误,数据被正确读取和保存等。除了hybrid_property的setter从未被调用。 getter是(我已经在两个打印语句中确认过),但是setter被完全忽略,因此清理函数从不会被调用。数据设置为 - 使用未清理的数据可以非常愉快地进行更改。
很明显,我还没有完全模拟我的动态版本中的静态代码版本,但我真的不知道问题出在哪里。据我所知,hybrid_property 应该注册setter就像它有吸气,但它不是。在静态版本中,setter被注册并使用得很好。
关于如何让最后一步工作的任何想法?
为什么要使用元类和'__html_columns__ = [” html_column']'?你为什么不使用自定义类型?基本上你想要一个'db.Text'类型,增加清理逻辑。也许检查[增加现有类型](http://docs.sqlalchemy.org/en/latest/core/custom_types.html#augmenting-existing-types)文档? – 2015-04-02 14:19:07