【问题标题】:Alternate preprocessing method to TypeDecorator in SQLAlchemySQLAlchemy 中 TypeDecorator 的替代预处理方法
【发布时间】:2025-11-27 01:40:01
【问题描述】:

我正在使用sqlalchemy.types.TypeDecorator 在插入数据库之前预处理文本字段。它工作得很好,除非我将 like 表达式与包含百分比的比较器(例如 User.name.like('%bar%'))一起使用,因为在执行查询之前,百分比已被 process_bind_param() 剥离。

这让我觉得TypeDecorator 不是实现预处理指令的正确方法。有没有:

  1. SQLAlchemy 中预处理列的另一种方法? (比如validator?)
  2. 或者也许是一种在执行查询时显式绕过process_bind_param 的方法?
  3. 或者我正在使用的解决方法(在本文末尾)——仅使用直接 SQL 查询并绕过 ORM——唯一的选择?

这是增强类型的最小实现:

import re
from sqlalchemy import Column, Integer, Text
import sqlalchemy.types as types
from sqlalchemy.ext.declarative import declarative_base

class AlphaOnlyText(types.TypeDecorator):
    """Replaces everything that's not A-Za-z or space with nothing."""
    impl = types.Text
    def process_bind_param(self, text, dialect):
        return None if text is None else re.sub(r'[^A-Za-z\-\s]', '' , text.strip())


Base = declarative_base()

class User(Base):
    __tablename__ = 'User'
    id = Column(Integer, primary_key=True)
    name = Column(AlphaOnlyText, unique=True)
    def __repr__(self):
        return "<User(name='{}')>".format(self.name)

下面是演示插入和查询工作的代码:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# Create the database
engine = create_engine('sqlite://', echo=True)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()

# Add a row
session.add(User(name='foobar'))

# Query the row
session.query(User).filter_by(name=='foobar').all()
# [<User(name='foobar')>]

这是失败的地方——当我使用like 表达式时,列的process_bind_param 会运行,在将过滤器添加到查询之前去除百分比:

filter = User.name.like('%bar%')
filter.compile().params
# {'name_1': '%bar%'}  <-- The '%' are still here

session.query(User).filter(filter).all()
# Nothing. (Percent symbols are stripped, so '%bar%' ==> 'bar'.)
#   If your engine was created with `echo=True`
#   you can see this directly in the logging statements

如果我绕过 ORM,我可以通过直接 SQL 得到我想要的,但我不想要求用户走这条路。

query_template = "SELECT * FROM user WHERE name LIKE :nm;"
session.execute(query_template, {'nm':'%bar%'}).fetchall()
# [(1, 'foobar')]

【问题讨论】:

    标签: python orm sqlalchemy


    【解决方案1】:

    选项 1,使用 validator,是正确的。 TypeDecorator 更多的是用于实际修改数据的类型——例如腌制一些东西以将其作为字符串存储到数据库中,然后无缝地为用户取消腌制回一个类。

    在这种情况下,@validates 装饰器正在对相同类型的数据执行验证。验证器的功能是将相同类型的不可接受的数据转换为可接受的数据,而不是拒绝/标记/记录格式不可接受的数据(可能是验证的预期主要用例)。

    代码如下:

    import re
    from sqlalchemy import Column, Integer, Text
    #import sqlalchemy.types as types  ## Don't need this anymore
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm import validates  ## This one for the validator
    
    Base = declarative_base()
    
    class User(Base)
        __tablename__ = 'User'
        id = Column(Integer, primary_key=True)
        name = Column(Text, unique=True)
    
        @validates("name")
        def alpha_only(self, key, value):
            return None if value is None else re.sub(r'[^A-Za-z\-\s]', '' , value.strip())
    
        def __repr__(self):
            return "<User(name='{}')>".format(self.name)
    

    【讨论】:

      最近更新 更多