Skip to content

Commit e1d306a

Browse files
authored
fix: resolve error where google/longrunning/operations.proto is missing (#18477)
Fixes b/566289715 Fixes b/566222096 Fixes #18478
1 parent 5013b91 commit e1d306a

3 files changed

Lines changed: 224 additions & 28 deletions

File tree

‎packages/googleapis-common-protos/google/longrunning/operations_proto_pb2.py‎

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
# Generated by the protocol buffer compiler. DO NOT EDIT!
1818
# NO CHECKED-IN PROTOBUF GENCODE
19-
# source: google/longrunning/operations_proto.proto
19+
# source: google/longrunning/operations.proto
2020
# Protobuf Python Version: 6.33.5
2121
"""Generated protocol buffer code."""
2222

@@ -27,12 +27,7 @@
2727
from google.protobuf.internal import builder as _builder
2828

2929
_runtime_version.ValidateProtobufRuntimeVersion(
30-
_runtime_version.Domain.PUBLIC,
31-
6,
32-
33,
33-
5,
34-
"",
35-
"google/longrunning/operations_proto.proto",
30+
_runtime_version.Domain.PUBLIC, 6, 33, 5, "", "google/longrunning/operations.proto"
3631
)
3732
# @@protoc_insertion_point(imports)
3833

@@ -50,13 +45,13 @@
5045
from google.rpc import status_pb2 as google_dot_rpc_dot_status__pb2
5146

5247
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
53-
b'\n)google/longrunning/operations_proto.proto\x12\x12google.longrunning\x1a\x1cgoogle/api/annotations.proto\x1a\x17google/api/client.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/protobuf/any.proto\x1a google/protobuf/descriptor.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x17google/rpc/status.proto"\xa8\x01\n\tOperation\x12\x0c\n\x04name\x18\x01 \x01(\t\x12&\n\x08metadata\x18\x02 \x01(\x0b\x32\x14.google.protobuf.Any\x12\x0c\n\x04\x64one\x18\x03 \x01(\x08\x12#\n\x05\x65rror\x18\x04 \x01(\x0b\x32\x12.google.rpc.StatusH\x00\x12(\n\x08response\x18\x05 \x01(\x0b\x32\x14.google.protobuf.AnyH\x00\x42\x08\n\x06result"#\n\x13GetOperationRequest\x12\x0c\n\x04name\x18\x01 \x01(\t"|\n\x15ListOperationsRequest\x12\x0c\n\x04name\x18\x04 \x01(\t\x12\x0e\n\x06\x66ilter\x18\x01 \x01(\t\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x12\n\npage_token\x18\x03 \x01(\t\x12\x1e\n\x16return_partial_success\x18\x05 \x01(\x08"~\n\x16ListOperationsResponse\x12\x31\n\noperations\x18\x01 \x03(\x0b\x32\x1d.google.longrunning.Operation\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\x12\x18\n\x0bunreachable\x18\x03 \x03(\tB\x03\xe0\x41\x06"&\n\x16\x43\x61ncelOperationRequest\x12\x0c\n\x04name\x18\x01 \x01(\t"&\n\x16\x44\x65leteOperationRequest\x12\x0c\n\x04name\x18\x01 \x01(\t"P\n\x14WaitOperationRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12*\n\x07timeout\x18\x02 \x01(\x0b\x32\x19.google.protobuf.Duration"=\n\rOperationInfo\x12\x15\n\rresponse_type\x18\x01 \x01(\t\x12\x15\n\rmetadata_type\x18\x02 \x01(\t2\xaa\x05\n\nOperations\x12\x94\x01\n\x0eListOperations\x12).google.longrunning.ListOperationsRequest\x1a*.google.longrunning.ListOperationsResponse"+\xda\x41\x0bname,filter\x82\xd3\xe4\x93\x02\x17\x12\x15/v1/{name=operations}\x12\x7f\n\x0cGetOperation\x12\'.google.longrunning.GetOperationRequest\x1a\x1d.google.longrunning.Operation"\'\xda\x41\x04name\x82\xd3\xe4\x93\x02\x1a\x12\x18/v1/{name=operations/**}\x12~\n\x0f\x44\x65leteOperation\x12*.google.longrunning.DeleteOperationRequest\x1a\x16.google.protobuf.Empty"\'\xda\x41\x04name\x82\xd3\xe4\x93\x02\x1a*\x18/v1/{name=operations/**}\x12\x88\x01\n\x0f\x43\x61ncelOperation\x12*.google.longrunning.CancelOperationRequest\x1a\x16.google.protobuf.Empty"1\xda\x41\x04name\x82\xd3\xe4\x93\x02$"\x1f/v1/{name=operations/**}:cancel:\x01*\x12Z\n\rWaitOperation\x12(.google.longrunning.WaitOperationRequest\x1a\x1d.google.longrunning.Operation"\x00\x1a\x1d\xca\x41\x1alongrunning.googleapis.com:Z\n\x0eoperation_info\x12\x1e.google.protobuf.MethodOptions\x18\x99\x08 \x01(\x0b\x32!.google.longrunning.OperationInfoB\xa2\x01\n\x16\x63om.google.longrunningB\x0fOperationsProtoP\x01ZCcloud.google.com/go/longrunning/autogen/longrunningpb;longrunningpb\xa2\x02\x05GLRUN\xaa\x02\x12Google.LongRunning\xca\x02\x12Google\\LongRunningb\x06proto3'
48+
b'\n#google/longrunning/operations.proto\x12\x12google.longrunning\x1a\x1cgoogle/api/annotations.proto\x1a\x17google/api/client.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/protobuf/any.proto\x1a google/protobuf/descriptor.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x17google/rpc/status.proto"\xa8\x01\n\tOperation\x12\x0c\n\x04name\x18\x01 \x01(\t\x12&\n\x08metadata\x18\x02 \x01(\x0b\x32\x14.google.protobuf.Any\x12\x0c\n\x04\x64one\x18\x03 \x01(\x08\x12#\n\x05\x65rror\x18\x04 \x01(\x0b\x32\x12.google.rpc.StatusH\x00\x12(\n\x08response\x18\x05 \x01(\x0b\x32\x14.google.protobuf.AnyH\x00\x42\x08\n\x06result"#\n\x13GetOperationRequest\x12\x0c\n\x04name\x18\x01 \x01(\t"|\n\x15ListOperationsRequest\x12\x0c\n\x04name\x18\x04 \x01(\t\x12\x0e\n\x06\x66ilter\x18\x01 \x01(\t\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x12\n\npage_token\x18\x03 \x01(\t\x12\x1e\n\x16return_partial_success\x18\x05 \x01(\x08"~\n\x16ListOperationsResponse\x12\x31\n\noperations\x18\x01 \x03(\x0b\x32\x1d.google.longrunning.Operation\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\x12\x18\n\x0bunreachable\x18\x03 \x03(\tB\x03\xe0\x41\x06"&\n\x16\x43\x61ncelOperationRequest\x12\x0c\n\x04name\x18\x01 \x01(\t"&\n\x16\x44\x65leteOperationRequest\x12\x0c\n\x04name\x18\x01 \x01(\t"P\n\x14WaitOperationRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12*\n\x07timeout\x18\x02 \x01(\x0b\x32\x19.google.protobuf.Duration"=\n\rOperationInfo\x12\x15\n\rresponse_type\x18\x01 \x01(\t\x12\x15\n\rmetadata_type\x18\x02 \x01(\t2\xaa\x05\n\nOperations\x12\x94\x01\n\x0eListOperations\x12).google.longrunning.ListOperationsRequest\x1a*.google.longrunning.ListOperationsResponse"+\xda\x41\x0bname,filter\x82\xd3\xe4\x93\x02\x17\x12\x15/v1/{name=operations}\x12\x7f\n\x0cGetOperation\x12\'.google.longrunning.GetOperationRequest\x1a\x1d.google.longrunning.Operation"\'\xda\x41\x04name\x82\xd3\xe4\x93\x02\x1a\x12\x18/v1/{name=operations/**}\x12~\n\x0f\x44\x65leteOperation\x12*.google.longrunning.DeleteOperationRequest\x1a\x16.google.protobuf.Empty"\'\xda\x41\x04name\x82\xd3\xe4\x93\x02\x1a*\x18/v1/{name=operations/**}\x12\x88\x01\n\x0f\x43\x61ncelOperation\x12*.google.longrunning.CancelOperationRequest\x1a\x16.google.protobuf.Empty"1\xda\x41\x04name\x82\xd3\xe4\x93\x02$"\x1f/v1/{name=operations/**}:cancel:\x01*\x12Z\n\rWaitOperation\x12(.google.longrunning.WaitOperationRequest\x1a\x1d.google.longrunning.Operation"\x00\x1a\x1d\xca\x41\x1alongrunning.googleapis.com:Z\n\x0eoperation_info\x12\x1e.google.protobuf.MethodOptions\x18\x99\x08 \x01(\x0b\x32!.google.longrunning.OperationInfoB\xa2\x01\n\x16\x63om.google.longrunningB\x0fOperationsProtoP\x01ZCcloud.google.com/go/longrunning/autogen/longrunningpb;longrunningpb\xa2\x02\x05GLRUN\xaa\x02\x12Google.LongRunning\xca\x02\x12Google\\LongRunningb\x06proto3'
5449
)
5550

