跳转到主要内容

Python HAL生成/解析库

项目描述

卤素

Python HAL 生成/解析库。

http://img.shields.io/pypi/v/halogen.svg http://img.shields.io/coveralls/paylogic/halogen/master.svg https://travis-ci.org/paylogic/halogen.svg?branch=master Documentation Status

卤素利用声明式样式的序列化,具有易于扩展的模式。模式结合了关于您数据模型、属性映射和高级访问的知识,以及复杂类型和数据转换。

库旨在以最明显的方式表示您的数据为 HAL 格式,同时也提供了通用的类似网页表单的功能,以便尽可能多地重用您的模式和类型。

模式

模式是序列化的主要构建块。它也是一种类型,这意味着您可以使用模式声明嵌套结构。

序列化

>>> Schema.serialize({"hello": "Hello World"})
>>> {"hello": "Hello World"}

简单地调用 Schema.serialize() 类方法,它可以接受字典或任何其他对象。

验证

序列化过程中不涉及验证。您的源数据或模型被认为是干净的,因为它来自存储,并且不是用户输入。当然,类型或属性访问器可能发生异常,但它们被认为是编程错误。

序列化字典

字典值会自动通过模式属性使用它们的名称作为键进行访问

import halogen

class Hello(halogen.Schema):
    hello = halogen.Attr()


serialized = Hello.serialize({"hello": "Hello World"})

结果

{
    "hello": "Hello World"
}

HAL 只是 JSON,但根据其规范,它应该有自链接来标识序列化的资源。为此,您应使用 HAL 特定属性并配置 self 的组合方式。

HAL 示例

import halogen
from flask import url_for

spell = {
    "uid": "abracadabra",
    "name": "Abra Cadabra",
    "cost": 10,
}

class Spell(halogen.Schema):

    self = halogen.Link(attr=lambda spell: url_for("spell.get" uid=spell['uid']))
    name = halogen.Attr()

serialized = Spell.serialize(spell)

结果

{
    "_links": {
        "self": {"href": "/spells/abracadabra"}
    },
    "name": "Abra Cadabra"
}

序列化对象

与字典键类似,模式属性也可以访问对象属性

import halogen
from flask import url_for

class Spell(object):
    uid = "abracadabra"
    name = "Abra Cadabra"
    cost = 10

spell = Spell()

class SpellSchema(halogen.Schema):
    self = halogen.Link(attr=lambda spell: url_for("spell.get" uid=spell.uid))
    name = halogen.Attr()

serialized = SpellSchema.serialize(spell)

结果

{
    "_links": {
        "self": {"href": "/spells/abracadabra"}
    },
    "name": "Abra Cadabra"
}

属性

属性构成模式,并封装了从您的模型中获取数据、根据特定类型转换数据的知识。

Attr()

模式中属性成员的名称是结果序列化到的键的名称。默认情况下,使用相同的属性名称访问源模型。

示例

import halogen
from flask import url_for

class Spell(object):
    uid = "abracadabra"
    name = "Abra Cadabra"
    cost = 10

spell = Spell()

class SpellSchema(halogen.Schema):
    self = halogen.Link(attr=lambda spell: url_for("spell.get" uid=spell.uid))
    name = halogen.Attr()

serialized = SpellSchema.serialize(spell)

结果

{
    "_links": {
        "self": {"href": "/spells/abracadabra"}
    },
    "name": "Abra Cadabra"
}

Attr("const")

如果属性表示一个常量,则可以将值指定为第一个参数。这个第一个参数是属性的类型的实例或子类。如果类型不是 halogen.types.Type 的实例或子类,则会被忽略。

import halogen
from flask import url_for

class Spell(object):
    uid = "abracadabra"
    name = "Abra Cadabra"
    cost = 10

spell = Spell()

class SpellSchema(halogen.Schema):
    self = halogen.Link(attr=lambda spell: url_for("spell.get" uid=spell.uid))
    name = halogen.Attr("custom name")

serialized = SpellSchema.serialize(spell)

结果

{
    "_links": {
        "self": {"href": "/spells/abracadabra"}
    },
    "name": "custom name"
}

在某些情况下,也可以将 attr 指定为可调用的函数,它返回一个常量值。

Attr(attr="foo")

如果属性名称与您的模型不匹配,则可以覆盖它

import halogen
from flask import url_for

class Spell(object):
    uid = "abracadabra"
    title = "Abra Cadabra"
    cost = 10

