Handling Partial Update

In proto3:

  1. All fields are optional
  2. Singular primitive fields, repeated fields, and map fields are initialized with default values (0, empty list, etc). There’s no way of telling whether a field was explicitly set to the default value (for example whether a boolean was set to false) or just not set at all.

If we want to do a partial update on resources, we need to know whether a field was set or not set at all. There are different strategies that can be used to represent unset, we’ll use a pattern called "Has Pattern" here.

Singular field absence

In proto3, for singular field types, you can use the parent message’s HasField() method to check if a message type field value has been set, but you can’t do it with non-message singular types.

For primitive types if you need HasField to you could use "google/protobuf/wrappers.proto". Wrappers are useful for places where you need to distinguish between the absence of a primitive typed field and its default value:

import "google/protobuf/wrappers.proto";

service PersonController {
    rpc PartialUpdate(PersonPartialUpdateRequest) returns (Person) {}
}

message Person {
    int32 id = 1;
    string name = 2;
    string email = 3;
}

message PersonPartialUpdateRequest {
    int32 id = 1;
    google.protobuf.StringValue name = 2;
    google.protobuf.StringValue email = 3;
}

Here is the client usage:

from google.protobuf.wrappers_pb2 import StringValue


with grpc.insecure_channel('localhost:50051') as channel:
    stub = hrm_pb2_grpc.PersonControllerStub(channel)
    request = hrm_pb2.PersonPartialUpdateRequest(id=1, name=StringValue(value="amy"))
    response = stub.PartialUpdate(request)
    print(response, end='')

The service implementation:

class PersonService(generics.GenericService):
    queryset = Person.objects.all()
    serializer_class = PersonProtoSerializer

    def PartialUpdate(self, request, context):
        instance = self.get_object()
        serializer = self.get_serializer(instance, message=request, partial=True)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return serializer.message

Or you can just use PartialUpdateModelMixin to get the same behavior:

class PersonService(mixins.PartialUpdateModelMixin,
                    generics.GenericService):
    queryset = Person.objects.all()
    serializer_class = PersonProtoSerializer

Repeated and map field absence

If you need to check whether repeated fields and map fields are set or not, you need to do it manually:

message PersonPartialUpdateRequest {
    int32 id = 1;
    google.protobuf.StringValue name = 2;
    google.protobuf.StringValue email = 3;
    repeated int32 groups = 4;
    bool is_groups_set = 5;
}