Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 77 additions & 15 deletions bigquery/google/cloud/bigquery/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,89 @@ class SchemaField(object):
'FLOAT', 'BOOLEAN', 'TIMESTAMP' or 'RECORD').

:type mode: str
:param mode: the type of the field (one of 'NULLABLE', 'REQUIRED',
:param mode: the mode of the field (one of 'NULLABLE', 'REQUIRED',
or 'REPEATED').

:type description: str
:param description: optional description for the field.

:type fields: list of :class:`SchemaField`, or None
:type fields: tuple of :class:`SchemaField`
:param fields: subfields (requires ``field_type`` of 'RECORD').
"""
def __init__(self, name, field_type, mode='NULLABLE', description=None,
fields=None):
self.name = name
self.field_type = field_type
self.mode = mode
self.description = description
self.fields = fields
def __init__(self, name, field_type, mode='NULLABLE',
description=None, fields=()):
self._name = name
self._field_type = field_type
self._mode = mode
self._description = description
self._fields = tuple(fields)

def __eq__(self, other):
@property
def name(self):
"""str: The name of the field."""
return self._name

@property
def field_type(self):
"""str: The type of the field.

Will be one of 'STRING', 'INTEGER', 'FLOAT', 'BOOLEAN',
'TIMESTAMP' or 'RECORD'.
"""
return self._field_type

@property
def mode(self):
"""str: The mode of the field.

Will be one of 'NULLABLE', 'REQUIRED', or 'REPEATED'.
"""
return self._mode

@property
def description(self):
"""Optional[str]: Description for the field."""
return self._description

@property
def fields(self):
"""tuple: Subfields contained in this field.

If ``field_type`` is not 'RECORD', this property must be
empty / unset.
"""
return self._fields

def _key(self):
"""A tuple key that unique-ly describes this field.

Used to compute this instance's hashcode and evaluate equality.

Returns:
tuple: The contents of this :class:`SchemaField`.
"""
return (
self.name == other.name and
self.field_type.lower() == other.field_type.lower() and
self.mode == other.mode and
self.description == other.description and
self.fields == other.fields)
self._name,
self._field_type.lower(),
self._mode,
self._description,
self._fields,
)

def __eq__(self, other):

This comment was marked as spam.

This comment was marked as spam.

if isinstance(other, SchemaField):
return self._key() == other._key()
else:
return NotImplemented

def __ne__(self, other):
if isinstance(other, SchemaField):
return self._key() != other._key()
else:
return NotImplemented

def __hash__(self):
return hash(self._key())

def __repr__(self):

This comment was marked as spam.

This comment was marked as spam.

return 'SchemaField{}'.format(self._key())
4 changes: 2 additions & 2 deletions bigquery/google/cloud/bigquery/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -1079,7 +1079,7 @@ def _parse_schema_resource(info):
present in ``info``.
"""
if 'fields' not in info:
return None
return ()

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.


schema = []
for r_field in info['fields']:
Expand Down Expand Up @@ -1109,7 +1109,7 @@ def _build_schema_resource(fields):
'mode': field.mode}
if field.description is not None:
info['description'] = field.description
if field.fields is not None:
if field.fields:
info['fields'] = _build_schema_resource(field.fields)
infos.append(info)
return infos
Expand Down
6 changes: 3 additions & 3 deletions bigquery/tests/unit/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ def _verifySchema(self, query, resource):
self.assertEqual(found.mode, expected['mode'])
self.assertEqual(found.description,
expected.get('description'))
self.assertEqual(found.fields, expected.get('fields'))
self.assertEqual(found.fields, expected.get('fields', ()))
else:
self.assertIsNone(query.schema)
self.assertEqual(query.schema, ())

def _verifyRows(self, query, resource):
expected = resource.get('rows')
Expand Down Expand Up @@ -166,7 +166,7 @@ def test_ctor_defaults(self):
self.assertIsNone(query.page_token)
self.assertEqual(query.query_parameters, [])
self.assertEqual(query.rows, [])
self.assertIsNone(query.schema)
self.assertEqual(query.schema, ())
self.assertIsNone(query.total_rows)
self.assertIsNone(query.total_bytes_processed)
self.assertEqual(query.udf_resources, [])
Expand Down
136 changes: 104 additions & 32 deletions bigquery/tests/unit/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,43 +26,72 @@ def _get_target_class():
def _make_one(self, *args, **kw):
return self._get_target_class()(*args, **kw)

def test_ctor_defaults(self):
def test_constructor_defaults(self):
field = self._make_one('test', 'STRING')
self.assertEqual(field.name, 'test')
self.assertEqual(field.field_type, 'STRING')
self.assertEqual(field.mode, 'NULLABLE')
self.assertIsNone(field.description)
self.assertIsNone(field.fields)
self.assertEqual(field._name, 'test')
self.assertEqual(field._field_type, 'STRING')
self.assertEqual(field._mode, 'NULLABLE')
self.assertIsNone(field._description)
self.assertEqual(field._fields, ())

def test_ctor_explicit(self):
def test_constructor_explicit(self):
field = self._make_one('test', 'STRING', mode='REQUIRED',
description='Testing')
self.assertEqual(field.name, 'test')
self.assertEqual(field.field_type, 'STRING')
self.assertEqual(field.mode, 'REQUIRED')
self.assertEqual(field.description, 'Testing')
self.assertIsNone(field.fields)

