完全指南:基于 python-docs-samples 的创建、存储与键管理实战)
示例工程【免费下载链接】python-docs-samplesCode samples used on cloud.google.com项目地址https://gitcode.com/GitHub_Trending/py/python-docs-samples点击查看免费下载导读本指南围绕 python-docs-samples 仓库中appengine/standard/ndb/entities目录下的官方代码片段展开系统讲解 Google App Engine 标准环境下 Datastore NDB API 的实体Entity操作从模型定义、实体创建的三种方式、类型检查到保存/读取/更新/删除、命名键与自动生成 ID、实体组父子层级、Expando 动态模型、生命周期钩子函数以及 ID 预分配等完整主题。读完本文你将掌握 NDB 实体的全生命周期操作并能结合 snippets.py 与 snippets_test.py 中的真实代码与测试用例在实际 App Engine 项目中正确使用 NDB。目录结构与运行前提本示例位于仓库 appengine/standard/ndb/entities/ 目录包含三个核心文件README.md官方说明注明这些片段分别对应官方文档中创建实体创建实体模型用户对象创建实体键等章节snippets.py全部可运行代码片段约 290 行snippets_test.py基于 pytest 与 App Enginetestbed的完整测试套件。运行前提以仓库实际配置为准NDB 属于 App Engine 标准环境 Python 2.7 时代的 SDK 扩展代码使用from google.appengine.ext import ndb导入。从仓库根目录的 conftest.py 可以看到测试有两个硬性前提一是运行环境必须为 Python 2six.PY3为真时整个appengine/standard目录会被跳过收集二是必须设置GAE_SDK_PATH环境变量指向本地安装的 App Engine SDK否则测试同样会被忽略。一、模型定义与实体创建的三种方式1.1 定义模型代码片段首先定义了一个最基础的Account模型演示了 NDB 属性Property的类型声明class Account(ndb.Model): username ndb.StringProperty() userid ndb.IntegerProperty() email ndb.StringProperty()这是 NDB 与底层 Datastore 的重要区别NDB 是模型优先schema-first的数据访问层通过类属性声明实体的字段与类型而不仅仅是存储键值对。仓库同目录的snippets.py中Revision、Friend、MyModel、ModelWithUser等模型均遵循同样的模式。1.2 方式一关键字参数创建def create_entity_using_keyword_arguments(): sandy Account(usernameSandy, userid123, emailsandyexample.com) return sandy这是最简洁的创建方式直接向构造函数传入属性名作为关键字参数。测试 snippets_test.py 验证返回结果确实是一个Account实例。1.3 方式二先实例化再逐属性赋值def create_entity_using_attributes(): sandy Account() sandy.username Sandy sandy.userid 123 sandy.email sandyexample.com return sandy当需要按条件分支赋值或从配置/表单中逐步填充字段时这种空实体 属性赋值的方式更灵活。1.4 方式三populate() 批量填充def create_entity_using_populate(): sandy Account() sandy.populate(usernameSandy, userid123, emailsandygmail.com) return sandypopulate()允许在一次调用中设置多个属性值本质上与关键字参数构造等价但可以在实体已经创建之后使用。1.5 内置类型检查NDB 属性在赋值时执行严格的类型校验。以下两段代码都会抛出异常def demonstrate_model_constructor_type_checking(): bad Account(usernameSandy, useridnot integer) # raises an exception return bad def demonstrate_entity_attribute_type_checking(sandy): sandy.username 42 # raises an exception第一个例子把字符串not integer传给IntegerProperty第二个例子把整数42赋给StringProperty。测试 snippets_test.py 明确断言这两处都会抛出datastore_errors.BadValueError。这正是 NDB 的价值所在在数据写入 Datastore 之前就通过模型声明拦截了类型错误。二、实体的保存、读取与键Key2.1 保存实体put()def save_entity(sandy): sandy_key sandy.put() return sandy_keyput()将实体写入 Datastore并返回该实体的ndb.Key。注意实体在调用put()之前并不会真正持久化此前创建的Account只是内存中的普通对象。2.2 通过键读取实体Key.get()def get_entity(sandy_key): sandy sandy_key.get() return sandyKey.get()以键为索引直接读取实体返回模型实例若不存在则返回None。测试 snippets_test.py 验证了读取结果仍是Account实例。2.3 从键获取 kind 与 iddef get_key_kind_and_id(sandy_key): kind_string sandy_key.kind() # returns Account ident sandy_key.id() # returns 2 return kind_string, identkind()返回实体类型名称字符串即模型类名Accountid()返回实体在 Datastore 中的标识。对于自动生成的数字 ID 返回整数如2对于字符串命名 ID 返回字符串。2.4 URL-safe 键的序列化与反序列化键可以编码成可在 URL、Cookie 或前端页面安全传递的字符串这是无状态 Web 应用中传递实体引用的标准手段def get_url_safe_key(sandy_key): url_string sandy_key.urlsafe() return url_string def get_entity_from_url_safe_key(url_string): sandy_key ndb.Key(urlsafeurl_string) sandy sandy_key.get() return sandyurlsafe()生成可安全放入 URL 的字符串反向操作通过ndb.Key(urlsafeurl_string)重建键随后即可get()实体。测试 snippets_test.py 验证了保存 → 生成 url-safe 字符串 → 还原键 → 读取实体 → 字段一致的完整往返流程。2.5 更新与删除实体更新就是读出来、改字段、再 putdef update_entity_from_key(key): sandy key.get() sandy.email sandyexample.co.uk sandy.put()测试 snippets_test.py 验证更新后key.get().email sandyexample.co.uk。删除只需调用实体键上的delete()def delete_entity(sandy): sandy.key.delete()测试 snippets_test.py 验证删除后sandy.key.get() is None即实体已从 Datastore 中移除。三、键的创建命名键、自动 ID 与直接设置3.1 使用字符串命名 IDDatastore 支持为实体指定字符串 ID。在创建实体时传入id关键字参数def create_entity_with_named_key(): account Account( usernameSandy, userid1234, emailsandyexample.com, idsandyexample.com ) return account.key.id() # returns sandyexample.com这里id参数直接决定了键的标识部分因此account.key.id()返回字符串sandyexample.com。命名 ID 适合业务上天然有唯一自然键如邮箱、用户名的场景。3.2 显式设置键键也可以在实体创建后通过赋值直接指定def set_key_directly(account): account.key ndb.Key(Account, sandyexample.com) # 也可以用模型类对象本身代替类名字符串 account.key ndb.Key(Account, sandyexample.com)ndb.Key的第一个参数可以是 kind 字符串也可以是模型类本身——仓库注释明确指出两种写法等价后者在重构模型类名时更安全。3.3 自动生成 ID如果不传idDatastore 会自动分配一个全局递增的数字 IDdef create_entity_with_generated_id(): # note: no id kwarg account Account(usernameSandy, userid1234, emailsandyexample.com) account.put() # account.key 形如 ndb.Key(Account, 71321839) return account关键在于put()之后键才包含最终 IDput()之前键的 ID 是未分配的。测试 snippets_test.py 断言result.key.id() is not None验证了这一行为。四、实体组Entity Group与父键层级NDB/Datastore 的键支持层级结构ancestor 链同一实体组内的实体可通过祖先后代查询和事务获得强一致性这是 Datastore 数据建模的核心概念。4.1 层级键的表示def demonstrate_entities_with_parent_hierarchy(): ndb.Key(Account, sandyexample.com, Message, 123, Revision, 1) ndb.Key(Account, sandyexample.com, Message, 123, Revision, 2) ndb.Key(Account, larryexample.com, Message, 456, Revision, 1) ndb.Key(Account, larryexample.com, Message, 789, Revision, 2)ndb.Key可以接收任意多对(kind, id)从左到右构成完整的祖先链Account → Message → Revision。上面的例子建模了两个账号sandy、larry各自的消息及其修订版本同一账号下的实体属于同一个实体组。4.2 三种等价的父键定义方式def equivalent_ways_to_define_key_with_parent(): # 方式一扁平列出整条链 ndb.Key(Account, sandyexample.com, Message, 123, Revision, 1) # 方式二用 parent 关键字指向上层键 ndb.Key( Revision, 1, parentndb.Key(Account, sandyexample.com, Message, 123) ) # 方式三逐级嵌套 parent ndb.Key( Revision, 1, parentndb.Key(Message, 123, parentndb.Key(Account, sandyexample.com)) )三种写法构造出的键完全相同可以根据代码可读性任选其一。4.3 创建根键与带父级的实体根键root key没有父级def create_root_key(): sandy_key ndb.Key(Account, sandyexample.com) return sandy_key创建带父键的实体关键步骤是先拿到父键再构造子实体def create_entity_with_parent_keys(): account_key ndb.Key(Account, sandyexample.com) # 请求 Datastore 分配一个 ID new_id ndb.Model.allocate_ids(size1, parentaccount_key)[0] # 用分配到的整数 ID 构造 Message 键 message_key ndb.Key(Message, new_id, parentaccount_key) # 在 Message 之下创建 Revision 并写入 initial_revision Revision(message_textHello, id1, parentmessage_key) initial_revision.put() return initial_revision这里展示了子实体的标准创建流程先有父键account_key通过allocate_ids(size1, parentaccount_key)让 Datastore 为Message分配数字 ID保证同组内唯一且不与现有实体冲突再用该 ID 构造带parent的message_key最后把Revision挂到message_key之下。反向操作是读取父键def get_parent_key_of_entity(initial_revision): message_key initial_revision.key.parent() return message_key测试 snippets_test.py 验证result.kind() Message确认父键层级还原正确。实体组的典型应用可参考仓库中的 guestbook 示例 overview/main.py每条留言都设置parentndb.Key(Book, guestbook_name)从而把同一留言簿的条目放入同一实体组配合 overview/main.py 中的query(ancestorancestor_key).order(-cls.date)祖先查询实现一致性读取。五、批量操作多个实体NDB 提供对列表的批量操作减少 RPC 往返次数def operate_on_multiple_keys_at_once(list_of_entities): list_of_keys ndb.put_multi(list_of_entities) list_of_entities ndb.get_multi(list_of_keys) ndb.delete_multi(list_of_keys)put_multi(list)批量写入返回键列表get_multi(list_of_keys)批量读取返回实体列表不存在的条目为Nonedelete_multi(list_of_keys)批量删除。测试 snippets_test.py 以两个Account(email...)实体验证了完整的批量生命周期。批量 API 天然支持异步/并行执行是批量导入、清理任务的首选写法。六、Expando 动态模型无 schema 的灵活性6.1 基本用法ndb.Expando允许在运行时动态添加任意属性不受模型声明限制class Mine(ndb.Expando): pass def create_entity_using_expando_model(): e Mine() e.foo 1 e.bar blah e.tags [exp, and, oh] e.put() return e赋值即定义属性。_properties字典会记录所有动态属性及其推断出的类型def get_properties_defined_on_expando(e): return e._properties # { # foo: GenericProperty(foo), # bar: GenericProperty(bar), # tags: GenericProperty(tags, repeatedTrue) # }注意tags因赋值为列表被自动推断为repeatedTrue。6.2 固定属性与动态属性混用Expando 同样可以声明固定属性此时两者共存class FlexEmployee(ndb.Expando): name ndb.StringProperty() age ndb.IntegerProperty() def create_expando_model_entity_with_defined_properties(): employee FlexEmployee(nameSandy, locationSF) return employeename走类型检查的固定属性通道而location是运行时动态属性。6.3 默认不索引的动态属性在 Expando 子类上设置_default_indexed False可让所有动态属性默认不被索引减少写放大与索引膨胀class Specialized(ndb.Expando): _default_indexed False def create_expando_model_entity_that_isnt_indexed_by_default(): e Specialized(fooa, bar[b]) return e._properties # { # foo: GenericProperty(foo, indexedFalse), # bar: GenericProperty(bar, indexedFalse, repeatedTrue) # }6.4 查询动态属性的正确姿势动态属性没有绑定到类因此不能通过类属性语法查询def demonstrate_wrong_way_to_query_expando(): FlexEmployee.query(FlexEmployee.location SF) # AttributeError正确做法是使用ndb.GenericProperty显式声明属性名def demonstrate_right_way_to_query_expando(): FlexEmployee.query(ndb.GenericProperty(location) SF)测试 snippets_test.py 精确断言错误写法抛出AttributeError正确写法正常执行。Expando 适合数据结构频繁演进、字段不稳定的场景但代价是失去静态类型检查需谨慎使用。七、实体生命周期钩子HooksNDB 模型支持在关键生命周期事件前后挂载回调。仓库示例实现了_pre_put_hook与_post_delete_hooknotification None def _notify(message): global notification notification message class Friend(ndb.Model): name ndb.StringProperty() def _pre_put_hook(self): _notify(Gee wiz I have a new friend!) classmethod def _post_delete_hook(cls, key, future): _notify(I have found occasion to rethink our friendship.)调用序列演示注意 delete 使用异步版本以观察钩子时序def demonstrate_model_put_and_delete_hooks(): f Friend() f.name Carole King f.put() # _pre_put_hook 在写入前同步调用 yield f fut f.key.delete_async() # 此刻 _post_delete_hook 尚未触发 fut.get_result() # 等待删除完成后_post_delete_hook 被调用 yield f测试 snippets_test.py 通过两步迭代验证put()后通知为Gee wiz I have a new friend!删除 future 取回结果后通知变为I have found occasion to rethink our friendship.。钩子适合实现审计日志、缓存失效、级联清理等横切逻辑_post_*系列钩子以类方法 future参数的形式工作正因如此它必须配合异步 API 才能精确控制时机。八、ID 预分配allocate_ids某些场景需要预先保留一批 ID例如先构造键、延迟写入实体。Model.allocate_ids支持三种调用形态class MyModel(ndb.Model): pass # 1. 从全局池分配 100 个 ID def reserve_model_ids(): first, last MyModel.allocate_ids(100) return first, last # 2. 在指定父键下分配ID 空间按实体组隔离 def reserve_model_ids_with_a_parent(p): first, last MyModel.allocate_ids(100, parentp) return first, last # 3. 分配 不超过 N 个max 语义返回实际分配区间 def reserve_model_ids_up_to(N): first, last MyModel.allocate_ids(maxN) return first, last返回的first、last是闭区间含两端的整数 ID 范围。拿到区间后即可批量构造键def construct_keys_from_range_of_reserved_ids(first, last): keys [ndb.Key(MyModel, id) for id in range(first, last 1)] return keys测试验证了三种形态的行为snippets_test.py 断言last - first 99size100 时区间跨度至少 99以及构造出的键数量恰为 100。allocate_ids特别适合批量上传前先生成占位键或分片导入类场景因为同一父键下的 ID 空间相互独立、可并行分配。九、与用户对象的集成ModelWithUser演示了如何把 App Engine 用户对象与实体字段结合并提供按用户查询的封装class ModelWithUser(ndb.Model): user_id ndb.StringProperty() color ndb.StringProperty() classmethod def get_by_user(cls, user): return cls.query().filter(cls.user_id user.user_id()).get()用法存储时取user.user_id()用户唯一标识写入user_id字段查询时用同一方法过滤。测试 snippets_test.py 构造了一个带_user_id123的用户对象写入后断言ModelWithUser.get_by_user(user)能精确命中同一实体。这为按登录用户存储/查询个性化数据提供了标准模式。十、测试验证体系本示例的测试价值不亚于代码本身。snippets_test.py 覆盖了上述全部函数其测试基础设施值得借鉴testbed 模拟环境测试通过testbedfixture由仓库根目录 conftest.py 从appengine_helper导入模拟 Datastore 本地运行环境无需真实 GCP 项目即可验证读写逻辑异常路径断言类型错误BadValueError、错误查询写法AttributeError都用pytest.raises显式验证保证错误的代码确实以预期方式失败端到端往返验证保存 → 取键 → 转 url-safe → 还原 → 读取 → 校验字段覆盖实体生命周期全链路数据一致性验证批量操作、父键层级、ID 预分配均有对应的数量与类型断言。若要在本地复现需要 Python 2 环境、安装 App Engine SDK 并设置GAE_SDK_PATH环境变量然后运行pytest appengine/standard/ndb/entities/前提条件与仓库根 conftest.py 中的pytest_ignore_collect逻辑一致。结语从 snippets.py 的代码与 snippets_test.py 的测试可以看到NDB 实体操作的核心心智模型可以归纳为三条主线实体 模型类实例 属性赋值 put() 持久化类型检查由 Property 在赋值时强制执行键 (kind, id) 链支持命名 ID、自动 ID、URL-safe 序列化、parent构造实体组是 Datastore 数据建模的枢纽进阶能力Expando 提供无 schema 灵活性Hooks 提供生命周期切面allocate_ids提供 ID 预分配put/get/delete_multi提供批量吞吐。这套代码同时是官方 NDB 文档中创建实体、创建实体模型、创建实体键、用户对象四个章节的配套示例是理解 App Engine Datastore NDB 实体体系最直接、可验证的参考实现。仓库中同目录下的 properties属性类型、queries查询、transactions事务等示例进一步覆盖了实体的查询与一致性语义可作为后续深入学习的路线图。赞分享示例工程【免费下载链接】python-docs-samplesCode samples used on cloud.google.com项目地址https://gitcode.com/GitHub_Trending/py/python-docs-samples点击查看免费下载相关推荐Wasp 前端测试完全指南用 Vitest、Testing Library 与 MSW 编写 React 单元测试和组件测试Wasp 前端测试完全指南用 Vitest、Testing Library 与 MSW 编写 React 单元测试和组件测试 本篇指南围绕 Wasp 全栈框架示例工程App Engine NDB Overview 示例解析基于 python-docs-samples 的 Guestbook 掌握 Datastore NDB Python APIApp Engine NDB Overview 示例解析基于 python docs samples 的 Guestbook 掌握 Datastore NDB示例工程4pplet Waffling60 Rev D ISO 矩阵布线图解读QMK 键位矩阵编号与 ISO 布局变体实战指南4pplet Waffling60 Rev D ISO 矩阵布线图解读QMK 键位矩阵编号与 ISO 布局变体实战指南 本文以 QMK 固件仓库中 keybo示例工程上一篇为什么选择 TimberAndroid 开发中最佳日志框架的终极对比下一篇Listen1打包发布教程Windows/Mac/Linux多平台部署方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考