Skip to content

Commit

Permalink
Add new get_hardware_engine_and_authenticate_user (quantumlib#6485)
Browse files Browse the repository at this point in the history
* Created using Colaboratory

* Created using Colaboratory

* add new public api for getting a prod engine instance which does authentication

* add new public api for getting a prod engine instance which does authentication

* expose authenticate_user to notebook

* add coverage
  • Loading branch information
senecameeks authored Mar 5, 2024
1 parent d75d43f commit 6dbac6b
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 24 deletions.
71 changes: 48 additions & 23 deletions cirq-google/cirq_google/engine/qcs_notebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,32 +82,14 @@ def get_qcs_objects_for_notebook(
ValueError: if processor_id is not specified and no processors are available.
"""

# Check for Google Application Default Credentials and run
# interactive login if the notebook is executed in Colab. In
# case the notebook is executed in Jupyter notebook or other
# IPython runtimes, no interactive login is provided, it is
# assumed that the `GOOGLE_APPLICATION_CREDENTIALS` env var is
# set or `gcloud auth application-default login` was executed
# already. For more information on using Application Default Credentials
# see https://cloud.google.com/docs/authentication/production
# Attempt to connect to the Quantum Engine API, and use a simulator if unable to connect.
if not virtual:
# Set up auth
try:
from google.colab import auth
except ImportError:
print("Not running in a colab kernel. Will use Application Default Credentials.")
else:
print("Getting OAuth2 credentials.")
print("Press enter after entering the verification code.")
try:
a = auth.authenticate_user(clear_output=False)
print(a)
print("Authentication complete.")
except Exception as exc:
print(f"Authentication failed: {exc}")
print("Using virtual engine instead.")
virtual = True
authenticate_user()
except Exception as exc:
print(f"Authentication failed: {exc}")
print("Using virtual engine instead.")
virtual = True

if not virtual:
# Set up production engine
Expand Down Expand Up @@ -152,3 +134,46 @@ def get_qcs_objects_for_notebook(
processor_id=processor_id,
is_simulator=is_simulator,
)


def authenticate_user(clear_output: bool = False) -> None:
"""Authenticates on Google Cloud.
Args:
clear_output: Optional bool for whether to clear output before
authenticating. Defaults to false.
Returns:
None.
Raises:
Exception: if authentication fails.
"""

# Check for Google Application Default Credentials and run
# interactive login if the notebook is executed in Colab. In
# case the notebook is executed in Jupyter notebook or other
# IPython runtimes, no interactive login is provided, it is
# assumed that the `GOOGLE_APPLICATION_CREDENTIALS` env var is
# set or `gcloud auth application-default login` was executed
# already. For more information on using Application Default Credentials
# see https://cloud.google.com/docs/authentication/production
# Attempt to connect to the Quantum Engine API, and use a simulator if unable to connect.
try:
from google.colab import auth
except ImportError:
print("Not running in a colab kernel. Will use Application Default Credentials.")
return

try:
print("Getting OAuth2 credentials.")
print("Press enter after entering the verification code.")
a = auth.authenticate_user(clear_output=clear_output)
print(a)
print("Authentication complete.")
except Exception as exc:
print(
"Authentication failed, you may not have permission to access a"
+ " hardware Engine. Use a virtual Engine instead."
)
raise exc
42 changes: 41 additions & 1 deletion cirq-google/cirq_google/engine/qcs_notebook_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
import pytest

import cirq_google as cg
from cirq_google.engine.qcs_notebook import get_qcs_objects_for_notebook, QCSObjectsForNotebook
from cirq_google.engine.qcs_notebook import (
get_qcs_objects_for_notebook,
QCSObjectsForNotebook,
authenticate_user,
)


def _assert_correct_types(result: QCSObjectsForNotebook):
Expand Down Expand Up @@ -132,3 +136,39 @@ def test_get_qcs_objects_for_notebook_auth_fails(engine_mock):
assert not result.signed_in
assert result.is_simulator
assert result.project_id == 'fake_project'


class TestAuthenticateUser:
"""Tests for the public API `get_hardware_engine_and_authenticate_user` which
authenticates the user and returns a production engine instance ."""

@mock.patch.dict('sys.modules', {'google.colab': mock.Mock()})
def test_authentication_succeeds_no_exceptions_thrown(self):
auth_mock = sys.modules['google.colab']

authenticate_user()

assert auth_mock.auth.authenticate_user.called

@mock.patch.dict('sys.modules', {'google.colab': mock.Mock()})
def test_authentication_failure(self):
project_id = "invalid-project"
# Mock authentication failure
auth_mock = sys.modules['google.colab']

auth_mock.auth.authenticate_user = mock.Mock(side_effect=Exception('mock auth failure'))

with pytest.raises(Exception, match="mock auth failure"):
authenticate_user(project_id)

@mock.patch.dict('sys.modules', {'google.colab': mock.Mock()})
@pytest.mark.parametrize('clear_output', ([True, False]))
def test_clear_output_is_passed(self, clear_output):
auth_mock = sys.modules['google.colab']

with mock.patch.object(
auth_mock.auth, 'authenticate_user', return_value=None
) as mock_authenticate_user:
authenticate_user(clear_output)

mock_authenticate_user.assert_called_with(clear_output=clear_output)

0 comments on commit 6dbac6b

Please sign in to comment.