Skip to content

Commit 1689211

Browse files
authored
Merge branch 'main' into start-index-2
2 parents e33e02d + 28a5750 commit 1689211

File tree

4 files changed

+247
-5
lines changed

4 files changed

+247
-5
lines changed

google/cloud/bigquery/client.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@
9090
from google.cloud.bigquery.dataset import Dataset
9191
from google.cloud.bigquery.dataset import DatasetListItem
9292
from google.cloud.bigquery.dataset import DatasetReference
93-
from google.cloud.bigquery.enums import AutoRowIDs
93+
94+
from google.cloud.bigquery.enums import AutoRowIDs, DatasetView, UpdateMode
9495
from google.cloud.bigquery.format_options import ParquetOptions
9596
from google.cloud.bigquery.job import (
9697
CopyJob,
@@ -864,6 +865,7 @@ def get_dataset(
864865
dataset_ref: Union[DatasetReference, str],
865866
retry: retries.Retry = DEFAULT_RETRY,
866867
timeout: TimeoutType = DEFAULT_TIMEOUT,
868+
dataset_view: Optional[DatasetView] = None,
867869
) -> Dataset:
868870
"""Fetch the dataset referenced by ``dataset_ref``
869871
@@ -881,7 +883,21 @@ def get_dataset(
881883
timeout (Optional[float]):
882884
The number of seconds to wait for the underlying HTTP transport
883885
before using ``retry``.
884-
886+
dataset_view (Optional[google.cloud.bigquery.enums.DatasetView]):
887+
Specifies the view that determines which dataset information is
888+
returned. By default, dataset metadata (e.g. friendlyName, description,
889+
labels, etc) and ACL information are returned. This argument can
890+
take on the following possible enum values.
891+
892+
* :attr:`~google.cloud.bigquery.enums.DatasetView.ACL`:
893+
Includes dataset metadata and the ACL.
894+
* :attr:`~google.cloud.bigquery.enums.DatasetView.FULL`:
895+
Includes all dataset metadata, including the ACL and table metadata.
896+
This view is not supported by the `datasets.list` API method.
897+
* :attr:`~google.cloud.bigquery.enums.DatasetView.METADATA`:
898+
Includes basic dataset metadata, but not the ACL.
899+
* :attr:`~google.cloud.bigquery.enums.DatasetView.DATASET_VIEW_UNSPECIFIED`:
900+
The server will decide which view to use. Currently defaults to FULL.
885901
Returns:
886902
google.cloud.bigquery.dataset.Dataset:
887903
A ``Dataset`` instance.
@@ -891,6 +907,12 @@ def get_dataset(
891907
dataset_ref, default_project=self.project
892908
)
893909
path = dataset_ref.path
910+
911+
if dataset_view:
912+
query_params = {"datasetView": dataset_view.value}
913+
else:
914+
query_params = {}
915+
894916
span_attributes = {"path": path}
895917
api_response = self._call_api(
896918
retry,
@@ -899,6 +921,7 @@ def get_dataset(
899921
method="GET",
900922
path=path,
901923
timeout=timeout,
924+
query_params=query_params,
902925
)
903926
return Dataset.from_api_repr(api_response)
904927

@@ -1198,6 +1221,7 @@ def update_dataset(
11981221
fields: Sequence[str],
11991222
retry: retries.Retry = DEFAULT_RETRY,
12001223
timeout: TimeoutType = DEFAULT_TIMEOUT,
1224+
update_mode: Optional[UpdateMode] = None,
12011225
) -> Dataset:
12021226
"""Change some fields of a dataset.
12031227
@@ -1237,6 +1261,20 @@ def update_dataset(
12371261
timeout (Optional[float]):
12381262
The number of seconds to wait for the underlying HTTP transport
12391263
before using ``retry``.
1264+
update_mode (Optional[google.cloud.bigquery.enums.UpdateMode]):
1265+
Specifies the kind of information to update in a dataset.
1266+
By default, dataset metadata (e.g. friendlyName, description,
1267+
labels, etc) and ACL information are updated. This argument can
1268+
take on the following possible enum values.
1269+
1270+
* :attr:`~google.cloud.bigquery.enums.UPDATE_MODE_UNSPECIFIED`:
1271+
The default value. Behavior defaults to UPDATE_FULL.
1272+
* :attr:`~google.cloud.bigquery.enums.UpdateMode.UPDATE_METADATA`:
1273+
Includes metadata information for the dataset, such as friendlyName, description, labels, etc.
1274+
* :attr:`~google.cloud.bigquery.enums.UpdateMode.UPDATE_ACL`:
1275+
Includes ACL information for the dataset, which defines dataset access for one or more entities.
1276+
* :attr:`~google.cloud.bigquery.enums.UpdateMode.UPDATE_FULL`:
1277+
Includes both dataset metadata and ACL information.
12401278
12411279
Returns:
12421280
google.cloud.bigquery.dataset.Dataset:
@@ -1250,6 +1288,11 @@ def update_dataset(
12501288
path = dataset.path
12511289
span_attributes = {"path": path, "fields": fields}
12521290

1291+
if update_mode:
1292+
query_params = {"updateMode": update_mode.value}
1293+
else:
1294+
query_params = {}
1295+
12531296
api_response = self._call_api(
12541297
retry,
12551298
span_name="BigQuery.updateDataset",
@@ -1259,6 +1302,7 @@ def update_dataset(
12591302
data=partial,
12601303
headers=headers,
12611304
timeout=timeout,
1305+
query_params=query_params,
12621306
)
12631307
return Dataset.from_api_repr(api_response)
12641308

google/cloud/bigquery/enums.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,24 @@ class CreateDisposition(object):
8080
returned in the job result."""
8181

8282

83+
class DatasetView(enum.Enum):
84+
"""DatasetView specifies which dataset information is returned."""
85+
86+
DATASET_VIEW_UNSPECIFIED = "DATASET_VIEW_UNSPECIFIED"
87+
"""The default value. Currently maps to the FULL view."""
88+
89+
METADATA = "METADATA"
90+
"""View metadata information for the dataset, such as friendlyName,
91+
description, labels, etc."""
92+
93+
ACL = "ACL"
94+
"""View ACL information for the dataset, which defines dataset access
95+
for one or more entities."""
96+
97+
FULL = "FULL"
98+
"""View both dataset metadata and ACL information."""
99+
100+
83101
class DefaultPandasDTypes(enum.Enum):
84102
"""Default Pandas DataFrem DTypes to convert BigQuery data. These
85103
Sentinel values are used instead of None to maintain backward compatibility,
@@ -409,6 +427,24 @@ class BigLakeTableFormat(object):
409427
"""Apache Iceberg format."""
410428

411429

430+
class UpdateMode(enum.Enum):
431+
"""Specifies the kind of information to update in a dataset."""
432+
433+
UPDATE_MODE_UNSPECIFIED = "UPDATE_MODE_UNSPECIFIED"
434+
"""The default value. Behavior defaults to UPDATE_FULL."""
435+
436+
UPDATE_METADATA = "UPDATE_METADATA"
437+
"""Includes metadata information for the dataset, such as friendlyName,
438+
description, labels, etc."""
439+
440+
UPDATE_ACL = "UPDATE_ACL"
441+
"""Includes ACL information for the dataset, which defines dataset access
442+
for one or more entities."""
443+
444+
UPDATE_FULL = "UPDATE_FULL"
445+
"""Includes both dataset metadata and ACL information."""
446+
447+
412448
class JobCreationMode(object):
413449
"""Documented values for Job Creation Mode."""
414450

tests/unit/test_client.py

Lines changed: 159 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@
6060

6161
from google.cloud.bigquery import job as bqjob
6262
import google.cloud.bigquery._job_helpers
63-
from google.cloud.bigquery.dataset import DatasetReference
63+
from google.cloud.bigquery.dataset import DatasetReference, Dataset
64+
from google.cloud.bigquery.enums import UpdateMode, DatasetView
6465
from google.cloud.bigquery import exceptions
6566
from google.cloud.bigquery import ParquetOptions
6667
import google.cloud.bigquery.retry
@@ -752,7 +753,7 @@ def test_get_dataset(self):
752753
final_attributes.assert_called_once_with({"path": "/%s" % path}, client, None)
753754

754755
conn.api_request.assert_called_once_with(
755-
method="GET", path="/%s" % path, timeout=7.5
756+
method="GET", path="/%s" % path, timeout=7.5, query_params={}
756757
)
757758
self.assertEqual(dataset.dataset_id, self.DS_ID)
758759

@@ -818,6 +819,72 @@ def test_get_dataset(self):
818819

819820
self.assertEqual(dataset.dataset_id, self.DS_ID)
820821

822+
def test_get_dataset_with_dataset_view(self):
823+
path = "projects/%s/datasets/%s" % (self.PROJECT, self.DS_ID)
824+
creds = _make_credentials()
825+
http = object()
826+
client = self._make_one(project=self.PROJECT, credentials=creds, _http=http)
827+
resource = {
828+
"id": "%s:%s" % (self.PROJECT, self.DS_ID),
829+
"datasetReference": {"projectId": self.PROJECT, "datasetId": self.DS_ID},
830+
}
831+
dataset_ref = DatasetReference(self.PROJECT, self.DS_ID)
832+
833+
test_cases = [
834+
(None, None),
835+
(DatasetView.DATASET_VIEW_UNSPECIFIED, "DATASET_VIEW_UNSPECIFIED"),
836+
(DatasetView.METADATA, "METADATA"),
837+
(DatasetView.ACL, "ACL"),
838+
(DatasetView.FULL, "FULL"),
839+
]
840+
841+
for dataset_view_arg, expected_param_value in test_cases:
842+
with self.subTest(
843+
dataset_view_arg=dataset_view_arg,
844+
expected_param_value=expected_param_value,
845+
):
846+
# Re-initialize the connection mock for each sub-test to reset side_effect
847+
conn = client._connection = make_connection(resource)
848+
849+
dataset = client.get_dataset(dataset_ref, dataset_view=dataset_view_arg)
850+
851+
self.assertEqual(dataset.dataset_id, self.DS_ID)
852+
853+
if expected_param_value:
854+
expected_query_params = {"datasetView": expected_param_value}
855+
else:
856+
expected_query_params = {}
857+
858+
conn.api_request.assert_called_once_with(
859+
method="GET",
860+
path="/%s" % path,
861+
timeout=DEFAULT_TIMEOUT,
862+
query_params=expected_query_params if expected_query_params else {},
863+
)
864+
865+
def test_get_dataset_with_invalid_dataset_view(self):
866+
invalid_view_values = [
867+
"INVALID_STRING",
868+
123,
869+
123.45,
870+
object(),
871+
]
872+
creds = _make_credentials()
873+
http = object()
874+
client = self._make_one(project=self.PROJECT, credentials=creds, _http=http)
875+
resource = {
876+
"id": "%s:%s" % (self.PROJECT, self.DS_ID),
877+
"datasetReference": {"projectId": self.PROJECT, "datasetId": self.DS_ID},
878+
}
879+
conn = client._connection = make_connection(resource)
880+
dataset_ref = DatasetReference(self.PROJECT, self.DS_ID)
881+
882+
for invalid_view_value in invalid_view_values:
883+
with self.subTest(invalid_view_value=invalid_view_value):
884+
conn.api_request.reset_mock() # Reset mock for each sub-test
885+
with self.assertRaises(AttributeError):
886+
client.get_dataset(dataset_ref, dataset_view=invalid_view_value)
887+
821888
def test_ensure_bqstorage_client_creating_new_instance(self):
822889
bigquery_storage = pytest.importorskip("google.cloud.bigquery_storage")
823890

@@ -2101,6 +2168,7 @@ def test_update_dataset(self):
21012168
},
21022169
path="/" + PATH,
21032170
timeout=7.5,
2171+
query_params={},
21042172
)
21052173
self.assertEqual(ds2.description, ds.description)
21062174
self.assertEqual(ds2.friendly_name, ds.friendly_name)
@@ -2114,6 +2182,94 @@ def test_update_dataset(self):
21142182
client.update_dataset(ds, [])
21152183
req = conn.api_request.call_args
21162184
self.assertEqual(req[1]["headers"]["If-Match"], "etag")
2185+
self.assertEqual(req[1].get("query_params"), {})
2186+
2187+
def test_update_dataset_w_update_mode(self):
2188+
PATH = f"projects/{self.PROJECT}/datasets/{self.DS_ID}"
2189+
creds = _make_credentials()
2190+
client = self._make_one(project=self.PROJECT, credentials=creds)
2191+
2192+
DESCRIPTION = "DESCRIPTION"
2193+
RESOURCE = {
2194+
"datasetReference": {"projectId": self.PROJECT, "datasetId": self.DS_ID},
2195+
"etag": "etag",
2196+
"description": DESCRIPTION,
2197+
}
2198+
dataset_ref = DatasetReference(self.PROJECT, self.DS_ID)
2199+
orig_dataset = Dataset(dataset_ref)
2200+
orig_dataset.description = DESCRIPTION
2201+
filter_fields = ["description"]
2202+
2203+
test_cases = [
2204+
(None, None),
2205+
(UpdateMode.UPDATE_MODE_UNSPECIFIED, "UPDATE_MODE_UNSPECIFIED"),
2206+
(UpdateMode.UPDATE_METADATA, "UPDATE_METADATA"),
2207+
(UpdateMode.UPDATE_ACL, "UPDATE_ACL"),
2208+
(UpdateMode.UPDATE_FULL, "UPDATE_FULL"),
2209+
]
2210+
2211+
for update_mode_arg, expected_param_value in test_cases:
2212+
with self.subTest(
2213+
update_mode_arg=update_mode_arg,
2214+
expected_param_value=expected_param_value,
2215+
):
2216+
conn = client._connection = make_connection(RESOURCE, RESOURCE)
2217+
2218+
new_dataset = client.update_dataset(
2219+
orig_dataset,
2220+
fields=filter_fields,
2221+
update_mode=update_mode_arg,
2222+
)
2223+
self.assertEqual(orig_dataset.description, new_dataset.description)
2224+
2225+
if expected_param_value:
2226+
expected_query_params = {"updateMode": expected_param_value}
2227+
else:
2228+
expected_query_params = {}
2229+
2230+
conn.api_request.assert_called_once_with(
2231+
method="PATCH",
2232+
path="/" + PATH,
2233+
data={"description": DESCRIPTION},
2234+
timeout=DEFAULT_TIMEOUT,
2235+
query_params=expected_query_params if expected_query_params else {},
2236+
)
2237+
2238+
def test_update_dataset_w_invalid_update_mode(self):
2239+
creds = _make_credentials()
2240+
client = self._make_one(project=self.PROJECT, credentials=creds)
2241+
2242+
DESCRIPTION = "DESCRIPTION"
2243+
resource = {
2244+
"datasetReference": {"projectId": self.PROJECT, "datasetId": self.DS_ID},
2245+
"etag": "etag",
2246+
}
2247+
2248+
dataset_ref = DatasetReference(self.PROJECT, self.DS_ID)
2249+
orig_dataset = Dataset(dataset_ref)
2250+
orig_dataset.description = DESCRIPTION
2251+
filter_fields = ["description"] # A non-empty list of fields is required
2252+
2253+
# Mock the connection to prevent actual API calls
2254+
# and to provide a minimal valid response if the call were to proceed.
2255+
conn = client._connection = make_connection(resource)
2256+
2257+
test_cases = [
2258+
"INVALID_STRING",
2259+
123,
2260+
123.45,
2261+
object(),
2262+
]
2263+
2264+
for invalid_update_mode in test_cases:
2265+
with self.subTest(invalid_update_mode=invalid_update_mode):
2266+
conn.api_request.reset_mock() # Reset mock for each sub-test
2267+
with self.assertRaises(AttributeError):
2268+
client.update_dataset(
2269+
orig_dataset,
2270+
fields=filter_fields,
2271+
update_mode=invalid_update_mode,
2272+
)
21172273

21182274
def test_update_dataset_w_custom_property(self):
21192275
# The library should handle sending properties to the API that are not
@@ -2145,6 +2301,7 @@ def test_update_dataset_w_custom_property(self):
21452301
data={"newAlphaProperty": "unreleased property"},
21462302
path=path,
21472303
timeout=DEFAULT_TIMEOUT,
2304+
query_params={},
21482305
)
21492306

21502307
self.assertEqual(dataset.dataset_id, self.DS_ID)

tests/unit/test_create_dataset.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,12 @@ def test_create_dataset_alreadyexists_w_exists_ok_true(PROJECT, DS_ID, LOCATION)
372372
},
373373
timeout=DEFAULT_TIMEOUT,
374374
),
375-
mock.call(method="GET", path=get_path, timeout=DEFAULT_TIMEOUT),
375+
mock.call(
376+
method="GET",
377+
path=get_path,
378+
timeout=DEFAULT_TIMEOUT,
379+
query_params={},
380+
),
376381
]
377382
)
378383

0 commit comments

Comments
 (0)