spell = Spell()

class SpellSchema(halogen.Schema):
    self = halogen.Link(attr=lambda spell: url_for("spell.get" uid=spell.uid))
    name = halogen.Attr(attr="title")

serialized = SpellSchema.serialize(spell)

结果

{
    "_links": {
        "self": {"href": "/spells/abracadabra"}
    },
    "name": "Abra Cadabra"
}

attr 参数接受源属性名称的字符串或点分隔的属性路径。这对于嵌套字典、相关对象和 Python 属性都有效。

import halogen

class SpellSchema(halogen.Schema):
    name = halogen.Attr(attr="path.to.my.attribute")

Attr(attr=lambda value: value)

attr 参数接受可调用的函数,该函数接受整个源模型并可以访问必要的属性。您可以通过传递函数或 lambda 来返回所需值,该值也可以只是一个常量。

import halogen
from flask import url_for

class Spell(object):
    uid = "abracadabra"
    title = "Abra Cadabra"
    cost = 10

spell = Spell()

class SpellSchema(halogen.Schema):
    self = halogen.Link(attr=lambda spell: url_for("spell.get" uid=spell.uid))
    name = halogen.Attr(attr=lambda value: value.title)

serialized = SpellSchema.serialize(spell)

结果

{
    "_links": {
        "self": {"href": "/spells/abracadabra"}
    },
    "name": "Abra Cadabra"
}

属性作为装饰器

有时访问器函数太大,不能使用 lambda。在这种情况下,可以将类的方法装饰为获取器访问器。

import halogen

class ShoppingCartSchema(halogen.Schema):

    @halogen.attr(AmountType(), default=None)
    def total(obj):
        return sum(
            (item.amount for item in obj.items),
            0,
        )

    @total.setter
    def set_total(obj, value):
        obj.total = value

Attr(attr=Acccessor)

如果模式用于双向序列化和反序列化,则可以传递 halogen.schema.Accessor,其中指定了 gettersetterGetter 是一个字符串或可调用的函数,用于从模型中获取值,而 setter 是一个字符串或可调用的函数,它知道反序列化的值应该存储在哪里。

Attr(Type())

属性获取值后,将其传递给其类型以完成序列化。卤素提供基本类型,例如 halogen.types.List 以实现值或模式的列表。模式也是一种类型,可以传递给属性以实现复杂结构。

示例

import halogen
from flask import url_for

class Book(object):
    uid = "good-book-uid"
    title = "Harry Potter and the Philosopher's Stone"
    genres = [
        {"uid": "fantasy-literature", "title": "fantasy literature"},
        {"uid": "mystery", "title": "mystery"},
        {"uid": "adventure", "title": "adventure"},
    ]

book = Book()

class GenreSchema(halogen.Schema):
    self = halogen.Link(attr=lambda genre: url_for("genre.get" uid=genre['uid']))
    title = halogen.Attr()

class BookSchema(halogen.Schema):
    self = halogen.Link(attr=lambda book: url_for("book.get" uid=book.uid))
    title = halogen.Attr()
    genres = halogen.Attr(halogen.types.List(GenreSchema))

serialized = BookSchema.serialize(book)

结果

{
    "_links": {
        "self": {"href": "good-book-uid"}
    },
    "genres": [
        {"_links": {"self": {"href": "fantasy-literature"}}, "title": "fantasy literature"},
        {"_links": {"self": {"href": "mystery"}}, "title": "mystery"},
        {"_links": {"self": {"href": "adventure"}}, "title": "adventure"}
    ],
    "title": "Harry Potter and the Philosopher's Stone"
}

Attr(Type(validators=[validator]))

类型可以获取可选的 validators 参数,它是一个包含 halogen.validators.Validator 对象的列表,其单个接口方法 validate 将在反序列化期间调用给定值。如果值无效,应引发 halogen.exceptions.ValidationError。卤素提供基本验证器,例如 halogen.validators.Range 以验证值是否在特定范围内。

Attr(default=value)

如果属性无法获取,将使用提供的 default 值;如果 default 值是可调用的,则将调用它以获取默认值。

Attr(required=False)

默认情况下,属性是必需的,因此在序列化期间无法获取属性且未提供默认值时,将引发异常(AttributeErrorKeyError,具体取决于输入)。可以通过将 required=False 传递给属性构造函数来放宽此限制。对于反序列化,适用相同的逻辑,但异常类型将变为 halogen.exceptions.ValidationError 以便人类可读(参见 反序列化)。

