Skip to content

Commit 5711a11

Browse files
feat: Add null_markers property to LoadJobConfig and CSVOptions
1 parent 69a2c2b commit 5711a11

File tree

5 files changed

+57
-0
lines changed

5 files changed

+57
-0
lines changed

google/cloud/bigquery/external_config.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,19 @@ def skip_leading_rows(self):
474474
def skip_leading_rows(self, value):
475475
self._properties["skipLeadingRows"] = str(value)
476476

477+
@property
478+
def null_markers(self) -> Optional[Iterable[str]]:
479+
"""Optional[Iterable[str]]: The strings that are interpreted as NULL values.
480+
481+
See
482+
https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#CsvOptions.FIELDS.null_markers
483+
"""
484+
return self._properties.get("nullMarkers")
485+
486+
@null_markers.setter
487+
def null_markers(self, value: Optional[Iterable[str]]):
488+
self._properties["nullMarkers"] = value
489+
477490
def to_api_repr(self) -> dict:
478491
"""Build an API representation of this object.
479492

google/cloud/bigquery/job/load.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,19 @@ def null_marker(self):
386386
def null_marker(self, value):
387387
self._set_sub_prop("nullMarker", value)
388388

389+
@property
390+
def null_markers(self) -> Optional[List[str]]:
391+
"""Optional[List[str]]: Represents a null value (CSV only).
392+
393+
See:
394+
https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.null_markers
395+
"""
396+
return self._get_sub_prop("nullMarkers")
397+
398+
@null_markers.setter
399+
def null_markers(self, value: Optional[List[str]]):
400+
self._set_sub_prop("nullMarkers", value)
401+
389402
@property
390403
def preserve_ascii_control_characters(self):
391404
"""Optional[bool]: Preserves the embedded ASCII control characters when sourceFormat is set to CSV.
@@ -854,6 +867,13 @@ def null_marker(self):
854867
"""
855868
return self.configuration.null_marker
856869

870+
@property
871+
def null_markers(self):
872+
"""See
873+
:attr:`google.cloud.bigquery.job.LoadJobConfig.null_markers`.
874+
"""
875+
return self.configuration.null_markers
876+
857877
@property
858878
def quote_character(self):
859879
"""See

tests/unit/job/test_load.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ def _verifyResourceProperties(self, job, resource):
140140
self.assertEqual(job.null_marker, config["nullMarker"])
141141
else:
142142
self.assertIsNone(job.null_marker)
143+
if "nullMarkers" in config:
144+
self.assertEqual(job.null_markers, config["nullMarkers"])
145+
else:
146+
self.assertIsNone(job.null_markers)
143147
if "quote" in config:
144148
self.assertEqual(job.quote_character, config["quote"])
145149
else:

tests/unit/job/test_load_config.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,22 @@ def test_null_marker_setter(self):
469469
config.null_marker = null_marker
470470
self.assertEqual(config._properties["load"]["nullMarker"], null_marker)
471471

472+
def test_null_markers_missing(self):
473+
config = self._get_target_class()()
474+
self.assertIsNone(config.null_markers)
475+
476+
def test_null_markers_hit(self):
477+
null_markers = ["", "NA"]
478+
config = self._get_target_class()()
479+
config._properties["load"]["nullMarkers"] = null_markers
480+
self.assertEqual(config.null_markers, null_markers)
481+
482+
def test_null_markers_setter(self):
483+
null_markers = ["", "NA"]
484+
config = self._get_target_class()()
485+
config.null_markers = null_markers
486+
self.assertEqual(config._properties["load"]["nullMarkers"], null_markers)
487+
472488
def test_preserve_ascii_control_characters_missing(self):
473489
config = self._get_target_class()()
474490
self.assertIsNone(config.preserve_ascii_control_characters)

tests/unit/test_external_config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ def test_from_api_repr_csv(self):
277277
"allowJaggedRows": False,
278278
"encoding": "encoding",
279279
"preserveAsciiControlCharacters": False,
280+
"nullMarkers": ["", "NA"],
280281
},
281282
},
282283
)
@@ -293,6 +294,7 @@ def test_from_api_repr_csv(self):
293294
self.assertEqual(ec.options.allow_jagged_rows, False)
294295
self.assertEqual(ec.options.encoding, "encoding")
295296
self.assertEqual(ec.options.preserve_ascii_control_characters, False)
297+
self.assertEqual(ec.options.null_markers, ["", "NA"])
296298

297299
got_resource = ec.to_api_repr()
298300

@@ -314,6 +316,7 @@ def test_to_api_repr_csv(self):
314316
options.skip_leading_rows = 123
315317
options.allow_jagged_rows = False
316318
options.preserve_ascii_control_characters = False
319+
options.null_markers = ["", "NA"]
317320
ec.csv_options = options
318321

319322
exp_resource = {
@@ -326,6 +329,7 @@ def test_to_api_repr_csv(self):
326329
"allowJaggedRows": False,
327330
"encoding": "encoding",
328331
"preserveAsciiControlCharacters": False,
332+
"nullMarkers": ["", "NA"],
329333
},
330334
}
331335

0 commit comments

Comments
 (0)