diff --git a/.coveragerc b/.coveragerc index cb9d233..a125c0b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,7 +2,6 @@ branch = True [report] -fail_under = 100 show_missing = True omit = google/cloud/apigateway/__init__.py diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index 4ef4414..0954585 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -1,3 +1,3 @@ docker: image: gcr.io/repo-automation-bots/owlbot-python:latest - digest: sha256:c66ba3c8d7bc8566f47df841f98cd0097b28fff0b1864c86f5817f4c8c3e8600 \ No newline at end of file + digest: sha256:df50e8d462f86d6bcb42f27ecad55bb12c404f1c65de9c6fe4c4d25120080bd6 diff --git a/.kokoro/samples/python3.9/common.cfg b/.kokoro/samples/python3.9/common.cfg new file mode 100644 index 0000000..fb5dcf7 --- /dev/null +++ b/.kokoro/samples/python3.9/common.cfg @@ -0,0 +1,40 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "py-3.9" +} + +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-py39" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-api-gateway/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "python-api-gateway/.kokoro/trampoline.sh" \ No newline at end of file diff --git a/.kokoro/samples/python3.9/continuous.cfg b/.kokoro/samples/python3.9/continuous.cfg new file mode 100644 index 0000000..a1c8d97 --- /dev/null +++ b/.kokoro/samples/python3.9/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.9/periodic-head.cfg b/.kokoro/samples/python3.9/periodic-head.cfg new file mode 100644 index 0000000..f9cfcd3 --- /dev/null +++ b/.kokoro/samples/python3.9/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-pubsub/.kokoro/test-samples-against-head.sh" +} diff --git a/.kokoro/samples/python3.9/periodic.cfg b/.kokoro/samples/python3.9/periodic.cfg new file mode 100644 index 0000000..50fec96 --- /dev/null +++ b/.kokoro/samples/python3.9/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.9/presubmit.cfg b/.kokoro/samples/python3.9/presubmit.cfg new file mode 100644 index 0000000..a1c8d97 --- /dev/null +++ b/.kokoro/samples/python3.9/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4f00c7c..62eb5a7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v4.0.1 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/.repo-metadata.json b/.repo-metadata.json index fb2b109..5d51e09 100644 --- a/.repo-metadata.json +++ b/.repo-metadata.json @@ -4,7 +4,7 @@ "product_documentation": "https://cloud.google.com/api-gateway", "client_documentation": "https://googleapis.dev/python/apigateway/latest", "issue_tracker": "", - "release_level": "beta", + "release_level": "ga", "language": "python", "library_type": "GAPIC_AUTO", "repo": "googleapis/python-api-gateway", diff --git a/CHANGELOG.md b/CHANGELOG.md index dda1c6b..1398ae9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## [1.0.0](https://www.github.com/googleapis/python-api-gateway/compare/v0.2.0...v1.0.0) (2021-06-24) + + +### Features + +* add always_use_jwt_access ([#38](https://www.github.com/googleapis/python-api-gateway/issues/38)) ([3ffe025](https://www.github.com/googleapis/python-api-gateway/commit/3ffe0253794bb43940293cfed2850567cdb86a9a)) +* bump release level to production/stable ([#25](https://www.github.com/googleapis/python-api-gateway/issues/25)) ([0eadb82](https://www.github.com/googleapis/python-api-gateway/commit/0eadb82978e33261a4aa56b54a546d418658080a)) + + +### Bug Fixes + +* exclude docs and tests from package ([#32](https://www.github.com/googleapis/python-api-gateway/issues/32)) ([8b176e5](https://www.github.com/googleapis/python-api-gateway/commit/8b176e56eaeb04e466f2496e4c043bfaf6e130a6)) + + +### Miscellaneous Chores + +* release as 1.0.0 ([#30](https://www.github.com/googleapis/python-api-gateway/issues/30)) ([4289246](https://www.github.com/googleapis/python-api-gateway/commit/42892467e4c6b594ad844a2ea733b65577d9d382)) + + +### Documentation + +* omit mention of Python 2.7 in 'CONTRIBUTING.rst' ([#1127](https://www.github.com/googleapis/python-api-gateway/issues/1127)) ([#35](https://www.github.com/googleapis/python-api-gateway/issues/35)) ([2f3c1cc](https://www.github.com/googleapis/python-api-gateway/commit/2f3c1cc627db8495de970931fd7c7898bebc9fec)) + ## [0.2.0](https://www.github.com/googleapis/python-api-gateway/compare/v0.1.0...v0.2.0) (2021-05-28) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 92951dc..6d6b45e 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -69,7 +69,6 @@ We use `nox `__ to instrument our tests. - To test your changes, run unit tests with ``nox``:: - $ nox -s unit-2.7 $ nox -s unit-3.8 $ ... @@ -144,7 +143,6 @@ Running System Tests # Run all system tests $ nox -s system-3.8 - $ nox -s system-2.7 # Run a single system test $ nox -s system-3.8 -- -k @@ -152,9 +150,8 @@ Running System Tests .. note:: - System tests are only configured to run under Python 2.7 and - Python 3.8. For expediency, we do not run them in older versions - of Python 3. + System tests are only configured to run under Python 3.8. + For expediency, we do not run them in older versions of Python 3. This alone will not run the tests. You'll need to change some local auth settings and change some configuration in your project to diff --git a/README.rst b/README.rst index 9639c7a..cd22ae8 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ Python Client for API Gateway ============================= -|beta| |pypi| |versions| +|GA| |pypi| |versions| `API Gateway`_ allows you to create, secure, and monitor APIs for Google Cloud serverless back ends, including Cloud Functions, Cloud Run, and App Engine. @@ -12,8 +12,8 @@ means you can better manage cost. - `Client Library Documentation`_ - `Product Documentation`_ -.. |beta| image:: https://img.shields.io/badge/support-beta-orange.svg - :target: https://github.com/googleapis/google-cloud-python/blob/master/README.rst#beta-support +.. |GA| image:: https://img.shields.io/badge/support-ga-gold.svg + :target: https://github.com/googleapis/google-cloud-python/blob/master/README.rst#general-availability .. |pypi| image:: https://img.shields.io/pypi/v/google-cloud-api-gateway.svg :target: https://pypi.org/project/google-cloud-api-gateway/ .. |versions| image:: https://img.shields.io/pypi/pyversions/google-cloud-api-gateway.svg diff --git a/docs/conf.py b/docs/conf.py index fb5e55c..56e6daa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -80,9 +80,9 @@ master_doc = "index" # General information about the project. -project = u"google-cloud-api-gateway" -copyright = u"2019, Google" -author = u"Google APIs" +project = "google-cloud-api-gateway" +copyright = "2019, Google" +author = "Google APIs" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -281,7 +281,7 @@ ( master_doc, "google-cloud-api-gateway.tex", - u"google-cloud-api-gateway Documentation", + "google-cloud-api-gateway Documentation", author, "manual", ) @@ -316,7 +316,7 @@ ( master_doc, "google-cloud-api-gateway", - u"google-cloud-api-gateway Documentation", + "google-cloud-api-gateway Documentation", [author], 1, ) @@ -335,7 +335,7 @@ ( master_doc, "google-cloud-api-gateway", - u"google-cloud-api-gateway Documentation", + "google-cloud-api-gateway Documentation", author, "google-cloud-api-gateway", "google-cloud-api-gateway Library", diff --git a/google/cloud/apigateway_v1/services/api_gateway_service/transports/base.py b/google/cloud/apigateway_v1/services/api_gateway_service/transports/base.py index bec6e35..aaa2d88 100644 --- a/google/cloud/apigateway_v1/services/api_gateway_service/transports/base.py +++ b/google/cloud/apigateway_v1/services/api_gateway_service/transports/base.py @@ -25,6 +25,7 @@ from google.api_core import retry as retries # type: ignore from google.api_core import operations_v1 # type: ignore from google.auth import credentials as ga_credentials # type: ignore +from google.oauth2 import service_account # type: ignore from google.cloud.apigateway_v1.types import apigateway from google.longrunning import operations_pb2 # type: ignore @@ -47,8 +48,6 @@ except pkg_resources.DistributionNotFound: # pragma: NO COVER _GOOGLE_AUTH_VERSION = None -_API_CORE_VERSION = google.api_core.__version__ - class ApiGatewayServiceTransport(abc.ABC): """Abstract transport class for ApiGatewayService.""" @@ -66,6 +65,7 @@ def __init__( scopes: Optional[Sequence[str]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, **kwargs, ) -> None: """Instantiate the transport. @@ -89,6 +89,8 @@ def __init__( API requests. If ``None``, then default info will be used. Generally, you only need to set this if you're developing your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. """ # Save the hostname. Default to port 443 (HTTPS) if none is specified. if ":" not in host: @@ -117,13 +119,20 @@ def __init__( **scopes_kwargs, quota_project_id=quota_project_id ) + # If the credentials is service account credentials, then always try to use self signed JWT. + if ( + always_use_jwt_access + and isinstance(credentials, service_account.Credentials) + and hasattr(service_account.Credentials, "with_always_use_jwt_access") + ): + credentials = credentials.with_always_use_jwt_access(True) + # Save the credentials. self._credentials = credentials - # TODO(busunkim): These two class methods are in the base transport + # TODO(busunkim): This method is in the base transport # to avoid duplicating code across the transport classes. These functions - # should be deleted once the minimum required versions of google-api-core - # and google-auth are increased. + # should be deleted once the minimum required versions of google-auth is increased. # TODO: Remove this function once google-auth >= 1.25.0 is required @classmethod @@ -144,27 +153,6 @@ def _get_scopes_kwargs( return scopes_kwargs - # TODO: Remove this function once google-api-core >= 1.26.0 is required - @classmethod - def _get_self_signed_jwt_kwargs( - cls, host: str, scopes: Optional[Sequence[str]] - ) -> Dict[str, Union[Optional[Sequence[str]], str]]: - """Returns kwargs to pass to grpc_helpers.create_channel depending on the google-api-core version""" - - self_signed_jwt_kwargs: Dict[str, Union[Optional[Sequence[str]], str]] = {} - - if _API_CORE_VERSION and ( - packaging.version.parse(_API_CORE_VERSION) - >= packaging.version.parse("1.26.0") - ): - self_signed_jwt_kwargs["default_scopes"] = cls.AUTH_SCOPES - self_signed_jwt_kwargs["scopes"] = scopes - self_signed_jwt_kwargs["default_host"] = cls.DEFAULT_HOST - else: - self_signed_jwt_kwargs["scopes"] = scopes or cls.AUTH_SCOPES - - return self_signed_jwt_kwargs - def _prep_wrapped_messages(self, client_info): # Precompute the wrapped methods. self._wrapped_methods = { diff --git a/google/cloud/apigateway_v1/services/api_gateway_service/transports/grpc.py b/google/cloud/apigateway_v1/services/api_gateway_service/transports/grpc.py index a6eb85d..c9e5b09 100644 --- a/google/cloud/apigateway_v1/services/api_gateway_service/transports/grpc.py +++ b/google/cloud/apigateway_v1/services/api_gateway_service/transports/grpc.py @@ -153,6 +153,7 @@ def __init__( scopes=scopes, quota_project_id=quota_project_id, client_info=client_info, + always_use_jwt_access=True, ) if not self._grpc_channel: @@ -208,14 +209,14 @@ def create_channel( and ``credentials_file`` are passed. """ - self_signed_jwt_kwargs = cls._get_self_signed_jwt_kwargs(host, scopes) - return grpc_helpers.create_channel( host, credentials=credentials, credentials_file=credentials_file, quota_project_id=quota_project_id, - **self_signed_jwt_kwargs, + default_scopes=cls.AUTH_SCOPES, + scopes=scopes, + default_host=cls.DEFAULT_HOST, **kwargs, ) diff --git a/google/cloud/apigateway_v1/services/api_gateway_service/transports/grpc_asyncio.py b/google/cloud/apigateway_v1/services/api_gateway_service/transports/grpc_asyncio.py index 8a27509..a78d933 100644 --- a/google/cloud/apigateway_v1/services/api_gateway_service/transports/grpc_asyncio.py +++ b/google/cloud/apigateway_v1/services/api_gateway_service/transports/grpc_asyncio.py @@ -81,14 +81,14 @@ def create_channel( aio.Channel: A gRPC AsyncIO channel object. """ - self_signed_jwt_kwargs = cls._get_self_signed_jwt_kwargs(host, scopes) - return grpc_helpers_async.create_channel( host, credentials=credentials, credentials_file=credentials_file, quota_project_id=quota_project_id, - **self_signed_jwt_kwargs, + default_scopes=cls.AUTH_SCOPES, + scopes=scopes, + default_host=cls.DEFAULT_HOST, **kwargs, ) @@ -199,6 +199,7 @@ def __init__( scopes=scopes, quota_project_id=quota_project_id, client_info=client_info, + always_use_jwt_access=True, ) if not self._grpc_channel: diff --git a/setup.py b/setup.py index 13a8bc8..5f6907a 100644 --- a/setup.py +++ b/setup.py @@ -20,12 +20,12 @@ import setuptools # type: ignore name = "google-cloud-api-gateway" -version = "0.2.0" +version = "1.0.0" description = "API Gateway client library" -release_status = "Development Status :: 4 - Beta" +release_status = "Development Status :: 5 - Production/Stable" url = "https://github.com/googleapis/python-api-gateway" dependencies = [ - "google-api-core[grpc] >= 1.22.2, < 2.0.0dev", + "google-api-core[grpc] >= 1.26.0, <2.0.0dev", "proto-plus >= 1.4.0", "packaging >= 14.3", ] @@ -45,7 +45,11 @@ author_email="googleapis-packages@google.com", license="Apache 2.0", url=url, - packages=setuptools.PEP420PackageFinder.find(), + packages=[ + package + for package in setuptools.PEP420PackageFinder.find() + if package.startswith("google") + ], namespace_packages=("google", "google.cloud"), platforms="Posix; MacOS X; Windows", include_package_data=True, diff --git a/testing/constraints-3.6.txt b/testing/constraints-3.6.txt index a85c4a1..2955fcf 100644 --- a/testing/constraints-3.6.txt +++ b/testing/constraints-3.6.txt @@ -19,6 +19,6 @@ # # e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev", # Then this file should have foo==1.14.0 -google-api-core==1.22.2 +google-api-core==1.26.0 proto-plus==1.4.0 packaging==14.3 diff --git a/tests/unit/gapic/apigateway_v1/test_api_gateway_service.py b/tests/unit/gapic/apigateway_v1/test_api_gateway_service.py index 5e28255..8414fc0 100644 --- a/tests/unit/gapic/apigateway_v1/test_api_gateway_service.py +++ b/tests/unit/gapic/apigateway_v1/test_api_gateway_service.py @@ -42,9 +42,6 @@ ) from google.cloud.apigateway_v1.services.api_gateway_service import pagers from google.cloud.apigateway_v1.services.api_gateway_service import transports -from google.cloud.apigateway_v1.services.api_gateway_service.transports.base import ( - _API_CORE_VERSION, -) from google.cloud.apigateway_v1.services.api_gateway_service.transports.base import ( _GOOGLE_AUTH_VERSION, ) @@ -56,8 +53,9 @@ import google.auth -# TODO(busunkim): Once google-api-core >= 1.26.0 is required: -# - Delete all the api-core and auth "less than" test cases +# TODO(busunkim): Once google-auth >= 1.25.0 is required transitively +# through google-api-core: +# - Delete the auth "less than" test cases # - Delete these pytest markers (Make the "greater than or equal to" tests the default). requires_google_auth_lt_1_25_0 = pytest.mark.skipif( packaging.version.parse(_GOOGLE_AUTH_VERSION) >= packaging.version.parse("1.25.0"), @@ -68,16 +66,6 @@ reason="This test requires google-auth >= 1.25.0", ) -requires_api_core_lt_1_26_0 = pytest.mark.skipif( - packaging.version.parse(_API_CORE_VERSION) >= packaging.version.parse("1.26.0"), - reason="This test requires google-api-core < 1.26.0", -) - -requires_api_core_gte_1_26_0 = pytest.mark.skipif( - packaging.version.parse(_API_CORE_VERSION) < packaging.version.parse("1.26.0"), - reason="This test requires google-api-core >= 1.26.0", -) - def client_cert_source_callback(): return b"cert bytes", b"key bytes" @@ -141,6 +129,18 @@ def test_api_gateway_service_client_from_service_account_info(client_class): assert client.transport._host == "apigateway.googleapis.com:443" +@pytest.mark.parametrize( + "client_class", [ApiGatewayServiceClient, ApiGatewayServiceAsyncClient,] +) +def test_api_gateway_service_client_service_account_always_use_jwt(client_class): + with mock.patch.object( + service_account.Credentials, "with_always_use_jwt_access", create=True + ) as use_jwt: + creds = service_account.Credentials(None, None, None) + client = client_class(credentials=creds) + use_jwt.assert_called_with(True) + + @pytest.mark.parametrize( "client_class", [ApiGatewayServiceClient, ApiGatewayServiceAsyncClient,] ) @@ -4356,7 +4356,6 @@ def test_api_gateway_service_transport_auth_adc_old_google_auth(transport_class) (transports.ApiGatewayServiceGrpcAsyncIOTransport, grpc_helpers_async), ], ) -@requires_api_core_gte_1_26_0 def test_api_gateway_service_transport_create_channel(transport_class, grpc_helpers): # If credentials and host are not provided, the transport class should use # ADC credentials. @@ -4385,79 +4384,6 @@ def test_api_gateway_service_transport_create_channel(transport_class, grpc_help ) -@pytest.mark.parametrize( - "transport_class,grpc_helpers", - [ - (transports.ApiGatewayServiceGrpcTransport, grpc_helpers), - (transports.ApiGatewayServiceGrpcAsyncIOTransport, grpc_helpers_async), - ], -) -@requires_api_core_lt_1_26_0 -def test_api_gateway_service_transport_create_channel_old_api_core( - transport_class, grpc_helpers -): - # If credentials and host are not provided, the transport class should use - # ADC credentials. - with mock.patch.object( - google.auth, "default", autospec=True - ) as adc, mock.patch.object( - grpc_helpers, "create_channel", autospec=True - ) as create_channel: - creds = ga_credentials.AnonymousCredentials() - adc.return_value = (creds, None) - transport_class(quota_project_id="octopus") - - create_channel.assert_called_with( - "apigateway.googleapis.com:443", - credentials=creds, - credentials_file=None, - quota_project_id="octopus", - scopes=("https://www.googleapis.com/auth/cloud-platform",), - ssl_credentials=None, - options=[ - ("grpc.max_send_message_length", -1), - ("grpc.max_receive_message_length", -1), - ], - ) - - -@pytest.mark.parametrize( - "transport_class,grpc_helpers", - [ - (transports.ApiGatewayServiceGrpcTransport, grpc_helpers), - (transports.ApiGatewayServiceGrpcAsyncIOTransport, grpc_helpers_async), - ], -) -@requires_api_core_lt_1_26_0 -def test_api_gateway_service_transport_create_channel_user_scopes( - transport_class, grpc_helpers -): - # If credentials and host are not provided, the transport class should use - # ADC credentials. - with mock.patch.object( - google.auth, "default", autospec=True - ) as adc, mock.patch.object( - grpc_helpers, "create_channel", autospec=True - ) as create_channel: - creds = ga_credentials.AnonymousCredentials() - adc.return_value = (creds, None) - - transport_class(quota_project_id="octopus", scopes=["1", "2"]) - - create_channel.assert_called_with( - "apigateway.googleapis.com:443", - credentials=creds, - credentials_file=None, - quota_project_id="octopus", - scopes=["1", "2"], - ssl_credentials=None, - options=[ - ("grpc.max_send_message_length", -1), - ("grpc.max_receive_message_length", -1), - ], - ) - - @pytest.mark.parametrize( "transport_class", [