pythonprotocol-buffersgrpc-python

Python protofub: how to pass response message from one grpc call to another


I'm new to grpc/protobuf so please excuse any terminology errors in my question.

I need to take a response from one gRPC request and feed it into the next request. I can't figure out how to populate the "spec" line.

Proto file1:

message UpdateClusterRequest {
    string service_name = 3;

    ClusterTemplate spec = 4;
    string config_revision = 5;
    string deploy_strategy = 6;

}

Proto file2:

message ClusterTemplate {
    message AppSettings {
        string version = 1;
        repeated InstanceType instance_layout = 2;
        repeated ClientIDTemplate client_ids = 3;
    }

    AppSettings app = 1;
}

So in my code, the template_response captures the output from the get_template_revisions gRPC API call. I then need to pass the contents to request.spec to the next gRPC API request, which is what I need help with.

template_response=get_template_revisions(client_stub,payload_project_id,metadata_okta_token_and_env)grpc_logger.debug(template_response.revisions[0].template.app)

request=app_pb2.UpdateClusterRequest()
request.spec = ???

response=client_stub.get_grpc_app_stub(grpc_stub_method).UpdateCluster(request=request,metadata=metadata_okta_token_and_env)

This is a heavily nested message mapping and I have tried many permutations without success below and not limited to:

request.spec.extend([template_response.revisions[0].template.app])

request.spec = template_response.revisions[0].template

request.spec.MergeFromString(template_response.revisions[0].template.app)

I've read all the python protobuf documentation and I just can't get it.


Solution

  • It's unclear from your question because the (message) type of template_response isn't explicit but hinted (template_response.revisions[0].template.app).

    So...if the Proto were:

    foo.proto:

    syntax = "proto3";
    
    
    message UpdateClusterRequest {
        string service_name = 3;
    
        ClusterTemplate spec = 4;
        string config_revision = 5;
        string deploy_strategy = 6;
    
    }
    
    message ClusterTemplate {
        message AppSettings {
            string version = 1;
            // repeated InstanceType instance_layout = 2;
            // repeated ClientIDTemplate client_ids = 3;
        }
    
        AppSettings app = 1;
    }
    
    // Assume TemplateResponse.Revision's template is a ClusterTemplate
    message TemplateResponse {
        message Revision {
            ClusterTemplate template = 1;
        }
    
        repeated Revision revisions = 1;
    }
    

    Note: I've commented out InstanceType and ClientIDTemplate because they're also undefined but not necessary for the explanation.

    And:

    python3 \
    -m grpc_tools.protoc \
    --proto_path=${PWD} \
    --python_out=${PWD} \
    ${PWD}/foo.proto
    

    Then:

    from google.protobuf.json_format import ParseDict
    
    import foo_pb2
    
    d = {
        "revisions":[
            {
                "template": {
                    "app": {
                        "version": "1",
                    }
                }
            },
            {
                "template": {
                    "app": {
                        "version": "2",
                    }
                }
            }
    
        ]
    }
    template_response = foo_pb2.TemplateResponse()
    
    # Create a TemplateResponse from the dictionary
    ParseDict(d,template_response)
    
    # Its type is <class 'foo_pb2.ClusterTemplate'>
    print(type(template_response.revisions[0].template))
    
    
    # Create `UpdateClusterResponse`
    update_cluster_request = foo_pb2.UpdateClusterRequest()
    
    # Scalar assignments
    update_cluster_request.service_name = "xxx"
    update_cluster_request.config_revision = "xxx"
    update_cluster_request.deploy_strategy = "xxx"
    
    # Uses `google.protobuf.message.CopyFrom`
    # Can't assign messages
    update_cluster_request.spec.CopyFrom(template_response.revisions[0].template)
    
    
    print(update_cluster_request)
    

    Python is a little gnarly around protocol buffers. In other language implementations, you'd be able to assign the message more "idiomatically" but, in Python, it's not possible to assign messages (among other quirks).

    So, assuming that template_response.revisions[*].template is exactly the same type as UpdateClusterRequest's spec type, then you can use CopyFrom to achieve this.