Flask + SQLAlchemy - 用于修改列设置器的自定义元类(动态 hybrid_property)
Flask + SQLAlchemy - custom metaclass to modify column setters (dynamic hybrid_property)
我有一个现有的、可用的 Flask 应用程序,它使用 SQLAlchemy。这个应用程序中的几个 models/tables 有存储原始 HTML 的列,我想在列的 setter 上注入一个函数,以便传入的原始 html 得到'cleansed'。我想在模型中这样做,这样我就不必在整个表单或路由代码中撒上 "clean this data"。
我现在已经可以这样做了:
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 时,为什么要这样做呢?
注意:以下都假设'application'是主要的Flask模块,并且它包含两个子模块:'db' - SQLAlchemy句柄和'clean_the_data',清理传入的函数HTML。
所以,我着手尝试制作一个新的基础模型 class,它在创建 class 时发现了一个需要清理的列,并自动处理了一些事情,所以而不是上面的代码,你可以这样做:
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 和 Flask 的幕后,元classes 的诡计组合使得这不是直截了当的(这也是为什么几乎匹配的问题 "Custom metaclass to create hybrid properties in SQLAlchemy"并没有多大帮助 - Flask 也会妨碍)。在 application/models/__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
设置完成后,您的模型 class 可能如下所示:
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 是(我已经在两者中用 print 语句确认),但是 setter 被完全忽略,因此永远不会调用更清洁的函数。虽然数据 是 设置 - 未清理的数据可以很愉快地进行更改。
显然,我还没有完全在我的动态版本中模拟代码的静态版本,但老实说,我不知道问题出在哪里。据我所知,hybrid_property 应该 注册 setter 就像它有 getter 一样,但事实并非如此。在静态版本中,setter 已注册并可以正常使用。
关于如何完成最后一步的任何想法?
也许使用自定义类型?
from sqlalchemy import TypeDecorator, Text
class CleanedHtml(TypeDecorator):
impl = Text
def process_bind_param(self, value, dialect):
return clean_the_data(value)
那么你可以这样写你的模型:
class Example(db.Model):
__tablename__ = 'example'
normal_column = db.Column(db.Integer, primary_key=True, autoincrement=True)
html_column = db.Column(CleanedHtml)
此处的文档中提供了更多解释:http://docs.sqlalchemy.org/en/latest/core/custom_types.html#augmenting-existing-types
我有一个现有的、可用的 Flask 应用程序,它使用 SQLAlchemy。这个应用程序中的几个 models/tables 有存储原始 HTML 的列,我想在列的 setter 上注入一个函数,以便传入的原始 html 得到'cleansed'。我想在模型中这样做,这样我就不必在整个表单或路由代码中撒上 "clean this data"。
我现在已经可以这样做了:
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 时,为什么要这样做呢?
注意:以下都假设'application'是主要的Flask模块,并且它包含两个子模块:'db' - SQLAlchemy句柄和'clean_the_data',清理传入的函数HTML。
所以,我着手尝试制作一个新的基础模型 class,它在创建 class 时发现了一个需要清理的列,并自动处理了一些事情,所以而不是上面的代码,你可以这样做:
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 和 Flask 的幕后,元classes 的诡计组合使得这不是直截了当的(这也是为什么几乎匹配的问题 "Custom metaclass to create hybrid properties in SQLAlchemy"并没有多大帮助 - Flask 也会妨碍)。在 application/models/__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
设置完成后,您的模型 class 可能如下所示:
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 是(我已经在两者中用 print 语句确认),但是 setter 被完全忽略,因此永远不会调用更清洁的函数。虽然数据 是 设置 - 未清理的数据可以很愉快地进行更改。
显然,我还没有完全在我的动态版本中模拟代码的静态版本,但老实说,我不知道问题出在哪里。据我所知,hybrid_property 应该 注册 setter 就像它有 getter 一样,但事实并非如此。在静态版本中,setter 已注册并可以正常使用。
关于如何完成最后一步的任何想法?
也许使用自定义类型?
from sqlalchemy import TypeDecorator, Text
class CleanedHtml(TypeDecorator):
impl = Text
def process_bind_param(self, value, dialect):
return clean_the_data(value)
那么你可以这样写你的模型:
class Example(db.Model):
__tablename__ = 'example'
normal_column = db.Column(db.Integer, primary_key=True, autoincrement=True)
html_column = db.Column(CleanedHtml)
此处的文档中提供了更多解释:http://docs.sqlalchemy.org/en/latest/core/custom_types.html#augmenting-existing-types