Skip to content

Commit 75a933a

Browse files
committed
fix!: Fix get_table_names and get_view_names without a default dataset
1 parent 80781ef commit 75a933a

File tree

4 files changed

+78
-108
lines changed

4 files changed

+78
-108
lines changed

sqlalchemy_bigquery/base.py

+25-34
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,9 @@
2222
import datetime
2323
from decimal import Decimal
2424
import random
25-
import operator
2625
import uuid
2726

2827
from google import auth
29-
import google.api_core.exceptions
3028
from google.cloud.bigquery import dbapi
3129
from google.cloud.bigquery.table import (
3230
RangePartitioning,
@@ -1053,11 +1051,6 @@ def dbapi(cls):
10531051
def import_dbapi(cls):
10541052
return dbapi
10551053

1056-
@staticmethod
1057-
def _build_formatted_table_id(table):
1058-
"""Build '<dataset_id>.<table_id>' string using given table."""
1059-
return "{}.{}".format(table.reference.dataset_id, table.table_id)
1060-
10611054
@staticmethod
10621055
def _add_default_dataset_to_job_config(job_config, project_id, dataset_id):
10631056
# If dataset_id is set, then we know the job_config isn't None
@@ -1106,36 +1099,34 @@ def create_connect_args(self, url):
11061099
)
11071100
return ([], {"client": client})
11081101

1109-
def _get_table_or_view_names(self, connection, item_types, schema=None):
1110-
current_schema = schema or self.dataset_id
1111-
get_table_name = (
1112-
self._build_formatted_table_id
1113-
if self.dataset_id is None
1114-
else operator.attrgetter("table_id")
1115-
)
1102+
def _get_default_schema_name(self, connection) -> str:
1103+
return connection.dialect.dataset_id
11161104

1105+
def _get_table_or_view_names(self, connection, item_types, schema=None):
11171106
client = connection.connection._client
1118-
datasets = client.list_datasets()
1119-
1120-
result = []
1121-
for dataset in datasets:
1122-
if current_schema is not None and current_schema != dataset.dataset_id:
1123-
continue
1124-
1125-
try:
1126-
tables = client.list_tables(
1127-
dataset.reference, page_size=self.list_tables_page_size
1107+
# `schema=None` means to search the default schema. If one isn't set in the
1108+
# connection string, then we have nothing to search so return an empty list.
1109+
#
1110+
# When using Alembic with `include_schemas=False`, it expects to compare to a
1111+
# single schema. If `include_schemas=True`, it will enumerate all schemas and
1112+
# then call `get_table_names`/`get_view_names` for each schema.
1113+
current_schema = schema or self.default_schema_name
1114+
if current_schema is None:
1115+
return []
1116+
try:
1117+
return [
1118+
table.table_id
1119+
for table in client.list_tables(
1120+
current_schema, page_size=self.list_tables_page_size
11281121
)
1129-
for table in tables:
1130-
if table.table_type in item_types:
1131-
result.append(get_table_name(table))
1132-
except google.api_core.exceptions.NotFound:
1133-
# It's possible that the dataset was deleted between when we
1134-
# fetched the list of datasets and when we try to list the
1135-
# tables from it. See:
1136-
# https://github.com/googleapis/python-bigquery-sqlalchemy/issues/105
1137-
pass
1138-
return result
1122+
if table.table_type in item_types
1123+
]
1124+
except NotFound:
1125+
# It's possible that the dataset was deleted between when we
1126+
# fetched the list of datasets and when we try to list the
1127+
# tables from it. See:
1128+
# https://github.com/googleapis/python-bigquery-sqlalchemy/issues/105
1129+
return []
11391130

11401131
@staticmethod
11411132
def _split_table_name(full_table_name):

tests/system/test_sqlalchemy_bigquery.py

+13-21
Original file line numberDiff line numberDiff line change
@@ -366,18 +366,6 @@ def test_reflect_dataset_does_not_exist(engine):
366366
)
367367

368368

