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
2 changes: 1 addition & 1 deletion .generator/requirements.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
click
gapic-generator==1.30.13 # https://github.com/googleapis/gapic-generator-python/releases/tag/v1.30.13
-e /usr/local/google/home/omairn/git/googleapis/google-cloud-python/packages/gapic-generator
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.

high

The use of a hardcoded local absolute path (-e /usr/local/google/...) will break the build for other developers and in CI/CD environments. Please revert this to a versioned dependency or a relative path if necessary for the experiment.

gapic-generator==1.30.13 # https://github.com/googleapis/gapic-generator-python/releases/tag/v1.30.13

nox
starlark-pyo3>=2025.1
build
Expand Down
25 changes: 25 additions & 0 deletions .librarian/generate-request.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"id": "google-cloud-dialogflow",
"version": "2.47.0",
"apis": [
{
"path": "google/cloud/dialogflow/v2beta1",
"service_config": "dialogflow_v2beta1.yaml"
},
{
"path": "google/cloud/dialogflow/v2",
"service_config": "dialogflow_v2.yaml"
}
],
"source_roots": [
"packages/google-cloud-dialogflow"
],
"preserve_regex": [
"packages/google-cloud-dialogflow/CHANGELOG.md",
"docs/CHANGELOG.md"
],
"remove_regex": [
"packages/google-cloud-dialogflow/"
],
"tag_format": "{id}-v{version}"
}
9 changes: 6 additions & 3 deletions packages/gapic-generator/gapic/schema/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,12 @@ def resource_messages(self) -> Mapping[str, wrappers.MessageType]:
if msg.options.Extensions[resource_pb2.resource].type
)
return collections.OrderedDict(
itertools.chain(
file_resource_messages,
resource_messages,
sorted(
itertools.chain(
file_resource_messages,
resource_messages,
),
key=lambda item: item[0]
)
)
Comment on lines 172 to 180
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

In modern Python (3.7+), the standard dict preserves insertion order. Following the general rules for this repository, you should use dict(sorted(...)) instead of collections.OrderedDict to ensure determinism more concisely.

Suggested change
return collections.OrderedDict(
itertools.chain(
file_resource_messages,
resource_messages,
sorted(
itertools.chain(file_resource_messages, resource_messages),
key=lambda item: item[0]
)
)
return dict(
sorted(
itertools.chain(file_resource_messages, resource_messages),
key=lambda item: item[0]
)
)
References
  1. To ensure dictionary keys remain sorted without manual effort, programmatically sort the dictionary before returning it (e.g., using dict(sorted(metadata.items()))) instead of relying on manual ordering in the code.


Expand Down
25 changes: 21 additions & 4 deletions packages/gapic-generator/gapic/schema/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
from gapic.utils import cached_proto_context
from gapic.utils import uri_sample
from gapic.utils import make_private
from gapic.utils import Options


@dataclasses.dataclass(frozen=True)
Expand Down Expand Up @@ -688,7 +689,16 @@ def resource_path(self) -> Optional[str]:
@property
def resource_type(self) -> Optional[str]:
resource = self.options.Extensions[resource_pb2.resource]
return resource.type[resource.type.find("/") + 1 :] if resource else None
if not resource:
return None

# Extract the standard short name (e.g., "Tool" from "ces.googleapis.com/Tool")
default_type = resource.type[resource.type.find("/") + 1 :]

# Check if the CLI provided an alias for this specific resource path
aliases = getattr(Options, "resource_name_aliases_global", {})

return aliases.get(resource.type, default_type)

@property
def resource_type_full_path(self) -> Optional[str]:
Expand Down Expand Up @@ -2278,9 +2288,9 @@ 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."""
request and response fields in the service, deterministically sorted."""

def gen_resources(message):
if message.resource_path:
Expand All @@ -2301,7 +2311,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 +2326,13 @@ def gen_indirect_resources_used(message):
)
)

return tuple(
sorted(
unique_messages,
key=lambda m: m.resource_type_full_path or m.name
)
)

@utils.cached_property
def resource_messages_dict(self) -> Dict[str, MessageType]:
"""Returns a dict from resource reference to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ class {{ service.async_client_name }}:
_DEFAULT_ENDPOINT_TEMPLATE = {{ service.client_name }}._DEFAULT_ENDPOINT_TEMPLATE
_DEFAULT_UNIVERSE = {{ service.client_name }}._DEFAULT_UNIVERSE

{% for message in service.resource_messages|sort(attribute="resource_type") %}
{% for message in service.resource_messages %}
{{ message.resource_type|snake_case }}_path = staticmethod({{ service.client_name }}.{{ message.resource_type|snake_case }}_path)
parse_{{ message.resource_type|snake_case}}_path = staticmethod({{ service.client_name }}.parse_{{ message.resource_type|snake_case }}_path)
{% endfor %}
{% for resource_msg in service.common_resources.values()|sort(attribute="type_name") %}
{% for resource_msg in service.common_resources.values() %}
common_{{ resource_msg.message_type.resource_type|snake_case }}_path = staticmethod({{ service.client_name }}.common_{{ resource_msg.message_type.resource_type|snake_case }}_path)
parse_common_{{ resource_msg.message_type.resource_type|snake_case }}_path = staticmethod({{ service.client_name }}.parse_common_{{ resource_msg.message_type.resource_type|snake_case }}_path)
{% endfor %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
return self._transport


{% for message in service.resource_messages|sort(attribute="resource_type") %}
{% for message in service.resource_messages %}
@staticmethod
def {{ message.resource_type|snake_case }}_path({% for arg in message.resource_path_args %}{{ arg }}: str,{% endfor %}) -> str:
"""Returns a fully-qualified {{ message.resource_type|snake_case }} string."""
Expand Down
15 changes: 15 additions & 0 deletions packages/gapic-generator/gapic/utils/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class Options:
rest_numeric_enums: bool = False
proto_plus_deps: Tuple[str, ...] = dataclasses.field(default=("",))
gapic_version: str = "0.0.0"
resource_name_aliases: Dict[str, str] = dataclasses.field(default_factory=dict)

# Class constants
PYTHON_GAPIC_PREFIX: str = "python-gapic-"
Expand All @@ -73,6 +74,7 @@ class Options:
# For example, 'google.cloud.api.v1+google.cloud.anotherapi.v2'
"proto-plus-deps",
"gapic-version", # A version string following https://peps.python.org/pep-0440
"resource-name-aliases",
)
)

Expand Down Expand Up @@ -187,6 +189,18 @@ def tweak_path(p):
proto_plus_deps = tuple(opts.pop("proto-plus-deps", ""))
if len(proto_plus_deps):
proto_plus_deps = tuple(proto_plus_deps[0].split("+"))

resource_name_aliases = {}
resource_alias_strings = opts.pop("resource-name-aliases", None)
if resource_alias_strings:
try:
# Strip single quotes that survive the Bazel/Shell transition
raw_string = resource_alias_strings[-1].strip("'")
resource_name_aliases = json.loads(raw_string)
except json.JSONDecodeError as e:
warnings.warn(f"Failed to parse resource-name-aliases JSON: {e} from string: {resource_alias_strings[-1]}")

Options.resource_name_aliases_global = resource_name_aliases

answer = Options(
name=opts.pop("name", [""]).pop(),
Expand All @@ -210,6 +224,7 @@ def tweak_path(p):
rest_numeric_enums=rest_numeric_enums,
proto_plus_deps=proto_plus_deps,
gapic_version=opts.pop("gapic-version", ["0.0.0"]).pop(),
resource_name_aliases=resource_name_aliases,
)

# Note: if we ever need to recursively check directories for sample
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