Skip to content

Commit 011f829

Browse files
Gaurang033Linchin
andcommitted
feat: add support for dataset.default_rounding_mode (googleapis#1688)
Co-authored-by: Lingqing Gan <[email protected]>
1 parent 963a405 commit 011f829

File tree

3 files changed

+153
-4
lines changed

3 files changed

+153
-4
lines changed

google/cloud/bigquery/dataset.py

+38
Original file line numberDiff line numberDiff line change
@@ -525,13 +525,51 @@ class Dataset(object):
525525
"friendly_name": "friendlyName",
526526
"default_encryption_configuration": "defaultEncryptionConfiguration",
527527
"storage_billing_model": "storageBillingModel",
528+
"default_rounding_mode": "defaultRoundingMode",
528529
}
529530

530531
def __init__(self, dataset_ref) -> None:
531532
if isinstance(dataset_ref, str):
532533
dataset_ref = DatasetReference.from_string(dataset_ref)
533534
self._properties = {"datasetReference": dataset_ref.to_api_repr(), "labels": {}}
534535

536+
@property
537+
def default_rounding_mode(self):
538+
"""Union[str, None]: defaultRoundingMode of the dataset as set by the user
539+
(defaults to :data:`None`).
540+
541+
Set the value to one of ``'ROUND_HALF_AWAY_FROM_ZERO'``, ``'ROUND_HALF_EVEN'``, or
542+
``'ROUNDING_MODE_UNSPECIFIED'``.
543+
544+
See `default rounding mode
545+
<https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets#Dataset.FIELDS.default_rounding_mode>`_
546+
in REST API docs and `updating the default rounding model
547+
<https://cloud.google.com/bigquery/docs/updating-datasets#update_rounding_mode>`_
548+
guide.
549+
550+
Raises:
551+
ValueError: for invalid value types.
552+
"""
553+
return self._properties.get("defaultRoundingMode")
554+
555+
@default_rounding_mode.setter
556+
def default_rounding_mode(self, value):
557+
possible_values = [
558+
"ROUNDING_MODE_UNSPECIFIED",
559+
"ROUND_HALF_AWAY_FROM_ZERO",
560+
"ROUND_HALF_EVEN",
561+
]
562+
if not isinstance(value, str) and value is not None:
563+
raise ValueError("Pass a string, or None")
564+
if value is None:
565+
self._properties["defaultRoundingMode"] = "ROUNDING_MODE_UNSPECIFIED"
566+
if value not in possible_values and value is not None:
567+
raise ValueError(
568+
f'rounding mode needs to be one of {",".join(possible_values)}'
569+
)
570+
if value:
571+
self._properties["defaultRoundingMode"] = value
572+
535573
@property
536574
def project(self):
537575
"""str: Project ID of the project bound to the dataset."""

tests/system/test_client.py

+13-3
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,13 @@ def test_get_dataset(self):
265265
self.assertEqual(got.friendly_name, "Friendly")
266266
self.assertEqual(got.description, "Description")
267267

268+
def test_create_dataset_with_default_rounding_mode(self):
269+
DATASET_ID = _make_dataset_id("create_dataset_rounding_mode")
270+
dataset = self.temp_dataset(DATASET_ID, default_rounding_mode="ROUND_HALF_EVEN")
271+
272+
self.assertTrue(_dataset_exists(dataset))
273+
self.assertEqual(dataset.default_rounding_mode, "ROUND_HALF_EVEN")
274+
268275
def test_update_dataset(self):
269276
dataset = self.temp_dataset(_make_dataset_id("update_dataset"))
270277
self.assertTrue(_dataset_exists(dataset))
@@ -2286,12 +2293,15 @@ def test_nested_table_to_arrow(self):
22862293
self.assertTrue(pyarrow.types.is_list(record_col[1].type))
22872294
self.assertTrue(pyarrow.types.is_int64(record_col[1].type.value_type))
22882295

2289-
def temp_dataset(self, dataset_id, location=None):
2296+
def temp_dataset(self, dataset_id, *args, **kwargs):
22902297
project = Config.CLIENT.project
22912298
dataset_ref = bigquery.DatasetReference(project, dataset_id)
22922299
dataset = Dataset(dataset_ref)
2293-
if location:
2294-
dataset.location = location
2300+
if kwargs.get("location"):
2301+
dataset.location = kwargs.get("location")
2302+
if kwargs.get("default_rounding_mode"):
2303+
dataset.default_rounding_mode = kwargs.get("default_rounding_mode")
2304+
22952305
dataset = helpers.retry_403(Config.CLIENT.create_dataset)(dataset)
22962306
self.to_delete.append(dataset)
22972307
return dataset

tests/unit/test_create_dataset.py