369-
def test_tables_list(engine, engine_using_test_dataset, bigquery_dataset):
370-
tables = sqlalchemy.inspect(engine).get_table_names()
371-
assert f"{bigquery_dataset}.sample" in tables
372-
assert f"{bigquery_dataset}.sample_one_row" in tables
373-
assert f"{bigquery_dataset}.sample_view" not in tables
374-
375-
tables = sqlalchemy.inspect(engine_using_test_dataset).get_table_names()
376-
assert "sample" in tables
377-
assert "sample_one_row" in tables
378-
assert "sample_view" not in tables
379-
380-
381369
def test_group_by(session, table, session_using_test_dataset, table_using_test_dataset):
382370
"""labels in SELECT clause should be correclty formatted (dots are replaced with underscores)"""
383371
for session, table in [
@@ -612,14 +600,15 @@ def test_schemas_names(inspector, inspector_using_test_dataset, bigquery_dataset
612600
assert f"{bigquery_dataset}" in datasets
613601

614602

615-
def test_table_names_in_schema(
616-
inspector, inspector_using_test_dataset, bigquery_dataset
617-
):
603+
def test_table_names(inspector, inspector_using_test_dataset, bigquery_dataset):
604+
tables = inspector.get_table_names()
605+
assert not tables
606+
618607
tables = inspector.get_table_names(bigquery_dataset)
619-
assert f"{bigquery_dataset}.sample" in tables
620-
assert f"{bigquery_dataset}.sample_one_row" in tables
621-
assert f"{bigquery_dataset}.sample_dml_empty" in tables
622-
assert f"{bigquery_dataset}.sample_view" not in tables
608+
assert "sample" in tables
609+
assert "sample_one_row" in tables
610+
assert "sample_dml_empty" in tables
611+
assert "sample_view" not in tables
623612
assert len(tables) == 3
624613

625614
tables = inspector_using_test_dataset.get_table_names()
@@ -632,8 +621,11 @@ def test_table_names_in_schema(
632621

633622
def test_view_names(inspector, inspector_using_test_dataset, bigquery_dataset):
634623
view_names = inspector.get_view_names()
635-
assert f"{bigquery_dataset}.sample_view" in view_names
636-
assert f"{bigquery_dataset}.sample" not in view_names
624+
assert not view_names
625+
626+
view_names = inspector.get_view_names(bigquery_dataset)
627+
assert "sample_view" in view_names
628+
assert "sample" not in view_names
637629

638630
view_names = inspector_using_test_dataset.get_view_names()
639631
assert "sample_view" in view_names

tests/unit/fauxdbi.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -482,8 +482,8 @@ def list_tables(self, dataset, page_size):
482482
google.cloud.bigquery.table.TableListItem(
483483
dict(
484484
tableReference=dict(
485-
projectId=dataset.project,
486-
datasetId=dataset.dataset_id,
485+
projectId="myproject",
486+
datasetId=dataset,
487487
tableId=row["name"],
488488
),
489489
type=row["type"].upper(),

tests/unit/test_sqlalchemy_bigquery.py

+38-51
Original file line numberDiff line numberDiff line change
@@ -65,83 +65,70 @@ def table_item(dataset_id, table_id, type_="TABLE"):
6565

6666

6767
@pytest.mark.parametrize(
68-
["datasets_list", "tables_lists", "expected"],
68+
["dataset", "tables_list", "expected"],
6969
[
70-
([], [], []),
71-
([dataset_item("dataset_1")], [[]], []),
70+
(None, [], []),
71+
("dataset", [], []),
7272
(
73-
[dataset_item("dataset_1"), dataset_item("dataset_2")],
73+
"dataset",
7474
[
75-
[table_item("dataset_1", "d1t1"), table_item("dataset_1", "d1t2")],
76-
[
77-
table_item("dataset_2", "d2t1"),
78-
table_item("dataset_2", "d2view", type_="VIEW"),
79-
table_item("dataset_2", "d2ext", type_="EXTERNAL"),
80-
table_item("dataset_2", "d2mv", type_="MATERIALIZED_VIEW"),
81-
],
75+
table_item("dataset", "t1"),
76+
table_item("dataset", "view", type_="VIEW"),
77+
table_item("dataset", "ext", type_="EXTERNAL"),
78+
table_item("dataset", "mv", type_="MATERIALIZED_VIEW"),
8279
],
83-
["dataset_1.d1t1", "dataset_1.d1t2", "dataset_2.d2t1", "dataset_2.d2ext"],
80+
["t1", "ext"],
8481
),
8582
(
86-
[dataset_item("dataset_1"), dataset_item("dataset_deleted")],
87-
[
88-
[table_item("dataset_1", "d1t1")],
89-
google.api_core.exceptions.NotFound("dataset_deleted"),
90-
],
91-
["dataset_1.d1t1"],
83+
"dataset",
84+
google.api_core.exceptions.NotFound("dataset_deleted"),
85+
[],
9286
),
9387
],
9488
)
9589
def test_get_table_names(
96-
engine_under_test, mock_bigquery_client, datasets_list, tables_lists, expected
90+
engine_under_test, mock_bigquery_client, dataset, tables_list, expected
9791
):
98-
mock_bigquery_client.list_datasets.return_value = datasets_list
99-
mock_bigquery_client.list_tables.side_effect = tables_lists
100-
table_names = sqlalchemy.inspect(engine_under_test).get_table_names()
101-
mock_bigquery_client.list_datasets.assert_called_once()
102-
assert mock_bigquery_client.list_tables.call_count == len(datasets_list)
92+
mock_bigquery_client.list_tables.side_effect = [tables_list]
93+
table_names = sqlalchemy.inspect(engine_under_test).get_table_names(schema=dataset)
94+
if dataset:
95+
mock_bigquery_client.list_tables.assert_called_once()
96+
else:
97+
mock_bigquery_client.list_tables.assert_not_called()
10398
assert list(sorted(table_names)) == list(sorted(expected))
10499

105100

106101
@pytest.mark.parametrize(
107-
["datasets_list", "tables_lists", "expected"],
102+
["dataset", "tables_list", "expected"],
108103
[
109-
([], [], []),
110-
([dataset_item("dataset_1")], [[]], []),
104+
(None, [], []),
105+
("dataset", [], []),
111106
(
112-
[dataset_item("dataset_1"), dataset_item("dataset_2")],
107+
"dataset",
113108
[
114-
[
115-
table_item("dataset_1", "d1t1"),
116-
table_item("dataset_1", "d1view", type_="VIEW"),
117-
],
118-
[
119-
table_item("dataset_2", "d2t1"),
120-
table_item("dataset_2", "d2view", type_="VIEW"),
121-
table_item("dataset_2", "d2ext", type_="EXTERNAL"),
122-
table_item("dataset_2", "d2mv", type_="MATERIALIZED_VIEW"),
123-
],
109+
table_item("dataset", "t1"),
110+
table_item("dataset", "view", type_="VIEW"),
111+
table_item("dataset", "ext", type_="EXTERNAL"),
112+
table_item("dataset", "mv", type_="MATERIALIZED_VIEW"),
124113
],
125-
["dataset_1.d1view", "dataset_2.d2view", "dataset_2.d2mv"],
114+
["view", "mv"],
126115
),
127116
(
128-
[dataset_item("dataset_1"), dataset_item("dataset_deleted")],
129-
[
130-
[table_item("dataset_1", "d1view", type_="VIEW")],
131-
google.api_core.exceptions.NotFound("dataset_deleted"),
132-
],
133-
["dataset_1.d1view"],
117+
"dataset_deleted",
118+
google.api_core.exceptions.NotFound("dataset_deleted"),
119+
[],
134120
),
135121
],
136122
)
137123
def test_get_view_names(
138-
inspector_under_test, mock_bigquery_client, datasets_list, tables_lists, expected
124+
inspector_under_test, mock_bigquery_client, dataset, tables_list, expected
139125
):
140-
mock_bigquery_client.list_datasets.return_value = datasets_list
141-
mock_bigquery_client.list_tables.side_effect = tables_lists
142-
view_names = inspector_under_test.get_view_names()
143-
mock_bigquery_client.list_datasets.assert_called_once()
144-
assert mock_bigquery_client.list_tables.call_count == len(datasets_list)
126+
mock_bigquery_client.list_tables.side_effect = [tables_list]
127+
view_names = inspector_under_test.get_view_names(schema=dataset)
128+
if dataset:
129+
mock_bigquery_client.list_tables.assert_called_once()
130+
else:
131+
mock_bigquery_client.list_tables.assert_not_called()
145132
assert list(sorted(view_names)) == list(sorted(expected))
146133

147134

0 commit comments

Comments
 (0)