SQLAlchemy中TypeDecorator的备用预处理方法

时间:2018-01-10 23:58:40

标签: python orm sqlalchemy

我在插入数据库之前使用sqlalchemy.types.TypeDecorator预处理文本字段。除非我使用包含百分比的比较器like表达式(例如User.name.like('%bar%')),否则它的效果很好,因为在执行查询之前,process_bind_param()会删除百分比。

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

  1. SQLAlchemy中另一种预处理列的方法? (比如validator?)
  2. 或者可能是在执行查询时明确绕过process_bind_param的方法吗?
  3. 或者是我正在使用的解决方法(在本文末尾) - 只使用直接SQL查询并绕过ORM - 唯一选项?
  4. 以下是增强类型的最小实现:

    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')]
    

1 个答案:

答案 0 :(得分:0)

使用validator的选项1是正确的。 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)