Type

类型负责单个值的序列化,例如整数、字符串、日期。类型也是 Schema 的基础。它具有 serialize() 和 deserialize() 方法,可以将属性值进行转换。与 Schema 类型不同,类型是实例化的。您可以通过在声明您的 schema 时向其构造函数传递参数来配置序列化行为。

类型可以在反序列化期间引发 halogen.exceptions.ValidationError,但序列化期望该类型知道如何转换的值。

类型子类化

在您的应用程序中常见的类型可以在 schema 之间共享。这可能包括日期时间类型、特定 URL 类型、国际化字符串以及任何需要特定格式的其他表示。

Type.serialize

Type.serialize 的默认实现是一个绕过。

类型的序列化方法是序列化值时的最后一次转换机会

示例

import halogen

class Amount(object):
    currency = "EUR"
    amount = 1


class AmountType(halogen.types.Type):
    def serialize(self, value):

        if value is None or not isinstance(value, Amount):
            return None

        return {
            "currency": value.currency,
            "amount": value.amount
        }


class Product(object):
    name = "Milk"

    def __init__(self):
        self.price = Amount()

product = Product()


class ProductSchema(halogen.Schema):

    name = halogen.Attr()
    price = halogen.Attr(AmountType())

serialized = ProductSchema.serialize(product)

结果

{
    "name": "Milk",
    "price": {
        "amount": 1,
        "currency": "EUR"
    }
}

可空类型

如果访问器返回 None 且不希望类型或嵌套 schema 进行进一步序列化,则可以将类型包装到 Nullable 类型中。

import halogen


class FreeProduct(object):
    """A free product, that doesn't have a price."""

    price = None


class AmountSchema(halogen.Schema):

    currency = halogen.Attr(required=True, default="USD")
    amount = halogen.Attr(required=True, default=0)


class FreeProductSchema(halogen.Schema):

    price_null = halogen.Attr(halogen.types.Nullable(AmountType()), attr="price")
    price_zero = halogen.Attr(AmountType(), attr="price")


serialized = FreeProductSchema.serialize(FreeProduct())

结果

{
    "price_null": None,
    "price_zero": {
        "amount": 0,
        "currency": "USD"
    }
}

HAL

超文本应用语言。

RFC

HAL(application/hal+json)的 JSON 变体已作为互联网草案发布: 草案-kelly-json-hal

href

“href”属性是必需的。

halogen.Link 将为您创建 href。您只需指向 halogen.Link 应该放在哪里或应该放入 href 中的内容即可。

静态变体
import halogen

class EventSchema(halogen.Schema):

    artist = halogen.Link(attr="/artists/some-artist")
可调用变体
import halogen

class EventSchema(halogen.Schema):

    help = halogen.Link(attr=lambda: current_app.config['DOC_URL'])

弃用

可以通过指定指向描述弃用的文档的弃用 URL 属性来弃用链接。

import halogen

class EventSchema(halogen.Schema):

    artist = halogen.Link(
        attr="/artists/some-artist",
        deprecation="http://docs.api.com/deprecations#artist",
    )

CURIE

CURIE 提供了对资源文档的链接。

import halogen

doc = halogen.Curie(
    name="doc,
    href="http://haltalk.herokuapp.com/docs/{rel}",
    templated=True
)

class BlogSchema(halogen.Schema):

    lastest_post = halogen.Link(attr="/posts/latest", curie=doc)
{
    "_links": {
        "curies": [
            {
              "name": "doc",
              "href": "http://haltalk.herokuapp.com/docs/{rel}",
              "templated": true
            }
        ],

        "doc:latest_posts": {
            "href": "/posts/latest"
        }
    }
}

Schema 也可以是链接的参数

import halogen

class BookLinkSchema(halogen.Schema):
    href = halogen.Attr("/books")

class BookSchema(halogen.Schema):

    books = halogen.Link(BookLinkSchema)

serialized = BookSchema.serialize({"books": ""})
{
    "_links": {
        "books": {
            "href": "/books"
        }
    }
}

嵌入

保留的 “_embedded” 属性是可选的。它是一个对象,其属性名称是链接关系类型(如 [RFC5988] 中定义的)和值是资源对象或资源对象的数组。

嵌入资源可以是来自目标 URI 的表示的全版本、部分版本或不一致版本。

