Skip to content

Commit dc3f531

Browse files
authored
Allow nested records w/ null values. (#7297)
Adds explicit unit tests for helpers added in #7022. Closes #7294.
1 parent ade4dd5 commit dc3f531

File tree

2 files changed

+125
-1
lines changed

2 files changed

+125
-1
lines changed

bigquery/google/cloud/bigquery/_helpers.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,10 @@ def _record_field_to_json(fields, row_value):
398398

399399
for subindex, subfield in enumerate(fields):
400400
subname = subfield.name
401-
subvalue = row_value[subname] if isdict else row_value[subindex]
401+
if isdict:
402+
subvalue = row_value.get(subname)
403+
else:
404+
subvalue = row_value[subindex]
402405
record[subname] = _field_to_json(subfield, subvalue)
403406
return record
404407

bigquery/tests/unit/test__helpers.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,127 @@ def test_w_datetime(self):
776776
self.assertEqual(self._call_fut(when), "12:13:41")
777777

778778

779+
def _make_field(field_type, mode="NULLABLE", name="testing", fields=()):
780+
from google.cloud.bigquery.schema import SchemaField
781+
782+
return SchemaField(name=name, field_type=field_type, mode=mode, fields=fields)
783+
784+
785+
class Test_scalar_field_to_json(unittest.TestCase):
786+
def _call_fut(self, field, value):
787+
from google.cloud.bigquery._helpers import _scalar_field_to_json
788+
789+
return _scalar_field_to_json(field, value)
790+
791+
def test_w_unknown_field_type(self):
792+
field = _make_field("UNKNOWN")
793+
original = object()
794+
converted = self._call_fut(field, original)
795+
self.assertIs(converted, original)
796+
797+
def test_w_known_field_type(self):
798+
field = _make_field("INT64")
799+
original = 42
800+
converted = self._call_fut(field, original)
801+
self.assertEqual(converted, str(original))
802+
803+
804+
class Test_repeated_field_to_json(unittest.TestCase):
805+
def _call_fut(self, field, value):
806+
from google.cloud.bigquery._helpers import _repeated_field_to_json
807+
808+
return _repeated_field_to_json(field, value)
809+
810+
def test_w_empty(self):
811+
field = _make_field("INT64", mode="REPEATED")
812+
original = []
813+
converted = self._call_fut(field, original)
814+
self.assertEqual(converted, original)
815+
self.assertEqual(field.mode, "REPEATED")
816+
817+
def test_w_non_empty(self):
818+
field = _make_field("INT64", mode="REPEATED")
819+
original = [42]
820+
converted = self._call_fut(field, original)
821+
self.assertEqual(converted, [str(value) for value in original])
822+
self.assertEqual(field.mode, "REPEATED")
823+
824+
825+
class Test_record_field_to_json(unittest.TestCase):
826+
def _call_fut(self, field, value):
827+
from google.cloud.bigquery._helpers import _record_field_to_json
828+
829+
return _record_field_to_json(field, value)
830+
831+
def test_w_empty(self):
832+
fields = []
833+
original = []
834+
converted = self._call_fut(fields, original)
835+
self.assertEqual(converted, {})
836+
837+
def test_w_non_empty_list(self):
838+
fields = [
839+
_make_field("INT64", name="one", mode="NULLABLE"),
840+
_make_field("STRING", name="two", mode="NULLABLE"),
841+
]
842+
original = [42, "two"]
843+
converted = self._call_fut(fields, original)
844+
self.assertEqual(converted, {"one": "42", "two": "two"})
845+
846+
def test_w_non_empty_dict(self):
847+
fields = [
848+
_make_field("INT64", name="one", mode="NULLABLE"),
849+
_make_field("STRING", name="two", mode="NULLABLE"),
850+
]
851+
original = {"one": 42, "two": "two"}
852+
converted = self._call_fut(fields, original)
853+
self.assertEqual(converted, {"one": "42", "two": "two"})
854+
855+
def test_w_missing_nullable(self):
856+
fields = [
857+
_make_field("INT64", name="one", mode="NULLABLE"),
858+
_make_field("STRING", name="two", mode="NULLABLE"),
859+
]
860+
original = {"one": 42}
861+
converted = self._call_fut(fields, original)
862+
self.assertEqual(converted, {"one": "42", "two": None})
863+
864+
865+
class Test_field_to_json(unittest.TestCase):
866+
def _call_fut(self, field, value):
867+
from google.cloud.bigquery._helpers import _field_to_json
868+
869+
return _field_to_json(field, value)
870+
871+
def test_w_none(self):
872+
field = _make_field("INT64")
873+
original = None
874+
converted = self._call_fut(field, original)
875+
self.assertIsNone(converted)
876+
877+
def test_w_repeated(self):
878+
field = _make_field("INT64", mode="REPEATED")
879+
original = [42, 17]
880+
converted = self._call_fut(field, original)
881+
self.assertEqual(converted, [str(value) for value in original])
882+
883+
def test_w_record(self):
884+
subfields = [
885+
_make_field("INT64", name="one"),
886+
_make_field("STRING", name="two"),
887+
]
888+
field = _make_field("RECORD", fields=subfields)
889+
original = {"one": 42, "two": "two"}
890+
converted = self._call_fut(field, original)
891+
self.assertEqual(converted, {"one": "42", "two": "two"})
892+
893+
def test_w_scalar(self):
894+
field = _make_field("INT64")
895+
original = 42
896+
converted = self._call_fut(field, original)
897+
self.assertEqual(converted, str(original))
898+
899+
779900
class Test_snake_to_camel_case(unittest.TestCase):
780901
def _call_fut(self, value):
781902
from google.cloud.bigquery._helpers import _snake_to_camel_case

0 commit comments

Comments
 (0)