简体   繁体   English

如何验证SQLAlchemy ORM中的列数据类型?

[英]How can I verify Column data types in the SQLAlchemy ORM?

Using the SQLAlchemy ORM, I want to make sure values are the right type for their columns. 我想使用SQLAlchemy ORM来确保值是其列的正确类型。

For example, say I have an Integer column. 例如,假设我有一个Integer列。 I try to insert the value “hello”, which is not a valid integer. 我尝试插入值“ hello”,它不是有效的整数。 SQLAlchemy will allow me to do this. SQLAlchemy将允许我执行此操作。 Only later, when I execute session.commit() , does it raise an exception: sqlalchemy.exc.DataError: (DataError) invalid input syntax integer: "hello"… . 只是稍后,当我执行session.commit() ,它才会引发异常: sqlalchemy.exc.DataError: (DataError) invalid input syntax integer: "hello"…

I am adding batches of records, and I don't want to commit after every single add(…) , for performance reasons. 我要添加成批的记录,出于性能原因,我不想在每个add(…)之后提交。

So how can I: 那么我该如何:

  • Raise the exception as soon as I do session.add(…) 我执行session.add(…)立即引发异常
  • Or, make sure the value I am inserting can be converted to the target Column datatype, before adding it to the batch? 还是在将我要插入的值添加到批处理之前 ,确保将其转换为目标Column数据类型?
  • Or any other way to prevent one bad record from spoiling an entire commit() . 或采取其他任何方法来防止不良记录破坏整个commit()

SQLAlchemy doesn't build this in as it defers to the DBAPI/database as the best and most efficient source of validation and coercion of values. SQLAlchemy没有内置此功能,因为它符合DBAPI /数据库的要求,是验证和强制值的最佳,最有效的来源。

To build your own validation, usually TypeDecorator or ORM-level validation is used. 为了构建自己的验证,通常使用TypeDecorator或ORM级验证。 TypeDecorator has the advantage that it operates at the core and can be pretty transparent, though it only occurs when SQL is actually emitted. TypeDecorator的优点是它在核心运行并且可以相当透明,尽管它仅在实际发出SQL时发生。

To do validation and coercion sooner, this is at the ORM level. 为了尽早进行验证和强制,这是在ORM级别上。

Validation can be ad-hoc, at the ORM layer, via @validates : 验证可以通过@validates在ORM层上@validates

http://docs.sqlalchemy.org/en/latest/orm/mapped_attributes.html#simple-validators http://docs.sqlalchemy.org/en/latest/orm/mapped_attributes.html#simple-validators

The event system that @validates uses is also available directly. @validates使用的事件系统也可以直接使用。 You can write a generalized solution that links validators of your choosing to the types being mapped: 您可以编写一个通用的解决方案,将您选择的验证器链接到要映射的类型:

from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import event
import datetime

Base= declarative_base()

def validate_int(value):
    if isinstance(value, basestring):
        value = int(value)
    else:
        assert isinstance(value, int)
    return value

def validate_string(value):
    assert isinstance(value, basestring)
    return value

def validate_datetime(value):
    assert isinstance(value, datetime.datetime)
    return value

validators = {
    Integer:validate_int,
    String:validate_string,
    DateTime:validate_datetime,
}

# this event is called whenever an attribute
# on a class is instrumented
@event.listens_for(Base, 'attribute_instrument')
def configure_listener(class_, key, inst):
    if not hasattr(inst.property, 'columns'):
        return
    # this event is called whenever a "set" 
    # occurs on that instrumented attribute
    @event.listens_for(inst, "set", retval=True)
    def set_(instance, value, oldvalue, initiator):
        validator = validators.get(inst.property.columns[0].type.__class__)
        if validator:
            return validator(value)
        else:
            return value


class MyObject(Base):
    __tablename__ = 'mytable'

    id = Column(Integer, primary_key=True)
    svalue = Column(String)
    ivalue = Column(Integer)
    dvalue = Column(DateTime)


m = MyObject()
m.svalue = "ASdf"

m.ivalue = "45"

m.dvalue = "not a date"

Validation and coercion can also be built at the type level using TypeDecorator, though this is only when SQL is being emitted, such as this example which coerces utf-8 strings to unicode: 验证和强制也可以使用TypeDecorator在类型级别上构建,尽管仅当发出SQL时才如此,例如本示例将utf-8字符串强制转换为unicode:

http://docs.sqlalchemy.org/en/latest/core/custom_types.html#coercing-encoded-strings-to-unicode http://docs.sqlalchemy.org/zh_CN/latest/core/custom_types.html#coercing-encoded-strings-to-unicode

Improving on the answer of @zzzeek , I suggest the following solution: 在改善@zzzeek的答案上,我建议以下解决方案:

from sqlalchemy import String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.event import listen_for

Base = declarative_base()

@listens_for(Base, 'attribute_instrument')
def configure_listener(table_cls, attr, col_inst):
    if not hasattr(col_inst.property, 'columns'):
        return
    validator = getattr(col_inst.property.columns[0].type, 'validator', None)
    if validator:
        # Only decorate columns, that need to be decorated
        @listens_for(col_inst, "set", retval=True)
        def set_(instance, value, oldvalue, initiator):
            return validator(value)

That lets you do things like: 这使您可以执行以下操作:

class Name(String):
    def validator(self, name):
        if isinstance(name, str):
            return name.upper()
        raise TypeError("name must be a string")

This has two benefits: Firstly, there is only an event triggered, when there actually is a validator attached to the data field object. 这有两个好处:首先,当实际上有一个验证器附加到数据字段对象时,仅触发一个事件。 It does not waste precious CPU cycles on set events for objects, that have no function for validation defined. 它不会在没有定义验证功能的对象的set事件上浪费宝贵的CPU周期。 Secondly, it allows you to define your own field types and just add a validator method there, so not all things that you want to store as Integer etc run through the same checks, just the ones derived from your new field type. 其次,它允许您定义自己的字段类型,并仅在其中添加一个验证器方法,因此,并非要存储为Integer等的所有内容都经过相同的检查,而只是从新字段类型派生的检查。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM