跳转到主要内容

将任意Protobuf消息对象转换为可用于Google Datastore的实体Protobuf对象的库。

项目描述

Protobuf消息到Google Datastore实体Protobuf消息翻译器

Tests Build Status Codecov

此库允许您将任意Protobuf消息对象存储在Google Datastore中。

它公开了将任意Protobuf消息对象转换为实体Protobuf对象(由Google Datastore使用)以及相反的方法。

它支持Google Datastore支持的所有原生类型。

原因,动机

如果您仅使用一种编程语言与Google Datastore一起工作,您可以利用该编程语言的多个Datastore ORM之一。这些ORM允许您为数据库模型定义模式,并使用本地编程语言类型与它们一起工作。

当您想要从多种编程语言中使用相同的集合数据存储实体时,这种方法就会崩溃。

针对该问题有多个解决方案,但一种方法是为编程语言无关的某种模型模式定义。

而这个库试图做到这一点。它利用原生protobuf消息定义作为数据库模型的模式。这样,这些定义可以被多种编程语言共享,每种语言只需要一个轻量级的翻译库(如这个库),它知道如何将任意Protobuf对象转换为实体Protobuf对象,反之亦然。

功能

目前,该库支持以下Protobuf字段类型和功能

  • 所有简单类型(字符串、int32、int64、double、float、bytes、bool、枚举)

  • 标量/容器类型(map、repeated)

  • 来自 Protobuf 标准库的复杂类型(google.protobuf.Timestampgoogle.protobuf.Structgoogle.types.LatLng

  • 使用导入和引用来自不同 Protobuf 定义文件中的类型。例如,你可以在文件 model1.proto 中有一个名为 Model1DB 的 Protobuf 消息定义,该定义有一个字段引用了来自 model2.proto 文件的 Model2DB

    为了使其工作,你需要确保包含所有生成的 Protobuf Python 文件的根目录在 PYTHONPATH 中可用。

    例如,如果生成的文件写入到 my_app/generated/,则 my_app/generated/ 需要包含在 PYTHONPATH 中,并且此目录需要是一个 Python 包(它需要包含 __init__.py 文件)。

有关 Google Datastore 支持的实际类型的更多信息,请参阅https://cloud.google.com/datastore/docs/concepts/entities#properties_and_value_types

支持的 Python 版本

  • Python 2.7
  • Python 3.6
  • Python 3.7

它也可能与 Python 3.4 和 3.5 一起工作,但我们没有对这些版本进行测试。

用法

此库公开了三个主要公共方法。

model_pb_to_entity_pb(model_pb, exclude_falsy_values=False, exclude_from_index=None)

此方法将任意 Protobuf 消息对象转换为可由 Google Datastore 使用的 Entity Protobuf 对象。

例如

from google.cloud import datastore
from google.protobuf.timestamp_pb2 import Timestamp

from protobuf_cloud_datastore_translator import model_pb_to_entity_pb

from generated.protobuf.models import my_model_pb2

# 1. Store your database model object which is represented using a custom Protobuf message class
# instance inside Google Datastore

# Create database model Protobuf instance
model_pb = my_model_pb2.MyModelDB()
# Other entity attributes
model_pb.key1 = 'value1'
model_pb.key2 = 200
model_pb.parameters['foo'] = 'bar'
model_pb.parameters['bar'] = 'baz'

start_time_timestamp = Timestamp()
start_time_timestamp.GetCurrentTime()

model_pb.start_time = start_time_timestamp

# Convert it to Entity Protobuf object which can be used with Google Datastore
entity_pb = model_pb_to_entity_pb(model_pb)

# Store it in the datastore
client = Client(...)
key = self.client.key('MyModelDB', 'some_primary_key')
entity_pb_translated.key.CopyFrom(key.to_protobuf())
entity = datastore.helpers.entity_from_protobuf(entity_pb)
client.put(entity)

model_pb_with_key_to_entity_pb(client, model_pb, exclude_falsy_values=False, exclude_from_index=None)

作为便利,此库还公开了 model_pb_to_entity_pb 方法。此方法假设你的 Protobuf 消息中有一个特殊的 key 字符串字段,该字段将作为 Entity 的主键。

在底层,此方法从传递给此方法的 client 对象中推断 Entity 组合主键的 project_idnamespace_id 部分。Entity kind 从 Protobuf 消息模型名称中推断出来。例如,如果 Protobuf 消息模型名称为 UserInfoDB,则实体类型设置为 UserInfoDB

例如

from google.cloud import datastore

from protobuf_cloud_datastore_translator import model_pb_to_entity_pb

model_pb = my_model_pb2.MyModelDB()
model_pb.key = 'key-1234'
# set model fields
# ...

client = Client(project='my-project', namespace='my-namespace')

entity_pb = model_pb_to_entity_pb(model_pb)

# Store it in the datastore
entity = datastore.helpers.entity_from_protobuf(entity_pb)
client.put(entity)

# In this scenario, actual key would look the same if you manually constructed it like this:
key = client.key('MyModelDB', 'key-1234', project='my-project', namespace='my-namespace')

entity_pb_to_model_pb(model_pb_class, entity_pb, strict=False)

此方法将 Google Datastore 返回的原始 Entity Protobuf 对象转换为提供的 Protobuf 消息类。

默认情况下,Datastore Entity Protobuf 对象上找到但不在 Protobuf 消息类上的字段将被忽略。如果在这种情况下你想抛出异常,可以将 strict=True 参数传递给方法。

例如

key = client.key('MyModelDB', 'some_primary_key')
entity = client.get(key)
entity_pb = datastore.helpers.entity_to_protobuf(entity)

model_pb = entity_pb_to_model_pb(my_model_pb2.MyModelPB, entity_pb)
print(model_pb)

从索引中排除 Protobuf 模型字段

默认情况下,Google Cloud Datastore 会自动索引每个实体(模型)属性。

通常不希望或需要索引每个字段(实体属性)。它还有一些限制(例如,要索引的简单字段的长度限制为 1500 字节等)。此外,不必要的索引会导致存储空间消耗增加。

此库允许你使用 Protobuf 字段选项扩展在字段级别定义要排除从索引中的模型字段。

例如

syntax = "proto3";

import "google/protobuf/descriptor.proto";

// Custom Protobuf option which specifies which model fields should be excluded
// from index
// NOTE: Keep in mind that it's important not to change the option name
// ("exclude_from_index") since this library uses that special option name to
// determine if a field should be excluded from index.
extend google.protobuf.FieldOptions {
    bool exclude_from_index = 50000;
}

message ExampleDBModelWithOptions1 {
    string string_key_one = 1 [(exclude_from_index) = true];
    string string_key_two = 2;
    string string_key_three = 3 [(exclude_from_index) = true];
    string string_key_four = 4;
    int32 int32_field_one = 5;
    int32 int32_field_two = 6 [(exclude_from_index) = true];
}

在此示例中,字段 string_key_onestring_key_threeint32_field_two 不会被索引(https://cloud.google.com/datastore/docs/concepts/indexes#unindexed_properties)。

在此示例中,字段选项扩展定义在定义模型的同一文件中,但实际上你可能会在自定义 protobuf 文件(例如 field_options.proto)中定义该扩展,并将该文件包含在其他包含你的数据库模型定义的文件中。

请注意,如果你在包中定义选项扩展,则该包需要与存储模型的包匹配。

例如

  1. protobuf/models/field_options.proto:
syntax = "proto3";

package models;

import "google/protobuf/descriptor.proto";

// Custom Protobuf option which specifies which model fields should be excluded
// from index
// NOTE: Keep in mind that it's important not to change the option name
// ("exclude_from_index") since this library uses that special option name to
// determine if a field should be excluded from index.
extend google.protobuf.FieldOptions {
    bool exclude_from_index = 50000;
}
  1. protobuf/models/my_model.proto:
syntax = "proto3";

package models;

import "models/field_options.proto";

message ExampleDBModelWithOptions1 {
    string string_key_one = 1 [(exclude_from_index) = true];
    string string_key_two = 2;
    string string_key_three = 3 [(exclude_from_index) = true];
    string string_key_four = 4;
    int32 int32_field_one = 5;
    int32 int32_field_two = 6 [(exclude_from_index) = true];
}

示例

例如,关于 Protobuf 消息定义,请参阅 protobuf/ 目录。

示例用法

from google.cloud import datastore

from protobuf_cloud_datastore_translator import model_pb_to_entity_pb
from protobuf_cloud_datastore_translator import entity_pb_to_model_pb

from generated.protobuf.models import my_model_pb2

# 1. Store your database model object which is represented using a custom Protobuf message class
# instance inside Google Datastore

# Create database model Protobuf instance
model_pb = my_model_pb2.MyModelDB()
model_pb.key1 = 'value1'
model_pb.key2 = 200

# Convert it to Entity Protobuf object which can be used with Google Datastore
entity_pb = model_pb_to_entity_pb(model_pb)

# Store it in the datastore
# To avoid conversion back and forth you can also use lower level client methods which
# work directly with the Entity Protobuf objects
# For information on the low level client usage, see
# https://github.com/GoogleCloudPlatform/google-cloud-datastore/blob/master/python/demos/trivial/adams.py#L66
client = Client(...)
key = self.client.key('MyModelDB', 'some_primary_key')
entity_pb_translated.key.CopyFrom(key.to_protobuf())

entity = datastore.helpers.entity_from_protobuf(entity_pb)
client.put(entity)

# 2. Retrieve entity from the datastore and convert it to your Protobuf DB model instance class
# Same here - you can also use low level client to retrieve Entity protobuf object directly and
# avoid unnecessary conversion round trip
key = client.key('MyModelDB', 'some_primary_key')
entity = client.get(key)
entity_pb = datastore.helpers.entity_to_protobuf(entity)

model_pb = entity_pb_to_model_pb(my_model_pb2.MyModelPB, entity_pb)
print(model_pb)

注意事项

默认值

在Protobuf语法版本3中,已删除“字段被设置”的概念,并将其与默认值的概念合并。这意味着即使在字段未设置的情况下,也会返回特定于该字段类型的默认值。

就这个库而言,这意味着当您转换/翻译未设置值的Protobuf对象时,翻译后的对象仍然会包含未设置的字段的默认值。

例如,这两个调用输出的最终结果将相同

# Field values are explicitly provided, but they match default values
example_pb = example_pb2.ExampleDBModel()
example_pb.bool_key = False
example_pb.string_key = ''
example_pb.int32_key = 0
example_pb.int64_key = 0
example_pb.double_key = 0.0
example_pb.float_key = 0.0
example_pb.enum_key = example_pb2.ExampleEnumModel.ENUM0
example_pb.bool_key = False
example_pb.bytes_key = b''
example_pb.null_key = 1

entity_pb_translated = model_pb_to_entity_pb(example_pb)
print(entity_pb_translated)

# No field values are provided, implicit default values are used during serialization
example_pb = example_pb2.ExampleDBModel()
entity_pb_translated = model_pb_to_entity_pb(example_pb)
print(entity_pb_translated)

如果您不希望在翻译的实体Protobuf对象上设置默认值并将其存储在数据存储中,您可以将exclude_falsy_values=True参数传递给model_pb_to_entity_pb方法。

详细信息请参阅

结构字段类型

该库直接支持google.protobuf.Struct字段类型。结构字段值被序列化为嵌入式实体。

请注意,google.protobuf.Struct字段类型模仿JSON类型,仅支持数值类型的numberhttps://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/struct.proto#L62)。这意味着所有数字(包括整数)都表示为双精度浮点值(在实体内部,这存储为value_pb.double_value)。

其他编程语言的翻译库

本节包含其他编程语言的翻译库列表,它们提供相同的功能。

测试

单元测试和集成测试可以在tests/目录中找到。

您可以使用tox运行单元测试、集成测试和其他代码检查。

# Run all tox targets
tox

# Run only lint checks
tox -e lint

# Run unit tests under Python 2.7
tox -e py2.7-unit-tests

# Run Integration tests under Python 3.7
tox -e py3.7-integration-tests

# Run unit and integration tests and generate and display code coverage report
tox -e coverage

注意1:集成测试依赖于Google Cloud Datastore模拟器正在运行(./scripts/run-datastore-emulator.sh)。

注意2:集成测试还运行跨编程语言兼容性测试,以验证Python和Go翻译库产生的输出完全相同。因此,这些测试还需要在系统上安装Golang >= 1.12。

许可证

版权所有 2019 Tomaz Muraus

根据Apache License,版本2.0(“许可证”);除非遵守许可证,否则您不得使用此作品。您可以在LICENSE文件中或通过https://apache.ac.cn/licenses/LICENSE-2.0获得许可证副本。

https://apache.ac.cn/licenses/LICENSE-2.0

通过贡献,您同意这些贡献是您的(或经您雇主批准的)并且您授予所有当前和未来的项目用户和开发者完整的、不可撤销的版权许可,根据项目的许可证。

项目详情


下载文件

为您的平台下载文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源分发

protobuf-cloud-datastore-translator-0.1.13.tar.gz (40.2 kB 查看哈希值

上传时间 源代码

构建分发

支持