diff --git a/.gitignore b/.gitignore
index 8c18b5e7..157bfb33 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,7 @@ pip-log.txt
.nox
.cache
.pytest_cache
+pytype_output
# Mac
@@ -58,6 +59,3 @@ system_tests/local_test_setup
# Make sure a generated file isn't accidentally committed.
pylintrc
pylintrc.test
-
-# pytype
-pytype_output
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5d4ea78e..ba764fa0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,23 @@
[1]: https://pypi.org/project/google-api-core/#history
+## [1.22.0](https://www.github.com/googleapis/python-api-core/compare/v1.21.0...v1.22.0) (2020-07-21)
+
+
+### Features
+
+* allow quota project to be passed to create_channel ([#58](https://www.github.com/googleapis/python-api-core/issues/58)) ([e2d9a7b](https://www.github.com/googleapis/python-api-core/commit/e2d9a7b209b7dfab300dc848fabbae8f42a2ab19))
+
+
+### Bug Fixes
+
+* _determine_timeout problem handling float type timeout ([#64](https://www.github.com/googleapis/python-api-core/issues/64)) ([2010373](https://www.github.com/googleapis/python-api-core/commit/2010373b27536d1191175624b297a709d70153fa))
+
+
+### Documentation
+
+* change the documentation for using 'six.moves.collections_abc.Mapping' instead of 'dict' in 'client_options.from_dict()' ([#53](https://www.github.com/googleapis/python-api-core/issues/53)) ([c890675](https://www.github.com/googleapis/python-api-core/commit/c890675dc9ebc084f105be81dc81c048f4f599ea))
+
## [1.21.0](https://www.github.com/googleapis/python-api-core/compare/v1.20.1...v1.21.0) (2020-06-18)
diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html
index 228529ef..6316a537 100644
--- a/docs/_templates/layout.html
+++ b/docs/_templates/layout.html
@@ -21,8 +21,8 @@
- On January 1, 2020 this library will no longer support Python 2 on the latest released version.
- Previously released library versions will continue to be available. For more information please
+ As of January 1, 2020 this library no longer supports Python 2 on the latest released version.
+ Library versions released prior to that date will continue to be available. For more information please
visit
Python 2 support on Google Cloud.
{% block body %} {% endblock %}
diff --git a/docs/conf.py b/docs/conf.py
index dd92a749..a53c37db 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -43,7 +43,7 @@
# autodoc/autosummary flags
autoclass_content = "both"
-autodoc_default_flags = ["members"]
+autodoc_default_options = {"members": True}
autosummary_generate = True
diff --git a/google/api_core/client_options.py b/google/api_core/client_options.py
index d272f40f..57000e95 100644
--- a/google/api_core/client_options.py
+++ b/google/api_core/client_options.py
@@ -33,7 +33,7 @@ def get_client_cert():
client = ImageAnnotatorClient(client_options=options)
-You can also pass a dictionary.
+You can also pass a mapping object.
.. code-block:: python
@@ -98,10 +98,10 @@ def __repr__(self):
def from_dict(options):
- """Construct a client options object from a dictionary.
+ """Construct a client options object from a mapping object.
Args:
- options (Dict[str, Any]): A dictionary with client options.
+ options (six.moves.collections_abc.Mapping): A mapping object with client options.
See the docstring for ClientOptions for details on valid arguments.
"""
diff --git a/google/api_core/gapic_v1/method.py b/google/api_core/gapic_v1/method.py
index 49982c03..8bf82569 100644
--- a/google/api_core/gapic_v1/method.py
+++ b/google/api_core/gapic_v1/method.py
@@ -61,6 +61,13 @@ def _determine_timeout(default_timeout, specified_timeout, retry):
Returns:
Optional[Timeout]: The timeout to apply to the method or ``None``.
"""
+ # If timeout is specified as a number instead of a Timeout instance,
+ # convert it to a ConstantTimeout.
+ if isinstance(specified_timeout, (int, float)):
+ specified_timeout = timeout.ConstantTimeout(specified_timeout)
+ if isinstance(default_timeout, (int, float)):
+ default_timeout = timeout.ConstantTimeout(default_timeout)
+
if specified_timeout is DEFAULT:
specified_timeout = default_timeout
@@ -78,12 +85,7 @@ def _determine_timeout(default_timeout, specified_timeout, retry):
else:
return default_timeout
- # If timeout is specified as a number instead of a Timeout instance,
- # convert it to a ConstantTimeout.
- if isinstance(specified_timeout, (int, float)):
- return timeout.ConstantTimeout(specified_timeout)
- else:
- return specified_timeout
+ return specified_timeout
class _GapicCallable(object):
diff --git a/google/api_core/grpc_helpers.py b/google/api_core/grpc_helpers.py
index 2203968e..dfc8442d 100644
--- a/google/api_core/grpc_helpers.py
+++ b/google/api_core/grpc_helpers.py
@@ -176,7 +176,12 @@ def wrap_errors(callable_):
return _wrap_unary_errors(callable_)
-def _create_composite_credentials(credentials=None, credentials_file=None, scopes=None, ssl_credentials=None):
+def _create_composite_credentials(
+ credentials=None,
+ credentials_file=None,
+ scopes=None,
+ ssl_credentials=None,
+ quota_project_id=None):
"""Create the composite credentials for secure channels.
Args:
@@ -191,6 +196,7 @@ def _create_composite_credentials(credentials=None, credentials_file=None, scope
are passed to :func:`google.auth.default`.
ssl_credentials (grpc.ChannelCredentials): Optional SSL channel
credentials. This can be used to specify different certificates.
+ quota_project_id (str): An optional project to use for billing and quota.
Returns:
grpc.ChannelCredentials: The composed channel credentials object.
@@ -210,6 +216,9 @@ def _create_composite_credentials(credentials=None, credentials_file=None, scope
else:
credentials, _ = google.auth.default(scopes=scopes)
+ if quota_project_id:
+ credentials = credentials.with_quota_project(quota_project_id)
+
request = google.auth.transport.requests.Request()
# Create the metadata plugin for inserting the authorization header.
@@ -229,7 +238,14 @@ def _create_composite_credentials(credentials=None, credentials_file=None, scope
)
-def create_channel(target, credentials=None, scopes=None, ssl_credentials=None, credentials_file=None, **kwargs):
+def create_channel(
+ target,
+ credentials=None,
+ scopes=None,
+ ssl_credentials=None,
+ credentials_file=None,
+ quota_project_id=None,
+ **kwargs):
"""Create a secure channel with credentials.
Args:
@@ -245,6 +261,7 @@ def create_channel(target, credentials=None, scopes=None, ssl_credentials=None,
credentials_file (str): A file with credentials that can be loaded with
:func:`google.auth.load_credentials_from_file`. This argument is
mutually exclusive with credentials.
+ quota_project_id (str): An optional project to use for billing and quota.
kwargs: Additional key-word args passed to
:func:`grpc_gcp.secure_channel` or :func:`grpc.secure_channel`.
@@ -259,7 +276,8 @@ def create_channel(target, credentials=None, scopes=None, ssl_credentials=None,
credentials=credentials,
credentials_file=credentials_file,
scopes=scopes,
- ssl_credentials=ssl_credentials
+ ssl_credentials=ssl_credentials,
+ quota_project_id=quota_project_id,
)
if HAS_GRPC_GCP:
diff --git a/google/api_core/grpc_helpers_async.py b/google/api_core/grpc_helpers_async.py
index 1dfe8b9a..9a994e9f 100644
--- a/google/api_core/grpc_helpers_async.py
+++ b/google/api_core/grpc_helpers_async.py
@@ -206,7 +206,14 @@ def wrap_errors(callable_):
return _wrap_stream_errors(callable_)
-def create_channel(target, credentials=None, scopes=None, ssl_credentials=None, credentials_file=None, **kwargs):
+def create_channel(
+ target,
+ credentials=None,
+ scopes=None,
+ ssl_credentials=None,
+ credentials_file=None,
+ quota_project_id=None,
+ **kwargs):
"""Create an AsyncIO secure channel with credentials.
Args:
@@ -222,6 +229,7 @@ def create_channel(target, credentials=None, scopes=None, ssl_credentials=None,
credentials_file (str): A file with credentials that can be loaded with
:func:`google.auth.load_credentials_from_file`. This argument is
mutually exclusive with credentials.
+ quota_project_id (str): An optional project to use for billing and quota.
kwargs: Additional key-word args passed to :func:`aio.secure_channel`.
Returns:
@@ -235,7 +243,8 @@ def create_channel(target, credentials=None, scopes=None, ssl_credentials=None,
credentials=credentials,
credentials_file=credentials_file,
scopes=scopes,
- ssl_credentials=ssl_credentials
+ ssl_credentials=ssl_credentials,
+ quota_project_id=quota_project_id,
)
return aio.secure_channel(target, composite_credentials, **kwargs)
diff --git a/setup.py b/setup.py
index efa4a12a..b1884dfe 100644
--- a/setup.py
+++ b/setup.py
@@ -22,7 +22,7 @@
name = "google-api-core"
description = "Google API client core library"
-version = "1.21.0"
+version = "1.22.0"
# Should be one of:
# 'Development Status :: 3 - Alpha'
# 'Development Status :: 4 - Beta'
@@ -31,7 +31,7 @@
dependencies = [
"googleapis-common-protos >= 1.6.0, < 2.0dev",
"protobuf >= 3.12.0",
- "google-auth >= 1.18.0, < 2.0dev",
+ "google-auth >= 1.19.1, < 2.0dev",
"requests >= 2.18.0, < 3.0.0dev",
"setuptools >= 34.0.0",
"six >= 1.10.0",
diff --git a/synth.metadata b/synth.metadata
index abbe2b0e..95ac3553 100644
--- a/synth.metadata
+++ b/synth.metadata
@@ -3,15 +3,15 @@
{
"git": {
"name": ".",
- "remote": "git@github.com:googleapis/python-api-core",
- "sha": "8212bb7f54ca08a498faddaca513580a1f890dbd"
+ "remote": "https://github.com/googleapis/python-api-core.git",
+ "sha": "c890675dc9ebc084f105be81dc81c048f4f599ea"
}
},
{
"git": {
"name": "synthtool",
"remote": "https://github.com/googleapis/synthtool.git",
- "sha": "f13864cd532f98a4682cec48105580fa9a5c9978"
+ "sha": "303271797a360f8a439203413f13a160f2f5b3b4"
}
}
]
diff --git a/tests/asyncio/test_grpc_helpers_async.py b/tests/asyncio/test_grpc_helpers_async.py
index d56c4c60..924a74ca 100644
--- a/tests/asyncio/test_grpc_helpers_async.py
+++ b/tests/asyncio/test_grpc_helpers_async.py
@@ -363,6 +363,23 @@ def test_create_channel_explicit_scoped(grpc_secure_channel, composite_creds_cal
grpc_secure_channel.assert_called_once_with(target, composite_creds)
+@mock.patch("grpc.composite_channel_credentials")
+@mock.patch("grpc.experimental.aio.secure_channel")
+def test_create_channel_explicit_with_quota_project(grpc_secure_channel, composite_creds_call):
+ target = "example.com:443"
+ composite_creds = composite_creds_call.return_value
+
+ credentials = mock.create_autospec(google.auth.credentials.Credentials, instance=True)
+
+ channel = grpc_helpers_async.create_channel(
+ target, credentials=credentials, quota_project_id="project-foo"
+ )
+
+ credentials.with_quota_project.assert_called_once_with("project-foo")
+ assert channel is grpc_secure_channel.return_value
+ grpc_secure_channel.assert_called_once_with(target, composite_creds)
+
+
@mock.patch("grpc.composite_channel_credentials")
@mock.patch("grpc.experimental.aio.secure_channel")
@mock.patch(
diff --git a/tests/unit/gapic/test_method.py b/tests/unit/gapic/test_method.py
index 0f9bee93..1ae27de0 100644
--- a/tests/unit/gapic/test_method.py
+++ b/tests/unit/gapic/test_method.py
@@ -32,6 +32,27 @@ def _utcnow_monotonic():
curr_value += delta
+def test__determine_timeout():
+ # Check _determine_timeout always returns a Timeout object.
+ timeout_type_timeout = timeout.ConstantTimeout(600.0)
+ returned_timeout = google.api_core.gapic_v1.method._determine_timeout(
+ 600.0, 600.0, None
+ )
+ assert isinstance(returned_timeout, timeout.ConstantTimeout)
+ returned_timeout = google.api_core.gapic_v1.method._determine_timeout(
+ 600.0, timeout_type_timeout, None
+ )
+ assert isinstance(returned_timeout, timeout.ConstantTimeout)
+ returned_timeout = google.api_core.gapic_v1.method._determine_timeout(
+ timeout_type_timeout, 600.0, None
+ )
+ assert isinstance(returned_timeout, timeout.ConstantTimeout)
+ returned_timeout = google.api_core.gapic_v1.method._determine_timeout(
+ timeout_type_timeout, timeout_type_timeout, None
+ )
+ assert isinstance(returned_timeout, timeout.ConstantTimeout)
+
+
def test_wrap_method_basic():
method = mock.Mock(spec=["__call__"], return_value=42)
@@ -154,7 +175,8 @@ def test_wrap_method_with_default_retry_and_timeout_using_sentinel(unusued_sleep
@mock.patch("time.sleep")
def test_wrap_method_with_overriding_retry_and_timeout(unusued_sleep):
- method = mock.Mock(spec=["__call__"], side_effect=[exceptions.NotFound(None), 42])
+ method = mock.Mock(spec=["__call__"], side_effect=[
+ exceptions.NotFound(None), 42])
default_retry = retry.Retry()
default_timeout = timeout.ConstantTimeout(60)
wrapped_method = google.api_core.gapic_v1.method.wrap_method(
diff --git a/tests/unit/test_grpc_helpers.py b/tests/unit/test_grpc_helpers.py
index e2f36662..f8fed403 100644
--- a/tests/unit/test_grpc_helpers.py
+++ b/tests/unit/test_grpc_helpers.py
@@ -335,6 +335,29 @@ def test_create_channel_explicit_scoped(grpc_secure_channel, composite_creds_cal
grpc_secure_channel.assert_called_once_with(target, composite_creds)
+@mock.patch("grpc.composite_channel_credentials")
+@mock.patch("grpc.secure_channel")
+def test_create_channel_explicit_with_quota_project(grpc_secure_channel, composite_creds_call):
+ target = "example.com:443"
+ composite_creds = composite_creds_call.return_value
+
+ credentials = mock.create_autospec(google.auth.credentials.Credentials, instance=True)
+
+ channel = grpc_helpers.create_channel(
+ target,
+ credentials=credentials,
+ quota_project_id="project-foo"
+ )
+
+ credentials.with_quota_project.assert_called_once_with("project-foo")
+
+ assert channel is grpc_secure_channel.return_value
+ if grpc_helpers.HAS_GRPC_GCP:
+ grpc_secure_channel.assert_called_once_with(target, composite_creds, None)
+ else:
+ grpc_secure_channel.assert_called_once_with(target, composite_creds)
+
+
@mock.patch("grpc.composite_channel_credentials")
@mock.patch("grpc.secure_channel")
@mock.patch(