feat: Add new gcloud commands, API clients, and third-party libraries across various services.

This commit is contained in:
2026-01-01 20:26:35 +01:00
parent 5e23cbece0
commit a19e592eb7
25221 changed files with 8324611 additions and 0 deletions

View File

@@ -0,0 +1,158 @@
# -*- coding: utf-8 -*- #
# Copyright 2015 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Base template using which the apis_map.py is generated."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
class APIDef(object):
"""Struct for info required to instantiate clients/messages for API versions.
Attributes:
apitools: ApitoolsClientDef for this API version.
gapic: GapicClientDef for this API version.
default_version: bool, Whether this API version is the default version for
the API.
enable_mtls: bool, Whether this API version supports mTLS.
mtls_endpoint_override: str, The mTLS endpoint for this API version. If
empty, the MTLS_BASE_URL in the API client will be used.
regional_endpoints: dict[str, str], The regional endpoints for this API
version. Dictionary maps location to endpoint URL.
"""
def __init__(self,
apitools=None,
gapic=None,
default_version=False,
enable_mtls=True,
mtls_endpoint_override='',
regional_endpoints=None):
self.apitools = apitools
self.gapic = gapic
self.default_version = default_version
self.enable_mtls = enable_mtls
self.mtls_endpoint_override = mtls_endpoint_override
self.regional_endpoints = regional_endpoints or {}
def __eq__(self, other):
return (isinstance(other, self.__class__) and
self.__dict__ == other.__dict__)
def __ne__(self, other):
return not self.__eq__(other)
def get_init_source(self):
src_fmt = 'APIDef({0}, {1}, {2}, {3}, "{4}")'
return src_fmt.format(self.apitools, self.gapic,
self.default_version,
self.enable_mtls, self.mtls_endpoint_override)
def __repr__(self):
return self.get_init_source()
class ApitoolsClientDef(object):
"""Struct for info required to instantiate clients/messages for API versions.
Attributes:
class_path: str, Path to the package containing api related modules.
client_classpath: str, Relative path to the client class for an API version.
client_full_classpath: str, Full path to the client class for an API
version.
messages_modulepath: str, Relative path to the messages module for an API
version.
messages_full_modulepath: str, Full path to the messages module for an API
version.
base_url: str, The base_url used for the default version of the API.
"""
def __init__(self,
class_path,
client_classpath,
messages_modulepath,
base_url):
self.class_path = class_path
self.client_classpath = client_classpath
self.messages_modulepath = messages_modulepath
self.base_url = base_url
@property
def client_full_classpath(self):
return self.class_path + '.' + self.client_classpath
@property
def messages_full_modulepath(self):
return self.class_path + '.' + self.messages_modulepath
def __eq__(self, other):
return (isinstance(other, self.__class__) and
self.__dict__ == other.__dict__)
def __ne__(self, other):
return not self.__eq__(other)
def get_init_source(self):
src_fmt = 'ApitoolsClientDef("{0}", "{1}", "{2}", "{3}")'
return src_fmt.format(self.class_path, self.client_classpath,
self.messages_modulepath, self.base_url)
def __repr__(self):
return self.get_init_source()
class GapicClientDef(object):
"""Struct for info required to instantiate clients/messages for API versions.
Attributes:
class_path: str, Path to the package containing api related modules.
client_full_classpath: str, Full path to the client class for an API
version.
async_client_full_classpath: str, Full path to the async client class for an
API version.
rest_client_full_classpath: str, Full path to the rest client class for an
API version.
"""
def __init__(self,
class_path):
self.class_path = class_path
@property
def client_full_classpath(self):
return self.class_path + '.client.GapicWrapperClient'
@property
def async_client_full_classpath(self):
return self.class_path + '.async_client.GapicWrapperClient'
@property
def rest_client_full_classpath(self):
return self.class_path + '.rest_client.GapicWrapperClient'
def __eq__(self, other):
return (isinstance(other, self.__class__) and
self.__dict__ == other.__dict__)
def __ne__(self, other):
return not self.__eq__(other)
def get_init_source(self):
src_fmt = 'GapicClientDef("{0}")'
return src_fmt.format(self.class_path)
def __repr__(self):
return self.get_init_source()

