Skip to content

Feat: Add api_key argument to Client constructor #1441

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

Merged
merged 3 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions google/cloud/storage/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ class Client(ClientWithProject):
:param extra_headers:
(Optional) Custom headers to be sent with the requests attached to the client.
For example, you can add custom audit logging headers.

:type api_key: string
:param api_key:
(Optional) An API key. Mutually exclusive with any other credentials.
This parameter is an alias for setting `client_options.api_key` and
will supercede any api key set in the `client_options` parameter.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this. While I understand that allowing api key-based access is the main goal, is there a specific reason why we need to add a parameter as an alias?

client_options is an existing parameter, and both a dictionary or ClientOption instance is supported, making it quite easy for users to pass it in.

client_options = {"api_key": api_key}
client = Client(client_options=client_options)

Could you share more context on the user journey and requirements for this, thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the result of a discussion with the PM. Basically, the overarching design philosophy was to make it as easy to use as possible, and this alias is inexpensive for us to maintain and improves usability and legibility.

"""

SCOPE = (
Expand All @@ -126,6 +132,8 @@ def __init__(
client_options=None,
use_auth_w_custom_endpoint=True,
extra_headers={},
*,
api_key=None,
):
self._base_connection = None

Expand All @@ -146,6 +154,17 @@ def __init__(

connection_kw_args = {"client_info": client_info}

# api_key should set client_options.api_key. Set it here whether
# client_options was specified as a dict, as a ClientOptions object, or
# None.
if api_key:
if client_options and not isinstance(client_options, dict):
client_options.api_key = api_key
else:
if not client_options:
client_options = {}
client_options["api_key"] = api_key

if client_options:
if isinstance(client_options, dict):
client_options = google.api_core.client_options.from_dict(
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
dependencies = [
"google-auth >= 2.26.1, < 3.0dev",
"google-api-core >= 2.15.0, <3.0.0dev",
"google-cloud-core >= 2.3.0, < 3.0dev",
"google-cloud-core >= 2.4.2, < 3.0dev",
# The dependency "google-resumable-media" is no longer used. However, the
# dependency is still included here to accommodate users who may be
# importing exception classes from the google-resumable-media without
Expand Down
47 changes: 47 additions & 0 deletions tests/unit/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,53 @@ def test_ctor_w_client_options_object(self):
self.assertEqual(client._connection.API_BASE_URL, api_endpoint)
self.assertEqual(client.api_endpoint, api_endpoint)

def test_ctor_w_api_key(self):
from google.auth.api_key import Credentials

PROJECT = "PROJECT"
api_key = "my_api_key"

client = self._make_one(project=PROJECT, api_key=api_key)

self.assertEqual(
client._connection.API_BASE_URL, client._connection.DEFAULT_API_ENDPOINT
)
self.assertIsInstance(client._credentials, Credentials)
self.assertEqual(client._credentials.token, api_key)

def test_ctor_w_api_key_and_client_options(self):
from google.auth.api_key import Credentials
from google.api_core.client_options import ClientOptions

PROJECT = "PROJECT"
api_key = "my_api_key"
api_endpoint = "https://www.foo-googleapis.com"
client_options = ClientOptions(api_endpoint=api_endpoint)

client = self._make_one(
project=PROJECT, client_options=client_options, api_key=api_key
)

self.assertEqual(client._connection.API_BASE_URL, api_endpoint)
self.assertIsInstance(client._credentials, Credentials)
self.assertEqual(client._credentials.token, api_key)

def test_ctor_w_api_key_and_client_dict(self):
from google.auth.api_key import Credentials

PROJECT = "PROJECT"
api_key = "my_api_key"
api_endpoint = "https://www.foo-googleapis.com"
client_options = {"api_endpoint": api_endpoint}

client = self._make_one(
project=PROJECT, client_options=client_options, api_key=api_key
)

self.assertEqual(client._connection.API_BASE_URL, api_endpoint)
self.assertIsInstance(client._credentials, Credentials)
self.assertEqual(client._credentials.token, api_key)

def test_ctor_w_universe_domain_and_matched_credentials(self):
PROJECT = "PROJECT"
universe_domain = "example.com"
Expand Down