5651
_globals = globals()
5752
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
5853
_builder.BuildTopDescriptorsAndMessages(
59-
DESCRIPTOR, "google.longrunning.operations_proto_pb2", _globals
54+
DESCRIPTOR, "google.longrunning.operations_pb2", _globals
6055
)
6156
if not _descriptor._USE_C_DESCRIPTORS:
6257
_globals["DESCRIPTOR"]._loaded_options = None
@@ -95,22 +90,22 @@
9590
]._serialized_options = (
9691
b'\332A\004name\202\323\344\223\002$"\037/v1/{name=operations/**}:cancel:\001*'
9792
)
98-
_globals["_OPERATION"]._serialized_start = 301
99-
_globals["_OPERATION"]._serialized_end = 469
100-
_globals["_GETOPERATIONREQUEST"]._serialized_start = 471
101-
_globals["_GETOPERATIONREQUEST"]._serialized_end = 506
102-
_globals["_LISTOPERATIONSREQUEST"]._serialized_start = 508
103-
_globals["_LISTOPERATIONSREQUEST"]._serialized_end = 632
104-
_globals["_LISTOPERATIONSRESPONSE"]._serialized_start = 634
105-
_globals["_LISTOPERATIONSRESPONSE"]._serialized_end = 760
106-
_globals["_CANCELOPERATIONREQUEST"]._serialized_start = 762
107-
_globals["_CANCELOPERATIONREQUEST"]._serialized_end = 800
108-
_globals["_DELETEOPERATIONREQUEST"]._serialized_start = 802
109-
_globals["_DELETEOPERATIONREQUEST"]._serialized_end = 840
110-
_globals["_WAITOPERATIONREQUEST"]._serialized_start = 842
111-
_globals["_WAITOPERATIONREQUEST"]._serialized_end = 922
112-
_globals["_OPERATIONINFO"]._serialized_start = 924
113-
_globals["_OPERATIONINFO"]._serialized_end = 985
114-
_globals["_OPERATIONS"]._serialized_start = 988
115-
_globals["_OPERATIONS"]._serialized_end = 1670
93+
_globals["_OPERATION"]._serialized_start = 295
94+
_globals["_OPERATION"]._serialized_end = 463
95+
_globals["_GETOPERATIONREQUEST"]._serialized_start = 465
96+
_globals["_GETOPERATIONREQUEST"]._serialized_end = 500
97+
_globals["_LISTOPERATIONSREQUEST"]._serialized_start = 502
98+
_globals["_LISTOPERATIONSREQUEST"]._serialized_end = 626
99+
_globals["_LISTOPERATIONSRESPONSE"]._serialized_start = 628
100+
_globals["_LISTOPERATIONSRESPONSE"]._serialized_end = 754
101+
_globals["_CANCELOPERATIONREQUEST"]._serialized_start = 756
102+
_globals["_CANCELOPERATIONREQUEST"]._serialized_end = 794
103+
_globals["_DELETEOPERATIONREQUEST"]._serialized_start = 796
104+
_globals["_DELETEOPERATIONREQUEST"]._serialized_end = 834
105+
_globals["_WAITOPERATIONREQUEST"]._serialized_start = 836
106+
_globals["_WAITOPERATIONREQUEST"]._serialized_end = 916
107+
_globals["_OPERATIONINFO"]._serialized_start = 918
108+
_globals["_OPERATIONINFO"]._serialized_end = 979
109+
_globals["_OPERATIONS"]._serialized_start = 982
110+
_globals["_OPERATIONS"]._serialized_end = 1664
116111
# @@protoc_insertion_point(module_scope)