为了在您的模式中创建 _embedded,您应该使用 halogen.Embedded

示例

import halogen

em = halogen.Curie(
    name="em",
    href="https://docs.event-manager.com/{rel}.html",
    templated=True,
    type="text/html"
)


class EventSchema(halogen.Schema):
    self = halogen.Link("/events/activity-event")
    collection = halogen.Link("/events/activity-event", curie=em)
    uid = halogen.Attr()


class PublicationSchema(halogen.Schema):
    self = halogen.Link(attr=lambda publication: "/campaigns/activity-campaign/events/activity-event")
    event = halogen.Link(attr=lambda publication: "/events/activity-event", curie=em)
    campaign = halogen.Link(attr=lambda publication: "/campaign/activity-event", curie=em)


class EventCollection(halogen.Schema):
    self = halogen.Link("/events")
    events = halogen.Embedded(halogen.types.List(EventSchema), attr=lambda collection: collection["events"], curie=em)
    publications = halogen.Embedded(
        attr_type=halogen.types.List(PublicationSchema),
        attr=lambda collection: collection["publications"],
        curie=em
    )


collections = {
    'events': [
        {"uid": 'activity-event'}
    ],
    'publications': [
        {
            "event": {"uid": "activity-event"},
            "campaign": {"uid": "activity-campaign"}
        }
    ]
}

serialized = EventCollection.serialize(collections)

结果

{
    "_embedded": {
        "em:events": [
            {
                "_links": {
                    "curies": [
                        {
                            "href": "https://docs.event-manager.com/{rel}.html",
                            "name": "em",
                            "templated": true,
                            "type": "text/html"
                        }
                    ],
                    "em:collection": {"href": "/events/activity-event"},
                    "self": {"href": "/events/activity-event"}
                },
                "uid": "activity-event"
            }
        ],
        "em:publications": [
            {
                "_links": {
                    "curies": [
                        {
                            "href": "https://docs.event-manager.com/{rel}.html",
                            "name": "em",
                            "templated": true,
                            "type": "text/html"
                        }
                    ],
                    "em:campaign": {"href": "/campaign/activity-event"},
                    "em:event": {"href": "/events/activity-event"},
                    "self": {"href": "/campaigns/activity-campaign/events/activity-event"}
                }
            }
        ]
    },
    "_links": {
        "curies": [
            {
                "href": "https://docs.event-manager.com/{rel}.html",
                "name": "em",
                "templated": true,
                "type": "text/html"
            }
        ],
        "self": {"href": "/events"}
    }
}

默认情况下,嵌入式资源是必需的,您可以通过将 required=False 传递给构造函数来使它们不是必需的,并且在序列化中会省略空值。

import halogen

class Schema(halogen.Schema):
    user1 = halogen.Embedded(PersonSchema, required=False)
    user2 = halogen.Embedded(PersonSchema)

serialized = Schema.serialize({'user2': Person("John", "Smith")})

结果

{
    "_embedded": {
        "user2": {
            "name": "John",
            "surname": "Smith"
        }
    }
}

反序列化

模式具有 deserialize 方法。如果未传递任何对象作为第二个参数,则方法 deserialize 将返回反序列化结果的字典。

示例

import halogen

class Hello(halogen.Schema):
    hello = halogen.Attr()

result = Hello.deserialize({"hello": "Hello World"})
print(result)

结果

{
    "hello": "Hello World"
}

然而,如果您将对象作为 deserialize 方法的第二个参数传递,则数据将被分配到对象的属性中。

示例

import halogen

class HellMessage(object):
    hello = ""


hello_message = HellMessage()


class Hello(halogen.Schema):
    hello = halogen.Attr()


Hello.deserialize({"hello": "Hello World"}, hello_message)
print(hello_message.hello)

结果

"Hello World"

Type.deserialize

如您所知,属性从它们在序列化时支持的类型中调用 serialize 方法,但在反序列化时,相同的属性将调用 deserialize 方法。这意味着当您编写自己的类型时,您不应该忘记为它们编写 deserialize 方法。

示例

import halogen
import decimal


class Amount(object):
    currency = "EUR"
    amount = 1

    def __init__(self, currency, amount):
        self.currency = currency
        self.amount = amount

    def __repr__(self):
        return "Amount: {currency} {amount}".format(currency=self.currency, amount=str(self.amount))


