使用 SQLAlchemy TypeDecorator 时 Marshmallow 验证错误地失败
Marshmallow validation incorrectly fails when using an SQLAlchemy TypeDecorator
我创建了一个基于 SQLAlchemy example 的 TypeDecorator,它在 32 个字符的十六进制字符串和存储 UUID 的 BINARY
数据库列之间进行转换:
from __future__ import absolute_import
import uuid
from sqlalchemy import types, func
#https://docs.sqlalchemy.org/en/13/core/custom_types.html#backend-agnostic-guid-type
class HashColumn(types.TypeDecorator):
impl=types.BINARY
def process_bind_param(self, value, dialect):
if value is not None:
return uuid.UUID(hex=value).bytes
def process_result_value(self, value, dialect):
return uuid.UUID(bytes=value).hex
def copy(self, **kw):
return HashColumn(self.impl.length)
型号:
def get_uuid():
return uuid.uuid4().hex
class School(db.Model):
"""
description: A School
"""
__tablename__ = "schools"
id = db.Column('school_id', HashColumn(length=16), primary_key=True, default=get_uuid)
...
但是,我遇到的问题是我的棉花糖模式(从 SQLAlchemy 模型生成)没有将此列视为 32 个字符的字符串:
架构:
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
...
class SchoolSchema(SQLAlchemyAutoSchema):
class Meta:
model = School
include_relationships = True
load_instance = True
include_fk = True
...
在我的代码中:
try:
new_object = SchoolSchema().load(data, session=db.session)
except ValidationError as err:
print(err.messages)
print(err.valid_data)
当 运行 此代码使用完全有效的 UUID a5fad20c691546ae8871390d980aae6d
时,marshmallow 抛出验证错误并给出以下输出:
{"id": ["Longer than maximum length 16."]}
因为我想在使用过程中将 UUID 格式化为 32 个字符的十六进制字符串(或 python UUID,如果适用),并在存储到数据库之前转换为 BINARY(16)
,我需要摆脱这个验证错误,但我不确定该怎么做,因为更改 SQLAlchemy 模型上的长度参数将意味着数据库表将被创建为 BINARY(32)
而不是 BINARY(16)
,加倍长度。
是否可以设置 SQLAlchemy TypeDecorator
以便它在数据库中存储一种长度 (BINARY(16)
) 的类型,但呈现不同的长度 (CHAR(32)
) 到 Python and/orSQLAlchemy 以便 marshmallow 可以正确地将长度验证为 32 个字符的字符串?
我已经在 Whosebug 上查看过类似这样的其他问题:
但这似乎是关于转换类型本身,我已经在我的示例代码中完成了。我似乎找不到任何提到如何转换类型的 length 的内容。
到目前为止我找到了两种方法来解决这个问题:
调整 TypeDecorator 的类型,使其像 CHAR
列而不是二进制列,并使用 load_dialect_impl
更改呈现给数据库的类型,将不同的长度指定为参数
class HashColumn(types.TypeDecorator):
impl=types.CHAR
def load_dialect_impl(self, dialect):
return dialect.type_descriptor(types.BINARY(16))
...
(其余class与题中基本相同)
此更改允许我将 HashColumn(length=16)
定义从我的数据库模型更改为 HashColumn(length=32)
,从而允许 marshmallow 正确解释长度。
或者,我可以更改 API PATCH/update 端点的实现以从数据库中获取和更新现有对象,而不是创建一个全新的对象并尝试合并他们的价值观。这完全删除了 Marshmallow 验证,因为 ID 不再用于创建新对象,但是,对我来说,这感觉像是一种太多的解决方法,并且意味着,因为没有使用 Marshmallow 验证,它也不会验证任何其他数据字段。
我创建了一个基于 SQLAlchemy example 的 TypeDecorator,它在 32 个字符的十六进制字符串和存储 UUID 的 BINARY
数据库列之间进行转换:
from __future__ import absolute_import
import uuid
from sqlalchemy import types, func
#https://docs.sqlalchemy.org/en/13/core/custom_types.html#backend-agnostic-guid-type
class HashColumn(types.TypeDecorator):
impl=types.BINARY
def process_bind_param(self, value, dialect):
if value is not None:
return uuid.UUID(hex=value).bytes
def process_result_value(self, value, dialect):
return uuid.UUID(bytes=value).hex
def copy(self, **kw):
return HashColumn(self.impl.length)
型号:
def get_uuid():
return uuid.uuid4().hex
class School(db.Model):
"""
description: A School
"""
__tablename__ = "schools"
id = db.Column('school_id', HashColumn(length=16), primary_key=True, default=get_uuid)
...
但是,我遇到的问题是我的棉花糖模式(从 SQLAlchemy 模型生成)没有将此列视为 32 个字符的字符串:
架构:
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
...
class SchoolSchema(SQLAlchemyAutoSchema):
class Meta:
model = School
include_relationships = True
load_instance = True
include_fk = True
...
在我的代码中:
try:
new_object = SchoolSchema().load(data, session=db.session)
except ValidationError as err:
print(err.messages)
print(err.valid_data)
当 运行 此代码使用完全有效的 UUID a5fad20c691546ae8871390d980aae6d
时,marshmallow 抛出验证错误并给出以下输出:
{"id": ["Longer than maximum length 16."]}
因为我想在使用过程中将 UUID 格式化为 32 个字符的十六进制字符串(或 python UUID,如果适用),并在存储到数据库之前转换为 BINARY(16)
,我需要摆脱这个验证错误,但我不确定该怎么做,因为更改 SQLAlchemy 模型上的长度参数将意味着数据库表将被创建为 BINARY(32)
而不是 BINARY(16)
,加倍长度。
是否可以设置 SQLAlchemy TypeDecorator
以便它在数据库中存储一种长度 (BINARY(16)
) 的类型,但呈现不同的长度 (CHAR(32)
) 到 Python and/orSQLAlchemy 以便 marshmallow 可以正确地将长度验证为 32 个字符的字符串?
我已经在 Whosebug 上查看过类似这样的其他问题:
但这似乎是关于转换类型本身,我已经在我的示例代码中完成了。我似乎找不到任何提到如何转换类型的 length 的内容。
到目前为止我找到了两种方法来解决这个问题:
调整 TypeDecorator 的类型,使其像
CHAR
列而不是二进制列,并使用load_dialect_impl
更改呈现给数据库的类型,将不同的长度指定为参数class HashColumn(types.TypeDecorator): impl=types.CHAR def load_dialect_impl(self, dialect): return dialect.type_descriptor(types.BINARY(16)) ...
(其余class与题中基本相同)
此更改允许我将
HashColumn(length=16)
定义从我的数据库模型更改为HashColumn(length=32)
,从而允许 marshmallow 正确解释长度。或者,我可以更改 API PATCH/update 端点的实现以从数据库中获取和更新现有对象,而不是创建一个全新的对象并尝试合并他们的价值观。这完全删除了 Marshmallow 验证,因为 ID 不再用于创建新对象,但是,对我来说,这感觉像是一种太多的解决方法,并且意味着,因为没有使用 Marshmallow 验证,它也不会验证任何其他数据字段。