+102-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def test_create_dataset_w_attrs(client, PROJECT, DS_ID):
6363
"datasetId": "starry-skies",
6464
"tableId": "northern-hemisphere",
6565
}
66+
DEFAULT_ROUNDING_MODE = "ROUND_HALF_EVEN"
6667
RESOURCE = {
6768
"datasetReference": {"projectId": PROJECT, "datasetId": DS_ID},
6869
"etag": "etag",
@@ -73,6 +74,7 @@ def test_create_dataset_w_attrs(client, PROJECT, DS_ID):
7374
"defaultTableExpirationMs": "3600",
7475
"labels": LABELS,
7576
"access": [{"role": "OWNER", "userByEmail": USER_EMAIL}, {"view": VIEW}],
77+
"defaultRoundingMode": DEFAULT_ROUNDING_MODE,
7678
}
7779
conn = client._connection = make_connection(RESOURCE)
7880
entries = [
@@ -88,8 +90,8 @@ def test_create_dataset_w_attrs(client, PROJECT, DS_ID):
8890
before.default_table_expiration_ms = 3600
8991
before.location = LOCATION
9092
before.labels = LABELS
93+
before.default_rounding_mode = DEFAULT_ROUNDING_MODE
9194
after = client.create_dataset(before)
92-
9395
assert after.dataset_id == DS_ID
9496
assert after.project == PROJECT
9597
assert after.etag == RESOURCE["etag"]
@@ -99,6 +101,7 @@ def test_create_dataset_w_attrs(client, PROJECT, DS_ID):
99101
assert after.location == LOCATION
100102
assert after.default_table_expiration_ms == 3600
101103
assert after.labels == LABELS
104+
assert after.default_rounding_mode == DEFAULT_ROUNDING_MODE
102105

103106
conn.api_request.assert_called_once_with(
104107
method="POST",
@@ -109,6 +112,7 @@ def test_create_dataset_w_attrs(client, PROJECT, DS_ID):
109112
"friendlyName": FRIENDLY_NAME,
110113
"location": LOCATION,
111114
"defaultTableExpirationMs": "3600",
115+
"defaultRoundingMode": DEFAULT_ROUNDING_MODE,
112116
"access": [
113117
{"role": "OWNER", "userByEmail": USER_EMAIL},
114118
{"view": VIEW, "role": None},
@@ -365,3 +369,100 @@ def test_create_dataset_alreadyexists_w_exists_ok_true(PROJECT, DS_ID, LOCATION)
365369
mock.call(method="GET", path=get_path, timeout=DEFAULT_TIMEOUT),
366370
]
367371
)
372+
373+
374+
def test_create_dataset_with_default_rounding_mode_if_value_is_none(
375+
PROJECT, DS_ID, LOCATION
376+
):
377+
default_rounding_mode = None
378+
path = "/projects/%s/datasets" % PROJECT
379+
resource = {
380+
"datasetReference": {"projectId": PROJECT, "datasetId": DS_ID},
381+
"etag": "etag",
382+
"id": "{}:{}".format(PROJECT, DS_ID),
383+
"location": LOCATION,
384+
}
385+
client = make_client(location=LOCATION)
386+
conn = client._connection = make_connection(resource)
387+
388+
ds_ref = DatasetReference(PROJECT, DS_ID)
389+
before = Dataset(ds_ref)
390+
before.default_rounding_mode = default_rounding_mode
391+
after = client.create_dataset(before)
392+
393+
assert after.dataset_id == DS_ID
394+
assert after.project == PROJECT
395+
assert after.default_rounding_mode is None
396+
397+
conn.api_request.assert_called_once_with(
398+
method="POST",
399+
path=path,
400+
data={
401+
"datasetReference": {"projectId": PROJECT, "datasetId": DS_ID},
402+
"labels": {},
403+
"location": LOCATION,
404+
"defaultRoundingMode": "ROUNDING_MODE_UNSPECIFIED",
405+
},
406+
timeout=DEFAULT_TIMEOUT,
407+
)
408+
409+
410+
def test_create_dataset_with_default_rounding_mode_if_value_is_not_string(
411+
PROJECT, DS_ID, LOCATION
412+
):
413+
default_rounding_mode = 10
414+
ds_ref = DatasetReference(PROJECT, DS_ID)
415+
dataset = Dataset(ds_ref)
416+
with pytest.raises(ValueError) as e:
417+
dataset.default_rounding_mode = default_rounding_mode
418+
assert str(e.value) == "Pass a string, or None"
419+
420+
421+
def test_create_dataset_with_default_rounding_mode_if_value_is_not_in_possible_values(
422+
PROJECT, DS_ID
423+
):
424+
default_rounding_mode = "ROUND_HALF_AWAY_FROM_ZEROS"
425+
ds_ref = DatasetReference(PROJECT, DS_ID)
426+
dataset = Dataset(ds_ref)
427+
with pytest.raises(ValueError) as e:
428+
dataset.default_rounding_mode = default_rounding_mode
429+
assert (
430+
str(e.value)
431+
== "rounding mode needs to be one of ROUNDING_MODE_UNSPECIFIED,ROUND_HALF_AWAY_FROM_ZERO,ROUND_HALF_EVEN"
432+
)
433+
434+
435+
def test_create_dataset_with_default_rounding_mode_if_value_is_in_possible_values(
436+
PROJECT, DS_ID, LOCATION
437+
):
438+
default_rounding_mode = "ROUND_HALF_AWAY_FROM_ZERO"
439+
path = "/projects/%s/datasets" % PROJECT
440+
resource = {
441+
"datasetReference": {"projectId": PROJECT, "datasetId": DS_ID},
442+
"etag": "etag",
443+
"id": "{}:{}".format(PROJECT, DS_ID),
444+
"location": LOCATION,
445+
}
446+
client = make_client(location=LOCATION)
447+
conn = client._connection = make_connection(resource)
448+
449+
ds_ref = DatasetReference(PROJECT, DS_ID)
450+
before = Dataset(ds_ref)
451+
before.default_rounding_mode = default_rounding_mode
452+
after = client.create_dataset(before)
453+
454+
assert after.dataset_id == DS_ID
455+
assert after.project == PROJECT
456+
assert after.default_rounding_mode is None
457+
458+
conn.api_request.assert_called_once_with(
459+
method="POST",
460+
path=path,
461+
data={
462+
"datasetReference": {"projectId": PROJECT, "datasetId": DS_ID},
463+
"labels": {},
464+
"location": LOCATION,
465+
"defaultRoundingMode": default_rounding_mode,
466+
},
467+
timeout=DEFAULT_TIMEOUT,
468+
)

0 commit comments

Comments
 (0)