class AmountType(halogen.types.Type):

    def serialize(self, value):

        if value is None or not isinstance(value, Amount):
            return None

        return {
            "currency": value.currency,
            "amount": value.amount
        }

    def deserialize(self, value):
        return Amount(value["currency"], decimal.Decimal(str(value["amount"])))


class ProductSchema(halogen.Schema):
    title = halogen.Attr()
    price = halogen.Attr(AmountType())


product = ProductSchema.deserialize({"title": "Pencil", "price": {"currency": "EUR", "amount": 0.30}})
print(product)

结果

{"price": Amount: EUR 0.3, "title": "Pencil"}

反序列化验证错误

在反序列化失败时,halogen 会引发特殊异常(halogen.exceptions.ValidationError)。该异常类具有 __unicode__ 方法,该方法可以渲染供用户轻松跟踪的易于阅读的错误结果。

示例

import halogen

class Hello(halogen.Schema):
    hello = halogen.Attr()

try:
    result = Hello.deserialize({})
except halogen.exceptions.ValidationError as exc:
    print(exc)

结果

{
    "errors": [
        {
            "errors": [
                    {
                        "type": "str",
                        "error": "Missing attribute."
                    }
                ],
            "attr": "hello"
        }
    ],
    "attr": "<root>"
}

在您有嵌套模式并使用 List 的情况下,halogen 还会将索引(从 0 开始计数)添加到列表中,以便您可以看到验证错误的确切位置。

示例

import halogen

class Product(halogen.Schema):

    """A product has a name and quantity."""

    name = halogen.Attr()
    quantity = halogen.Attr()


class NestedSchema(halogen.Schema):

    """An example nested schema."""

    products = halogen.Attr(
        halogen.types.List(
            Product,
        ),
        default=[],
    )

try:
    result = NestedSchema.deserialize({
        "products": [
            {
                "name": "name",
                "quantity": 1
            },
            {
                "name": "name",
            }

        ]
    })
except halogen.exceptions.ValidationError as exc:
    print(exc)

结果

{
    "errors": [
        {
            "errors": [
                {
                    "index": 1,
                    "errors": [
                        {
                            "errors": [
                                {
                                    "type": "str",
                                    "error": "Missing attribute."
                                }
                            ],
                            "attr": "quantity"
                        }
                    ]
                }
            ],
            "attr": "products"
        }
    ],
    "attr": "<root>"
}

请注意,如果在属性反序列化时发生 ValueError 异常,它将被捕获并以 halogen.exceptions.ValidationError 的形式重新抛出。这是为了消除在反序列化过程中在类型和属性中引发 halogen 特定异常的需要。

提供上下文

在序列化或反序列化对象时,并非所有用于(反)序列化的数据都存在于对象本身中。您可以将此数据作为单独的关键字参数传递给 serializedeserialize,以提供上下文。此上下文将在所有嵌套模式、类型和属性中可用。

序列化示例

class ErrorSchema(halogen.Schema):
    message = halogen.Attr(
        attr=lambda error, language: error["message"][language]
    )

error = ErrorSchema.serialize({
    "message": {
        "dut": "Ongeldig e-mailadres",
        "eng": "Invalid email address"
    }
}, language="dut")

print error

结果

{"message": "Ongeldig e-mailadres"}

反序列化示例

import halogen


class Book(halogen.Schema):

    @halogen.attr()
    def title(obj, language):
        return obj['title'][language]

class Author(halogen.Schema):
    name = halogen.Attr(attr='author.name')
    books = halogen.Attr(
        halogen.types.List(Book),
        attr='author.books',
    )

author = Author.deserialize({
    "author": {
        "name": "Roald Dahl",
        "books": [
            {
                "title": {
                    "dut": "De Heksen",
                    "eng": "The Witches"
                }
            },
            {
                "title": {
                    "dut": "Sjakie en de chocoladefabriek",
                    "eng": "Charlie and the Chocolate Factory"
                }
            }
        ]
    }
}, language="eng")

print author

结果

{
    "name": "Roald Dahl",
    "books": [
        {"title": "The Witches"},
        {"title": "Charlie and the Chocolate Factory"}
    ]
}

供应商媒体类型

处理验证和业务逻辑错误与处理 HAL 响应一样重要。Halogen 提供了对供应商错误媒体类型的支持,该媒体类型完全兼容 HAL。

vnd.error

供应商错误(application/vnd.error+json)现在已作为互联网草案发布:draft-vnd-error

