“它在运行时不执行任何动态操作,因此运行时开销为零 。这仍然是你的Class 。” — attrs 文档
Klaviyo API DTO一般来说,在合理的情况下包装第三方库被认为是良好的做法 。首先,它有助于统一使用,例如具有许多选项的库的所需设置和默认设置 。其次,它创建了一个应用普遍变革的中心点 。第三,它提供了抽象库细节的机会,从而提供了将其替换为替代方案的灵活性,在这种情况下,包装器充当适配器 。出于所有这些原因,我们将 attrs 包装在为开发人员提供的工具中 。
在揭开该工具之前,让我们看一个简单的示例 。想象一下图书馆中有一个用于图书的简单 API 。此 API 的用户希望使用搜索参数来查询图书 。以下是我们的一位开发人员如何编写 API 的查询请求 DTO:
from App.views.apis.v3.dtos import api_dto, fieldfrom app.views.apis.v3.validation import common_validators@api_dto(ApiResourceEnum.BOOK, enable_boolean_filters=True)class BookQueryDTO:id: str | None = Nonetitle: str | None = field(default=None,external_desc="Title of the book you are querying for",example="Harry Potter and The Sorcerer's Stone",filter_operators={FilterOperators.CONTAINS, FilterOperators.EQUALS},validator=common_validators.max_len(100)sortable=True)author_id: str | None = field(default=None,filter_operators={FilterOperators.EQUALS},)page_cursor: str | None = Nonereturn_fields: list[str] | None = Nonesort: str | None = None
上面的示例将我们将在接下来的部分中讨论的几个重要部分结合在一起:
@api_dto 装饰器
attrs 字段包装器
用于请求验证的 common_validators 模块
我们的 @api_dto 装饰器:包装 attrs @define我们的 @api_dto 装饰器是用如下代码实现的:
from attrs import define, resolve_typesdef api_dto(resource: ApiResource,enable_boolean_filters: bool = False,non_dto_sort_fields: list | None = None,min_max_page_size: tuple[int | None, int | None] | None = None,) -> Callable:# ...# Arg validation# ...def inner(py_dto_cls: type) -> ApiDtoClass:generated_attr_dto = resolve_types(define(frozen=True, kw_only=True, auto_attribs=True)(py_dto_cls))setattr(generated_attr_dto, "__api_dto__", True)setattr(generated_attr_dto, "__resource__", resource)setattr(generated_attr_dto, "__boolean_filters_enabled__", enable_boolean_filters)if non_dto_sort_fields:setattr(generated_attr_dto, "__non_dto_sort_fields__", non_dto_sort_fields)if min_max_page_size:setattr(generated_attr_dto, "__min_max_page_size__", min_max_page_size)# ...# More Validation to ensure proper setup# ...return generated_attr_dtoreturn inner
它将 attrs 定义装饰器应用到传入的类,并具有预定的配置:
freeze=True 这些 DTO 应被冻结,以在 API 请求中的整个生命周期中强制执行不变性 。这也使得对象可散列,这有利于缓存请求和响应 。
kw_only=True 由于这些 DTO 可能具有多个属性,因此为了清楚起见,必须仅使用关键字参数来实例化这些属性 。
auto_attribs=True 这是 attrs 的一个很好的功能,它避免了将每个属性分配给字段的需要 。它还强制执行类型注释 。
这里一个更重要的细节是,define 装饰器默认生成一个开槽类 (slots=True),因此这些 DTO 的内存占用较小,这是有助于扩展的又一个因素 。
尽管 attrs 在定义装饰器中还有其他几个参数,但到目前为止我们的 API DTO 还不需要它们,而且我们的包装器使我们的内部开发人员不必考虑它们 。
最后,我们在这个修饰类上解析_types(),以允许前向引用的字符串类型提示 。这可确保定义每个属性的类型并准备好用于序列化/反序列化 。
您可能已经注意到,生成此类对象后,接下来的几行会在类(而不是实例)上设置一些属性值:
__resource__ 属性引用 ApiResource 对象 。该对象存储此 DTO 建模的资源的类型等 。然后由序列化和文档工具使用 。每个域都有一个枚举来保存其所有 ApiResource 对象 。然后在装饰器上提供枚举,例如 ApiResourceEnum.BOOK 。
__api_dto__ 是一个标志,指示此类是使用此装饰器生成的 。这充当在 DTO 注册表期间进行验证的水印,以确保所有 API DTO 都是从此装饰器生成的 。
__boolean_filters_enabled__ 属性是一个开关,允许使用 AND / OR / NOT 布尔运算符过滤 DTO 中的字段 。
__non_dto_sort_fields__ 和 __min_max_page_size__ 帮助解析和处理此 DTO 的请求查询参数 。
我们的 API DTO 字段:包装 attrs 字段attrs 允许将元数据附加到属性,事实证明,这非常漂亮! 我们用它来存储 API 不同部分使用的属性信息:文档、过滤、编辑等 。
我们没有依赖开发人员在这个字典中自由设置值,而是在 attrs 字段函数周围添加了一个简单的包装器 。该包装器提供了一个一致的接口,用于设置其他关键字参数,如filter_operators、sortable、external_desc 等(见下文) 。这是代表我们的字段包装器的片段:
推荐阅读
- 使用 SQL 的方式查询消息队列数据以及踩坑指南
- 你是否知道如何使用Python Matplotlib创建令人惊叹的数据可视化?
- Spring为什么建议构造器注入
- 传统机器学习算法在实际业务中的使用场景
- 防止误删除文件/目录:深入探索Linux命令chattr
- ChatGPT的使用教程
- 男士洗面奶怎么洗脸 使用男士洗面奶的方法
- 工业双氧水的作用和使用范围 工业双氧水的危害及注意事项
- 菲洛嘉清洁面膜的使用方法 清洁面膜的使用方法
- 精华液和精华乳的区别 精华液和水乳的正确使用顺序