使用 Marshmallow 反序列化强制执行严格的 fields.Date 格式

Enforce strict fields.Date format with Marshmallow deserialization

我正在为 Flask 项目使用 Marshmallow 2.15.3,并希望强制执行严格的日期和日期时间格式。严格的意思是我只想接受与以下格式相同的字符串。我遇到的是 Date 和 DateTime 的处理上的一些差异。格式:

DATE_FORMAT = '%Y-%m-%d'
DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S'

例如,使用 DateTime 我可以执行以下操作:

dt = fields.DateTime(format=DATETIME_FORMAT)
dt.deserialize('2018-01-01')  # fails, as desired
dt.deserialize('2018-01-01T05:06:08.012312+02:00')  # fails, as desired
dt.deserialize('2018-01-01T05:06:08')  # works, as desired and according to format

有了 Date,我可以执行以下操作:

d = fields.Date() # does not accept format argument
d.deserialize('2018-01')  # fails, as desired
d.deserialize('2018-01-01T05:06:08.012312+02:00')  # works, NOT as desired
d.deserialize('2018-01-01')  # works, as desired and according to format

虽然 DateTime 不允许额外信息,但 Date 允许。据我了解,日期字段没有 format 参数。有什么办法可以解决这个问题以获得类似的功能,并针对太短和太长的输入值强制执行我的严格格式?

对于面向未来的代码,我发现 Marshmallow 3.0.0b17 中的 Date class 现在是 DateTime 的子 class 而不是 Field, 使其继承 format kwarg (relevant commit).

对于版本 2.15.3 和(2.X.X 一般而言)我找不到任何内置函数。解决方法是猴子修补 fields.Date class。修改后看起来像这样:

class Date(Field):
    """ISO8601-formatted date string.

    :param kwargs: The same keyword arguments that :class:`Field` receives.
    """
    default_error_messages = {
        'invalid': 'Not a valid date.',
        'format': '"{input}" cannot be formatted as a date.',
    }

    def __init__(self, format=None, **kwargs):
        super(Date, self).__init__(**kwargs)
        self.dateformat = format

    def _serialize(self, value, attr, obj):
        if value is None:
            return None
        try:
            return value.isoformat()
        except AttributeError:
            self.fail('format', input=value)
        return value

    def _deserialize(self, value, attr, data):
        """Deserialize an ISO8601-formatted date string to a
        :class:`datetime.date` object.
        """
        if not value:  # falsy values are invalid
            self.fail('invalid')
        elif self.dateformat:
            try:
                return dt.datetime.strptime(value, self.dateformat).date()
            except (TypeError, AttributeError, ValueError):
                raise self.fail('invalid')
        try:
            return utils.from_iso_date(value)
        except (AttributeError, TypeError, ValueError):
            self.fail('invalid')

此处的修改是添加了 __init__ 定义,在 _deserialize 下添加了整个 elif self.dateformat 子句。这允许我使用提交的格式反序列化,例如:

d = fields.Date('%Y-%m-%d') # now accepts a format
d.deserialize('2018-01-01T05:06:08.012312+02:00')  # fails, as desired