Skip to content

Commit 66a3d66

Browse files
committed
Making SchemaField immutable.
1 parent eeed3d1 commit 66a3d66

File tree

2 files changed

+122
-49
lines changed

2 files changed

+122
-49
lines changed

bigquery/google/cloud/bigquery/schema.py

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class SchemaField(object):
2626
'FLOAT', 'BOOLEAN', 'TIMESTAMP' or 'RECORD').
2727
2828
:type mode: str
29-
:param mode: the type of the field (one of 'NULLABLE', 'REQUIRED',
29+
:param mode: the mode of the field (one of 'NULLABLE', 'REQUIRED',
3030
or 'REPEATED').
3131
3232
:type description: str
@@ -35,31 +35,75 @@ class SchemaField(object):
3535
:type fields: tuple of :class:`SchemaField`
3636
:param fields: subfields (requires ``field_type`` of 'RECORD').
3737
"""
38-
def __init__(self, name, field_type, mode='NULLABLE', description=None,
39-
fields=()):
40-
self.name = name
41-
self.field_type = field_type
42-
self.mode = mode
43-
self.description = description
44-
self.fields = tuple(fields)
38+
def __init__(self, name, field_type, mode='NULLABLE',
39+
description=None, fields=()):
40+
self._name = name
41+
self._field_type = field_type
42+
self._mode = mode
43+
self._description = description
44+
self._fields = tuple(fields)
4545

46-
def _key(self):
46+
@property
47+
def name(self):
48+
"""str: The name of the field."""
49+
return self._name
50+
51+
@property
52+
def field_type(self):
53+
"""str: The type of the field.
54+
55+
Will be one of 'STRING', 'INTEGER', 'FLOAT', 'BOOLEAN',
56+
'TIMESTAMP' or 'RECORD'.
57+
"""
58+
return self._field_type
59+
60+
@property
61+
def mode(self):
62+
"""str: The mode of the field.
63+
64+
65+
Will be one of 'NULLABLE', 'REQUIRED', or 'REPEATED'.
4766
"""
48-
A tuple describing the contents of this :class:`SchemaField`.
67+
return self._mode
68+
69+
@property
70+
def description(self):
71+
"""Optional[str]: Description for the field."""
72+
return self._description
73+
74+
@property
75+
def fields(self):
76+
"""tuple: Subfields contained in this field.
77+
78+
If ``field_type`` is not 'RECORD', this property must be
79+
empty / unset.
80+
"""
81+
return self._fields
82+
83+
def _key(self):
84+
"""A tuple key that unique-ly describes this field.
85+
4986
Used to compute this instance's hashcode and evaluate equality.
87+
88+
Returns:
89+
tuple: The contents of this :class:`SchemaField`.
5090
"""
5191
return (
52-
self.name,
53-
self.field_type.lower(),
54-
self.mode,
55-
self.description,
56-
self.fields)
92+
self._name,
93+
self._field_type.lower(),
94+
self._mode,
95+
self._description,
96+
self._fields,
97+
)
5798

5899
def __eq__(self, other):
59-
return self._key() == other._key()
100+
if isinstance(other, SchemaField):
101+
return self._key() == other._key()
102+
else:
103+
return NotImplemented
60104

61105
def __hash__(self):
62106
return hash(self._key())
63107

64108
def __repr__(self):
65-
return "SchemaField{}".format(self._key())
109+
return 'SchemaField{}'.format(self._key())

bigquery/tests/unit/test_schema.py

Lines changed: 61 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -26,43 +26,72 @@ def _get_target_class():
2626
def _make_one(self, *args, **kw):
2727
return self._get_target_class()(*args, **kw)
2828

29-
def test_ctor_defaults(self):
29+
def test_constructor_defaults(self):
3030
field = self._make_one('test', 'STRING')
31-
self.assertEqual(field.name, 'test')
32-
self.assertEqual(field.field_type, 'STRING')
33-
self.assertEqual(field.mode, 'NULLABLE')
34-
self.assertIsNone(field.description)
35-
self.assertEqual(field.fields, ())
31+
self.assertEqual(field._name, 'test')
32+
self.assertEqual(field._field_type, 'STRING')
33+
self.assertEqual(field._mode, 'NULLABLE')
34+
self.assertIsNone(field._description)
35+
self.assertEqual(field._fields, ())
3636

37-
def test_ctor_explicit(self):
37+
def test_constructor_explicit(self):
3838
field = self._make_one('test', 'STRING', mode='REQUIRED',
3939
description='Testing')
40-
self.assertEqual(field.name, 'test')
41-
self.assertEqual(field.field_type, 'STRING')
42-
self.assertEqual(field.mode, 'REQUIRED')
43-
self.assertEqual(field.description, 'Testing')
44-
self.assertEqual(field.fields, ())
45-
46-
def test_ctor_subfields(self):
40+
self.assertEqual(field._name, 'test')
41+
self.assertEqual(field._field_type, 'STRING')
42+
self.assertEqual(field._mode, 'REQUIRED')
43+
self.assertEqual(field._description, 'Testing')
44+
self.assertEqual(field._fields, ())
45+
46+
def test_constructor_subfields(self):
47+
sub_field1 = self._make_one('area_code', 'STRING')
48+
sub_field2 = self._make_one('local_number', 'STRING')
4749
field = self._make_one(
48-
'phone_number', 'RECORD',
49-
fields=[self._make_one('area_code', 'STRING'),
50-
self._make_one('local_number', 'STRING')])
51-
self.assertEqual(field.name, 'phone_number')
52-
self.assertEqual(field.field_type, 'RECORD')
53-
self.assertEqual(field.mode, 'NULLABLE')
54-
self.assertIsNone(field.description)
55-
self.assertEqual(len(field.fields), 2)
56-
self.assertEqual(field.fields[0].name, 'area_code')
57-
self.assertEqual(field.fields[0].field_type, 'STRING')
58-
self.assertEqual(field.fields[0].mode, 'NULLABLE')
59-
self.assertIsNone(field.fields[0].description)
60-
self.assertEqual(field.fields[0].fields, ())
61-
self.assertEqual(field.fields[1].name, 'local_number')
62-
self.assertEqual(field.fields[1].field_type, 'STRING')
63-
self.assertEqual(field.fields[1].mode, 'NULLABLE')
64-
self.assertIsNone(field.fields[1].description)
65-
self.assertEqual(field.fields[1].fields, ())
50+
'phone_number',
51+
'RECORD',
52+
fields=[sub_field1, sub_field2],
53+
)
54+
self.assertEqual(field._name, 'phone_number')
55+
self.assertEqual(field._field_type, 'RECORD')
56+
self.assertEqual(field._mode, 'NULLABLE')
57+
self.assertIsNone(field._description)
58+
self.assertEqual(len(field._fields), 2)
59+
self.assertIs(field._fields[0], sub_field1)
60+
self.assertIs(field._fields[1], sub_field2)
61+
62+
def test_name_property(self):
63+
name = 'lemon-ness'
64+
schema_field = self._make_one(name, 'INTEGER')
65+
self.assertIs(schema_field.name, name)
66+
67+
def test_field_type_property(self):
68+
field_type = 'BOOLEAN'
69+
schema_field = self._make_one('whether', field_type)
70+
self.assertIs(schema_field.field_type, field_type)
71+
72+
def test_mode_property(self):
73+
mode = 'REPEATED'
74+
schema_field = self._make_one('again', 'FLOAT', mode=mode)
75+
self.assertIs(schema_field.mode, mode)
76+
77+
def test_description_property(self):
78+
description = 'It holds some data.'
79+
schema_field = self._make_one(
80+
'do', 'TIMESTAMP', description=description)
81+
self.assertIs(schema_field.description, description)
82+
83+
def test_fields_property(self):
84+
sub_field1 = self._make_one('one', 'STRING')
85+
sub_field2 = self._make_one('fish', 'INTEGER')
86+
fields = (sub_field1, sub_field2)
87+
schema_field = self._make_one('boat', 'RECORD', fields=fields)
88+
self.assertIs(schema_field.fields, fields)
89+
90+
def test___eq___wrong_type(self):
91+
field = self._make_one('test', 'STRING')
92+
other = object()
93+
self.assertNotEqual(field, other)
94+
self.assertIs(field.__eq__(other), NotImplemented)
6695

6796
def test___eq___name_mismatch(self):
6897
field = self._make_one('test', 'STRING')

0 commit comments

Comments
 (0)