
后端Web框架【免费下载链接】eveREST API framework designed for human beings项目地址https://gitcode.com/gh_mirrors/ev/eve点击查看免费下载本文基于 Eve 官方文档仓库中的 snippet 示例 docs/snippets/list_of_items.rst讲解如何用 Eve 构建一个列表 嵌套子条目的双层 REST API客户端可以用一次GET /lists/id拿到包含全部条目的完整列表也可以用POST /lists/id/items直接追加条目而不是 PATCH 整个列表还可以单独对某个条目做GET/PUT/PATCH/DELETE。核心实现思路是利用 Eve 的数据库事件钩子on_fetched_item_lists、on_deleted_item_lists在父文档返回前注入嵌套的子文档、在父文档删除时级联清理子条目。读完本文你将掌握双层资源的路由与 schema 设计、data_relation外键约束、事件钩子的注册时机与回调签名以及整套用 curl 可复现的验证流程。一、需求拆解为什么要双层 CRUD而不是内嵌数组在构建购物清单、订单明细、分组任务这类数据模型时通常会遇到两种设计选择单资源 内嵌数组把items直接作为lists文档里的一个数组字段。好处是一次 GET 就能拿到全部数据坏处是难以单独对某个子条目做 CRUD——对子条目的增删改都要 PATCH 整个父文档客户端要么先读后写竞态风险要么得自己维护子条目的局部更新逻辑。双层资源 事件钩子lists和items是两个独立资源items通过外键list_id关联父列表并使用嵌套 URLlists/list_id/items暴露。客户端既可以GET /lists/id一次拿到含所有条目的完整列表也可以POST /lists/id/items单独追加条目还能对单个条目做完整的GET/PUT/PATCH/DELETE。本示例选择方案 2。原文明确指出其代价是每次读取父列表都会产生两次数据库查询一次查 lists、一次查 items换来的是子条目拥有完全独立的 CRUD 能力。这是典型的读放大换写灵活的取舍在子条目数量不大、读频率可控的场景下非常实用。二、核心思路用数据库事件钩子把嵌套伪装成内嵌Eve 的Eve类多重继承了Flask和events库的Events类见 eve/flaskapp.py 中class Eve(Flask, Events)的定义因此框架会在请求处理的固定节点抛出可订阅的事件任何注册到这些事件上的回调都会被执行。这正是本示例的支点on_fetched_item_resource在单个文档item 级被查询出来、尚未返回给客户端之前触发on_deleted_item_resource在单个文档被真实删除之后触发。本示例注册了两个钩子after_fetching_lists挂在app.on_fetched_item_lists上当客户端GET /lists/id命中lists资源的 item 端点时钩子拿到响应文档response从response[_id]取出父列表 id构造过滤条件{list_id: ObjectId(list_id)}再用底层 Mongo 驱动mongo.db.items.find(f)查出所有子条目直接塞进response[items]。这样客户端看到的响应就像一个包含内嵌数组的文档。after_deleting_lists挂在app.on_deleted_item_lists上当客户端DELETE /lists/id删除父列表后钩子以同样的list_id过滤条件执行mongo.db.items.delete_many(f)把该列表下的所有子条目一并清除避免产生孤儿数据。两点值得注意response回调在getitem()内部于 eve/methods/get.py 处触发getattr(app, on_fetched_item)(resource, response)与on_fetched_item_resource依次调用回调中直接修改response字典即可改变最终返回给客户端的 JSON——这正是注入子文档的机制依据。after_deleting_lists的入参item是被删除文档的原始内容original它由 eve/methods/delete.py 在app.data.remove()执行真实删除之后回调时传入。由于回调发生在删除之后此时用item[_id]去delete_many是安全的父文档已不存在只剩子条目需要清理。三、main.py注册钩子并启动应用文档给出的main.py全量代码如下节选自 docs/snippets/list_of_items.rstfrom eve import Eve from bson.objectid import ObjectId app Eve() mongo app.data.driver def after_fetching_lists(response): list_id response[_id] f {list_id: ObjectId(list_id)} response[items] list(mongo.db.items.find(f)) def after_deleting_lists(item): list_id item[_id] f {list_id: ObjectId(list_id)} mongo.db.items.delete_many(f) app.on_fetched_item_lists after_fetching_lists app.on_deleted_item_lists after_deleting_lists app.run()逐行解读app Eve()以默认配置实例化应用默认会读取当前目录下的settings.py若文件名不同可用Eve(settingsmy_settings.py)指定。mongo app.data.driverapp.data是当前数据层默认是eve.io.mongo.Mongodriver是底层 PyMongo 驱动对象。因此mongo.db就是 MongoDB 数据库句柄mongo.db.items即items资源对应的集合。这与 eve/io/mongo/mongo.py 中数据驱动对集合的直接访问方式一致。两个钩子函数通过运算符注册。Events库允许多个回调同时挂在一个事件上即追加订阅。注意命名规律on_fetched_item_listson_fetched_item_ 资源名lists同理on_deleted_item_lists。若资源名不同如orders钩子名也相应变为on_fetched_item_orders。app.run()以 Flask 开发服务器启动默认监听127.0.0.1:5000。四、settings.py双层资源的路由与 schema 设计配置文件节选自 docs/snippets/list_of_items.rst包含连接配置、可用方法与DOMAIN定义三部分import os DEBUG True MONGO_HOST os.environ.get(MONGO_HOST, localhost) MONGO_PORT os.environ.get(MONGO_PORT, 27017) MONGO_USERNAME os.environ.get(MONGO_USERNAME, user) MONGO_PASSWORD os.environ.get(MONGO_PASSWORD, user) MONGO_DBNAME os.environ.get(MONGO_DBNAME, listtest) RESOURCE_METHODS [GET, POST, DELETE] ITEM_METHODS [GET, PUT, PATCH, DELETE] DOMAIN { lists: { schema: { title: { type: string } } }, items: { url: lists/regex([a-f0-9]{24}):list_id/items, schema: { list_id: { type: objectid, data_relation: { resource: lists, field: _id } }, name: { type: string, required: True } } } }各部分作用环境变量读取MONGO_*均通过os.environ.get()读取并提供默认值便于本地起服务默认listtest库、user/user账号。DEBUG True开启调试模式方便观察请求日志。RESOURCE_METHODSlists和items两个集合级端点统一允许GET查列表/列表分页、POST追加文档、DELETE整批删除。注意此配置是全局的作用于DOMAIN中所有资源。ITEM_METHODS单文档端点统一允许GET、PUT整体替换、PATCH部分更新、DELETE。lists资源schema 只有title字符串一个业务字段其余_id、_created、_updated、_etag等元数据字段由 Eve 自动维护。items资源的url这是整个示例的关键。它把items资源的端点从默认的/items改写为嵌套路径lists/regex([a-f0-9]{24}):list_id/itemsregex(...)是 Eve 内建的 Flask 路由转换器见 eve/flaskapp.py 的RegexConverter允许用正则约束路径参数[a-f0-9]{24}恰好匹配 MongoDB ObjectId 的 24 位十六进制表示参数名list_id会作为sub_resource_lookup传递进方法处理器见 eve/endpoints.py 的item_endpoint(**lookup)以及 eve/io/mongo/mongo.py 的find(resource, req, sub_resource_lookup)用于在查询时自动加上list_id过滤条件。于是客户端访问GET /lists/58960f83a663e2e6746dfa6a/items就能只拿到该列表下的条目访问单个条目则是GET /lists/list_id/items/item_id。items资源的list_id字段类型为objectid并声明了data_relationresource: lists声明外键指向lists资源field: _id声明关联字段是父列表的_id。该声明会驱动两层行为其一Eve 的 Mongo 校验器在写入时会校验list_id必须真实存在于lists集合见 eve/io/mongo/validation.py 的_validate_data_relation其二如果启用embeddableEve 还可以自动把关联文档内嵌进响应但本示例没有使用该特性而是用事件钩子手动注入——这是两种可选的嵌套实现路径。name字段required: True即创建条目时name为必填。五、端到端验证用 curl 复现完整流程文档给出了可直接复现的 curl 演示序列URL 与响应节选自 docs/snippets/list_of_items.rst。运行前请确认 MongoDB 已启动、settings.py中的账号密码与本地库一致然后执行python main.py。5.1 创建父列表$ curl -i -X POST http://127.0.0.1:5000/lists -d titleMy List HTTP/1.0 201 CREATED { _id: 58960f83a663e2e6746dfa6a, ... }服务端返回201 CREATED并给出新列表的_id示例为58960f83a663e2e6746dfa6a。这个 id 将作为后续所有请求的路径参数。5.2 追加两个子条目$ curl -i -X POST http://127.0.0.1:5000/lists/58960f83a663e2e6746dfa6a/items -d nameAlice HTTP/1.0 201 CREATED $ curl -i -X POST http://127.0.0.1:5000/lists/58960f83a663e2e6746dfa6a/items -d nameBob HTTP/1.0 201 CREATED两次 POST 均返回201 CREATED。注意这里的 URL 正是上一步settings.py中url规则生成的嵌套端点list_id段匹配[a-f0-9]{24}正则请求会命中items资源的集合端点且list_id被自动注入查询条件因此即便不显式传list_id字段子条目也会被正确归属到该父列表下。5.3 一次 GET 拿到含全部条目的完整列表$ curl -i -X GET http://127.0.0.1:5000/lists/58960f83a663e2e6746dfa6a HTTP/1.0 200 OK { _created: Sat, 04 Feb 2017 17:29:39 GMT, _etag: 01799f6be25a044ab95cfeb2dc0f834d11b796d8, _id: 58960f83a663e2e6746dfa6a, _updated: Sat, 04 Feb 2017 17:29:39 GMT, items: [ { _created: Sat, 04 Feb 2017 17:30:06 GMT, _etag: 72ad9248ad5bf45c7bfe3e03a1b9bc384d94572f, _id: 58960f9ea663e2e6746dfa6b, _updated: Sat, 04 Feb 2017 17:30:06 GMT, list_id: 58960f83a663e2e6746dfa6a, name: Alice, quantity: 1 }, { _created: Sat, 04 Feb 2017 17:30:13 GMT, _etag: 447f51b057fb5e0a70472e96ff883c64b5e2e308, _id: 58960fa5a663e2e6746dfa6c, _updated: Sat, 04 Feb 2017 17:30:13 GMT, list_id: 58960f83a663e2e6746dfa6a, name: Bob, quantity: 1 } ], title: My List }这就是事件钩子发挥作用的时刻after_fetching_lists把items数组注入进了lists文档。可以看到父列表自带_id、_etag、_created、_updated等 Eve 自动元数据items数组中的每个子条目都包含自己的_id、_etag与list_id外键响应中的_etag是父列表在数据库中的真实 etag_updated也保持数据库原值——因为钩子修改发生在 eve/methods/get.py 回调点之后而 etag/last_modified 在数据库查询阶段就已计算eve/methods/get.py 的注释明确说明即使回调修改了文档last_modified和etag也不会更新它们始终反映数据库状态。这意味着客户端缓存校验If-Match/If-None-Match仍以父文档本身为准注入的子文档不会干扰缓存语义。5.4 删除单个子条目条目级 DELETE$ curl -i -X DELETE http://127.0.0.1:5000/lists/58960f83a663e2e6746dfa6a/items/58960f9ea663e2e6746dfa6b -H If-Match: 72ad9248ad5bf45c7bfe3e03a1b9bc384d94572f HTTP/1.0 204 NO CONTENTDELETE返回204 NO CONTENT。请求头中的If-Match: etag是 Eve 默认开启的并发控制IF_MATCH要求客户端必须先获取条目当前的_etag如 5.3 响应中 Alice 条目的72ad9248ad5bf45c7bfe3e03a1b9bc384d94572f带上去删除若 etag 与服务端不一致Eve 会返回412 Precondition Failed。这是所有条目级写操作PUT/PATCH/DELETE都要遵循的规范。5.5 删除后再读确认级联清理$ curl -i -X GET http://127.0.0.1:5000/lists/58960f83a663e2e6746dfa6a HTTP/1.0 200 OK { _created: Sat, 04 Feb 2017 17:29:39 GMT, _etag: 01799f6be25a044ab95cfeb2dc0f834d11b796d8, _id: 58960f83a663e2e6746dfa6a, _updated: Sat, 04 Feb 2017 17:29:39 GMT, items: [ { _created: Sat, 04 Feb 2017 17:30:13 GMT, _etag: 447f51b057fb5e0a70472e96ff883c64b5e2e308, _id: 58960fa5a663e2e6746dfa6c, _updated: Sat, 04 Feb 2017 17:30:13 GMT, list_id: 58960f83a663e2e6746dfa6a, name: Bob, quantity: 1 } ], title: My List }Alice 的条目已被删除items数组中只剩 Bob——条目级 DELETE 独立生效。同理若此时执行DELETE /lists/58960f83a663e2e6746dfa6a删除父列表after_deleting_lists钩子会触发delete_many把剩余的 Bob 条目一并清空避免孤儿数据残留。六、机制原理与工程要点6.1 事件钩子的完整时序以GET /lists/id为例一次请求的完整调用链为请求命中lists资源的 item 端点item_endpoint(**lookup)按 HTTP 方法分发eve/endpoints.pygetitem()通过app.data.find_one()从 MongoDB 取出父文档eve/methods/get.py构建响应文档后触发on_fetched_item与on_fetched_item_lists钩子eve/methods/get.pyafter_fetching_lists执行第二次查询items.find把结果注入response[items]响应渲染JSON/XML并返回客户端。以DELETE /lists/id为例deleteitem()校验If-Match、取出原始文档触发on_delete_item/on_delete_item_lists删除前可做权限或业务校验app.data.remove()真实删除父文档eve/methods/delete.py触发on_deleted_item/on_deleted_item_listseve/methods/delete.pyafter_deleting_lists执行delete_many清理子条目。注意两个回调的语义差异on_fetched_*的入参是响应字典可修改以改变输出on_deleted_*的入参是已删除的原始文档此时不能再写回只能做后续清理或日志。若需要删除前拦截应使用on_delete_item_lists而不是on_deleted_item_lists。6.2 嵌套 URL 与 sub-resource 查找items资源之所以能通过lists/list_id/items访问是因为Eve 在flaskapp.py注册路由时使用RegexConverter解析url中的regex(...)片段把list_id捕获为路径参数捕获的参数作为lookup传入collections_endpoint/item_endpointeve/endpoints.py最终在数据驱动层变成sub_resource_lookup自动拼入查询eve/io/mongo/mongo.py 的参数说明因此GET /lists/id/items天然只返回该列表下的条目无需在查询里手写where条件。这正是外层 URL 结构 内层外键双层模型能同时支持父级聚合读与子级独立 CRUD 的底层原因。6.3data_relation引用完整性由谁保证items.list_id的data_relation声明resource: lists、field: _id在写入时由 Mongo 校验器强制执行_validate_data_relation会在写入前检查目标lists文档是否存在不存在则校验失败eve/io/mongo/validation.py。这保证了不会产生指向不存在父列表的子条目。需要特别说明的是MongoDB 本身没有外键约束这里的引用完整性完全由应用层Eve 校验器保证而删除父列表时清理子条目则不在data_relation能力范围内必须像本示例一样通过on_deleted_item_lists钩子手工实现级联删除。这两者合起来才是这套双层模型完整的一致性保障。6.4 取舍与适用边界两次查询每次读取父列表都要多一次items.find。原文明确指出这是本方案的代价。当条目量很大时可考虑在钩子中使用$in批量查询或改用内嵌数组模型当需要分页子条目时也可让客户端直接访问GET /lists/id/items?page...而不是依赖注入数组。缓存语义注入的items不影响父文档的_etag/_updated因此子条目变化不会使父列表缓存失效。若业务需要子条目变化即父列表缓存失效需自行在on_fetched_item_lists中重算或采用其他缓存策略。批量端点RESOURCE_METHODS允许了对lists和items的集合级DELETE整批删除使用时务必谨慎可结合认证授权auth限制该能力。七、参考与延伸本文对应的完整示例源码与 curl 演示见 docs/snippets/list_of_items.rst同一 snippet 档案还包含 docs/snippets/hooks_blueprints.rst钩子与蓝图组合与 docs/snippets/template.rstsnippet 编写模板。事件钩子的完整清单与使用规范见 docs/extensions.rstRESOURCE_METHODS/ITEM_METHODS/DOMAIN等配置项的全量说明见 docs/config.rst。若想深入源码事件钩子的触发点集中在 eve/methods/get.py 与 eve/methods/delete.py数据驱动层的查询/删除接口见 eve/io/mongo/mongo.py 与 eve/io/mongo/mongo.pydata_relation校验实现见 eve/io/mongo/validation.py。运行时配套测试可见 tests/methods/get.py其中data_relation相关用例覆盖了外键关联的读写行为与 tests/methods/delete.py可作为理解关联语义的补充参考。赞分享后端Web框架【免费下载链接】eveREST API framework designed for human beings项目地址https://gitcode.com/gh_mirrors/ev/eve点击查看免费下载相关推荐Eve Snippet 归档实战用事件钩子实现 Blueprint 扩展与列表级/条目级 CRUDEve Snippet 归档实战用事件钩子实现 Blueprint 扩展与列表级/条目级 CRUD 本文以 Eve 官方文档中的 Snippet 归档 do后端Web框架Karakeep Lists 完全指南手动列表、智能列表与协作分享的底层实现Karakeep Lists 完全指南手动列表、智能列表与协作分享的底层实现 Lists列表是 Karakeep 的核心组织层任意一条收藏链接、笔记或后端前端移动开发AI 应用知识管理全文检索MCP 服务cuDF 列表过滤Lists FilteringAPI 完全指南retention/deletion mask 与列表内去重实现剖析cuDF 列表过滤Lists FilteringAPI 完全指南retention/deletion mask 与列表内去重实现剖析 导读 本文围绕 cu数据分析数据工程机器学习上一篇抖音无水印下载工具 douyin-downloader3 条命令起步单条到整站一篇查完下一篇GitHub加速插件终极指南如何让国内访问GitHub速度提升500%创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考