上面的示例中发生了一些有趣的事情,我们将看看它是如何在幕后引导的(包括使同一个类中可以具有相同名称的方法而无需任何真正的重载的魔力) ) 。
准备 Klaviyo API ViewSet 类大致分为三个步骤:
@api_revision 装饰器将所有 ViewSet 的所有方法的全局注册表填充到特定修订版,并按类名键入 。例如:
{"BooksViewSet": {"list": [Revision(...), Revision(...)],"create": [Revision(...)],},"FooViewSet": {"list": [Revision(...), Revision(...), Revision(...)],"create": [Revision(...)],"retrieve": [Revision(...), Revision(...)],"update": [Revision(...)],"partial_update": [Revision(...)],"destroy": [Revision(...)],},"BarViewSet": {...}}上面注册表中的 Revision 对象是简单的数据类:
@dataclassclass Revision:"""A single API method revision's information, defaults are set in the api_revision decorator"""revision_date: strfunc: Callableauto_deprecate: booldeprecation_date: strremoval_date: stringress_dto_cls: Type[API_DTO]egress_dto_cls: Type[API_DTO] = None这意味着每个方法修订版都有一个与其绑定的 DTO,并存储在该全局注册表中 。
2. BaseApiViewSet 是使用 ApiV3Metaclass 元类构建的 。元类读取此全局注册表,并将方法名称的映射附加到所有端点修订版,作为相应 ViewSet 上的类属性 。
元类大致如下所示:
class ApiV3Metaclass(type):def __new__(mcs, class_name, bases, attrs):# attributes to attach to the classattrs_to_build = dict()# collection of our api methods and revisions, we want to structure this data# to make incoming requests as fast as possible to route at runtime# { method_name -> [Revision(...), Revision(...), ...] }revision_list_by_viewset_method = defaultdict(list)# Create the revision methods on the class based on the revision fed into the# @api_revision decoratorfor (viewset_method_name,revisions,) in _funcs_and_revisions_by_class_and_method[class_name].items():revision_list_by_viewset_method[viewset_method_name] = sorted(revisions,key=lambda revision: RevisionDate(revision.revision_date),reverse=True,)attrs_to_build["revisions_by_method"] = revision_list_by_viewset_method # ...# More setup # ...return super(APIV3Metaclass, mcs).__new__(mcs, class_name, bases, attrs_to_build)每个视图集都有一个 revisions_by_method 属性,如下所示:
{"list": [Revision(...), Revision(...), Revision(...)],"create": [Revision(...)],"retrieve": [Revision(...), Revision(...)],"update": [Revision(...)],"partial_update": [Revision(...)],"destroy": [Revision(...)],}有一个有趣的 Python 解释器细节,它使装饰器与元类无缝工作,从而使此设置成为可能:
在Python中,类主体在使用确定的元类设置类之前执行 。这里有关于这个过程的更多细节,但对于我们的场景来说,这意味着装饰器首先执行(填充全局注册表),然后执行元类 __new__ 方法,该方法使用此全局注册表创建一个类属性,该属性存储修订 方法 。
修饰方法从未真正附加到类,而仅作为 Revision 对象中的引用存在 。这就是为什么可以有同名的方法!
3. 基础方法(来自BaseApiViewSet)根据版本头查找要调用的方法
BaseApiViewset 类包含所有 ViewSet 操作方法(列表、创建、检索等)的简单实现 。这个简单的部分实际上是将所有这些组合在一起的:
(回顾)路由器将请求分派到相应的 ViewSet 类 。由于装饰方法从未附加,因此存在的这些方法的唯一实现来自基本方法,该方法在此处被调用 。
基本方法解析请求标头以获取所请求端点的修订日期 。它从 ViewSet 类上的 revisions_by_method 查找中获取特定的 Revision 对象 。回想一下,此 Revision 对象保存对端点特定版本的 DTO 和函数引用 。
最后,序列化器将 JSON 构建到绑定到该修订版的 DTO 中,并将其传递给函数,执行该函数并将响应序列化回 JSON!
所有 API ViewSet 都继承自 BaseApiViewset 并使用此机制工作 。
序列化我们的 API 使用 cattrs 来完成与 JSON 的序列化/反序列化 。这是一个方便的 Python 库,可以帮助构建非结构化数据(如字典),反之亦然 。该库功能强大,提供多种可能的转换 。(尽管这听起来似乎没什么大不了的,但我认为将原始值转换为枚举的能力非常方便且非常简洁 。)
cattrs 与 attrs 集成得很好,这使得它成为我们 API 的一个简单选择 。我还喜欢使用 ExceptionGroups 在 cattrs 中进行异常处理:它在序列化器层中很有用,我们需要精确地(外部或内部)查明 DTO 未能创建的位置及其原因 。
推荐阅读
- 使用 SQL 的方式查询消息队列数据以及踩坑指南
- 你是否知道如何使用Python Matplotlib创建令人惊叹的数据可视化?
- Spring为什么建议构造器注入
- 传统机器学习算法在实际业务中的使用场景
- 防止误删除文件/目录:深入探索Linux命令chattr
- ChatGPT的使用教程
- 男士洗面奶怎么洗脸 使用男士洗面奶的方法
- 工业双氧水的作用和使用范围 工业双氧水的危害及注意事项
- 菲洛嘉清洁面膜的使用方法 清洁面膜的使用方法
- 精华液和精华乳的区别 精华液和水乳的正确使用顺序
