diff --git a/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 b/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 index c34babd763..4fc0102ddf 100644 --- a/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 +++ b/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 @@ -214,6 +214,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta): scopes=client_options.scopes, api_mtls_endpoint=client_options.api_endpoint, client_cert_source=client_options.client_cert_source, + quota_project_id=client_options.quota_project_id, ) {% for method in service.methods.values() -%} diff --git a/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/base.py.j2 b/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/base.py.j2 index 8d03088780..3e5836c76d 100644 --- a/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/base.py.j2 +++ b/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/base.py.j2 @@ -33,6 +33,7 @@ class {{ service.name }}Transport(abc.ABC): credentials: credentials.Credentials = None, credentials_file: typing.Optional[str] = None, scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES, + quota_project_id: typing.Optional[str] = None, **kwargs, ) -> None: """Instantiate the transport. @@ -49,6 +50,8 @@ class {{ service.name }}Transport(abc.ABC): be loaded with :func:`google.auth.load_credentials_from_file`. This argument is mutually exclusive with credentials. scope (Optional[Sequence[str]]): A list of scopes. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. """ # Save the hostname. Default to port 443 (HTTPS) if none is specified. if ':' not in host: @@ -61,9 +64,14 @@ class {{ service.name }}Transport(abc.ABC): raise exceptions.DuplicateCredentialArgs("'credentials_file' and 'credentials' are mutually exclusive") if credentials_file is not None: - credentials, _ = auth.load_credentials_from_file(credentials_file, scopes=scopes) + credentials, _ = auth.load_credentials_from_file( + credentials_file, + scopes=scopes, + quota_project_id=quota_project_id + ) + elif credentials is None: - credentials, _ = auth.default(scopes=scopes) + credentials, _ = auth.default(scopes=scopes, quota_project_id=quota_project_id) # Save the credentials. self._credentials = credentials diff --git a/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc.py.j2 b/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc.py.j2 index 245602bf50..d5fb0818bd 100644 --- a/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc.py.j2 +++ b/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc.py.j2 @@ -44,7 +44,8 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport): scopes: Sequence[str] = None, channel: grpc.Channel = None, api_mtls_endpoint: str = None, - client_cert_source: Callable[[], Tuple[bytes, bytes]] = None) -> None: + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + quota_project_id: Optional[str] = None) -> None: """Instantiate the transport. Args: @@ -71,6 +72,8 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport): callback to provide client SSL certificate bytes and private key bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` is None. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport @@ -89,7 +92,7 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport): host = api_mtls_endpoint if ":" in api_mtls_endpoint else api_mtls_endpoint + ":443" if credentials is None: - credentials, _ = auth.default(scopes=self.AUTH_SCOPES) + credentials, _ = auth.default(scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id) # Create SSL credentials with client_cert_source or application # default SSL credentials. @@ -108,6 +111,7 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport): credentials_file=credentials_file, ssl_credentials=ssl_credentials, scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, ) # Run the base constructor. @@ -115,7 +119,8 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport): host=host, credentials=credentials, credentials_file=credentials_file, - scopes=scopes or self.AUTH_SCOPES + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, ) self._stubs = {} # type: Dict[str, Callable] @@ -126,6 +131,7 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport): credentials: credentials.Credentials = None, credentials_file: str = None, scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, **kwargs) -> grpc.Channel: """Create and return a gRPC channel object. Args: @@ -141,6 +147,8 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport): scopes (Optional[Sequence[str]]): A optional list of scopes needed for this service. These are only used when credentials are not specified and are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. kwargs (Optional[dict]): Keyword arguments, which are passed to the channel creation. Returns: @@ -156,6 +164,7 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport): credentials=credentials, credentials_file=credentials_file, scopes=scopes, + quota_project_id=quota_project_id, **kwargs ) diff --git a/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc_asyncio.py.j2 b/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc_asyncio.py.j2 index af182c11b8..700f1e7462 100644 --- a/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc_asyncio.py.j2 +++ b/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc_asyncio.py.j2 @@ -45,6 +45,7 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport): credentials: credentials.Credentials = None, credentials_file: Optional[str] = None, scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, **kwargs) -> aio.Channel: """Create and return a gRPC AsyncIO channel object. Args: @@ -60,6 +61,8 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport): scopes (Optional[Sequence[str]]): A optional list of scopes needed for this service. These are only used when credentials are not specified and are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. kwargs (Optional[dict]): Keyword arguments, which are passed to the channel creation. Returns: @@ -71,6 +74,7 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport): credentials=credentials, credentials_file=credentials_file, scopes=scopes, + quota_project_id=quota_project_id, **kwargs ) @@ -81,7 +85,9 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport): scopes: Optional[Sequence[str]] = None, channel: aio.Channel = None, api_mtls_endpoint: str = None, - client_cert_source: Callable[[], Tuple[bytes, bytes]] = None) -> None: + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + quota_project_id=None, + ) -> None: """Instantiate the transport. Args: @@ -109,6 +115,8 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport): callback to provide client SSL certificate bytes and private key bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` is None. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -143,6 +151,7 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport): credentials_file=credentials_file, ssl_credentials=ssl_credentials, scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, ) # Run the base constructor. @@ -150,7 +159,8 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport): host=host, credentials=credentials, credentials_file=credentials_file, - scopes=scopes or self.AUTH_SCOPES + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, ) self._stubs = {} diff --git a/gapic/templates/setup.py.j2 b/gapic/templates/setup.py.j2 index 1b9a43d1d9..e163e98389 100644 --- a/gapic/templates/setup.py.j2 +++ b/gapic/templates/setup.py.j2 @@ -16,7 +16,7 @@ setuptools.setup( platforms='Posix; MacOS X; Windows', include_package_data=True, install_requires=( - 'google-api-core[grpc] >= 1.21.0, < 2.0.0dev', + 'google-api-core[grpc] >= 1.22.0, < 2.0.0dev', 'libcst >= 0.2.5', 'proto-plus >= 1.1.0', {%- if api.requires_package(('google', 'iam', 'v1')) %} diff --git a/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 b/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 index 3b63c2db3d..753e335413 100644 --- a/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 +++ b/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 @@ -112,6 +112,7 @@ def test_{{ service.client_name|snake_case }}_client_options(client_class, trans scopes=None, api_mtls_endpoint="squid.clam.whelk", client_cert_source=None, + quota_project_id=None, ) # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is @@ -127,6 +128,7 @@ def test_{{ service.client_name|snake_case }}_client_options(client_class, trans scopes=None, api_mtls_endpoint=client.DEFAULT_ENDPOINT, client_cert_source=None, + quota_project_id=None, ) # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is @@ -142,6 +144,7 @@ def test_{{ service.client_name|snake_case }}_client_options(client_class, trans scopes=None, api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, client_cert_source=None, + quota_project_id=None, ) # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is @@ -158,6 +161,7 @@ def test_{{ service.client_name|snake_case }}_client_options(client_class, trans scopes=None, api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, client_cert_source=client_cert_source_callback, + quota_project_id=None, ) @@ -175,6 +179,7 @@ def test_{{ service.client_name|snake_case }}_client_options(client_class, trans scopes=None, api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, client_cert_source=None, + quota_project_id=None, ) # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is @@ -191,15 +196,29 @@ def test_{{ service.client_name|snake_case }}_client_options(client_class, trans scopes=None, api_mtls_endpoint=client.DEFAULT_ENDPOINT, client_cert_source=None, + quota_project_id=None, ) # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has # unsupported value. - os.environ["GOOGLE_API_USE_MTLS"] = "Unsupported" - with pytest.raises(MutualTLSChannelError): - client = client_class() + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() - del os.environ["GOOGLE_API_USE_MTLS"] + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, '__init__') as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, + quota_project_id="octopus", + ) @pytest.mark.parametrize("client_class,transport_class,transport_name", [ @@ -221,6 +240,7 @@ def test_{{ service.client_name|snake_case }}_client_options_scopes(client_class scopes=["1", "2"], api_mtls_endpoint=client.DEFAULT_ENDPOINT, client_cert_source=None, + quota_project_id=None, ) @@ -243,6 +263,7 @@ def test_{{ service.client_name|snake_case }}_client_options_credentials_file(cl scopes=None, api_mtls_endpoint=client.DEFAULT_ENDPOINT, client_cert_source=None, + quota_project_id=None, ) @@ -259,6 +280,7 @@ def test_{{ service.client_name|snake_case }}_client_options_from_dict(): scopes=None, api_mtls_endpoint="squid.clam.whelk", client_cert_source=None, + quota_project_id=None, ) @@ -1001,12 +1023,15 @@ def test_{{ service.name|snake_case }}_base_transport_with_credentials_file(): load_creds.return_value = (credentials.AnonymousCredentials(), None) transport = transports.{{ service.name }}Transport( credentials_file="credentials.json", + quota_project_id="octopus", ) load_creds.assert_called_once_with("credentials.json", scopes=( {%- for scope in service.oauth_scopes %} '{{ scope }}', {%- endfor %} - )) + ), + quota_project_id="octopus", + ) def test_{{ service.name|snake_case }}_auth_adc(): @@ -1017,8 +1042,9 @@ def test_{{ service.name|snake_case }}_auth_adc(): adc.assert_called_once_with(scopes=( {%- for scope in service.oauth_scopes %} '{{ scope }}', - {%- endfor %} - )) + {%- endfor %}), + quota_project_id=None, + ) def test_{{ service.name|snake_case }}_transport_auth_adc(): @@ -1026,13 +1052,13 @@ def test_{{ service.name|snake_case }}_transport_auth_adc(): # ADC credentials. with mock.patch.object(auth, 'default') as adc: adc.return_value = (credentials.AnonymousCredentials(), None) - transports.{{ service.name }}GrpcTransport(host="squid.clam.whelk") + transports.{{ service.name }}GrpcTransport(host="squid.clam.whelk", quota_project_id="octopus") adc.assert_called_once_with(scopes=( {%- for scope in service.oauth_scopes %} '{{ scope }}', - {%- endfor %} - )) - + {%- endfor %}), + quota_project_id="octopus", + ) def test_{{ service.name|snake_case }}_host_no_port(): {% with host = (service.host|default('localhost', true)).split(':')[0] -%} @@ -1122,6 +1148,7 @@ def test_{{ service.name|snake_case }}_grpc_transport_channel_mtls_with_client_c {%- endfor %} ), ssl_credentials=mock_ssl_cred, + quota_project_id=None, ) assert transport.grpc_channel == mock_grpc_channel @@ -1160,6 +1187,7 @@ def test_{{ service.name|snake_case }}_grpc_asyncio_transport_channel_mtls_with_ {%- endfor %} ), ssl_credentials=mock_ssl_cred, + quota_project_id=None, ) assert transport.grpc_channel == mock_grpc_channel @@ -1200,6 +1228,7 @@ def test_{{ service.name|snake_case }}_grpc_transport_channel_mtls_with_adc( {%- endfor %} ), ssl_credentials=mock_ssl_cred, + quota_project_id=None, ) assert transport.grpc_channel == mock_grpc_channel @@ -1240,6 +1269,7 @@ def test_{{ service.name|snake_case }}_grpc_asyncio_transport_channel_mtls_with_ {%- endfor %} ), ssl_credentials=mock_ssl_cred, + quota_project_id=None, ) assert transport.grpc_channel == mock_grpc_channel diff --git a/requirements.txt b/requirements.txt index afd9f7aac6..0c6e8cabed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ click==7.1.2 -google-api-core==1.21.0 +google-api-core==1.22.0 googleapis-common-protos==1.52.0 jinja2==2.11.2 MarkupSafe==1.1.1