Package: protobuf-cloud-datastore-translator
Protobuf Message to Google Datastore Entity Protobuf Message TranslatorThis library allows you to store arbitrary Protobuf message objects inside the Google Datastore.It exposes methods for translating arbitrary Protobuf message objects to Entity Protobuf objects
which are used by Google Datastore and vice-versa.It supports all the native which are supported by the Google Datastore.Why, MotivationIf you are working with Google Datastore from a single programming language you can utilize
one of the multiple Datastore ORMs for that programming language. Those ORMs allow you to define
schema for your database models and work with them using native programming language types.This approach brakes down when you want to work with the same set of datastore entities from
multiple programming language.There are multiple solutions for that problem, but one approach is to define some kind of model
schema which is programming language agnostic.And this library tries to do just that. It utilizes native protobuf message definitions as a schema
for database models. This way those definitions can be shared by multiple programming language and
each language just needs a light translator library (like this one) which knows how to translate
arbitrary Protobuf object into Entity Protobuf object and vice-versa.FeaturesRight now the library supports the following Protobuf field types and functionality:All the simple types (string, int32, int64, double, float, bytes, bool, enum)Scalar / container types (map, repeated)Complex types from Protobuf standard library (google.protobuf.Timestamp,google.protobuf.Struct,google.types.LatLng)Using imports and referencing types from different Protobuf definition files. For example,
you can have Protobuf message definition calledModel1DBinside filemodel1.protowhich
has a field which referencesModel2DBfrommodel2.protofile.For that to work, you need to make sure that the root directory which contains all the generated
Protobuf Python files is available inPYTHONPATH.For example, if generated files are written tomy_app/generated/,my_app/generated/needs
to be inPYTHONPATHand this directory needs to be a Python package (it needs to contain__init__.pyfile).For more information on the actual types supported by Google Datastore, refer tohttps://cloud.google.com/datastore/docs/concepts/entities#properties_and_value_types.Supported Python versionsPython 2.7Python 3.6Python 3.7It may also work with Python 3.4 and 3.5, but we don't test against those versions.UsageThis library exposes three main public methods.model_pb_to_entity_pb(model_pb, exclude_falsy_values=False, exclude_from_index=None)This method converts arbitrary Protobuf message objects to the Entity Protobuf object which can
be used with Google Datastore.For example:fromgoogle.cloudimportdatastorefromgoogle.protobuf.timestamp_pb2importTimestampfromprotobuf_cloud_datastore_translatorimportmodel_pb_to_entity_pbfromgenerated.protobuf.modelsimportmy_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 instancemodel_pb=my_model_pb2.MyModelDB()# Other entity attributesmodel_pb.key1='value1'model_pb.key2=200model_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 Datastoreentity_pb=model_pb_to_entity_pb(model_pb)# Store it in the datastoreclient=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)As a convenience, this library also exposesmodel_pb_to_entity_pbmethod. This method assumes
there is a specialkeystring field on your Protobuf message which will act as an Entity
primary key.Underneath, this method infersproject_idandnamespace_idparts of the Entity composite
primary key from theclientobject which is passed to this method. Entitykindis inferred
from the Protobuf message model name. For example, if the Protobuf message model name isUserInfoDB, entity kind would be set toUserInfoDB.For example:fromgoogle.cloudimportdatastorefromprotobuf_cloud_datastore_translatorimportmodel_pb_to_entity_pbmodel_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 datastoreentity=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)This method converts raw Entity Protobuf object as returned by the Google Datastore to provided
Protobuf message class.By default, fields which are found on the Datastore Entity Protobuf object, but not on the
Protobuf message class are ignored. If you want an exception to be thrown in such scenario, you
can passstrict=Trueargument to the method.For example: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)Excluding Protobuf Model Fields from IndexesBy default, Google Cloud Datstore automatically indexes each entity (model) property.Indexing each field (entity property) is usually not desired nor needed. It also has some
limitations (for example, size of a simple field which is to be indexed is limited to1500bytes, etc.). In addition to that, uncessary indexing causes increased storage space consumption.This library allows you to define which model fields to exclude from index on the field basis
utilizing Protobuf field options extension.For example: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.extendgoogle.protobuf.FieldOptions{boolexclude_from_index=50000;}messageExampleDBModelWithOptions1{stringstring_key_one=1[(exclude_from_index)=true];stringstring_key_two=2;stringstring_key_three=3[(exclude_from_index)=true];stringstring_key_four=4;int32int32_field_one=5;int32int32_field_two=6[(exclude_from_index)=true];}In this example, fieldsstring_key_one,string_key_threeandint32_field_twowon't be
indexed (https://cloud.google.com/datastore/docs/concepts/indexes#unindexed_properties).In this example, field option extension is defined in the same file where model is defined, but in
reality you will likely define that extension inside a custom protobuf file (e.gfield_options.proto) and include that file inside other files which contain your database model
definitions.Keep in mind that if you define option extension inside a package, that package needs to match the
package under which the models are stored.For example:protobuf/models/field_options.proto:syntax="proto3";packagemodels;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.extendgoogle.protobuf.FieldOptions{boolexclude_from_index=50000;}protobuf/models/my_model.proto:syntax="proto3";packagemodels;import"models/field_options.proto";messageExampleDBModelWithOptions1{stringstring_key_one=1[(exclude_from_index)=true];stringstring_key_two=2;stringstring_key_three=3[(exclude_from_index)=true];stringstring_key_four=4;int32int32_field_one=5;int32int32_field_two=6[(exclude_from_index)=true];}ExamplesFor example Protobuf message definitions, seeprotobuf/directory.Example usage:fromgoogle.cloudimportdatastorefromprotobuf_cloud_datastore_translatorimportmodel_pb_to_entity_pbfromprotobuf_cloud_datastore_translatorimportentity_pb_to_model_pbfromgenerated.protobuf.modelsimportmy_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 instancemodel_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 Datastoreentity_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#L66client=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 tripkey=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)GotchasDefault valuesIn Protobuf syntax version 3 a concept of field being set has been removed and combined with a
concept of a default value. This means that even when a field is not set, a default value which
is specific to that field type will be returned.As far as this library is concerned, this means when you are converting / translating Protobuf
object with no values set, translated object will still contain default values for fields which
are not set.For example, the output / end result of both those two calls will be the same:# Field values are explicitly provided, but they match default valuesexample_pb=example_pb2.ExampleDBModel()example_pb.bool_key=Falseexample_pb.string_key=''example_pb.int32_key=0example_pb.int64_key=0example_pb.double_key=0.0example_pb.float_key=0.0example_pb.enum_key=example_pb2.ExampleEnumModel.ENUM0example_pb.bool_key=Falseexample_pb.bytes_key=b''example_pb.null_key=1entity_pb_translated=model_pb_to_entity_pb(example_pb)print(entity_pb_translated)# No field values are provided, implicit default values are used during serializationexample_pb=example_pb2.ExampleDBModel()entity_pb_translated=model_pb_to_entity_pb(example_pb)print(entity_pb_translated)If you don't want default values to be set on the translated Entity Protobuf objects and stored
inside the datastore, you can passexclude_falsy_values=Trueargument to themodel_pb_to_entity_pbmethod.For details, see:https://developers.google.com/protocol-buffers/docs/reference/python-generatedhttps://github.com/protocolbuffers/protobuf/issues/1606https://github.com/googleapis/google-cloud-python/issues/1402https://github.com/googleapis/google-cloud-python/pull/1450https://github.com/googleapis/google-cloud-python/pull/1329Struct Field typeThis library supportsgoogle.protobuf.Structfield type out of the box. Struct field values
are serialized as an embedded entity.Keep in mind thatgoogle.protobuf.Structfield type mimics JSON type which only supportsnumbertype for numeric values (https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/struct.proto#L62).
This means all the numbers (including integers) are represented as double precision floating
point values (internally on the Entity, that's stored asvalue_pb.double_value).Translator Libraries for Other Programming LanguagesThis section contains a list of translator libraries for other programming languages which offer
the same functionality.Golang -go-protobuf-cloud-datastore-entity-translatorTestsUnit and integration tests can be found insidetests/directory.You can run unit and integration tests and other lint checks by using tox.# Run all tox targetstox# Run only lint checkstox-elint# Run unit tests under Python 2.7tox-epy2.7-unit-tests# Run Integration tests under Python 3.7tox-epy3.7-integration-tests# Run unit and integration tests and generate and display code coverage reporttox-ecoverageNOTE 1: Integration tests depend on the Google Cloud Datastore Emulator to be running
(./scripts/run-datastore-emulator.sh).NOTE 2: Integration tests also run cross programming language compatibility tests which
verify that the Python and Go translator libraries produce exactly the same output. As such,
those tests also require Golang >= 1.12 to be installed on the system.LicenseCopyright 2019 Tomaz MurausLicensed under the Apache License, Version 2.0 (the "License"); you may not use this work except
in compliance with the License. You may obtain a copy of the License in theLICENSEfile,
or at:http://www.apache.org/licenses/LICENSE-2.0By contributing you agree that these contributions are your own (or approved by your employer) and
you grant a full, complete, irrevocable copyright license to all users and developers of the
project, present and future, pursuant to the license of the project.