如何从 JSL 文档定义的 JSON 模式中具体化属性数据 class

How to materialize attrs data class from JSON schema defined by JSL document

假设您使用 Python JSL library for defining JSON schema of your data and you using attrs library for quick definition of your DTO.

您如何根据其 JSON 架构定义(如 jsl.Document class)轻松验证数据结构并将其具体化为 attrs 实例符合其 JSL 定义而无需额外的样板文件?

因为创建 JSL 文档并复制它们的定义只是为了拥有相应的 attrs DTO 感觉不是正确的方法。

定义一个函数来使用 JSL 类型来使用实际数据进行验证,一旦字典根据模式进行验证,就收集 JSL 文档属性并使用 make_class 方法动态生成 attrs 实例.

这是 Python 3.6.1 的概念证明:

from typing import TypeVar, Type

import attr
import jsl
import jsonschema


class Demo(jsl.Document):
    """
    Demo schema
    """
    ip = jsl.IPv4Field(required=True)
    """IPv4 address string"""
    headers = jsl.DictField(required=True,
                            min_properties=1,
                            additional_properties=jsl.StringField())
    """Dictionary of HTTP headers"""
    email = jsl.EmailField()
    """Optional User email"""


T = TypeVar('T')  # Nice hack using generic type hinting. It preserves auto-completion


def reify(schema: Type[T], data: dict) -> T:
    """
    Consumes JSON schema (as jsl.Document object) with dictionary data for validation
    and if data is valid then “attrs” instance is produced having same structure
    as schema object with populated data.

    :param T schema: Schema type (as jsl.Document type.)
    :param dict data: Data dictionary for validation against **schema**.
    :return: Schema transformed into equivalent **attrs** instance with populated
    **data**.
    :rtype: T
    :raises: ValueError — When **data** does not conforms with **schema**.
    """
    try:
        jsonschema.validate(data, schema.get_schema())
        props = [name for name, _ in schema.get_schema()['properties'].items()]
        fields = {key: attr.ib(default=None) for key in props}
        # noinspection PyTypeChecker
        return attr.make_class(schema.__name__, fields)(**data)
    except jsonschema.ValidationError as e:
        raise ValueError(f'Payload does not conform to JSON schema: {e.message}')


demo = reify(Demo, {'ip': '1.2.3.4', 'headers': {'Accept': '*/*'}})

print(demo)
print(f"{demo.ip} Headers: {demo.headers} Email {demo.email}")

# Prints:
# Demo(ip='1.2.3.4', headers={'Accept': '*/*'}, email=None)
# 1.2.3.4 Headers: {'Accept': '*/*'} Email None

作为一个不错的奖励,PyCharm 将从 JSL 文档 class.

中推断出正确的自动完成保留文档