Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions gapic/schema/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ def build(
opts: Options = Options(),
prior_protos: Optional[Mapping[str, "Proto"]] = None,
load_services: bool = True,
skip_context_analysis: bool = False,
all_resources: Optional[Mapping[str, wrappers.MessageType]] = None,
) -> "Proto":
"""Build and return a Proto instance.
Expand All @@ -130,6 +131,8 @@ def build(
load_services (bool): Toggle whether the proto file should
load its services. Not doing so enables a two-pass fix for
LRO response and metadata types in certain situations.
skip_context_analysis (bool): Toggle whether to skip context
analysis. Defaults to False.
"""
return _ProtoBuilder(
file_descriptor,
Expand All @@ -138,6 +141,7 @@ def build(
opts=opts,
prior_protos=prior_protos or {},
load_services=load_services,
skip_context_analysis=skip_context_analysis,
all_resources=all_resources or {},
).proto

Expand Down Expand Up @@ -463,8 +467,8 @@ def disambiguate_keyword_sanitize_fname(
naming=naming,
opts=opts,
prior_protos=pre_protos,
# Ugly, ugly hack.
load_services=False,
skip_context_analysis=True,
)

# A file descriptor's file-level resources are NOT visible to any importers.
Expand Down Expand Up @@ -1102,6 +1106,7 @@ def __init__(
opts: Options = Options(),
prior_protos: Optional[Mapping[str, Proto]] = None,
load_services: bool = True,
skip_context_analysis: bool = False,
all_resources: Optional[Mapping[str, wrappers.MessageType]] = None,
):
self.proto_messages: Dict[str, wrappers.MessageType] = {}
Expand All @@ -1111,6 +1116,7 @@ def __init__(
self.file_to_generate = file_to_generate
self.prior_protos = prior_protos or {}
self.opts = opts
self.skip_context_analysis = skip_context_analysis

# Iterate over the documentation and place it into a dictionary.
#
Expand Down Expand Up @@ -1217,7 +1223,7 @@ def proto(self) -> Proto:

# If this is not a file being generated, we do not need to
# do anything else.
if not self.file_to_generate:
if not self.file_to_generate or self.skip_context_analysis:
return naive

visited_messages: Set[wrappers.MessageType] = set()
Expand Down
81 changes: 81 additions & 0 deletions tests/unit/schema/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4499,3 +4499,84 @@ def test_method_settings_invalid_multiple_issues():
assert re.match(".*squid.*not.*uuid4.*", error_yaml[method_example1][1].lower())
assert re.match(".*octopus.*not.*uuid4.*", error_yaml[method_example1][2].lower())
assert re.match(".*method.*not found.*", error_yaml[method_example2][0].lower())


def test_proto_build_skip_context_analysis():
"""Test that Proto.build works with skip_context_analysis=True."""
fdp = make_file_pb2(
name="my_proto.proto",
package="google.example.v1",
messages=(make_message_pb2(name="MyMessage"),),
)

# When skip_context_analysis is True, it should still return a Proto object
# but skip the context analysis phase (resolving collisions etc which happens in with_context).
# Since we can't easily check internal state of "naive" vs "context-aware" without
# relying on implementation details, we at least ensure it runs and returns a Proto.

proto = api.Proto.build(
fdp, file_to_generate=True, naming=make_naming(), skip_context_analysis=True
)

assert isinstance(proto, api.Proto)
assert "google.example.v1.MyMessage" in proto.messages
assert proto.file_to_generate is True


def test_api_build_uses_skip_context_analysis():
"""Test that API.build calls Proto.build with skip_context_analysis=True in the first pass."""

fd = make_file_pb2(
name="test.proto",
package="google.example.v1",
messages=(make_message_pb2(name="Msg"),),
)

with mock.patch.object(
api.Proto, "build", side_effect=api.Proto.build
) as mock_proto_build:
api.API.build([fd], package="google.example.v1")

# API.build makes 2 passes (plus maybe more for other things).
# The first pass should have skip_context_analysis=True.

# Gather all calls
calls = mock_proto_build.call_args_list

# There should be at least 2 calls for our file (Pass 1 and Pass 2).
# We look for the one with skip_context_analysis=True.

found_skip = False
for call in calls:
# call.kwargs contains arguments
if call.kwargs.get("skip_context_analysis") is True:
found_skip = True
# Also verify load_services is False as per current implementation of Pass 1
assert call.kwargs.get("load_services") is False
break

assert found_skip, "Proto.build was not called with skip_context_analysis=True"


def test_proto_builder_skip_context_analysis_property():
"""Test that _ProtoBuilder sets the skip_context_analysis property correctly."""
fdp = descriptor_pb2.FileDescriptorProto(
name="my_proto_file.proto",
package="google.example.v1",
)

builder = api._ProtoBuilder(
fdp,
file_to_generate=True,
naming=make_naming(),
skip_context_analysis=True,
)

assert builder.skip_context_analysis is True

# Verify that .proto property returns the naive proto when skip_context_analysis is True
# We can check if the return value is the same as the 'naive' one constructed inside.
# But since we can't access 'naive' local variable, we verify behavior.

proto = builder.proto
assert isinstance(proto, api.Proto)
Loading