‎packages/googleapis-common-protos/noxfile.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
UNIT_TEST_EXTERNAL_DEPENDENCIES: List[str] = []
5353
UNIT_TEST_LOCAL_DEPENDENCIES: List[str] = []
5454
UNIT_TEST_DEPENDENCIES: List[str] = []
55-
UNIT_TEST_EXTRAS: List[str] = []
55+
UNIT_TEST_EXTRAS: List[str] = ["grpc"]
5656
UNIT_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = {}
5757

5858
SYSTEM_TEST_PYTHON_VERSIONS: List[str] = [
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# Copyright 2026 Google LLC All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import importlib
16+
import sys
17+
import uuid
18+
from unittest import mock
19+
20+
import pytest
21+
from google.protobuf import descriptor_pb2, descriptor_pool
22+
23+
24+
def test_operations_descriptor_name():
25+
"""Validates the canonical proto name registered in the descriptor pool."""
26+
from google.longrunning import operations_pb2
27+
28+
# The descriptor name MUST be the canonical path 'google/longrunning/operations.proto'.
29+
# If named 'google/longrunning/operations_proto.proto', any downstream proto that
30+
# imports 'google/longrunning/operations.proto' will fail to resolve.
31+
assert operations_pb2.DESCRIPTOR.name == "google/longrunning/operations.proto"
32+
33+
# Verify lookup by canonical path succeeds in the global descriptor pool
34+
pool = descriptor_pool.Default()
35+
file_desc = pool.FindFileByName("google/longrunning/operations.proto")
36+
assert file_desc is not None
37+
assert file_desc.name == "google/longrunning/operations.proto"
38+
39+
40+
def test_operations_message_instantiation():
41+
"""Validates message instantiation from both public module entry points."""
42+
from google.longrunning import operations_pb2, operations_proto_pb2
43+
44+
op1 = operations_pb2.Operation(name="operations/123", done=True)
45+
assert op1.name == "operations/123"
46+
assert op1.done is True
47+
48+
op2 = operations_proto_pb2.Operation(name="operations/456", done=False)
49+
assert op2.name == "operations/456"
50+
assert op2.done is False
51+
52+
53+
def test_downstream_dependency_resolution():
54+
"""Regression test for b/566222096.
55+
56+
Downstream clients compile protos containing:
57+
import "google/longrunning/operations.proto";
58+
59+
When their generated *_pb2 modules load, protobuf calls:
60+
61+
```
62+
_descriptor_pool.Default().AddSerializedFile(...)
63+
```
64+
65+
which checks that 'google/longrunning/operations.proto' exists in the pool.
66+
"""
67+
# Ensure operations_pb2 is imported and registered
68+
from google.longrunning import operations_pb2 # noqa: F401
69+
70+
pool = descriptor_pool.Default()
71+
72+
# Create a uniquely named proto to avoid collision during repeated test runs
73+
unique_name = f"google/test/downstream_{uuid.uuid4().hex}.proto"
74+
75+
downstream_file = descriptor_pb2.FileDescriptorProto()
76+
downstream_file.name = unique_name
77+
downstream_file.package = "google.test"
78+
downstream_file.dependency.append("google/longrunning/operations.proto")
79+
80+
# 1. This step will throw the TypeError if operations.proto is missing (under C++ runtime).
81+
pool.Add(downstream_file)
82+
83+
# 2. Retrieve the descriptor from the pool (required because pool.Add() returns None).
84+
file_descriptor = pool.FindFileByName(unique_name)
85+
assert file_descriptor is not None
86+
assert file_descriptor.name == unique_name
87+
assert "google/longrunning/operations.proto" in [
88+
dep.name for dep in file_descriptor.dependencies
89+
]
90+
91+
92+
def test_operations_servicer_interface():
93+
"""Verifies OperationsServicer default method implementations raise NotImplementedError."""
94+
from google.longrunning import operations_pb2_grpc
95+
96+
servicer = operations_pb2_grpc.OperationsServicer()
97+
context = mock.Mock()
98+
request = mock.Mock()
99+
100+
# Servicer interface must define all RPCs and default to NotImplementedError
101+
for method in [
102+
servicer.ListOperations,
103+
servicer.GetOperation,
104+
servicer.DeleteOperation,
105+
servicer.CancelOperation,
106+
servicer.WaitOperation,
107+
]:
108+
with pytest.raises(NotImplementedError):
109+
method(request, context)
110+
111+
112+
def test_add_operations_servicer_to_server():
113+
"""Verifies server handler registration for OperationsServicer."""
114+
from google.longrunning import operations_pb2_grpc
115+
116+
servicer = operations_pb2_grpc.OperationsServicer()
117+
mock_server = mock.Mock()
118+
119+
operations_pb2_grpc.add_OperationsServicer_to_server(servicer, mock_server)
120+
121+
# Assert that generic RPC handlers were registered with the server
122+
assert mock_server.add_generic_rpc_handlers.called
123+
assert mock_server.add_generic_rpc_handlers.call_count == 1
124+
125+
# Verify the handler structure passed to the server
126+
handlers = mock_server.add_generic_rpc_handlers.call_args[0][0]
127+
assert len(handlers) == 1
128+
assert hasattr(handlers[0], "service_name")
129+
assert handlers[0].service_name() == "google.longrunning.Operations"
130+
131+
132+
def test_operations_client_stubs_and_static_methods():
133+
"""Verifies OperationsStub client calls and static method dispatch."""
134+
from google.longrunning import operations_pb2_grpc
135+
136+
# 1. Test OperationsStub channel binding and RPC dispatch
137+
mock_callable = mock.Mock(return_value=mock.Mock())
138+
mock_channel = mock.Mock()
139+
mock_channel.unary_unary = mock.Mock(return_value=mock_callable)
140+
mock_channel.unary_stream = mock.Mock(return_value=mock_callable)
141+
142+
stub = operations_pb2_grpc.OperationsStub(mock_channel)
143+
assert mock_channel.unary_unary.call_count == 5
144+
145+
request = mock.Mock()
146+
for rpc in [
147+
stub.ListOperations,
148+
stub.GetOperation,
149+
stub.DeleteOperation,
150+
stub.CancelOperation,
151+
stub.WaitOperation,
152+
]:
153+
response = rpc(request)
154+
assert response is not None
155+
156+
assert mock_callable.call_count == 5
157+
158+
# 2. Test Operations static invocation helpers
159+
with mock.patch(
160+
"grpc.experimental.unary_unary", return_value=mock.Mock()
161+
) as mock_unary:
162+
for static_rpc in [
163+
operations_pb2_grpc.Operations.ListOperations,
164+
operations_pb2_grpc.Operations.GetOperation,
165+
operations_pb2_grpc.Operations.DeleteOperation,
166+
operations_pb2_grpc.Operations.CancelOperation,
167+
operations_pb2_grpc.Operations.WaitOperation,
168+
]:
169+
result = static_rpc(request, "localhost:50051")
170+
assert result is not None
171+
172+
assert mock_unary.call_count == 5
173+
174+
175+
def test_operations_pb2_non_grpc_fallback_environment():
176+
"""Verifies module loading and class contract in non-gRPC environments.
177+
178+
In environments where gRPC is unsupported, the module must fall back to pure
179+
protobuf-only imports without throwing exceptions, and all message classes
180+
must remain instantiable.
181+
"""
182+
# 1. Simulate an environment where the gRPC-dependent pb2 module is unavailable
183+
with mock.patch.dict(sys.modules, {"google.longrunning.operations_grpc_pb2": None}):
184+
# Reload to force execution of the fallback import path
185+
if "google.longrunning.operations_pb2" in sys.modules:
186+
fallback_module = importlib.reload(
187+
sys.modules["google.longrunning.operations_pb2"]
188+
)
189+
else:
190+
import google.longrunning.operations_pb2 as fallback_module # pragma: NO COVER
191+
192+
# 2. Assert that canonical messages can still be resolved and instantiated
193+
assert hasattr(fallback_module, "Operation")
194+
assert hasattr(fallback_module, "GetOperationRequest")
195+
196+
op = fallback_module.Operation(name="operations/789", done=True)
197+
assert op.name == "operations/789"
198+
assert op.done is True
199+
200+
# 3. Restore the environment back to its gRPC-enabled state
201+
importlib.reload(sys.modules["google.longrunning.operations_pb2"])

0 commit comments

Comments
 (0)