Skip to content

Commit 1a77bc6

Browse files
authored
🐛 (BigQueryHook) fix compatibility with sqlalchemy engine (#19508)
1 parent d8c4449 commit 1a77bc6

3 files changed

Lines changed: 51 additions & 0 deletions

File tree

airflow/providers/google/cloud/hooks/bigquery.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
_check_google_client_version as gbq_check_google_client_version,
5252
_test_google_api_imports as gbq_test_google_api_imports,
5353
)
54+
from sqlalchemy import create_engine
5455

5556
from airflow.exceptions import AirflowException
5657
from airflow.hooks.dbapi import DbApiHook
@@ -114,6 +115,7 @@ def __init__(
114115
self.running_job_id = None # type: Optional[str]
115116
self.api_resource_configs = api_resource_configs if api_resource_configs else {} # type Dict
116117
self.labels = labels
118+
self.credentials_path = "bigquery_hook_credentials.json"
117119

118120
def get_conn(self) -> "BigQueryConnection":
119121
"""Returns a BigQuery PEP 249 connection object."""
@@ -150,6 +152,41 @@ def get_client(self, project_id: Optional[str] = None, location: Optional[str] =
150152
credentials=self._get_credentials(),
151153
)
152154

155+
def get_uri(self) -> str:
156+
"""Override DbApiHook get_uri method for get_sqlalchemy_engine()"""
157+
return f"bigquery://{self.project_id}"
158+
159+
def get_sqlalchemy_engine(self, engine_kwargs=None):
160+
"""
161+
Get an sqlalchemy_engine object.
162+
163+
:param engine_kwargs: Kwargs used in :func:`~sqlalchemy.create_engine`.
164+
:return: the created engine.
165+
"""
166+
connection = self.get_connection(self.gcp_conn_id)
167+
if connection.extra_dejson.get("extra__google_cloud_platform__key_path"):
168+
credentials_path = connection.extra_dejson['extra__google_cloud_platform__key_path']
169+
return create_engine(self.get_uri(), credentials_path=credentials_path, **engine_kwargs)
170+
elif connection.extra_dejson.get("extra__google_cloud_platform__keyfile_dict"):
171+
credential_file_content = json.loads(
172+
connection.extra_dejson["extra__google_cloud_platform__keyfile_dict"]
173+
)
174+
return create_engine(self.get_uri(), credentials_info=credential_file_content, **engine_kwargs)
175+
try:
176+
# 1. If the environment variable GOOGLE_APPLICATION_CREDENTIALS is set
177+
# ADC uses the service account key or configuration file that the variable points to.
178+
# 2. If the environment variable GOOGLE_APPLICATION_CREDENTIALS isn't set
179+
# ADC uses the service account that is attached to the resource that is running your code.
180+
return create_engine(self.get_uri(), **engine_kwargs)
181+
except Exception as e:
182+
self.log.error(e)
183+
raise AirflowException(
184+
"For now, we only support instantiating SQLAlchemy engine by"
185+
" using ADC"
186+
", extra__google_cloud_platform__key_path"
187+
"and extra__google_cloud_platform__keyfile_dict"
188+
)
189+
153190
@staticmethod
154191
def _resolve_table_reference(
155192
table_resource: Dict[str, Any],

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ def write_version(filename: str = os.path.join(*[my_dir, "airflow", "git_version
343343
# _check_google_client_version (airflow/providers/google/cloud/hooks/bigquery.py:49)
344344
'pandas-gbq<0.15.0',
345345
pandas_requirement,
346+
'sqlalchemy-bigquery>=1.2.1',
346347
]
347348
grpc = [
348349
'google-auth>=1.0.0, <3.0.0',

tests/providers/google/cloud/hooks/test_bigquery.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -930,6 +930,19 @@ def test_insert_job(self, mock_client, mock_query_job):
930930
)
931931
mock_query_job.from_api_repr.return_value.result.assert_called_once_with()
932932

933+
def test_dbapi_get_uri(self):
934+
assert self.hook.get_uri().startswith('bigquery://')
935+
936+
def test_dbapi_get_sqlalchemy_engine(self):
937+
with pytest.raises(
938+
AirflowException,
939+
match="For now, we only support instantiating SQLAlchemy engine by"
940+
" using ADC"
941+
", extra__google_cloud_platform__key_path"
942+
"and extra__google_cloud_platform__keyfile_dict",
943+
):
944+
self.hook.get_sqlalchemy_engine()
945+
933946

934947
class TestBigQueryTableSplitter(unittest.TestCase):
935948
def test_internal_need_default_project(self):

0 commit comments

Comments
 (0)