diff --git a/google/cloud/bigquery/_helpers.py b/google/cloud/bigquery/_helpers.py index 88a0944c48f3..222cf2579c17 100644 --- a/google/cloud/bigquery/_helpers.py +++ b/google/cloud/bigquery/_helpers.py @@ -54,9 +54,9 @@ def _record_from_json(value, field): for subfield, cell in zip(field.fields, value['f']): converter = _CELLDATA_FROM_JSON[subfield.field_type] if field.mode == 'REPEATED': - value = [converter(item, field) for item in cell['v']] + value = [converter(item, subfield) for item in cell['v']] else: - value = converter(cell['v'], field) + value = converter(cell['v'], subfield) record[subfield.name] = value return record diff --git a/unit_tests/bigquery/test__helpers.py b/unit_tests/bigquery/test__helpers.py index 6521a6338b23..8e885c31cd59 100644 --- a/unit_tests/bigquery/test__helpers.py +++ b/unit_tests/bigquery/test__helpers.py @@ -15,6 +15,255 @@ import unittest +class Test_not_null(unittest.TestCase): + + def _callFUT(self, value, field): + from google.cloud.bigquery._helpers import _not_null + return _not_null(value, field) + + def test_w_none_nullable(self): + self.assertFalse(self._callFUT(None, _Field('NULLABLE'))) + + def test_w_none_required(self): + self.assertTrue(self._callFUT(None, _Field('REQUIRED'))) + + def test_w_value(self): + self.assertTrue(self._callFUT(object(), object())) + + +class Test_int_from_json(unittest.TestCase): + + def _callFUT(self, value, field): + from google.cloud.bigquery._helpers import _int_from_json + return _int_from_json(value, field) + + def test_w_none_nullable(self): + self.assertIsNone(self._callFUT(None, _Field('NULLABLE'))) + + def test_w_none_required(self): + with self.assertRaises(TypeError): + self._callFUT(None, _Field('REQUIRED')) + + def test_w_string_value(self): + coerced = self._callFUT('42', object()) + self.assertEqual(coerced, 42) + + def test_w_float_value(self): + coerced = self._callFUT(42, object()) + self.assertEqual(coerced, 42) + + +class Test_float_from_json(unittest.TestCase): + + def _callFUT(self, value, field): + from google.cloud.bigquery._helpers import _float_from_json + return _float_from_json(value, field) + + def test_w_none_nullable(self): + self.assertIsNone(self._callFUT(None, _Field('NULLABLE'))) + + def test_w_none_required(self): + with self.assertRaises(TypeError): + self._callFUT(None, _Field('REQUIRED')) + + def test_w_string_value(self): + coerced = self._callFUT('3.1415', object()) + self.assertEqual(coerced, 3.1415) + + def test_w_float_value(self): + coerced = self._callFUT(3.1415, object()) + self.assertEqual(coerced, 3.1415) + + +class Test_bool_from_json(unittest.TestCase): + + def _callFUT(self, value, field): + from google.cloud.bigquery._helpers import _bool_from_json + return _bool_from_json(value, field) + + def test_w_none_nullable(self): + self.assertIsNone(self._callFUT(None, _Field('NULLABLE'))) + + def test_w_none_required(self): + with self.assertRaises(AttributeError): + self._callFUT(None, _Field('REQUIRED')) + + def test_w_value_t(self): + coerced = self._callFUT('T', object()) + self.assertTrue(coerced) + + def test_w_value_true(self): + coerced = self._callFUT('True', object()) + self.assertTrue(coerced) + + def test_w_value_1(self): + coerced = self._callFUT('1', object()) + self.assertTrue(coerced) + + def test_w_value_other(self): + coerced = self._callFUT('f', object()) + self.assertFalse(coerced) + + +class Test_datetime_from_json(unittest.TestCase): + + def _callFUT(self, value, field): + from google.cloud.bigquery._helpers import _datetime_from_json + return _datetime_from_json(value, field) + + def test_w_none_nullable(self): + self.assertIsNone(self._callFUT(None, _Field('NULLABLE'))) + + def test_w_none_required(self): + with self.assertRaises(TypeError): + self._callFUT(None, _Field('REQUIRED')) + + def test_w_string_value(self): + import datetime + from google.cloud._helpers import _EPOCH + coerced = self._callFUT('1.234567', object()) + self.assertEqual( + coerced, + _EPOCH + datetime.timedelta(seconds=1, microseconds=234567)) + + def test_w_float_value(self): + import datetime + from google.cloud._helpers import _EPOCH + coerced = self._callFUT(1.234567, object()) + self.assertEqual( + coerced, + _EPOCH + datetime.timedelta(seconds=1, microseconds=234567)) + + +class Test_record_from_json(unittest.TestCase): + + def _callFUT(self, value, field): + from google.cloud.bigquery._helpers import _record_from_json + return _record_from_json(value, field) + + def test_w_none_nullable(self): + self.assertIsNone(self._callFUT(None, _Field('NULLABLE'))) + + def test_w_none_required(self): + with self.assertRaises(TypeError): + self._callFUT(None, _Field('REQUIRED')) + + def test_w_nullable_subfield_none(self): + subfield = _Field('NULLABLE', 'age', 'INTEGER') + field = _Field('REQUIRED', fields=[subfield]) + value = {'f': [{'v': None}]} + coerced = self._callFUT(value, field) + self.assertEqual(coerced, {'age': None}) + + def test_w_scalar_subfield(self): + subfield = _Field('REQUIRED', 'age', 'INTEGER') + field = _Field('REQUIRED', fields=[subfield]) + value = {'f': [{'v': 42}]} + coerced = self._callFUT(value, field) + self.assertEqual(coerced, {'age': 42}) + + def test_w_repeated_subfield(self): + subfield = _Field('REPEATED', 'color', 'STRING') + field = _Field('REQUIRED', fields=[subfield]) + value = {'f': [{'v': ['red', 'yellow', 'blue']}]} + coerced = self._callFUT(value, field) + self.assertEqual(coerced, {'color': ['red', 'yellow', 'blue']}) + + def test_w_record_subfield(self): + full_name = _Field('REQUIRED', 'full_name', 'STRING') + area_code = _Field('REQUIRED', 'area_code', 'STRING') + local_number = _Field('REQUIRED', 'local_number', 'STRING') + rank = _Field('REQUIRED', 'rank', 'INTEGER') + phone = _Field('NULLABLE', 'phone', 'RECORD', + fields=[area_code, local_number, rank]) + person = _Field('REQUIRED', 'person', 'RECORD', + fields=[full_name, phone]) + value = { + 'f': [ + {'v': 'Phred Phlyntstone'}, + {'v': {'f': [{'v': '800'}, {'v': '555-1212'}, {'v': 1}]}}, + ], + } + expected = { + 'full_name': 'Phred Phlyntstone', + 'phone': { + 'area_code': '800', + 'local_number': '555-1212', + 'rank': 1, + } + } + coerced = self._callFUT(value, person) + self.assertEqual(coerced, expected) + + +class Test_string_from_json(unittest.TestCase): + + def _callFUT(self, value, field): + from google.cloud.bigquery._helpers import _string_from_json + return _string_from_json(value, field) + + def test_w_none_nullable(self): + self.assertIsNone(self._callFUT(None, _Field('NULLABLE'))) + + def test_w_none_required(self): + self.assertIsNone(self._callFUT(None, _Field('RECORD'))) + + def test_w_string_value(self): + coerced = self._callFUT('Wonderful!', object()) + self.assertEqual(coerced, 'Wonderful!') + + +class Test_rows_from_json(unittest.TestCase): + + def _callFUT(self, value, field): + from google.cloud.bigquery._helpers import _rows_from_json + return _rows_from_json(value, field) + + def test_w_record_subfield(self): + full_name = _Field('REQUIRED', 'full_name', 'STRING') + area_code = _Field('REQUIRED', 'area_code', 'STRING') + local_number = _Field('REQUIRED', 'local_number', 'STRING') + rank = _Field('REQUIRED', 'rank', 'INTEGER') + phone = _Field('NULLABLE', 'phone', 'RECORD', + fields=[area_code, local_number, rank]) + color = _Field('REPEATED', 'color', 'STRING') + schema = [full_name, phone, color] + rows = [ + {'f': [ + {'v': 'Phred Phlyntstone'}, + {'v': {'f': [{'v': '800'}, {'v': '555-1212'}, {'v': 1}]}}, + {'v': ['orange', 'black']}, + ]}, + {'f': [ + {'v': 'Bharney Rhubble'}, + {'v': {'f': [{'v': '877'}, {'v': '768-5309'}, {'v': 2}]}}, + {'v': ['brown']}, + ]}, + {'f': [ + {'v': 'Wylma Phlyntstone'}, + {'v': None}, + {'v': []}, + ]}, + ] + phred_phone = { + 'area_code': '800', + 'local_number': '555-1212', + 'rank': 1, + } + bharney_phone = { + 'area_code': '877', + 'local_number': '768-5309', + 'rank': 2, + } + expected = [ + ('Phred Phlyntstone', phred_phone, ['orange', 'black']), + ('Bharney Rhubble', bharney_phone, ['brown']), + ('Wylma Phlyntstone', None, []), + ] + coerced = self._callFUT(rows, schema) + self.assertEqual(coerced, expected) + + class Test_ConfigurationProperty(unittest.TestCase): def _getTargetClass(self): @@ -114,3 +363,12 @@ def __init__(self): del wrapper.attr self.assertEqual(wrapper.attr, None) self.assertEqual(wrapper._configuration._attr, None) + + +class _Field(object): + + def __init__(self, mode, name='unknown', field_type='UNKNOWN', fields=()): + self.mode = mode + self.name = name + self.field_type = field_type + self.fields = fields