Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow default_host and default_scopes to be passed to create_channel #134

Merged
merged 12 commits into from Feb 5, 2021
77 changes: 70 additions & 7 deletions google/api_core/grpc_helpers.py
Expand Up @@ -17,6 +17,8 @@
import collections

import grpc
from packaging import version
import pkg_resources
import six

from google.api_core import exceptions
Expand All @@ -33,6 +35,20 @@
except ImportError:
HAS_GRPC_GCP = False

try:
# google.auth.__version__ was added in 1.26.0
_GOOGLE_AUTH_VERSION = google.auth.__version__
except AttributeError:
try: # try pkg_resources if it is available
_GOOGLE_AUTH_VERSION = pkg_resources.get_distribution("google-auth").version
except pkg_resources.DistributionNotFound: # pragma: NO COVER
_GOOGLE_AUTH_VERSION = None

if _GOOGLE_AUTH_VERSION is not None and version.parse(_GOOGLE_AUTH_VERSION) >= version.parse("1.25.0"):
_GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST = True
else:
_GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST = False

# The list of gRPC Callable interfaces that return iterators.
_STREAM_WRAP_CLASSES = (grpc.UnaryStreamMultiCallable, grpc.StreamStreamMultiCallable)

Expand Down Expand Up @@ -179,9 +195,11 @@ def wrap_errors(callable_):
def _create_composite_credentials(
credentials=None,
credentials_file=None,
default_scopes=None,
scopes=None,
ssl_credentials=None,
quota_project_id=None):
quota_project_id=None,
default_host=None):
"""Create the composite credentials for secure channels.

Args:
Expand All @@ -191,12 +209,16 @@ def _create_composite_credentials(
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.
default_scopes (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`.
scopes (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`.
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.
default_host (str): The default endpoint. e.g., "pubsub.googleapis.com".

Returns:
grpc.ChannelCredentials: The composed channel credentials object.
Expand All @@ -210,21 +232,55 @@ def _create_composite_credentials(
)

if credentials_file:
credentials, _ = google.auth.load_credentials_from_file(credentials_file, scopes=scopes)
# TODO: remove this if/else once google-auth >= 1.25.0 is required
if _GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
credentials, _ = google.auth.load_credentials_from_file(
credentials_file,
scopes=scopes,
default_scopes=default_scopes
)
else:
credentials, _ = google.auth.load_credentials_from_file(
credentials_file,
scopes=scopes or default_scopes,
)
elif credentials:
credentials = google.auth.credentials.with_scopes_if_required(credentials, scopes)
# TODO: remove this if/else once google-auth >= 1.25.0 is required
if _GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
credentials = google.auth.credentials.with_scopes_if_required(
credentials,
scopes=scopes,
default_scopes=default_scopes
)
else:
credentials = google.auth.credentials.with_scopes_if_required(
credentials,
scopes=scopes or default_scopes,
)

else:
credentials, _ = google.auth.default(scopes=scopes)
# TODO: remove this if/else once google-auth >= 1.25.0 is required
if _GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
credentials, _ = google.auth.default(scopes=scopes, default_scopes=default_scopes)
else:
credentials, _ = google.auth.default(scopes=scopes or default_scopes)

if quota_project_id and isinstance(credentials, google.auth.credentials.CredentialsWithQuotaProject):
credentials = credentials.with_quota_project(quota_project_id)

request = google.auth.transport.requests.Request()

# Create the metadata plugin for inserting the authorization header.
metadata_plugin = google.auth.transport.grpc.AuthMetadataPlugin(
credentials, request
)

# TODO: remove this if/else once google-auth >= 1.25.0 is required
if _GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
metadata_plugin = google.auth.transport.grpc.AuthMetadataPlugin(
credentials, request, default_host=default_host,
)
else:
metadata_plugin = google.auth.transport.grpc.AuthMetadataPlugin(
credentials, request
)

# Create a set of grpc.CallCredentials using the metadata plugin.
google_auth_credentials = grpc.metadata_call_credentials(metadata_plugin)
Expand All @@ -245,6 +301,8 @@ def create_channel(
ssl_credentials=None,
credentials_file=None,
quota_project_id=None,
default_scopes=None,
default_host=None,
**kwargs):
"""Create a secure channel with credentials.

Expand All @@ -262,6 +320,9 @@ def create_channel(
: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.
default_scopes (Sequence[str]): Default scopes passed by a Google client
library. Use 'scopes' for user-defined scopes.
default_host (str): The default endpoint. e.g., "pubsub.googleapis.com".
kwargs: Additional key-word args passed to
:func:`grpc_gcp.secure_channel` or :func:`grpc.secure_channel`.

Expand All @@ -275,9 +336,11 @@ def create_channel(
composite_credentials = _create_composite_credentials(
credentials=credentials,
credentials_file=credentials_file,
default_scopes=default_scopes,
scopes=scopes,
ssl_credentials=ssl_credentials,
quota_project_id=quota_project_id,
default_host=default_host,
)

if HAS_GRPC_GCP:
Expand Down
7 changes: 7 additions & 0 deletions google/api_core/grpc_helpers_async.py
Expand Up @@ -213,6 +213,8 @@ def create_channel(
ssl_credentials=None,
credentials_file=None,
quota_project_id=None,
default_scopes=None,
default_host=None,
**kwargs):
"""Create an AsyncIO secure channel with credentials.

Expand All @@ -230,6 +232,9 @@ def create_channel(
: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.
default_scopes (Sequence[str]): Default scopes passed by a Google client
library. Use 'scopes' for user-defined scopes.
default_host (str): The default endpoint. e.g., "pubsub.googleapis.com".
kwargs: Additional key-word args passed to :func:`aio.secure_channel`.

Returns:
Expand All @@ -243,8 +248,10 @@ def create_channel(
credentials=credentials,
credentials_file=credentials_file,
scopes=scopes,
default_scopes=default_scopes,
ssl_credentials=ssl_credentials,
quota_project_id=quota_project_id,
default_host=default_host
)

return aio.secure_channel(target, composite_credentials, **kwargs)
Expand Down
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -34,6 +34,7 @@
"google-auth >= 1.21.1, < 2.0dev",
"requests >= 2.18.0, < 3.0.0dev",
"setuptools >= 40.3.0",
"packaging >= 14.3",
"six >= 1.13.0",
"pytz",
'futures >= 3.2.0; python_version < "3.2"',
Expand Down
1 change: 1 addition & 0 deletions testing/constraints-3.6.txt
Expand Up @@ -10,6 +10,7 @@ protobuf==3.12.0
google-auth==1.21.1
requests==2.18.0
setuptools==40.3.0
packaging==14.3
six==1.13.0
grpcio==1.29.0
grpcio-gcp==0.2.2
Expand Down