View File

@@ -0,0 +1,352 @@
# -*- coding: utf-8 -*- #
# Copyright 2016 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utility wrappers around apitools generator."""
from __future__ import absolute_import
from __future__ import annotations
from __future__ import division
from __future__ import unicode_literals
import collections
import dataclasses
import logging
import os
from apitools.gen import gen_client
from googlecloudsdk.api_lib.regen import api_def
from googlecloudsdk.api_lib.regen import resource_generator
from googlecloudsdk.core.util import files
from mako import runtime
from mako import template
import six
_INIT_FILE_CONTENT = """\
# -*- coding: utf-8 -*- #
# Copyright 2016 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
class NoDefaultApiError(Exception):
"""Multiple apis versions are specified but no default is set."""
class WrongDiscoveryDocError(Exception):
"""Unexpected discovery doc."""
def GenerateApitoolsApi(
discovery_doc, output_dir, root_package, api_name, api_version, api_config):
"""Invokes apitools generator for given api."""
args = [gen_client.__file__]
unelidable_request_methods = api_config.get('unelidable_request_methods')
if unelidable_request_methods:
args.append('--unelidable_request_methods={0}'.format(
','.join(api_config['unelidable_request_methods'])))
args.extend([
'--init-file=empty',
'--nogenerate_cli',
'--infile={0}'.format(discovery_doc),
'--outdir={0}'.format(os.path.join(output_dir, api_name, api_version)),
'--overwrite',
'--apitools_version=CloudSDK',
'--user_agent=google-cloud-sdk',
'--version-identifier={0}'.format(api_version),
'--root_package',
'{0}.{1}.{2}'.format(root_package, api_name, api_version),
'client',
])
logging.debug('Apitools gen %s', args)
gen_client.main(args)
package_dir, dir_name = os.path.split(output_dir)
for subdir in [dir_name, api_name, api_version]:
package_dir = os.path.join(package_dir, subdir)
init_file = os.path.join(package_dir, '__init__.py')
if not os.path.isfile(init_file):
logging.warning('%s does not have __init__.py file, generating ...',
package_dir)
files.WriteFileContents(init_file, _INIT_FILE_CONTENT)
def _CamelCase(snake_case):
return ''.join(x.capitalize() for x in snake_case.split('_'))
def _MakeApitoolsClientDef(root_package, api_name, api_version):
"""Makes an ApitoolsClientDef."""
class_path = '.'.join([root_package, api_name, api_version])
# TODO(b/142448542) Roll back the hack
if api_name == 'admin' and api_version == 'v1':
client_classpath = 'admin_v1_client.AdminDirectoryV1'
else:
client_classpath = '.'.join([
'_'.join([api_name, api_version, 'client']),
_CamelCase(api_name) + _CamelCase(api_version)])
messages_modulepath = '_'.join([api_name, api_version, 'messages'])
base_url = ''
client_full_classpath = class_path + '.' + client_classpath
try:
client_classpath_def = _GetClientClassFromDef(client_full_classpath)
base_url = client_classpath_def.BASE_URL
except Exception: # pylint: disable=broad-except
# unreleased api or test not in "googlecloudsdk.generated_clients.apis"
pass
apitools_def = api_def.ApitoolsClientDef(
class_path=class_path,
client_classpath=client_classpath,
messages_modulepath=messages_modulepath,
base_url=base_url)
return apitools_def
def _GetClientClassFromDef(client_full_classpath):
"""Returns the client class for the API definition specified in the args."""
module_path, client_class_name = client_full_classpath.rsplit('.', 1)
module_obj = __import__(module_path, fromlist=[client_class_name])
return getattr(module_obj, client_class_name)
def _MakeGapicClientDef(root_package, api_name, api_version):
"""Makes a GapicClientDef."""
gapic_root_package = '.'.join(root_package.split('.')[:-1])
class_path = '.'.join(
[gapic_root_package, 'gapic_wrappers', api_name, api_version])
return api_def.GapicClientDef(
class_path=class_path)
def _MakeApiMap(apis_config_map):
"""Combines package_map and api_config maps into ApiDef map.
Args:
apis_config_map: {api_name->api_version->ApiConfig},
description of each api.
Returns:
{api_name->api_version->ApiDef()}.
Raises:
NoDefaultApiError: if for some api with multiple versions
default was not specified.
"""
# Validate that each API has exactly one default version configured.
default_versions_map = {}
for api_name, api_version_config in apis_config_map.items():
for api_version, api_config in api_version_config.items():
if api_config.default or len(api_version_config) == 1:
if api_name in default_versions_map:
raise NoDefaultApiError(
'Multiple default client versions found for [{}]!'
.format(api_name))
default_versions_map[api_name] = api_version
apis_without_default = (
set(apis_config_map.keys()).difference(default_versions_map.keys()))
if apis_without_default:
raise NoDefaultApiError('No default client versions found for [{0}]!'
.format(', '.join(sorted(apis_without_default))))
apis_map = collections.defaultdict(dict)
for api_name, api_version_config in apis_config_map.items():
for api_version, api_config in api_version_config.items():
if doc_data := api_config.discovery_doc:
apitools_client = _MakeApitoolsClientDef(
api_config.root_package, api_name, api_version
)
discovery_doc = resource_generator.DiscoveryDoc.FromJson(doc_data)
regional_endpoints = discovery_doc.endpoints
else:
apitools_client = None
regional_endpoints = None
if api_config.gcloud_gapic_library:
gapic_client = _MakeGapicClientDef(
api_config.root_package, api_name, api_version)
else:
gapic_client = None
default = (api_version == default_versions_map[api_name])
enable_mtls = api_config.enable_mtls
mtls_endpoint_override = api_config.mtls_endpoint_override or ''
apis_map[api_name][api_version] = api_def.APIDef(
apitools_client,
gapic_client,
default, enable_mtls, mtls_endpoint_override, regional_endpoints)
return apis_map
@dataclasses.dataclass(frozen=True)
class ApiConfig:
"""Configuration for an API.
root_package: dot notation of where client is generated
discovery_doc: full path to where discovery doc is located
gcloud_gapic_library: build target of gcloud gapic library
enable_mtls: whether to enable mtls
mtls_endpoint_override: mtls endpoint override
default: whether this is the default version of the API
"""
root_package: str
discovery_doc: str | None
gcloud_gapic_library: str | None
enable_mtls: bool
mtls_endpoint_override: str | None
default: bool
@classmethod
def FromData(cls, data, root_dir, discovery_doc_dir):
"""Creates an ApiConfig from regen config data.
Args:
data: yaml data from regen config
root_dir: where the api clients are generated
discovery_doc_dir: where the discovery docs are generated
"""
if ((doc_name := data.get('discovery_doc')) and
discovery_doc_dir is not None):
discovery_doc = os.path.join(discovery_doc_dir, doc_name)
else:
discovery_doc = None
return cls(
root_package=root_dir.replace('/', '.'),
discovery_doc=discovery_doc,
gcloud_gapic_library=data.get('gcloud_gapic_library'),
enable_mtls=data.get('enable_mtls', True),
mtls_endpoint_override=data.get('mtls_endpoint_override'),
default=data.get('default', False),
)
def GenerateApiMap(output_file, apis_config_map):
"""Create an apis_map.py file for the given packages and api_config.
Args:
output_file: Path of the output apis map file.
apis_config_map: {api_name->api_version->ApiConfig}, regeneration
config for all apis.
"""
api_def_filename, _ = os.path.splitext(api_def.__file__)
api_def_source = files.ReadFileContents(api_def_filename + '.py')
tpl = template.Template(
filename=os.path.join(os.path.dirname(__file__), 'template.tpl')
)
logging.debug('Generating api map at %s', output_file)
api_map = _MakeApiMap(apis_config_map)
logging.debug('Creating following api map %s', api_map)
with files.FileWriter(output_file) as apis_map_file:
ctx = runtime.Context(
apis_map_file, api_def_source=api_def_source, apis_map=api_map
)
tpl.render_context(ctx)
def GenerateApitoolsResourceModule(
discovery_doc,
output_dir,
api_name,
api_version,
custom_resources,
):
"""Create resource.py file for given api and its discovery doc.
Args:
discovery_doc: str, Path to the discovery doc.
output_dir: str, Path to the base output directory (module will be
generated underneath here in api_name/api_version subdir).
api_name: str, name of the api.
api_version: str, the version for the api.
custom_resources: dict, dictionary of custom resource collections.
Raises:
WrongDiscoveryDocError: if discovery doc api name/version does not match.
"""
discovery_doc = resource_generator.DiscoveryDoc.FromJson(discovery_doc)
if discovery_doc.api_version != api_version:
logging.warning(
'Discovery api version %s does not match %s, '
'this client will be accessible via new alias.',
discovery_doc.api_version, api_version)
if discovery_doc.api_name != api_name:
raise WrongDiscoveryDocError('api name {0}, expected {1}'.format(
discovery_doc.api_name, api_name))
resource_collections = discovery_doc.GetResourceCollections(
custom_resources, api_version)
if custom_resources:
# Check if this is redefining one of the existing collections.
matched_resources = set([])
for collection in resource_collections:
if collection.name in custom_resources:
apitools_compatible = custom_resources[collection.name].get(
'apitools_compatible', True
)
if not apitools_compatible:
continue
matched_resources.add(collection.name)
custom_path = custom_resources[collection.name]['path']
if isinstance(custom_path, dict):
collection.flat_paths.update(custom_path)
elif isinstance(custom_path, six.string_types):
collection.flat_paths[
resource_generator.DEFAULT_PATH_NAME] = custom_path
# Remaining must be new custom resources.
for collection_name in set(custom_resources.keys()) - matched_resources:
collection_def = custom_resources[collection_name]
collection_path = collection_def['path']
apitools_compatible = collection_def.get(
'apitools_compatible', True
)
if not apitools_compatible:
continue
enable_uri_parsing = collection_def.get('enable_uri_parsing', True)
collection_info = discovery_doc.MakeResourceCollection(
collection_name, collection_path, enable_uri_parsing, api_version)
resource_collections.append(collection_info)
api_dir = os.path.join(output_dir, api_name, api_version)
if not os.path.exists(api_dir):
os.makedirs(api_dir)
resource_file_name = os.path.join(api_dir, 'resources.py')
if resource_collections:
logging.debug('Generating resource module at %s', resource_file_name)
tpl = template.Template(filename=os.path.join(os.path.dirname(__file__),
'resources.tpl'))
with files.FileWriter(resource_file_name) as output_file:
ctx = runtime.Context(output_file,
collections=sorted(resource_collections),
base_url=resource_collections[0].base_url,
docs_url=discovery_doc.docs_url)
tpl.render_context(ctx)
elif os.path.isfile(resource_file_name):
logging.debug('Removing existing resource module at %s', resource_file_name)
os.remove(resource_file_name)

View File

@@ -0,0 +1,339 @@
# -*- coding: utf-8 -*- #
# Copyright 2016 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Resource definition generator."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from collections import OrderedDict
import json
import re
from googlecloudsdk.api_lib.util import resource as resource_util
from googlecloudsdk.core.util import files
import six
_COLLECTION_SUB_RE = r'[a-zA-Z][a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)+'
_METHOD_ID_RE_RAW = r'(?P<collection>{collection})\.get'.format(
collection=_COLLECTION_SUB_RE)
_METHOD_ID_RE = re.compile(_METHOD_ID_RE_RAW)
DEFAULT_PATH_NAME = ''
class Error(Exception):
"""Errors raised by this module."""
class NoMatchingMethodError(Error):
"""Raised when no matching method can be found."""
class UnsupportedDiscoveryDoc(Error):
"""Raised when some unsupported feature is detected."""
class ConflictingCollection(Error):
"""Raised when collection names conflict and need to be resolved."""
class DiscoveryDoc(object):
"""Encapsulates access to discovery doc."""
def __init__(self, discovery_doc_dict):
self._discovery_doc_dict = discovery_doc_dict
@classmethod
def FromJson(cls, path):
with files.FileReader(path) as f:
return cls(json.load(f, object_pairs_hook=OrderedDict))
@property
def api_name(self):
return self._discovery_doc_dict['name']
@property
def api_version(self):
return self._discovery_doc_dict['version']
@property
def base_url(self):
return self._discovery_doc_dict['baseUrl']
@property
def docs_url(self):
return self._discovery_doc_dict['documentationLink']
@property
def endpoints(self):
endpoints = {}
for endpoint in self._discovery_doc_dict.get('endpoints', []):
if ((url := endpoint.get('endpointUrl')) and
(location := endpoint.get('location'))):
# Skip staging endpoints if prod is already available.
if location in endpoints and 'staging-' in url:
continue
endpoints[location] = url
return endpoints or None
def GetResourceCollections(self, custom_resources, api_version):
"""Returns all resources collections found in this discovery doc.
Args:
custom_resources: {str, str}, A mapping of collection name to path that
have been registered manually in the yaml file.
api_version: Override api_version for each found resource collection.
Returns:
list(resource_util.CollectionInfo).
Raises:
UnsupportedDiscoveryDoc: if collections have different base URLs.
"""
collections = self._ExtractResources(
api_version, self._discovery_doc_dict)
if collections:
url_api_version = resource_util.SplitEndpointUrl(
collections[0].base_url)[1]
for c in collections:
if url_api_version != resource_util.SplitEndpointUrl(c.base_url)[1]:
raise UnsupportedDiscoveryDoc(
'In client {0}/{1}, collection {2} is using url {3}, but '
'collection {4} is using url {5}'.format(
c.api_name,
api_version,
collections[0].name,
collections[0].base_url,
c.name,
c.base_url))
collections.extend(
self._GenerateMissingParentCollections(
collections, custom_resources, api_version))
return collections
def _ExtractResources(self, api_version, infos):
"""Extract resource definitions from discovery doc."""
collections = []
if infos.get('methods'):
methods = infos.get('methods')
get_method = methods.get('get')
if get_method:
collection_info = self._GetCollectionFromMethod(
api_version, get_method)
collections.append(collection_info)
if infos.get('resources'):
for _, info in infos.get('resources').items():
subresource_collections = self._ExtractResources(api_version, info)
collections.extend(subresource_collections)
return collections
def _GetCollectionFromMethod(self, api_version, get_method):
"""Created collection_info object given discovery doc get_method."""
collection_name = _ExtractCollectionName(get_method['id'])
# Remove api name from collection. It might not match passed in, or
# even api name in url. We choose to use api name as defined by url.
collection_name = collection_name.split('.', 1)[1]
flat_path = get_method.get('flatPath')
path = get_method.get('path')
return self._MakeResourceCollection(
api_version, collection_name, path, flat_path
)
def _MakeResourceCollection(
self, api_version, collection_name, path, flat_path=None
):
"""Make resource collection object given its name and path."""
if flat_path == path:
flat_path = None
# Normalize base url so it includes api_version.
url = self.base_url + path
url_api_name, _, path = resource_util.SplitEndpointUrl(url)
if flat_path:
_, _, flat_path = resource_util.SplitEndpointUrl(
self.base_url + flat_path
)
# Use url_api_name instead as it is assumed to be source of truth.
# Also note that the client api_version identifier may differ from the API
# version in the URL for interface-based versioned APIs (as of 2024).
url = url[:-len(path)]
return resource_util.CollectionInfo(
url_api_name,
api_version,
url,
self.docs_url,
collection_name,
path,
{DEFAULT_PATH_NAME: flat_path} if flat_path else {},
resource_util.GetParamsFromPath(path),
)
def _GenerateMissingParentCollections(
self, collections, custom_resources, api_version
):
"""Generates parent collections for any existing collection missing one.
Args:
collections: [resource.CollectionInfo], The existing collections from the
discovery doc.
custom_resources: {str, str}, A mapping of collection name to path that
have been registered manually in the yaml file.
api_version: Override api_version for each found resource collection.
Raises:
ConflictingCollection: If multiple parent collections have the same name
but different paths, and a custom resource has not been declared to
resolve the conflict.
Returns:
[resource.CollectionInfo], Additional collections to include in the
resource module.
"""
all_names = {c.name: c for c in collections}
all_paths = {c.GetPath(DEFAULT_PATH_NAME) for c in collections}
generated = []
in_progress = list(collections)
to_process = []
ignored = {}
while in_progress:
# We need to do multiple passes to recursively create all parent
# collections of generated collections as well.
for c in in_progress:
parent_name, parent_path = _GetParentCollection(c)
if not parent_name:
continue # No parent collection.
if parent_path in all_paths:
continue # Parent path is already explicitly registered.
if parent_name in custom_resources:
# There is a manual entry to resolve this, don't add this collection.
ignored.setdefault(parent_name, set()).add(parent_path)
continue
if parent_name in all_names:
# Parent path is not registered, but a collection with the parent name
# already exists. This conflict needs to be resolved manually.
raise ConflictingCollection(
'In API [{api}/{version}], the parent of collection [{c}] is not '
'registered, but a collection with [{parent_name}] and path '
'[{existing_path}] already exists. Update the api config file to '
'manually add the parent collection with a path of '
'[{parent_path}].'.format(
api=c.api_name, version=api_version, c=c.name,
parent_name=parent_name, existing_path=
all_names[parent_name].GetPath(DEFAULT_PATH_NAME),
parent_path=parent_path))
parent_collection = self.MakeResourceCollection(
parent_name, parent_path, True, api_version)
to_process.append(parent_collection)
all_names[parent_name] = parent_collection
all_paths.add(parent_path)
generated.extend(to_process)
in_progress = to_process
to_process = []
# Print warnings if people have declared custom resources that are
# unnecessary.
for name, paths in six.iteritems(ignored):
if len(paths) > 1:
# There are multiple unique paths for this collection name. It is
# required to be declared to disambiguate.
continue
path = paths.pop()
if path == custom_resources[name]['path']:
# There is 1 path and it is the same as the custom one registered.
print(('WARNING: Custom resource [{}] in API [{}/{}] is redundant.'
.format(name, self.api_name, api_version)))
return generated
def MakeResourceCollection(self, collection_name, path, enable_uri_parsing,
api_version):
_, url_api_version, _ = resource_util.SplitEndpointUrl(self.base_url)
if url_api_version:
base_url = self.base_url
else:
base_url = '{}{}/'.format(self.base_url, api_version)
return resource_util.CollectionInfo(
self.api_name, api_version, base_url, self.docs_url,
collection_name, path, {}, resource_util.GetParamsFromPath(path),
enable_uri_parsing)
def _ExtractCollectionName(method_id):
"""Extract the name of the collection from a method ID."""
match = _METHOD_ID_RE.match(method_id)
if match:
return match.group('collection')
else:
raise NoMatchingMethodError(
'Method {0} does not match regexp {1}.'
.format(method_id, _METHOD_ID_RE_RAW))
def _GetParentCollection(collection_info):
"""Generates the name and path for a parent collection.
Args:
collection_info: resource.CollectionInfo, The collection to calculate the
parent of.
Returns:
(str, str), A tuple of parent name and path or (None, None) if there is no
parent.
"""
params = collection_info.GetParams(DEFAULT_PATH_NAME)
if len(params) < 2:
# There is only 1 param, this is the top level.
return None, None
path = collection_info.GetPath(DEFAULT_PATH_NAME)
# Chop off the last segment in the path.
# a/{a}/b/{b} --> a/{a}
# a/{a}/b --> a/{a}
# a/{a}/b/{b}/{c} --> a/{a}
# a/{a}/b/c/{b}/{c} --> a/{a}
parts = path.split('/')
_PopSegments(parts, True)
_PopSegments(parts, False)
if not parts:
return None, None
parent_path = '/'.join(parts)
# Sometimes the parent is just all parameters (when the parent can be a
# projects, org, or folder. This is not useful as a parent collection so just
# skip it.
_PopSegments(parts, True)
if not parts:
return None, None
if '.' in collection_info.name:
# The discovery doc uses dotted paths for collections, chop off the last
# segment and use that.
parent_name, _ = collection_info.name.rsplit('.', 1)
else:
# The discovery doc uses short names for collections, use the name of the
# last static part of the path.
parent_name = parts[-1]
return parent_name, parent_path
def _PopSegments(parts, is_params):
if parts:
while (parts[-1].startswith('{') == is_params and
parts[-1].endswith('}') == is_params):
parts.pop()
if not parts:
break

View File

@@ -0,0 +1,80 @@
# -*- coding: utf-8 -*- #
# -*- coding: utf-8 -*- #
# Copyright 2023 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Resource definitions for Cloud Platform Apis generated from apitools."""
import enum
<%!
def SplitPath(path, max_length):
"""Splits path into chunks of max_length."""
parts = []
while path:
if len(path) < max_length:
index = max_length
else:
# Prefer to split on last '/'.
index = path.rfind('/', 0, max_length - 1)
if index < 0:
index = min(max_length - 1, len(path) - 1)
parts.append(path[:index+1])
path = path[index+1:]
return parts
%>
BASE_URL = '${base_url}'
DOCS_URL = '${docs_url}'
class Collections(enum.Enum):
"""Collections for all supported apis."""
% for collection_info in collections:
${collection_info.name.upper().replace('.', '_')} = (
'${collection_info.name}',
% for i, part in enumerate(SplitPath(collection_info.path, 80 - 3 - 6)):
% if i:
% endif
'${part}'\
% endfor
,
% if collection_info.flat_paths:
{
% for path_name, flat_path in collection_info.flat_paths.items():
'${path_name}':
% for i, part in enumerate(SplitPath(flat_path, 80 - 3 - 14)):
% if i:
% endif
'${part}'\
% endfor
,
% endfor
},
% else:
{},
% endif
${collection_info.params},
${collection_info.enable_uri_parsing}
)
% endfor
def __init__(self, collection_name, path, flat_paths, params,
enable_uri_parsing):
self.collection_name = collection_name
self.path = path
self.flat_paths = flat_paths
self.params = params
self.enable_uri_parsing = enable_uri_parsing

View File

@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*- #
## Copyright 2015 Google LLC. All Rights Reserved.
##
## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
## You may obtain a copy of the License at
##
## http://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.
${api_def_source}
MAP = {
% for api_name, api_versions in sorted(apis_map.items()):
'${api_name}': {
% for api_version, api_def in sorted(api_versions.items()):
'${api_version}':
APIDef(
% if api_def.apitools:
apitools=ApitoolsClientDef(
class_path='${api_def.apitools.class_path}',
client_classpath='${api_def.apitools.client_classpath}',
base_url='${api_def.apitools.base_url}',
messages_modulepath='${api_def.apitools.messages_modulepath}'),
% endif
% if api_def.gapic:
gapic=GapicClientDef(
class_path='${api_def.gapic.class_path}'),
% endif
default_version=${api_def.default_version},
enable_mtls=${api_def.enable_mtls},
mtls_endpoint_override='${api_def.mtls_endpoint_override}',
regional_endpoints=${api_def.regional_endpoints}),
% endfor
},
% endfor
}