Skip to content

Commit a8338fd

Browse files
authored
Merge pull request #2272 from tseaver/2265-bigquery-test-json-helpers-directly
Add explicit tests for column-marshalling helpers.
2 parents e553ddd + 2de745f commit a8338fd

File tree

2 files changed

+260
-2
lines changed

2 files changed

+260
-2
lines changed

google/cloud/bigquery/_helpers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ def _record_from_json(value, field):
5454
for subfield, cell in zip(field.fields, value['f']):
5555
converter = _CELLDATA_FROM_JSON[subfield.field_type]
5656
if field.mode == 'REPEATED':
57-
value = [converter(item, field) for item in cell['v']]
57+
value = [converter(item, subfield) for item in cell['v']]
5858
else:
59-
value = converter(cell['v'], field)
59+
value = converter(cell['v'], subfield)
6060
record[subfield.name] = value
6161
return record
6262

unit_tests/bigquery/test__helpers.py

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,255 @@
1515
import unittest
1616

1717

18+
class Test_not_null(unittest.TestCase):
19+
20+
def _callFUT(self, value, field):
21+
from google.cloud.bigquery._helpers import _not_null
22+
return _not_null(value, field)
23+
24+
def test_w_none_nullable(self):
25+
self.assertFalse(self._callFUT(None, _Field('NULLABLE')))
26+
27+
def test_w_none_required(self):
28+
self.assertTrue(self._callFUT(None, _Field('REQUIRED')))
29+
30+
def test_w_value(self):
31+
self.assertTrue(self._callFUT(object(), object()))
32+
33+
34+
class Test_int_from_json(unittest.TestCase):
35+
36+
def _callFUT(self, value, field):
37+
from google.cloud.bigquery._helpers import _int_from_json
38+
return _int_from_json(value, field)
39+
40+
def test_w_none_nullable(self):
41+
self.assertIsNone(self._callFUT(None, _Field('NULLABLE')))
42+
43+
def test_w_none_required(self):
44+
with self.assertRaises(TypeError):
45+
self._callFUT(None, _Field('REQUIRED'))
46+
47+
def test_w_string_value(self):
48+
coerced = self._callFUT('42', object())
49+
self.assertEqual(coerced, 42)
50+
51+
def test_w_float_value(self):
52+
coerced = self._callFUT(42, object())
53+
self.assertEqual(coerced, 42)
54+
55+
56+
class Test_float_from_json(unittest.TestCase):
57+
58+
def _callFUT(self, value, field):
59+
from google.cloud.bigquery._helpers import _float_from_json
60+
return _float_from_json(value, field)
61+
62+
def test_w_none_nullable(self):
63+
self.assertIsNone(self._callFUT(None, _Field('NULLABLE')))
64+
65+
def test_w_none_required(self):
66+
with self.assertRaises(TypeError):
67+
self._callFUT(None, _Field('REQUIRED'))
68+
69+
def test_w_string_value(self):
70+
coerced = self._callFUT('3.1415', object())
71+
self.assertEqual(coerced, 3.1415)
72+
73+
def test_w_float_value(self):
74+
coerced = self._callFUT(3.1415, object())
75+
self.assertEqual(coerced, 3.1415)
76+
77+
78+
class Test_bool_from_json(unittest.TestCase):
79+
80+
def _callFUT(self, value, field):
81+
from google.cloud.bigquery._helpers import _bool_from_json
82+
return _bool_from_json(value, field)
83+
84+
def test_w_none_nullable(self):
85+
self.assertIsNone(self._callFUT(None, _Field('NULLABLE')))
86+
87+
def test_w_none_required(self):
88+
with self.assertRaises(AttributeError):
89+
self._callFUT(None, _Field('REQUIRED'))
90+
91+
def test_w_value_t(self):
92+
coerced = self._callFUT('T', object())
93+
self.assertTrue(coerced)
94+
95+
def test_w_value_true(self):
96+
coerced = self._callFUT('True', object())
97+
self.assertTrue(coerced)
98+
99+
def test_w_value_1(self):
100+
coerced = self._callFUT('1', object())
101+
self.assertTrue(coerced)
102+
103+
def test_w_value_other(self):
104+
coerced = self._callFUT('f', object())
105+
self.assertFalse(coerced)
106+
107+
108+
class Test_datetime_from_json(unittest.TestCase):
109+
110+
def _callFUT(self, value, field):
111+
from google.cloud.bigquery._helpers import _datetime_from_json
112+
return _datetime_from_json(value, field)
113+
114+
def test_w_none_nullable(self):
115+
self.assertIsNone(self._callFUT(None, _Field('NULLABLE')))
116+
117+
def test_w_none_required(self):
118+
with self.assertRaises(TypeError):
119+
self._callFUT(None, _Field('REQUIRED'))
120+
121+
def test_w_string_value(self):
122+
import datetime
123+
from google.cloud._helpers import _EPOCH
124+
coerced = self._callFUT('1.234567', object())
125+
self.assertEqual(
126+
coerced,
127+
_EPOCH + datetime.timedelta(seconds=1, microseconds=234567))
128+
129+
def test_w_float_value(self):
130+
import datetime
131+
from google.cloud._helpers import _EPOCH
132+
coerced = self._callFUT(1.234567, object())
133+
self.assertEqual(
134+
coerced,
135+
_EPOCH + datetime.timedelta(seconds=1, microseconds=234567))
136+
137+
138+
class Test_record_from_json(unittest.TestCase):
139+
140+
def _callFUT(self, value, field):
141+
from google.cloud.bigquery._helpers import _record_from_json
142+
return _record_from_json(value, field)
143+
144+
def test_w_none_nullable(self):
145+
self.assertIsNone(self._callFUT(None, _Field('NULLABLE')))
146+
147+
def test_w_none_required(self):
148+
with self.assertRaises(TypeError):
149+
self._callFUT(None, _Field('REQUIRED'))
150+
151+
def test_w_nullable_subfield_none(self):
152+
subfield = _Field('NULLABLE', 'age', 'INTEGER')
153+
field = _Field('REQUIRED', fields=[subfield])
154+
value = {'f': [{'v': None}]}
155+
coerced = self._callFUT(value, field)
156+
self.assertEqual(coerced, {'age': None})
157+
158+
def test_w_scalar_subfield(self):
159+
subfield = _Field('REQUIRED', 'age', 'INTEGER')
160+
field = _Field('REQUIRED', fields=[subfield])
161+
value = {'f': [{'v': 42}]}
162+
coerced = self._callFUT(value, field)
163+
self.assertEqual(coerced, {'age': 42})
164+
165+
def test_w_repeated_subfield(self):
166+
subfield = _Field('REPEATED', 'color', 'STRING')
167+
field = _Field('REQUIRED', fields=[subfield])
168+
value = {'f': [{'v': ['red', 'yellow', 'blue']}]}
169+
coerced = self._callFUT(value, field)
170+
self.assertEqual(coerced, {'color': ['red', 'yellow', 'blue']})
171+
172+
def test_w_record_subfield(self):
173+
full_name = _Field('REQUIRED', 'full_name', 'STRING')
174+
area_code = _Field('REQUIRED', 'area_code', 'STRING')
175+
local_number = _Field('REQUIRED', 'local_number', 'STRING')
176+
rank = _Field('REQUIRED', 'rank', 'INTEGER')
177+
phone = _Field('NULLABLE', 'phone', 'RECORD',
178+
fields=[area_code, local_number, rank])
179+
person = _Field('REQUIRED', 'person', 'RECORD',
180+
fields=[full_name, phone])
181+
value = {
182+
'f': [
183+
{'v': 'Phred Phlyntstone'},
184+
{'v': {'f': [{'v': '800'}, {'v': '555-1212'}, {'v': 1}]}},
185+
],
186+
}
187+
expected = {
188+
'full_name': 'Phred Phlyntstone',
189+
'phone': {
190+
'area_code': '800',
191+
'local_number': '555-1212',
192+
'rank': 1,
193+
}
194+
}
195+
coerced = self._callFUT(value, person)
196+
self.assertEqual(coerced, expected)
197+
198+
199+
class Test_string_from_json(unittest.TestCase):
200+
201+
def _callFUT(self, value, field):
202+
from google.cloud.bigquery._helpers import _string_from_json
203+
return _string_from_json(value, field)
204+
205+
def test_w_none_nullable(self):
206+
self.assertIsNone(self._callFUT(None, _Field('NULLABLE')))
207+
208+
def test_w_none_required(self):
209+
self.assertIsNone(self._callFUT(None, _Field('RECORD')))
210+
211+
def test_w_string_value(self):
212+
coerced = self._callFUT('Wonderful!', object())
213+
self.assertEqual(coerced, 'Wonderful!')
214+
215+
216+
class Test_rows_from_json(unittest.TestCase):
217+
218+
def _callFUT(self, value, field):
219+
from google.cloud.bigquery._helpers import _rows_from_json
220+
return _rows_from_json(value, field)
221+
222+
def test_w_record_subfield(self):
223+
full_name = _Field('REQUIRED', 'full_name', 'STRING')
224+
area_code = _Field('REQUIRED', 'area_code', 'STRING')
225+
local_number = _Field('REQUIRED', 'local_number', 'STRING')
226+
rank = _Field('REQUIRED', 'rank', 'INTEGER')
227+
phone = _Field('NULLABLE', 'phone', 'RECORD',
228+
fields=[area_code, local_number, rank])
229+
color = _Field('REPEATED', 'color', 'STRING')
230+
schema = [full_name, phone, color]
231+
rows = [
232+
{'f': [
233+
{'v': 'Phred Phlyntstone'},
234+
{'v': {'f': [{'v': '800'}, {'v': '555-1212'}, {'v': 1}]}},
235+
{'v': ['orange', 'black']},
236+
]},
237+
{'f': [
238+
{'v': 'Bharney Rhubble'},
239+
{'v': {'f': [{'v': '877'}, {'v': '768-5309'}, {'v': 2}]}},
240+
{'v': ['brown']},
241+
]},
242+
{'f': [
243+
{'v': 'Wylma Phlyntstone'},
244+
{'v': None},
245+
{'v': []},
246+
]},
247+
]
248+
phred_phone = {
249+
'area_code': '800',
250+
'local_number': '555-1212',
251+
'rank': 1,
252+
}
253+
bharney_phone = {
254+
'area_code': '877',
255+
'local_number': '768-5309',
256+
'rank': 2,
257+
}
258+
expected = [
259+
('Phred Phlyntstone', phred_phone, ['orange', 'black']),
260+
('Bharney Rhubble', bharney_phone, ['brown']),
261+
('Wylma Phlyntstone', None, []),
262+
]
263+
coerced = self._callFUT(rows, schema)
264+
self.assertEqual(coerced, expected)
265+
266+
18267
class Test_ConfigurationProperty(unittest.TestCase):
19268

20269
def _getTargetClass(self):
@@ -114,3 +363,12 @@ def __init__(self):
114363
del wrapper.attr
115364
self.assertEqual(wrapper.attr, None)
116365
self.assertEqual(wrapper._configuration._attr, None)
366+
367+
368+
class _Field(object):
369+
370+
def __init__(self, mode, name='unknown', field_type='UNKNOWN', fields=()):
371+
self.mode = mode
372+
self.name = name
373+
self.field_type = field_type
374+
self.fields = fields

0 commit comments

Comments
 (0)