Skip to content
Merged
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
12 changes: 9 additions & 3 deletions packages/gapic-generator/gapic/schema/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,16 @@ def resource_messages(self) -> Mapping[str, wrappers.MessageType]:
for msg in self.messages.values()
if msg.options.Extensions[resource_pb2.resource].type
)

# Convert the set to a sorted tuple using the resource path or message name.
# This is needed to prevent non-deterministic code generation.
return collections.OrderedDict(
itertools.chain(
file_resource_messages,
resource_messages,
sorted(
Comment thread
ohmayr marked this conversation as resolved.
itertools.chain(
file_resource_messages,
resource_messages,
),
key=lambda item: item[0]
)
)

Expand Down
13 changes: 11 additions & 2 deletions packages/gapic-generator/gapic/schema/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2278,7 +2278,7 @@ def names(self) -> FrozenSet[str]:
return frozenset(answer)

@utils.cached_property
def resource_messages(self) -> FrozenSet[MessageType]:
def resource_messages(self) -> Sequence['MessageType']:
"""Returns all the resource message types used in all
request and response fields in the service."""

Expand All @@ -2301,7 +2301,7 @@ def gen_indirect_resources_used(message):
if resource:
yield resource

return frozenset(
unique_messages = frozenset(
msg
for method in self.methods.values()
for msg in chain(
Expand All @@ -2316,6 +2316,15 @@ def gen_indirect_resources_used(message):
)
)

# Convert the set to a sorted tuple using the resource path or message name.
# This is needed to prevent non-deterministic code generation.
sorted_messages = sorted(
Comment thread
ohmayr marked this conversation as resolved.
unique_messages,
key=lambda m: m.resource_type_full_path or m.name
)
Comment on lines +2321 to +2324
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current sorting key m.resource_type_full_path or m.name may not guarantee deterministic ordering if multiple messages share the same resource type path or if a message name conflicts with a resource type path. Since the input is a frozenset (which is unordered), any tie in the sort key will result in non-deterministic output order across different runs.

Consider using a tuple as a sort key to provide a consistent tie-breaker, such as (m.resource_type_full_path or "", m.name).

Suggested change
sorted_messages = sorted(
unique_messages,
key=lambda m: m.resource_type_full_path or m.name
)
sorted_messages = sorted(
unique_messages,
key=lambda m: (m.resource_type_full_path or "", m.name)
)
References
  1. To ensure dictionary keys remain sorted without manual effort, programmatically sort the dictionary before returning it instead of relying on manual ordering in the code.


return tuple(sorted_messages)

@utils.cached_property
def resource_messages_dict(self) -> Dict[str, MessageType]:
"""Returns a dict from resource reference to
Expand Down
16 changes: 8 additions & 8 deletions packages/gapic-generator/tests/unit/schema/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1740,17 +1740,17 @@ def test_file_level_resources():
expected = collections.OrderedDict(
(
(
"nomenclature.linnaen.com/Species",
"nomenclature.linnaen.com/Phylum",
wrappers.CommonResource(
type_name="nomenclature.linnaen.com/Species",
pattern="families/{family}/genera/{genus}/species/{species}",
type_name="nomenclature.linnaen.com/Phylum",
pattern="kingdoms/{kingdom}/phyla/{phylum}",
).message_type,
),
(
"nomenclature.linnaen.com/Phylum",
"nomenclature.linnaen.com/Species",
wrappers.CommonResource(
type_name="nomenclature.linnaen.com/Phylum",
pattern="kingdoms/{kingdom}/phyla/{phylum}",
type_name="nomenclature.linnaen.com/Species",
pattern="families/{family}/genera/{genus}/species/{species}",
).message_type,
),
)
Expand All @@ -1767,7 +1767,7 @@ def test_file_level_resources():
# The service doesn't own any method that owns a message that references
# Phylum, so the service doesn't count it among its resource messages.
expected.pop("nomenclature.linnaen.com/Phylum")
expected = frozenset(expected.values())
expected = tuple(expected.values())
actual = service.resource_messages

assert actual == expected
Expand Down Expand Up @@ -1822,7 +1822,7 @@ def test_resources_referenced_but_not_typed(reference_attr="type"):
name_resource_opts.child_type = species_resource_opts.type

api_schema = api.API.build([fdp], package="nomenclature.linneaen.v1")
expected = {api_schema.messages["nomenclature.linneaen.v1.Species"]}
expected = (api_schema.messages["nomenclature.linneaen.v1.Species"],)
actual = api_schema.services[
"nomenclature.linneaen.v1.SpeciesService"
].resource_messages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,12 +267,12 @@ def test_resource_messages():
),
)

expected = {
squid_resource,
expected = (
clam_resource,
whelk_resource,
squamosa_message,
}
squid_resource,
whelk_resource,
)
actual = service.resource_messages
assert expected == actual

Expand Down Expand Up @@ -557,7 +557,7 @@ def test_resource_response():
),
)

expected = {squid_resource, clam_resource}
expected = (clam_resource, squid_resource)
actual = mollusc_service.resource_messages
assert expected == actual

Expand Down
Loading