该媒体类型正在尝试标准化表示问题的格式,以便许多客户端可以表达和理解。多个反序列化错误可以通过路径属性映射到有效负载的相关键,该属性表示有效负载键的 JSON Pointer,因此可以映射到与该键一起序列化的 UI 元素。

import halogen
from halogen.vnd.error import Error, VNDError


class AuthorSchema(halogen.Schema):
    name = halogen.Attr(required=True)


class PublisherSchema(halogen.Schema):
    name = halogen.Attr(required=True)
    address = halogen.Attr()


class BookSchema(halogen.Schema):
    title = halogen.Attr(required=True)
    year = halogen.Attr(halogen.types.Int(), required=True)
    authors = halogen.Attr(halogen.types.List(AuthorSchema), required=True)
    publisher = halogen.Attr(PublisherSchema)

try:
    BookSchema.deserialize(
        dict(
            # title is skipped
            year="abc",  # Not an integer
            authors=[dict(name="John Smith"), dict()],  # Second author has no name
            publisher=dict(address="Chasey Lane 42, Los Angeles, US"),  # No name
        ),
    )
except halogen.exceptions.ValidationError as e:
    error = Error.from_validation_exception(e)

>>> error.errors
>>>
    [
        {"path": "/authors/1/name", "message": "Missing attribute."),
        {"path": "/title", "message": "Missing attribute."),
        {"path": "/year", "message": "'abc' is not an integer"),
        {"path": "/publisher/name", "message": "Missing attribute."),
    }

错误可能或可能不与有效负载相关,但有时与另一个资源相关。在这种情况下,错误中应返回关于链接。

{
    "_links": {
        "about": {"href": "/products/1"}
    },
    "message": "The product is sold out."
}

i18n

错误消息应国际化并尊重 Accept-Language 和 Content-Language HTTP 头。

联系

如果您有任何问题、错误报告、建议等,请在 GitHub 项目页面 上创建一个问题。

许可证

本软件根据 MIT 许可证 许可。

请参阅 许可证文件

© 2013 Oleg Pidsadnyi,Paylogic 国际及其他人。

变更日志

1.7.0

  • 修复 List 类型在反序列化非列表对象时引发无关错误(或在没有传递字典时完全不引发错误)的问题。

1.6.1

  • 1.6.0版本的热修复:修复序列化/反序列化错误 Schema.serialize(…) 使用了kwargs,其中有一些属性使用了不需要kwargs的可调用对象。

1.6.0

  • 修复了DeprecationWarnings的抛出(youtux)

  • 取消对python 2.6的支持,并添加了对python 3.5、3.6、3.7、3.8的支持

1.5.0

  • 允许将上下文关键字参数传递给反序列化方法(blaise-io)

1.4.1

  • 修复了软件包设置问题(olegpidsadnyi)

1.4.0

  • 支持vnd.error响应(olegpidsadnyi)

1.3.5

  • 添加ISO日期时间类型(moisesribeiro)

1.3.4

  • 可空类型(olegpidsadnyi)

1.3.3

  • 严格验证ISO8601(olegpidsadnyi)

1.3.2

  • 提高序列化性能(youtux)

1.3.1

  • 修复String.deserialize以强制文本类型(olegpidsadnyi)

1.3.0

  • 属性作为装饰器(olegpidsadnyi)

1.2.1

  • 使用datetime.isoformat进行datetime序列化(bubenkoff)

1.1.3

  • 正确处理schema类的继承(bubenkoff)

1.1.2

  • 正确处理String和Int类型的反序列化(bubenkoff)

1.1.1

  • 在Link中添加了弃用属性(olegpidsadnyi)

1.1.0

  • 添加常用类型(bubenkoff)

1.0.8

  • 正确处理和文档化< span class="docutils literal">required和< span class="docutils literal">default(bubenkoff)

  • 正确获取验证器的比较值(lazy和constant)(bubenkoff)

  • 增加测试覆盖率(bubenkoff)

1.0.6

  • 在属性反序列化中尊重ValueError(bubenkoff)

1.0.4

  • 正确渲染和文档化反序列化错误(bubenkoff)

1.0.3

  • 允许将嵌入字段标记为非必需(mattupstate)

  • 在序列化文档中保留字段顺序(mattupstate)

1.0.0

  • 首次公开发布

支持者:

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误日志 StatusPage StatusPage 状态页面