后端API设计【免费下载链接】django-ninja Fast, Async-ready, Openapi, type hints based framework for building APIs项目地址https://gitcode.com/gh_mirrors/dj/django-ninja点击查看免费下载本文基于 Django Ninja 官方 Enhancement Proposals增强提案文档 docs/docs/proposals/cbv.md 展开系统解读Class Based Operations基于类的操作这一设计提案的动机、方案与权衡。文中将提案的核心思想与当前仓库中 Router、Operation、参数解析与异步支持的源码实现进行对照帮助读者理解在什么场景下函数式 API 操作会出现重复样板代码提案如何通过装饰整个类 构造器统一初始化来化解以及该方案在异步环境下所面临的语言层面限制。读完本文读者将掌握这一提案的完整技术细节并能在实际工程中识别出适合类化重构的 API 结构。一、提案的定位这是一份设计蓝图而非已实现功能首先必须明确一个前提cbv.md是 Django Ninja 仓库docs/docs/proposals/目录下的一份正式提案文档文档开头使用了醒目的警告标注This is just a proposal and it isnot present in library code, but eventually this can be a part of Django Ninja.也就是说文中所描述的router.path(...)类装饰器语法当前并不存在于库代码中。对仓库源码进行检索可以印证这一点ninja/router.py中的Router类目前只提供了get、post、delete、patch、put、api_operation、add_api_operation、add_router等装饰器与注册方法并不存在path方法。因此本文将其定位为设计提案 源码现状对照来讲解读者若要在生产代码中使用类似能力需要在理解底层机制后自行封装或等待官方在后续版本中采纳该提案。提案的产生遵循了 Django Ninja 官方的 Enhancement Proposals 机制见 docs/docs/proposals/index.md通过 Pull Request 在docs/docs/proposals/下新增提案页或通过 issue 发起讨论。cbv.md正是该机制下的第一份提案其讨论主题是——当多个 API 操作共享大量初始化逻辑尤其是权限校验时能否用类来组织代码。二、Problem函数式操作中的重复样板代码提案从一个非常典型的真实场景出发一个 Todo 应用包含**项目Project与任务Task**两个模型每个项目拥有多个任务每个项目有一个所有者用户用户不能访问不属于自己的项目。对应 Django 模型结构如下原文代码Task中的project外键通过字符串引用Project需要定义在Project之后或使用字符串引用class Project(models.Model): title models.CharField(max_length100) owner models.ForeignKey(auth.User, on_deletemodels.CASCADE) class Task(models.Model): project models.ForeignKey(Project, on_deletemodels.CASCADE) title models.CharField(max_length100) completed models.BooleanField()现在要为它实现三个 API 操作列出某项目下的所有任务查看某个任务详情执行完成任务动作。同时每个操作都必须校验用户只能访问自己项目下的任务否则返回 404。用 Django Ninja 当前最标准的函数式写法代码长这样原文代码router Router() router.get(/project/{project_id}/tasks/, responseList[TaskOut]) def task_list(request): user_projects request.user.project_set project get_object_or_404(user_projects, idproject_id)) return project.task_set.all() router.get(/project/{project_id}/tasks/{task_id}/, responseTaskOut) def details(request, task_id: int): user_projects request.user.project_set project get_object_or_404(user_projects, idproject_id)) user_tasks project.task_set.all() return get_object_or_404(user_tasks, idtask_id) router.post(/project/{project_id}/tasks/{task_id}/complete, responseTaskOut) def complete(request, task_id: int): user_projects request.user.project_set project get_object_or_404(user_projects, idproject_id)) user_tasks project.task_set.all() task get_object_or_404(user_tasks, idtask_id) task.completed True task.save() return task提案敏锐地指出了其中的问题这三段代码里取出当前用户的项目集合 → 用project_id查找并校验归属 → 继续取任务集合这几行逻辑被反复复制user_projects request.user.project_set project get_object_or_404(user_projects, idproject_id))这种重复会带来一系列现实困扰样板代码膨胀每新增一个针对项目下任务的操作都要再复制一遍校验代码即使提取成函数也只是少了 3 行代码依然被污染原文原话You can extract it to a function, but it will just make it 3 lines smaller, and it will still be pretty polluted出错概率升高复制粘贴时极易遗漏或改错参数原文代码中的project_id在task_list里实际未在函数签名中声明就是一个值得注意的笔误业务焦点被稀释真实的业务逻辑查任务、改任务状态淹没在权限校验样板中可读性下降。三、Solution用path装饰整个类让构造器承担公共初始化提案给出的核心创意是把类本身当作一个 API 路径单元来装饰。不再是一个函数对应一个 operation而是一个类对应一段路径前缀类的方法对应具体的 HTTP 操作。3.1 提案语法全貌以下是提案中的完整示例原文代码request与project_id作为__init__的参数传入from ninja import Router router Router() router.path(/project/{project_id}/tasks) class Tasks: def __init__(self, request, project_idint): user_projects request.user.project_set self.project get_object_or_404(user_projects, idproject_id)) self.tasks self.project.task_set.all() router.get(/, responseList[TaskOut]) def task_list(self, request): return self.tasks router.get(/{task_id}/, responseTaskOut) def details(self, request, task_id: int): return get_object_or_404(self.tasks, idtask_id) router.post(/{task_id}/complete, responseTaskOut) def complete(self, request, task_id: int): task get_object_or_404(self.tasks, idtask_id) task.completed True task.save() return task这一设计的精妙之处在于路径的拼接与状态的共享类级装饰器router.path(/project/{project_id}/tasks)声明了路径前缀并把{project_id}这样的路径参数暴露给__init__类内每个方法的装饰器路径/、/{task_id}/、/{task_id}/complete会自动与类级前缀拼接形成完整的 operation 路径三个操作等价于函数式版本中的三个路由但共享同一个self.tasks状态。3.2 构造器承担公共初始化提案把方案的核心高亮放在__init__上router.path(/project/{project_id}/tasks) class Tasks: def __init__(self, request, project_idint): user_projects request.user.project_set self.project get_object_or_404(user_projects, idproject_id)) self.tasks self.project.task_set.all()所有公共的初始化与权限校验逻辑都收拢进构造器request由框架注入业务代码无需再手工传参project_id由路径参数注入框架负责从 URL 中解析并做类型转换校验失败项目不存在或不属于当前用户时get_object_or_404直接抛出 404构造过程即中止后续方法根本不会执行——这相当于把前置守卫提升到了类实例化的层面校验通过后self.project、self.tasks成为实例属性各个业务方法只需直接消费它们。提案指出这样重构后主业务操作只专注于任务本身通过self.tasks属性暴露权限与初始化逻辑完全从各个方法体中剥离。此外提案还明确说明api实例与router实例都应当支持类路径You can use bothapiandrouterinstances to support class paths即api.path(...)与router.path(...)语义一致。3.3 提案方案的收益对比维度函数式写法类式提案写法权限校验代码每个函数各写一份仅__init__一处业务方法参数每个函数独立声明project_id、task_id公共路径参数进__init__方法只声明自身所需参数共享状态每次重复查询self.tasks缓存复用新增操作成本复制校验样板新增一个方法即可四、源码对照提案语法与 Django Ninja 现有机制的衔接点虽然router.path尚未实现但提案所依赖的底层能力在仓库中均已存在这保证了方案在架构上的可行性。理解这些衔接点也有助于读者在现有框架内模拟类似模式。4.1 路径前缀拼接Router 已有类似机制提案要求类级路径前缀 方法级路径自动拼接。仓库中Router.add_router(prefix, router, ...)见 ninja/router.py已经实现了前缀 子路由的拼接语义将子 Router 挂载到父 Router 时prefix会作为路径前缀。Router.urls_paths与build_routers共同完成这种层级化路径的组合。因此路径前缀合并在框架内并非全新概念router.path可以视作把同一层级的前缀合并能力延伸到类内部。4.2 操作注册管线类方法可直接复用现有管线提案中每个方法仍然使用router.get(...)、router.post(...)装饰这与当前库完全一致。在现有实现中这些装饰器最终都汇聚到Router.api_operation→Router.add_api_operation见 ninja/router.py后者为每个路径维护一个PathView并按 HTTP 方法追加Operation。换句话说只要在类实例化后把绑定方法bound method当作view_func传入现有的add_api_operation管线即可复用全部现有能力——包括response响应模型、auth、throttle、tags、summary、openapi_extra等全部装饰器参数。4.3 参数解析__init__需要新的注入通道当前Operation的参数解析基于函数签名Operation.__init__中通过ViewSignature(path, view_func)见 ninja/operation.py解析view_func的签名并生成参数模型请求到达时由Operation._get_values统一解析出路径参数、查询参数、请求体等见 ninja/operation.py 的执行链_run_checks→_get_values→view_func(request, **values)。提案的类式写法中__init__也要接收request和路径参数如project_id这意味着框架需要先解析出__init__的参数值、实例化类、再调用业务方法。从现有结构看可以推断有两种可行的落地路径让__init__走与view_func相同的签名解析与参数注入逻辑即把__init__视为一个前置 operation或者将类实例化 方法调用包装成一个合成函数交给现有Operation管线统一处理。两种路径都不需要改动参数解析的核心机制属于对现有管线的扩展而非重写。此外提案中def __init__(self, request, project_idint)这种用默认值int表达类型的写法而非project_id: int注解与 Django Ninja 基于类型注解typing annotations的参数解析体系并不兼容——可以推断若该提案落地__init__的路径参数应当改为标准注解形式project_id: int才能被ViewSignature正确识别。4.4 认证/权限与request.user的协作不变提案中的权限校验依赖request.user.project_set这与 Django Ninja 现有的认证体系完全兼容Operation._run_authentication在调用业务函数之前执行认证回调并把认证结果写入request.auth见 ninja/operation.pyrequest.user则来自 Django 自身的中间件与django.contrib.auth。类式方案中认证逻辑发生在__init__之前由框架统一处理__init__内只做基于已认证用户的授权校验分层清晰与现有架构不冲突。五、Issueasync与__init__的语言层面矛盾提案在最后坦诚地列出了一个关键设计难题原文如下The__init__method:def __init__(self, request, project_idint):— Python doesnt support theasynckeyword for__init__, so to support async operations we need some other method for initialization, but__init__sounds the most logical.这是提案中最具讨论价值的部分问题本质__init__是同步的构造方法Python 语言规范不允许async def __init__。如果类的构造过程中包含异步操作例如在异步环境下await查询数据库、调用外部 API就无法把初始化逻辑放进__init__两难处境__init__在语义上是最自然的初始化位置提案原文__init__sounds the most logical但异步场景又需要一种替代的初始化通道潜在替代方向从提案上下文可以推断几种候选方案——显式的异步工厂方法如async def create(...)类方法、独立的async def init(...)钩子由框架在调用业务方法前自动await、或把异步初始化放在第一个业务方法内部惰性完成。提案没有给出定论而是将选择权交给社区讨论。5.1 结合仓库现状Django Ninja 的异步能力边界Django Ninja 对异步操作的支持已经相当成熟框架通过is_async(view_func)检测函数是否为协程并据此选择Operation或AsyncOperation见 ninja/operation.py 中PathView.add_operation的分支逻辑AsyncOperation.run使用async def执行完整的_run_checks→_get_values→view_func流程见 ninja/operation.py。异步相关的最佳实践可参考 docs/docs/guides/async-support.md。对照这一现状类式提案的异步短板就更加突出函数式异步操作只需在函数前加async关键字即可见 docs/docs/guides/async-support.md 中async def say_after(...)的示例而类式方案中即使业务方法可以写成async def构造阶段的异步化仍受__init__限制——这正是提案公开征求社区意见的核心点。5.2 一个值得注意的细节同步操作内做 ORM 查询没有障碍值得注意的是提案示例中的__init__执行的是同步 ORM 查询get_object_or_404、.all()这在 Django Ninja 现有的同步Operation.run执行链中不存在任何障碍_get_values解析参数后同步调用view_func(request, **values)类实例化同样同步发生。因此对于纯同步的 API类式提案的实现路径是清晰可行的真正的设计争议集中在异步场景的初始化通道上。六、结语提案的价值与落地展望cbv.md作为 Django Ninja Enhancement Proposals 体系下的首份提案其价值至少体现在三个层面问题识别精准它指出的跨操作重复初始化/授权样板在真实工程中极其常见尤其是嵌套资源 属主校验这类 CRUD 场景方案简洁优雅仅通过装饰类 构造器注入 路径拼接三个概念就实现了对函数式样板代码的大幅压缩且与现有 Router/Operation 管线高度兼容权衡讨论坦诚它没有回避async __init__这一硬约束而是将其明确列为待社区决策的开放问题体现了提案机制的严谨性。对于希望立即在现有 Django Ninja 项目中缓解同类问题的读者在提案落地之前可以结合仓库现有能力采取替代策略例如用 docs/docs/guides/routers.md 中的 Router 层级组织嵌套资源将公共授权逻辑提取为可复用的认证类参考 docs/docs/guides/authentication.md或利用装饰器组合ninja/decorators.py中的decorate_view封装通用前置逻辑。这些手段虽然不及类装饰来得彻底但同样是消除重复样板的有效路径且完全基于当前库的稳定 API。最后回到提案本身它是一份待讨论的设计蓝图而非可直接使用的功能。感兴趣的读者可以在仓库的 docs/docs/proposals/index.md 查看提案机制的说明并关注cbv.md的后续演进——如果该提案被采纳Django Ninja 将同时具备函数式与类式两种操作组织方式覆盖从轻量单函数到重度嵌套资源的不同工程需求。赞分享后端API设计【免费下载链接】django-ninja Fast, Async-ready, Openapi, type hints based framework for building APIs项目地址https://gitcode.com/gh_mirrors/dj/django-ninja点击查看免费下载相关推荐解锁kiosk模式chromium_os-raspberry_pi专属功能配置与应用场景实战解锁kiosk模式chromium_os raspberry_pi专属功能配置与应用场景实战 chromium_os raspberry_pi是一款专为树莓派ZXing代码复用度量识别并消除重复代码ZXing代码复用度量识别并消除重复代码 引言代码复用的重要性 在软件开发过程中代码复用是提高效率、降低维护成本的关键实践。ZXingZebra Cro图像处理计算机视觉Czkawka 磁盘清理指南14 个免费工具快速找回被重复文件占用的空间Czkawka 磁盘清理指南14 个免费工具快速找回被重复文件占用的空间 打开磁盘属性1TB 的硬盘只剩 4GB却不知道空间都被谁吃掉了。Czkawka桌面应用上一篇Calibre 格式转换快速指南5分钟搞定电子书设备兼容难题下一篇如何自定义你的MacBook Pro触控栏MTMR配置文件完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
