diff --git a/.coveragerc b/.coveragerc index a724114..535122d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,7 +2,6 @@ branch = True [report] -fail_under = 100 show_missing = True omit = google/analytics/data/__init__.py diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index da616c9..e2b39f9 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 + digest: sha256:99d90d097e4a4710cc8658ee0b5b963f4426d0e424819787c3ac1405c9a26719 diff --git a/.kokoro/docs/common.cfg b/.kokoro/docs/common.cfg index cc08746..55bb9de 100644 --- a/.kokoro/docs/common.cfg +++ b/.kokoro/docs/common.cfg @@ -30,7 +30,7 @@ env_vars: { env_vars: { key: "V2_STAGING_BUCKET" - value: "docs-staging-v2" + value: "docs-staging-v2-staging" } # It will upload the docker image after successful builds. diff --git a/.kokoro/samples/python3.9/common.cfg b/.kokoro/samples/python3.9/common.cfg new file mode 100644 index 0000000..a024f66 --- /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-analytics-data/.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-analytics-data/.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/CHANGELOG.md b/CHANGELOG.md index c8d7e78..ef830c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [0.7.0](https://www.github.com/googleapis/python-analytics-data/compare/v0.6.1...v0.7.0) (2021-07-10) + + +### Features + +* add `minute_ranges` field to `RunRealtimeReportRequest` object ([#101](https://www.github.com/googleapis/python-analytics-data/issues/101)) ([8523e6a](https://www.github.com/googleapis/python-analytics-data/commit/8523e6ad87f126766576d71b05d68478960bd10b)) +* add always_use_jwt_access ([1678019](https://www.github.com/googleapis/python-analytics-data/commit/16780195811dd93333afe6e27b674dd5e78705a3)) + + +### Bug Fixes + +* disable always_use_jwt_access ([#97](https://www.github.com/googleapis/python-analytics-data/issues/97)) ([1678019](https://www.github.com/googleapis/python-analytics-data/commit/16780195811dd93333afe6e27b674dd5e78705a3)) + + +### Documentation + +* document the increase of the number of allowed dimensions in a report query ([8523e6a](https://www.github.com/googleapis/python-analytics-data/commit/8523e6ad87f126766576d71b05d68478960bd10b)) +* omit mention of Python 2.7 in 'CONTRIBUTING.rst' ([#1127](https://www.github.com/googleapis/python-analytics-data/issues/1127)) ([#87](https://www.github.com/googleapis/python-analytics-data/issues/87)) ([6e30719](https://www.github.com/googleapis/python-analytics-data/commit/6e30719c4158c0e2e7580bff373e94cf7dd91475)), closes [#1126](https://www.github.com/googleapis/python-analytics-data/issues/1126) + ### [0.6.1](https://www.github.com/googleapis/python-analytics-data/compare/v0.6.0...v0.6.1) (2021-06-16) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 559f9db..26d9f67 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -68,15 +68,12 @@ Using ``nox`` We use `nox `__ to instrument our tests. - To test your changes, run unit tests with ``nox``:: + $ nox -s unit - $ nox -s unit-2.7 - $ nox -s unit-3.8 - $ ... +- To run a single unit test:: -- Args to pytest can be passed through the nox command separated by a `--`. For - example, to run a single test:: + $ nox -s unit-3.9 -- -k - $ nox -s unit-3.8 -- -k .. note:: @@ -143,8 +140,7 @@ Running System Tests - To run system tests, you can execute:: # Run all system tests - $ nox -s system-3.8 - $ nox -s system-2.7 + $ nox -s system # Run a single system test $ nox -s system-3.8 -- -k @@ -152,9 +148,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 @@ -218,8 +213,8 @@ Supported versions can be found in our ``noxfile.py`` `config`_. .. _config: https://github.com/googleapis/python-analytics-data/blob/master/noxfile.py -We also explicitly decided to support Python 3 beginning with version -3.6. Reasons for this include: +We also explicitly decided to support Python 3 beginning with version 3.6. +Reasons for this include: - Encouraging use of newest versions of Python 3 - Taking the lead of `prominent`_ open-source `projects`_ diff --git a/docs/conf.py b/docs/conf.py index 40cb0ab..2593b6f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -80,9 +80,9 @@ master_doc = "index" # General information about the project. -project = u"google-analytics-data" -copyright = u"2019, Google" -author = u"Google APIs" +project = "google-analytics-data" +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-analytics-data.tex", - u"google-analytics-data Documentation", + "google-analytics-data Documentation", author, "manual", ) @@ -316,7 +316,7 @@ ( master_doc, "google-analytics-data", - u"google-analytics-data Documentation", + "google-analytics-data Documentation", [author], 1, ) @@ -335,7 +335,7 @@ ( master_doc, "google-analytics-data", - u"google-analytics-data Documentation", + "google-analytics-data Documentation", author, "google-analytics-data", "google-analytics-data Library", diff --git a/google/analytics/data/__init__.py b/google/analytics/data/__init__.py index 7f10e59..f6e294d 100644 --- a/google/analytics/data/__init__.py +++ b/google/analytics/data/__init__.py @@ -60,6 +60,7 @@ from google.analytics.data_v1beta.types.data import MetricHeader from google.analytics.data_v1beta.types.data import MetricMetadata from google.analytics.data_v1beta.types.data import MetricValue +from google.analytics.data_v1beta.types.data import MinuteRange from google.analytics.data_v1beta.types.data import NumericValue from google.analytics.data_v1beta.types.data import OrderBy from google.analytics.data_v1beta.types.data import Pivot @@ -104,6 +105,7 @@ "MetricHeader", "MetricMetadata", "MetricValue", + "MinuteRange", "NumericValue", "OrderBy", "Pivot", diff --git a/google/analytics/data_v1alpha/services/alpha_analytics_data/transports/base.py b/google/analytics/data_v1alpha/services/alpha_analytics_data/transports/base.py index db59832..179c040 100644 --- a/google/analytics/data_v1alpha/services/alpha_analytics_data/transports/base.py +++ b/google/analytics/data_v1alpha/services/alpha_analytics_data/transports/base.py @@ -24,6 +24,7 @@ from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore from google.auth import credentials as ga_credentials # type: ignore +from google.oauth2 import service_account # type: ignore from google.analytics.data_v1alpha.types import analytics_data_api @@ -43,8 +44,6 @@ except pkg_resources.DistributionNotFound: # pragma: NO COVER _GOOGLE_AUTH_VERSION = None -_API_CORE_VERSION = google.api_core.__version__ - class AlphaAnalyticsDataTransport(abc.ABC): """Abstract transport class for AlphaAnalyticsData.""" @@ -65,6 +64,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. @@ -88,6 +88,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: @@ -97,7 +99,7 @@ def __init__( scopes_kwargs = self._get_scopes_kwargs(self._host, scopes) # Save the scopes. - self._scopes = scopes or self.AUTH_SCOPES + self._scopes = scopes # If no credentials are provided, then determine the appropriate # defaults. @@ -116,13 +118,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 @@ -143,27 +152,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/analytics/data_v1alpha/services/alpha_analytics_data/transports/grpc.py b/google/analytics/data_v1alpha/services/alpha_analytics_data/transports/grpc.py index 3e6e1b8..e044c95 100644 --- a/google/analytics/data_v1alpha/services/alpha_analytics_data/transports/grpc.py +++ b/google/analytics/data_v1alpha/services/alpha_analytics_data/transports/grpc.py @@ -57,6 +57,7 @@ def __init__( client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, ) -> None: """Instantiate the transport. @@ -97,6 +98,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. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport @@ -149,6 +152,7 @@ def __init__( scopes=scopes, quota_project_id=quota_project_id, client_info=client_info, + always_use_jwt_access=always_use_jwt_access, ) if not self._grpc_channel: @@ -204,14 +208,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/analytics/data_v1alpha/services/alpha_analytics_data/transports/grpc_asyncio.py b/google/analytics/data_v1alpha/services/alpha_analytics_data/transports/grpc_asyncio.py index c63da4a..9fa3e67 100644 --- a/google/analytics/data_v1alpha/services/alpha_analytics_data/transports/grpc_asyncio.py +++ b/google/analytics/data_v1alpha/services/alpha_analytics_data/transports/grpc_asyncio.py @@ -78,14 +78,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, ) @@ -103,6 +103,7 @@ def __init__( client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id=None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, ) -> None: """Instantiate the transport. @@ -144,6 +145,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. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -195,6 +198,7 @@ def __init__( scopes=scopes, quota_project_id=quota_project_id, client_info=client_info, + always_use_jwt_access=always_use_jwt_access, ) if not self._grpc_channel: diff --git a/google/analytics/data_v1beta/__init__.py b/google/analytics/data_v1beta/__init__.py index b7282cc..0f581f7 100644 --- a/google/analytics/data_v1beta/__init__.py +++ b/google/analytics/data_v1beta/__init__.py @@ -46,6 +46,7 @@ from .types.data import MetricHeader from .types.data import MetricMetadata from .types.data import MetricValue +from .types.data import MinuteRange from .types.data import NumericValue from .types.data import OrderBy from .types.data import Pivot @@ -86,6 +87,7 @@ "MetricMetadata", "MetricType", "MetricValue", + "MinuteRange", "NumericValue", "OrderBy", "Pivot", diff --git a/google/analytics/data_v1beta/services/beta_analytics_data/transports/base.py b/google/analytics/data_v1beta/services/beta_analytics_data/transports/base.py index a8d1ff4..0b359f0 100644 --- a/google/analytics/data_v1beta/services/beta_analytics_data/transports/base.py +++ b/google/analytics/data_v1beta/services/beta_analytics_data/transports/base.py @@ -24,6 +24,7 @@ from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore from google.auth import credentials as ga_credentials # type: ignore +from google.oauth2 import service_account # type: ignore from google.analytics.data_v1beta.types import analytics_data_api @@ -43,8 +44,6 @@ except pkg_resources.DistributionNotFound: # pragma: NO COVER _GOOGLE_AUTH_VERSION = None -_API_CORE_VERSION = google.api_core.__version__ - class BetaAnalyticsDataTransport(abc.ABC): """Abstract transport class for BetaAnalyticsData.""" @@ -65,6 +64,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. @@ -88,6 +88,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: @@ -97,7 +99,7 @@ def __init__( scopes_kwargs = self._get_scopes_kwargs(self._host, scopes) # Save the scopes. - self._scopes = scopes or self.AUTH_SCOPES + self._scopes = scopes # If no credentials are provided, then determine the appropriate # defaults. @@ -116,13 +118,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 @@ -143,27 +152,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/analytics/data_v1beta/services/beta_analytics_data/transports/grpc.py b/google/analytics/data_v1beta/services/beta_analytics_data/transports/grpc.py index 224a21a..b283aff 100644 --- a/google/analytics/data_v1beta/services/beta_analytics_data/transports/grpc.py +++ b/google/analytics/data_v1beta/services/beta_analytics_data/transports/grpc.py @@ -57,6 +57,7 @@ def __init__( client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, ) -> None: """Instantiate the transport. @@ -97,6 +98,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. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport @@ -149,6 +152,7 @@ def __init__( scopes=scopes, quota_project_id=quota_project_id, client_info=client_info, + always_use_jwt_access=always_use_jwt_access, ) if not self._grpc_channel: @@ -204,14 +208,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/analytics/data_v1beta/services/beta_analytics_data/transports/grpc_asyncio.py b/google/analytics/data_v1beta/services/beta_analytics_data/transports/grpc_asyncio.py index 4e697a9..8ab0eab 100644 --- a/google/analytics/data_v1beta/services/beta_analytics_data/transports/grpc_asyncio.py +++ b/google/analytics/data_v1beta/services/beta_analytics_data/transports/grpc_asyncio.py @@ -78,14 +78,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, ) @@ -103,6 +103,7 @@ def __init__( client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id=None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, ) -> None: """Instantiate the transport. @@ -144,6 +145,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. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -195,6 +198,7 @@ def __init__( scopes=scopes, quota_project_id=quota_project_id, client_info=client_info, + always_use_jwt_access=always_use_jwt_access, ) if not self._grpc_channel: diff --git a/google/analytics/data_v1beta/types/__init__.py b/google/analytics/data_v1beta/types/__init__.py index 3ccd5c9..80f4321 100644 --- a/google/analytics/data_v1beta/types/__init__.py +++ b/google/analytics/data_v1beta/types/__init__.py @@ -45,6 +45,7 @@ MetricHeader, MetricMetadata, MetricValue, + MinuteRange, NumericValue, OrderBy, Pivot, @@ -88,6 +89,7 @@ "MetricHeader", "MetricMetadata", "MetricValue", + "MinuteRange", "NumericValue", "OrderBy", "Pivot", diff --git a/google/analytics/data_v1beta/types/analytics_data_api.py b/google/analytics/data_v1beta/types/analytics_data_api.py index 70fd077..8a188ac 100644 --- a/google/analytics/data_v1beta/types/analytics_data_api.py +++ b/google/analytics/data_v1beta/types/analytics_data_api.py @@ -547,6 +547,15 @@ class RunRealtimeReportRequest(proto.Message): Toggles whether to return the current state of this Analytics Property's Realtime quota. Quota is returned in `PropertyQuota <#PropertyQuota>`__. + minute_ranges (Sequence[google.analytics.data_v1beta.types.MinuteRange]): + The minute ranges of event data to read. If + unspecified, one minute range for the last 30 + minutes will be used. If multiple minute ranges + are requested, each response row will contain a + zero based minute range index. If two minute + ranges overlap, the event data for the + overlapping minutes is included in the response + rows for both minute ranges. """ property = proto.Field(proto.STRING, number=1,) @@ -562,6 +571,9 @@ class RunRealtimeReportRequest(proto.Message): ) order_bys = proto.RepeatedField(proto.MESSAGE, number=8, message=data.OrderBy,) return_property_quota = proto.Field(proto.BOOL, number=9,) + minute_ranges = proto.RepeatedField( + proto.MESSAGE, number=10, message=data.MinuteRange, + ) class RunRealtimeReportResponse(proto.Message): diff --git a/google/analytics/data_v1beta/types/data.py b/google/analytics/data_v1beta/types/data.py index 573f56e..58c19aa 100644 --- a/google/analytics/data_v1beta/types/data.py +++ b/google/analytics/data_v1beta/types/data.py @@ -22,6 +22,7 @@ "MetricAggregation", "MetricType", "DateRange", + "MinuteRange", "Dimension", "DimensionExpression", "Metric", @@ -107,12 +108,54 @@ class DateRange(proto.Message): name = proto.Field(proto.STRING, number=3,) +class MinuteRange(proto.Message): + r"""A contiguous set of minutes: startMinutesAgo, startMinutesAgo + + 1, ..., endMinutesAgo. Requests are allowed up to 2 minute + ranges. + + Attributes: + start_minutes_ago (int): + The inclusive start minute for the query as a number of + minutes before now. For example, ``"startMinutesAgo": 29`` + specifies the report should include event data from 29 + minutes ago and after. Cannot be after ``endMinutesAgo``. + + If unspecified, ``startMinutesAgo`` is defaulted to 29. + Standard Analytics properties can request up to the last 30 + minutes of event data (``startMinutesAgo <= 29``), and 360 + Analytics properties can request up to the last 60 minutes + of event data (``startMinutesAgo <= 59``). + end_minutes_ago (int): + The inclusive end minute for the query as a number of + minutes before now. Cannot be before ``startMinutesAgo``. + For example, ``"endMinutesAgo": 15`` specifies the report + should include event data from prior to 15 minutes ago. + + If unspecified, ``endMinutesAgo`` is defaulted to 0. + Standard Analytics properties can request any minute in the + last 30 minutes of event data (``endMinutesAgo <= 29``), and + 360 Analytics properties can request any minute in the last + 60 minutes of event data (``endMinutesAgo <= 59``). + name (str): + Assigns a name to this minute range. The dimension + ``dateRange`` is valued to this name in a report response. + If set, cannot begin with ``date_range_`` or ``RESERVED_``. + If not set, minute ranges are named by their zero based + index in the request: ``date_range_0``, ``date_range_1``, + etc. + """ + + start_minutes_ago = proto.Field(proto.INT32, number=1, optional=True,) + end_minutes_ago = proto.Field(proto.INT32, number=2, optional=True,) + name = proto.Field(proto.STRING, number=3,) + + class Dimension(proto.Message): r"""Dimensions are attributes of your data. For example, the dimension city indicates the city from which an event originates. Dimension values in report responses are strings; for example, city could be "Paris" or "New York". Requests are - allowed up to 8 dimensions. + allowed up to 9 dimensions. Attributes: name (str): diff --git a/owlbot.py b/owlbot.py index 4e47074..3121640 100644 --- a/owlbot.py +++ b/owlbot.py @@ -41,7 +41,9 @@ # ---------------------------------------------------------------------------- templated_files = common.py_library(cov_level=99, microgenerator=True) s.move( - templated_files, excludes=[".coveragerc"] + templated_files, excludes=[ + ".coveragerc" + ] ) # the microgenerator has a good coveragerc file # fix coverage target @@ -58,4 +60,11 @@ """``^[a-zA-Z0-9_]$``""", ) -s.shell.run(["nox", "-s", "blacken"], hide_output=False) \ No newline at end of file +# Block pushing non-cloud libraries to Cloud RAD +s.replace( + ".kokoro/docs/common.cfg", + r'value: "docs-staging-v2"', + r'value: "docs-staging-v2-staging"' +) + +s.shell.run(["nox", "-s", "blacken"], hide_output=False) diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index 7700f43..f5a566e 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,2 +1,2 @@ -google-analytics-data==0.6.0 +google-analytics-data==0.6.1 google-auth-oauthlib==0.4.4 \ No newline at end of file diff --git a/setup.py b/setup.py index ae64bdd..3927709 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ import os import setuptools # type: ignore -version = "0.6.1" +version = "0.7.0" package_root = os.path.abspath(os.path.dirname(__file__)) @@ -45,7 +45,7 @@ platforms="Posix; MacOS X; Windows", include_package_data=True, install_requires=( - "google-api-core[grpc] >= 1.22.2, < 2.0.0dev", + "google-api-core[grpc] >= 1.26.0, < 2.0.0dev", "libcst >= 0.2.5", "proto-plus >= 1.4.0", "packaging >= 14.3", diff --git a/testing/constraints-3.6.txt b/testing/constraints-3.6.txt index 33059b8..9371b39 100644 --- a/testing/constraints-3.6.txt +++ b/testing/constraints-3.6.txt @@ -5,7 +5,7 @@ # # 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 libcst==0.2.5 proto-plus==1.4.0 packaging==14.3 diff --git a/tests/unit/gapic/data_v1alpha/test_alpha_analytics_data.py b/tests/unit/gapic/data_v1alpha/test_alpha_analytics_data.py index 72848d7..fb8e09e 100644 --- a/tests/unit/gapic/data_v1alpha/test_alpha_analytics_data.py +++ b/tests/unit/gapic/data_v1alpha/test_alpha_analytics_data.py @@ -31,9 +31,6 @@ AlphaAnalyticsDataClient, ) from google.analytics.data_v1alpha.services.alpha_analytics_data import transports -from google.analytics.data_v1alpha.services.alpha_analytics_data.transports.base import ( - _API_CORE_VERSION, -) from google.analytics.data_v1alpha.services.alpha_analytics_data.transports.base import ( _GOOGLE_AUTH_VERSION, ) @@ -50,8 +47,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"), @@ -62,16 +60,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" @@ -135,6 +123,36 @@ def test_alpha_analytics_data_client_from_service_account_info(client_class): assert client.transport._host == "analyticsdata.googleapis.com:443" +@pytest.mark.parametrize( + "client_class", [AlphaAnalyticsDataClient, AlphaAnalyticsDataAsyncClient,] +) +def test_alpha_analytics_data_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_not_called() + + +@pytest.mark.parametrize( + "transport_class,transport_name", + [ + (transports.AlphaAnalyticsDataGrpcTransport, "grpc"), + (transports.AlphaAnalyticsDataGrpcAsyncIOTransport, "grpc_asyncio"), + ], +) +def test_alpha_analytics_data_client_service_account_always_use_jwt_true( + transport_class, transport_name +): + with mock.patch.object( + service_account.Credentials, "with_always_use_jwt_access", create=True + ) as use_jwt: + creds = service_account.Credentials(None, None, None) + transport = transport_class(credentials=creds, always_use_jwt_access=True) + use_jwt.assert_called_once_with(True) + + @pytest.mark.parametrize( "client_class", [AlphaAnalyticsDataClient, AlphaAnalyticsDataAsyncClient,] ) @@ -1428,7 +1446,6 @@ def test_alpha_analytics_data_transport_auth_adc_old_google_auth(transport_class (transports.AlphaAnalyticsDataGrpcAsyncIOTransport, grpc_helpers_async), ], ) -@requires_api_core_gte_1_26_0 def test_alpha_analytics_data_transport_create_channel(transport_class, grpc_helpers): # If credentials and host are not provided, the transport class should use # ADC credentials. @@ -1460,82 +1477,6 @@ def test_alpha_analytics_data_transport_create_channel(transport_class, grpc_hel ) -@pytest.mark.parametrize( - "transport_class,grpc_helpers", - [ - (transports.AlphaAnalyticsDataGrpcTransport, grpc_helpers), - (transports.AlphaAnalyticsDataGrpcAsyncIOTransport, grpc_helpers_async), - ], -) -@requires_api_core_lt_1_26_0 -def test_alpha_analytics_data_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( - "analyticsdata.googleapis.com:443", - credentials=creds, - credentials_file=None, - quota_project_id="octopus", - scopes=( - "https://www.googleapis.com/auth/analytics", - "https://www.googleapis.com/auth/analytics.readonly", - ), - ssl_credentials=None, - options=[ - ("grpc.max_send_message_length", -1), - ("grpc.max_receive_message_length", -1), - ], - ) - - -@pytest.mark.parametrize( - "transport_class,grpc_helpers", - [ - (transports.AlphaAnalyticsDataGrpcTransport, grpc_helpers), - (transports.AlphaAnalyticsDataGrpcAsyncIOTransport, grpc_helpers_async), - ], -) -@requires_api_core_lt_1_26_0 -def test_alpha_analytics_data_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( - "analyticsdata.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", [ @@ -1560,10 +1501,7 @@ def test_alpha_analytics_data_grpc_transport_client_cert_source_for_mtls( "squid.clam.whelk:443", credentials=cred, credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/analytics", - "https://www.googleapis.com/auth/analytics.readonly", - ), + scopes=None, ssl_credentials=mock_ssl_channel_creds, quota_project_id=None, options=[ @@ -1672,10 +1610,7 @@ def test_alpha_analytics_data_transport_channel_mtls_with_client_cert_source( "mtls.squid.clam.whelk:443", credentials=cred, credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/analytics", - "https://www.googleapis.com/auth/analytics.readonly", - ), + scopes=None, ssl_credentials=mock_ssl_cred, quota_project_id=None, options=[ @@ -1722,10 +1657,7 @@ def test_alpha_analytics_data_transport_channel_mtls_with_adc(transport_class): "mtls.squid.clam.whelk:443", credentials=mock_cred, credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/analytics", - "https://www.googleapis.com/auth/analytics.readonly", - ), + scopes=None, ssl_credentials=mock_ssl_cred, quota_project_id=None, options=[ diff --git a/tests/unit/gapic/data_v1beta/test_beta_analytics_data.py b/tests/unit/gapic/data_v1beta/test_beta_analytics_data.py index c22e2f8..8fe99c0 100644 --- a/tests/unit/gapic/data_v1beta/test_beta_analytics_data.py +++ b/tests/unit/gapic/data_v1beta/test_beta_analytics_data.py @@ -31,9 +31,6 @@ BetaAnalyticsDataClient, ) from google.analytics.data_v1beta.services.beta_analytics_data import transports -from google.analytics.data_v1beta.services.beta_analytics_data.transports.base import ( - _API_CORE_VERSION, -) from google.analytics.data_v1beta.services.beta_analytics_data.transports.base import ( _GOOGLE_AUTH_VERSION, ) @@ -50,8 +47,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"), @@ -62,16 +60,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" @@ -135,6 +123,36 @@ def test_beta_analytics_data_client_from_service_account_info(client_class): assert client.transport._host == "analyticsdata.googleapis.com:443" +@pytest.mark.parametrize( + "client_class", [BetaAnalyticsDataClient, BetaAnalyticsDataAsyncClient,] +) +def test_beta_analytics_data_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_not_called() + + +@pytest.mark.parametrize( + "transport_class,transport_name", + [ + (transports.BetaAnalyticsDataGrpcTransport, "grpc"), + (transports.BetaAnalyticsDataGrpcAsyncIOTransport, "grpc_asyncio"), + ], +) +def test_beta_analytics_data_client_service_account_always_use_jwt_true( + transport_class, transport_name +): + with mock.patch.object( + service_account.Credentials, "with_always_use_jwt_access", create=True + ) as use_jwt: + creds = service_account.Credentials(None, None, None) + transport = transport_class(credentials=creds, always_use_jwt_access=True) + use_jwt.assert_called_once_with(True) + + @pytest.mark.parametrize( "client_class", [BetaAnalyticsDataClient, BetaAnalyticsDataAsyncClient,] ) @@ -1660,7 +1678,6 @@ def test_beta_analytics_data_transport_auth_adc_old_google_auth(transport_class) (transports.BetaAnalyticsDataGrpcAsyncIOTransport, grpc_helpers_async), ], ) -@requires_api_core_gte_1_26_0 def test_beta_analytics_data_transport_create_channel(transport_class, grpc_helpers): # If credentials and host are not provided, the transport class should use # ADC credentials. @@ -1692,82 +1709,6 @@ def test_beta_analytics_data_transport_create_channel(transport_class, grpc_help ) -@pytest.mark.parametrize( - "transport_class,grpc_helpers", - [ - (transports.BetaAnalyticsDataGrpcTransport, grpc_helpers), - (transports.BetaAnalyticsDataGrpcAsyncIOTransport, grpc_helpers_async), - ], -) -@requires_api_core_lt_1_26_0 -def test_beta_analytics_data_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( - "analyticsdata.googleapis.com:443", - credentials=creds, - credentials_file=None, - quota_project_id="octopus", - scopes=( - "https://www.googleapis.com/auth/analytics", - "https://www.googleapis.com/auth/analytics.readonly", - ), - ssl_credentials=None, - options=[ - ("grpc.max_send_message_length", -1), - ("grpc.max_receive_message_length", -1), - ], - ) - - -@pytest.mark.parametrize( - "transport_class,grpc_helpers", - [ - (transports.BetaAnalyticsDataGrpcTransport, grpc_helpers), - (transports.BetaAnalyticsDataGrpcAsyncIOTransport, grpc_helpers_async), - ], -) -@requires_api_core_lt_1_26_0 -def test_beta_analytics_data_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( - "analyticsdata.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", [ @@ -1792,10 +1733,7 @@ def test_beta_analytics_data_grpc_transport_client_cert_source_for_mtls( "squid.clam.whelk:443", credentials=cred, credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/analytics", - "https://www.googleapis.com/auth/analytics.readonly", - ), + scopes=None, ssl_credentials=mock_ssl_channel_creds, quota_project_id=None, options=[ @@ -1904,10 +1842,7 @@ def test_beta_analytics_data_transport_channel_mtls_with_client_cert_source( "mtls.squid.clam.whelk:443", credentials=cred, credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/analytics", - "https://www.googleapis.com/auth/analytics.readonly", - ), + scopes=None, ssl_credentials=mock_ssl_cred, quota_project_id=None, options=[ @@ -1954,10 +1889,7 @@ def test_beta_analytics_data_transport_channel_mtls_with_adc(transport_class): "mtls.squid.clam.whelk:443", credentials=mock_cred, credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/analytics", - "https://www.googleapis.com/auth/analytics.readonly", - ), + scopes=None, ssl_credentials=mock_ssl_cred, quota_project_id=None, options=[