Skip to content

Commit 957f6e0

Browse files
Add SchemaField serialization and deserialization. (#3786)
1 parent 87a46a8 commit 957f6e0

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed

bigquery/google/cloud/bigquery/schema.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,25 @@ def __init__(self, name, field_type, mode='NULLABLE',
4343
self._description = description
4444
self._fields = tuple(fields)
4545

46+
@classmethod
47+
def from_api_repr(cls, api_repr):
48+
"""Return a ``SchemaField`` object deserialized from a dictionary.
49+
50+
Args:
51+
api_repr (Mapping[str, str]): The serialized representation
52+
of the SchemaField, such as what is output by
53+
:meth:`to_api_repr`.
54+
55+
Returns:
56+
SchemaField: The ``SchemaField`` object.
57+
"""
58+
return cls(
59+
field_type=api_repr['type'].upper(),
60+
fields=[cls.from_api_repr(f) for f in api_repr.get('fields', ())],
61+
mode=api_repr['mode'].upper(),
62+
name=api_repr['name'],
63+
)
64+
4665
@property
4766
def name(self):
4867
"""str: The name of the field."""
@@ -84,6 +103,28 @@ def fields(self):
84103
"""
85104
return self._fields
86105

106+
def to_api_repr(self):
107+
"""Return a dictionary representing this schema field.
108+
109+
Returns:
110+
dict: A dictionary representing the SchemaField in a serialized
111+
form.
112+
"""
113+
# Put together the basic representation. See http://bit.ly/2hOAT5u.
114+
answer = {
115+
'mode': self.mode.lower(),
116+
'name': self.name,
117+
'type': self.field_type.lower(),
118+
}
119+
120+
# If this is a RECORD type, then sub-fields are also included,
121+
# add this to the serialized representation.
122+
if self.field_type.upper() == 'RECORD':
123+
answer['fields'] = [f.to_api_repr() for f in self.fields]
124+
125+
# Done; return the serialized dictionary.
126+
return answer
127+
87128
def _key(self):
88129
"""A tuple key that unique-ly describes this field.
89130

bigquery/tests/unit/test_schema.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,47 @@ def test_constructor_subfields(self):
6161
self.assertIs(field._fields[0], sub_field1)
6262
self.assertIs(field._fields[1], sub_field2)
6363

64+
def test_to_api_repr(self):
65+
field = self._make_one('foo', 'INTEGER', 'NULLABLE')
66+
self.assertEqual(field.to_api_repr(), {
67+
'mode': 'nullable',
68+
'name': 'foo',
69+
'type': 'integer',
70+
})
71+
72+
def test_to_api_repr_with_subfield(self):
73+
subfield = self._make_one('bar', 'INTEGER', 'NULLABLE')
74+
field = self._make_one('foo', 'RECORD', 'REQUIRED', fields=(subfield,))
75+
self.assertEqual(field.to_api_repr(), {
76+
'fields': [{
77+
'mode': 'nullable',
78+
'name': 'bar',
79+
'type': 'integer',
80+
}],
81+
'mode': 'required',
82+
'name': 'foo',
83+
'type': 'record',
84+
})
85+
86+
def test_from_api_repr(self):
87+
field = self._get_target_class().from_api_repr({
88+
'fields': [{
89+
'mode': 'nullable',
90+
'name': 'bar',
91+
'type': 'integer',
92+
}],
93+
'mode': 'required',
94+
'name': 'foo',
95+
'type': 'record',
96+
})
97+
self.assertEqual(field.name, 'foo')
98+
self.assertEqual(field.field_type, 'RECORD')
99+
self.assertEqual(field.mode, 'REQUIRED')
100+
self.assertEqual(len(field.fields), 1)
101+
self.assertEqual(field.fields[0].name, 'bar')
102+
self.assertEqual(field.fields[0].field_type, 'INTEGER')
103+
self.assertEqual(field.fields[0].mode, 'NULLABLE')
104+
64105
def test_name_property(self):
65106
name = 'lemon-ness'
66107
schema_field = self._make_one(name, 'INTEGER')

0 commit comments

Comments
 (0)