def test_ctor_subfields(self):
self.assertEqual(field._name, 'test')
self.assertEqual(field._field_type, 'STRING')
self.assertEqual(field._mode, 'REQUIRED')
self.assertEqual(field._description, 'Testing')
self.assertEqual(field._fields, ())

def test_constructor_subfields(self):
sub_field1 = self._make_one('area_code', 'STRING')
sub_field2 = self._make_one('local_number', 'STRING')
field = self._make_one(
'phone_number', 'RECORD',
fields=[self._make_one('area_code', 'STRING'),
self._make_one('local_number', 'STRING')])
self.assertEqual(field.name, 'phone_number')
self.assertEqual(field.field_type, 'RECORD')
self.assertEqual(field.mode, 'NULLABLE')
self.assertIsNone(field.description)
self.assertEqual(len(field.fields), 2)
self.assertEqual(field.fields[0].name, 'area_code')
self.assertEqual(field.fields[0].field_type, 'STRING')
self.assertEqual(field.fields[0].mode, 'NULLABLE')
self.assertIsNone(field.fields[0].description)
self.assertIsNone(field.fields[0].fields)
self.assertEqual(field.fields[1].name, 'local_number')
self.assertEqual(field.fields[1].field_type, 'STRING')
self.assertEqual(field.fields[1].mode, 'NULLABLE')
self.assertIsNone(field.fields[1].description)
self.assertIsNone(field.fields[1].fields)
'phone_number',
'RECORD',
fields=[sub_field1, sub_field2],
)
self.assertEqual(field._name, 'phone_number')
self.assertEqual(field._field_type, 'RECORD')
self.assertEqual(field._mode, 'NULLABLE')
self.assertIsNone(field._description)
self.assertEqual(len(field._fields), 2)
self.assertIs(field._fields[0], sub_field1)
self.assertIs(field._fields[1], sub_field2)

def test_name_property(self):
name = 'lemon-ness'
schema_field = self._make_one(name, 'INTEGER')
self.assertIs(schema_field.name, name)

def test_field_type_property(self):
field_type = 'BOOLEAN'
schema_field = self._make_one('whether', field_type)
self.assertIs(schema_field.field_type, field_type)

def test_mode_property(self):
mode = 'REPEATED'
schema_field = self._make_one('again', 'FLOAT', mode=mode)
self.assertIs(schema_field.mode, mode)

def test_description_property(self):
description = 'It holds some data.'
schema_field = self._make_one(
'do', 'TIMESTAMP', description=description)
self.assertIs(schema_field.description, description)

def test_fields_property(self):
sub_field1 = self._make_one('one', 'STRING')
sub_field2 = self._make_one('fish', 'INTEGER')
fields = (sub_field1, sub_field2)
schema_field = self._make_one('boat', 'RECORD', fields=fields)
self.assertIs(schema_field.fields, fields)

def test___eq___wrong_type(self):
field = self._make_one('test', 'STRING')
other = object()
self.assertNotEqual(field, other)
self.assertIs(field.__eq__(other), NotImplemented)

def test___eq___name_mismatch(self):
field = self._make_one('test', 'STRING')
Expand Down Expand Up @@ -111,3 +140,46 @@ def test___eq___hit_w_fields(self):
field = self._make_one('test', 'RECORD', fields=[sub1, sub2])
other = self._make_one('test', 'RECORD', fields=[sub1, sub2])
self.assertEqual(field, other)

def test___ne___wrong_type(self):
field = self._make_one('toast', 'INTEGER')
other = object()
self.assertNotEqual(field, other)
self.assertIs(field.__ne__(other), NotImplemented)

def test___ne___same_value(self):
field1 = self._make_one('test', 'TIMESTAMP', mode='REPEATED')
field2 = self._make_one('test', 'TIMESTAMP', mode='REPEATED')
# unittest ``assertEqual`` uses ``==`` not ``!=``.
comparison_val = (field1 != field2)
self.assertFalse(comparison_val)

def test___ne___different_values(self):
field1 = self._make_one(
'test1', 'FLOAT', mode='REPEATED', description='Not same')
field2 = self._make_one(
'test2', 'FLOAT', mode='NULLABLE', description='Knot saym')
self.assertNotEqual(field1, field2)

def test___hash__set_equality(self):
sub1 = self._make_one('sub1', 'STRING')
sub2 = self._make_one('sub2', 'STRING')
field1 = self._make_one('test', 'RECORD', fields=[sub1])
field2 = self._make_one('test', 'RECORD', fields=[sub2])
set_one = {field1, field2}
set_two = {field1, field2}
self.assertEqual(set_one, set_two)

def test___hash__not_equals(self):
sub1 = self._make_one('sub1', 'STRING')
sub2 = self._make_one('sub2', 'STRING')
field1 = self._make_one('test', 'RECORD', fields=[sub1])
field2 = self._make_one('test', 'RECORD', fields=[sub2])
set_one = {field1}
set_two = {field2}
self.assertNotEqual(set_one, set_two)

def test___repr__(self):
field1 = self._make_one('field1', 'STRING')
expected = "SchemaField('field1', 'string', 'NULLABLE', None, ())"
self.assertEqual(repr(field1), expected)