跳转到主要内容

从字典中简单创建数据类。

项目描述

dacite

Build Status Coverage Status License Version Python versions Code style: black

此模块简化了从字典创建数据类(PEP 557)的过程。

安装

要安装dacite,只需使用pip

$ pip install dacite

要求

dacite支持的最低Python版本为3.6。

快速入门

from dataclasses import dataclass
from dacite import from_dict


@dataclass
class User:
    name: str
    age: int
    is_active: bool


data = {
    'name': 'John',
    'age': 30,
    'is_active': True,
}

user = from_dict(data_class=User, data=data)

assert user == User(name='John', age=30, is_active=True)

特性

Dacite支持以下特性

  • 嵌套结构
  • (基本)类型检查
  • 可选字段(即typing.Optional
  • 联合
  • 前向引用
  • 集合
  • 自定义类型钩子

动机

在函数或方法之间传递普通字典作为数据容器不是一种好的做法。当然,你可以始终创建自己的自定义类,但如果你只想在单个对象中合并几个字段,这种解决方案就是一种过度。

幸运的是,Python有很好的解决方案来解决这个问题 - 数据类。感谢@dataclass装饰器,你可以以声明的方式轻松创建一个具有给定字段列表的新自定义类型。数据类按设计支持类型提示。

然而,即使您在使用数据类,您也必须以某种方式创建它们的实例。在许多此类情况下,您的输入是一个字典 - 它可以来自 HTTP 请求的有效负载或数据库的原始数据。如果您想将这些字典转换为数据类,dacite就是您的最佳选择。

这个库最初是为了简化创建类型提示的数据传输对象(DTO)而创建的,这些对象可以在应用程序架构的边界之间进行交互。

重要的是要提到,dacite不是一个数据验证库。有几十个出色的数据验证项目,在dacite中重复此功能是没有意义的。如果您想首先验证您的数据,您应该将dacite与数据验证库之一结合使用。

请查看用例部分以获取实际示例。

使用方法

Dacite基于一个单一函数 - dacite.from_dict。此函数接受3个参数

  • data_class - 数据类类型
  • data - 输入数据的字典
  • config(可选)- 创建过程的配置,dacite.Config类的一个实例

配置是一个(数据)类,具有以下字段

  • type_hooks
  • cast
  • forward_references
  • check_types
  • strict
  • strict_unions_match

以下示例展示了from_dict函数的所有特性和所有Config参数的使用。

嵌套结构

您可以通过传递嵌套字典的数据来创建合适的结果。

@dataclass
class A:
    x: str
    y: int


@dataclass
class B:
    a: A


data = {
    'a': {
        'x': 'test',
        'y': 1,
    }
}

result = from_dict(data_class=B, data=data)

assert result == B(a=A(x='test', y=1))

可选字段

每当您的数据类有一个Optional字段并且您不会为此字段提供输入数据时,它将取None值。

from typing import Optional

@dataclass
class A:
    x: str
    y: Optional[int]


data = {
    'x': 'test',
}

result = from_dict(data_class=A, data=data)

assert result == A(x='test', y=None)

联合

如果您的字段可以接受多种类型,您应该使用Union。Dacite将按顺序尝试将数据与提供的类型匹配。如果没有匹配,它将引发UnionMatchError异常。

from typing import Union

@dataclass
class A:
    x: str

@dataclass
class B:
    y: int

@dataclass
class C:
    u: Union[A, B]


data = {
    'u': {
        'y': 1,
    },
}

result = from_dict(data_class=C, data=data)

assert result == C(u=B(y=1))

集合

Dacite支持定义为集合的字段。它适用于基本类型和数据类。

@dataclass
class A:
    x: str
    y: int


@dataclass
class B:
    a_list: List[A]


data = {
    'a_list': [
        {
            'x': 'test1',
            'y': 1,
        },
        {
            'x': 'test2',
            'y': 2,
        }
    ],
}

result = from_dict(data_class=B, data=data)

assert result == B(a_list=[A(x='test1', y=1), A(x='test2', y=2)])

类型钩子

如果您想将数据类字段的输入数据转换为给定类型的新值,可以使用Config.type_hooks参数。您必须传递以下映射:{Type: callable},其中callableCallable[[Any], Any]

@dataclass
class A:
    x: str


data = {
    'x': 'TEST',
}

result = from_dict(data_class=A, data=data, config=Config(type_hooks={str: str.lower}))

assert result == A(x='test')

如果数据类字段类型是Optional[T],您可以将Optional[T]或仅T作为type_hooks中的键传递。对于泛型集合也是如此,例如,当字段类型为List[T]时,您可以使用List[T]来转换整个集合或T来转换每个项。

转换

这是一个非常常见的情况,您希望仅通过调用您的类型并传入输入值来从输入数据创建字段类型的实例。当然,您可以使用type_hooks={T: T}来实现此目标,但cast=[T]是一个更简单、更易于表达的方法。它还适用于基类 - 如果T是类型S的基类,则所有类型为S的字段也将被“转换”。

from enum import Enum

class E(Enum):
    X = 'x'
    Y = 'y'
    Z = 'z'

@dataclass
class A:
    e: E


data = {
    'e': 'x',
}

result = from_dict(data_class=A, data=data, config=Config(cast=[E]))

# or

result = from_dict(data_class=A, data=data, config=Config(cast=[Enum]))

assert result == A(e=E.X)

向前引用

可以以{'name': Type}映射的形式将向前引用的定义传递给Config.forward_references。当评估每个字段的类型时,此字典作为globalns参数传递给typing.get_type_hints()

@dataclass
class X:
    y: "Y"

@dataclass
class Y:
    s: str

data = from_dict(X, {"y": {"s": "text"}}, Config(forward_references={"Y": Y}))
assert data == X(Y("text"))

类型检查

在很少的情况下,dacite内置类型检查器无法验证您的类型(例如,自定义泛型类)或您使用其他库覆盖了此类功能,并且您不想重复验证您的类型。在这种情况下,您可以使用Config(check_types=False)禁用类型检查。默认情况下启用类型检查。

T = TypeVar('T')


class X(Generic[T]):
    pass


@dataclass
class A:
    x: X[str]


x = X[str]()

assert from_dict(A, {'x': x}, config=Config(check_types=False)) == A(x=x)

严格模式

默认情况下,from_dict 会忽略输入数据中的额外键(不匹配数据类字段)。如果您想改变这种行为,请将 Config.strict 设置为 True。在遇到意外的键时,from_dict 将引发 UnexpectedDataError 异常。

严格联合匹配

Union 允许为给定的字段定义多个可能的类型。默认情况下,dacite 尝试为提供的数据找到第一个匹配的类型,并返回该类型的实例。这意味着还有可能在 Union 类型列表中存在其他匹配的类型。使用 strict_unions_match 只允许匹配单个类型,否则 dacite 将引发 StrictUnionMatchError

异常

每当出现错误时,from_dict 将引发适当的异常。其中有一些

  • WrongTypeError - 当输入值的类型与数据类字段的类型不匹配时引发
  • MissingValueError - 当您未为必填字段提供值时引发
  • UnionMatchError - 当提供的数据不匹配 Union 的任何类型时引发
  • ForwardReferenceError - 当在数据类中遇到未定义的前向引用时引发
  • UnexpectedDataError - 当启用 strict 模式且输入数据没有匹配的键时引发
  • StrictUnionMatchError - 当启用 strict_unions_match 模式且输入数据具有模糊的 Union 匹配时引发

开发

首先,如果您想提交您的拉取请求,非常感谢!我非常感激您的支持。

请记住,每个新功能、错误修复或改进都应该进行测试。100% 的代码覆盖率是必须的。

我们使用一些静态代码分析工具来提高代码质量(blackmypypylint)。请在提交 PR 之前确保您没有生成任何错误/警告。您可以在 .travis.yml 文件中找到当前配置。

最后但同样重要的是,如果您想引入新功能,请首先在 issue 中进行讨论。

如何开始

克隆 dacite 仓库

$ git clone git@github.com:konradhalas/dacite.git

以您喜欢的方式创建和激活虚拟环境

$ python3 -m venv dacite-env
$ source dacite-env/bin/activate

安装所有 dacite 依赖项

$ pip install -e .[dev]

要运行测试,只需执行

$ pytest

用例

在我们收到许多“原始”数据(Python 字典)作为系统输入的情况下有很多用例。HTTP 请求有效负载是一个非常常见的用例。在大多数 Web 框架中,我们以简单字典的形式接收请求数据。与其将此字典传递到您的“业务”代码,不如创建一些更“健壮”的代码。

以下是一个简单的 flask 应用 - 它有一个单个的 /products 端点。您可以使用此端点在系统中“创建”产品。我们的核心 create_product 函数期望数据类作为参数。多亏了 dacite,我们可以轻松地从 POST 请求有效负载构建这样的数据类。

from dataclasses import dataclass
from typing import List

from flask import Flask, request, Response

import dacite

app = Flask(__name__)


@dataclass
class ProductVariantData:
    code: str
    description: str = ''
    stock: int = 0


@dataclass
class ProductData:
    name: str
    price: float
    variants: List[ProductVariantData]


def create_product(product_data: ProductData) -> None:
    pass  # your business logic here


@app.route("/products", methods=['POST'])
def products():
    product_data = dacite.from_dict(
        data_class=ProductData,
        data=request.get_json(),
    )
    create_product(product_data=product_data)
    return Response(status=201)

如果我们想验证我们的数据(例如,检查 code 是否有 6 个字符)怎么办?这样的功能超出了 dacite 的范围,但我们可以轻松地将其与数据验证库之一结合使用。让我们尝试使用 marshmallow

首先,我们必须定义我们的数据验证模式

from marshmallow import Schema, fields, ValidationError


def validate_code(code):
    if len(code) != 6:
        raise ValidationError('Code must have 6 characters.')


class ProductVariantDataSchema(Schema):
    code = fields.Str(required=True, validate=validate_code)
    description = fields.Str(required=False)
    stock = fields.Int(required=False)


class ProductDataSchema(Schema):
    name = fields.Str(required=True)
    price = fields.Decimal(required=True)
    variants = fields.Nested(ProductVariantDataSchema(many=True))

然后在端点中使用它们

@app.route("/products", methods=['POST'])
def products():
    schema = ProductDataSchema()
    result, errors = schema.load(request.get_json())
    if errors:
        return Response(
            response=json.dumps(errors), 
            status=400, 
            mimetype='application/json',
        )
    product_data = dacite.from_dict(
        data_class=ProductData,
        data=result,
    )
    create_product(product_data=product_data)
    return Response(status=201)

仍然,dacite 帮助我们从“原始”字典创建具有验证数据的数据类。

更新日志

更新日志 中跟踪 dacite 的更新。

作者

Konrad Hałas 创建。

项目详细信息


下载文件

下载您平台对应的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。

源码分发

本版本没有提供源码分发文件。请参阅 生成分发存档的教程

构建分发

dacite1-1.6.99-py3-none-any.whl (12.8 kB 查看哈希值)

上传时间 Python 3

由以下支持