diff --git a/README.rst b/README.rst index c3586a4b..fed0e56e 100644 --- a/README.rst +++ b/README.rst @@ -180,7 +180,7 @@ Connection String Parameters There are many situations where you can't call ``create_engine`` directly, such as when using tools like `Flask SQLAlchemy `_. For situations like these, or for situations where you want the ``Client`` to have a `default_query_job_config `_, you can pass many arguments in the query of the connection string. -The ``credentials_path``, ``credentials_info``, ``credentials_base64``, ``location``, ``arraysize`` and ``list_tables_page_size`` parameters are used by this library, and the rest are used to create a `QueryJobConfig `_ +The ``credentials_path``, ``credentials_info``, ``credentials_base64``, ``credentials_access_token``, ``location``, ``arraysize`` and ``list_tables_page_size`` parameters are used by this library, and the rest are used to create a `QueryJobConfig `_ Note that if you want to use query strings, it will be more reliable if you use three slashes, so ``'bigquery:///?a=b'`` will work reliably, but ``'bigquery://?a=b'`` might be interpreted as having a "database" of ``?a=b``, depending on the system being used to parse the connection string. @@ -234,6 +234,15 @@ To create the base64 encoded string you can use the command line tool ``base64`` Alternatively, you can use an online generator like `www.base64encode.org _` to paste your credentials JSON file to be encoded. +Also, for authentication can be used GCP Access Token as credentials by passing ``credentials_access_token`` parameter. + +.. code-block:: python + + engine = create_engine('bigquery://', credentials_access_token='YM5/mbURNVpTzK2QC6LoHiaPQgszwchg4XdgcSNADPzYRIMeA3khUHTb30zkvV77kD3kCg5cgSn9buzX5dxJaUYCVwpjOfD/OvNqRTOJV2C') + +To generate access token use `google.oauth2.credentials.UserAccessTokenCredentials `_ function. +Keep in mind that Access Tokens have a maximum expiration time of 1 hour. + Creating tables ^^^^^^^^^^^^^^^ diff --git a/sqlalchemy_bigquery/_helpers.py b/sqlalchemy_bigquery/_helpers.py index b03e232a..1ee9ccbb 100644 --- a/sqlalchemy_bigquery/_helpers.py +++ b/sqlalchemy_bigquery/_helpers.py @@ -10,7 +10,7 @@ from google.api_core import client_info import google.auth from google.cloud import bigquery -from google.oauth2 import service_account +from google.oauth2 import service_account, credentials as oauth_credentials import sqlalchemy import base64 import json @@ -33,6 +33,7 @@ def create_bigquery_client( credentials_info=None, credentials_path=None, credentials_base64=None, + credentials_access_token=None, default_query_job_config=None, location=None, project_id=None, @@ -54,6 +55,9 @@ def create_bigquery_client( ) credentials = credentials.with_scopes(SCOPES) default_project = credentials.project_id + elif credentials_access_token: + credentials = oauth_credentials.Credentials(credentials_access_token) + _, default_project = google.auth.default(scopes=SCOPES) else: credentials, default_project = google.auth.default(scopes=SCOPES) diff --git a/sqlalchemy_bigquery/base.py b/sqlalchemy_bigquery/base.py index 48455836..f3aa00f4 100644 --- a/sqlalchemy_bigquery/base.py +++ b/sqlalchemy_bigquery/base.py @@ -759,6 +759,7 @@ def __init__( location=None, credentials_info=None, credentials_base64=None, + credentials_access_token=None, list_tables_page_size=1000, *args, **kwargs, @@ -768,6 +769,7 @@ def __init__( self.credentials_path = credentials_path self.credentials_info = credentials_info self.credentials_base64 = credentials_base64 + self.credentials_access_token = credentials_access_token self.location = location self.dataset_id = None self.list_tables_page_size = list_tables_page_size @@ -816,6 +818,7 @@ def create_connect_args(self, url): credentials_path=self.credentials_path, credentials_info=self.credentials_info, credentials_base64=self.credentials_base64, + credentials_access_token=self.credentials_access_token, project_id=project_id, location=self.location, default_query_job_config=default_query_job_config, diff --git a/tests/system/test_helpers.py b/tests/system/test_helpers.py index 42cfab7f..db33855a 100644 --- a/tests/system/test_helpers.py +++ b/tests/system/test_helpers.py @@ -7,6 +7,7 @@ import base64 import os import json +import google.auth import pytest @@ -25,6 +26,11 @@ def credentials_path(): return os.environ["GOOGLE_APPLICATION_CREDENTIALS"] +@pytest.fixture +def credentials_access_token(): + return 'access_token' + + @pytest.fixture def credentials_info(credentials_path): with open(credentials_path) as credentials_file: @@ -83,6 +89,30 @@ def test_create_bigquery_client_with_credentials_info_respects_project( assert bqclient.project == "connection-url-project" +def test_create_bigquery_client_with_credentials_access_token( + module_under_test, credentials_access_token +): + bqclient = module_under_test.create_bigquery_client( + credentials_access_token=credentials_access_token + ) + _, default_project = google.auth.default() + assert bqclient.project == default_project + + +def test_create_bigquery_client_with_credentials_access_token_respects_project( + module_under_test, credentials_access_token +): + """Test that project_id is used, even when there is a default project. + + https://github.com/googleapis/python-bigquery-sqlalchemy/issues/48 + """ + bqclient = module_under_test.create_bigquery_client( + credentials_access_token=credentials_access_token, + project_id="connection-url-project", + ) + assert bqclient.project == "connection-url-project" + + def test_create_bigquery_client_with_credentials_base64( module_under_test, credentials_base64, credentials_info ): diff --git a/tests/unit/test_helpers.py b/tests/unit/test_helpers.py index 02bc8bee..c0a70a9d 100644 --- a/tests/unit/test_helpers.py +++ b/tests/unit/test_helpers.py @@ -32,6 +32,11 @@ def module_under_test(): return _helpers +@pytest.fixture +def credentials_access_token(): + return 'access_token' + + def test_create_bigquery_client_with_credentials_path(monkeypatch, module_under_test): mock_service_account = mock.create_autospec(service_account.Credentials) mock_service_account.from_service_account_file.return_value = ( @@ -108,6 +113,30 @@ def test_create_bigquery_client_with_credentials_info_respects_project( assert bqclient.project == "connection-url-project" +def test_create_bigquery_client_with_credentials_access_token( + module_under_test, credentials_access_token +): + bqclient = module_under_test.create_bigquery_client( + credentials_access_token=credentials_access_token + ) + _, default_project = google.auth.default() + assert bqclient.project == default_project + + +def test_create_bigquery_client_with_credentials_access_token_respects_project( + module_under_test, credentials_access_token +): + """Test that project_id is used, even when there is a default project. + + https://github.com/googleapis/python-bigquery-sqlalchemy/issues/48 + """ + bqclient = module_under_test.create_bigquery_client( + credentials_access_token=credentials_access_token, + project_id="connection-url-project", + ) + assert bqclient.project == "connection-url-project" + + def test_create_bigquery_client_with_credentials_base64(monkeypatch, module_under_test): mock_service_account = mock.create_autospec(service_account.Credentials) mock_service_account.from_service_account_info.return_value = (