【问题标题】:Flask + SQLAlchemy - custom metaclass to modify column setters (dynamic hybrid_property)Flask + SQLAlchemy - 用于修改列设置器的自定义元类(动态混合属性)
【发布时间】:2015-06-07 00:15:09
【问题描述】:

我有一个使用 SQLAlchemy 的现有 Flask 应用程序。此应用程序中的几个模型/表具有存储原始 HTML 的列,我想在列的设置器上注入一个函数,以便传入的原始 html 得到“清理”。我想在模型中执行此操作,因此我不必在表单或路由代码中全部“清理此数据”。

我目前已经可以这样做了:

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 名称从未见过,调用了清理函数,并使用了清理后的数据。万岁。

我当然可以停下来,只吃那些丑陋的列处理方式,但是当你可以弄乱元类时,为什么要这样做呢?

注意:以下均假设“application”是主要的 Flask 模块,并且它包含两个子模块:“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 和 Flask 将诡计与元类相结合使得这不那么简单(这也是为什么几乎匹配的问题“在 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

设置完成后,您的模型类可能如下所示:

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)

几乎有效,因为没有错误,数据被正确读取和保存等。除了混合属性的设置器永远不会被调用。 getter 是(我在两者中都用 print 语句确认过),但是 setter 被完全忽略,因此从不调用清洁器函数。数据 已设置 - 使用未清理的数据进行更改非常愉快。

显然,我在动态版本中没有完全模拟静态版本的代码,但老实说,我不知道问题出在哪里。据我所见,hybrid_property 应该 注册 setter 就像它拥有 getter 一样,但事实并非如此。在静态版本中,setter 被注册和使用就好了。

关于如何完成最后一步的任何想法?

【问题讨论】:

  • 为什么要使用元类和__html_columns__ = ['html_column']?为什么不使用自定义类型?基本上你想要一个带有清理逻辑的db.Text 类型。也许检查Augmenting Existing Types 文档?

标签: python flask sqlalchemy flask-sqlalchemy metaclass


【解决方案1】:

也许使用自定义类型?

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

【讨论】:

  • 那个解决方案太干净了 :) 谢谢你 - 出于某种原因,我什至没有想到那种解决方案。我仍然想知道为什么我上面的代码不起作用,但现在这是学术问题。
猜你喜欢
  • 2012-12-14
  • 2020-07-02
  • 2021-04-02
  • 2013-01-07
  • 1970-01-01
  • 2011-11-06
  • 1970-01-01
  • 2022-01-02
  • 1970-01-01
相关资源
最近更新 更多