Skip to content

Commit

Permalink
BigQuery: add to/from API representation for Table & Dataset referenc…
Browse files Browse the repository at this point in the history
…es. (#4020)

* BigQuery: add to/from API representation for Table & Dataset references.

Also, implement equality and hashing for Table & Dataset references.
This will make it easier to use the TableReference and DatasetReference
classes as typed properties in the QueryJob and other job classes.

* Fix lint errors.

* Replace unique-ly with uniquely.
  • Loading branch information
tswast authored Sep 22, 2017
1 parent 8f0055f commit f3fa77f
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 1 deletion.
39 changes: 39 additions & 0 deletions bigquery/google/cloud/bigquery/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,45 @@ def table(self, table_id):
"""
return TableReference(self, table_id)

@classmethod
def from_api_repr(cls, resource):
project = resource['projectId']
dataset_id = resource['datasetId']
return cls(project, dataset_id)

def to_api_repr(self):
return {
'projectId': self._project,
'datasetId': self._dataset_id,
}

def _key(self):
"""A tuple key that uniquely describes this field.
Used to compute this instance's hashcode and evaluate equality.
Returns:
tuple: The contents of this :class:`DatasetReference`.
"""
return (
self._project,
self._dataset_id,
)

def __eq__(self, other):
if not isinstance(other, DatasetReference):
return NotImplemented
return self._key() == other._key()

def __ne__(self, other):
return not self == other

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

def __repr__(self):
return 'DatasetReference{}'.format(self._key())


class Dataset(object):
"""Datasets are containers for tables.
Expand Down
2 changes: 1 addition & 1 deletion bigquery/google/cloud/bigquery/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def to_api_repr(self):
return answer

def _key(self):
"""A tuple key that unique-ly describes this field.
"""A tuple key that uniquely describes this field.
Used to compute this instance's hashcode and evaluate equality.
Expand Down
57 changes: 57 additions & 0 deletions bigquery/google/cloud/bigquery/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,63 @@ def path(self):
return '/projects/%s/datasets/%s/tables/%s' % (
self._project, self._dataset_id, self._table_id)

@classmethod
def from_api_repr(cls, resource):
"""Factory: construct a table reference given its API representation
:type resource: dict
:param resource: table reference representation returned from the API
:rtype: :class:`google.cloud.bigquery.table.TableReference`
:returns: Table reference parsed from ``resource``.
"""
from google.cloud.bigquery.dataset import DatasetReference

project = resource['projectId']
dataset_id = resource['datasetId']
table_id = resource['tableId']
return cls(DatasetReference(project, dataset_id), table_id)

def to_api_repr(self):
"""Construct the API resource representation of this table reference.
:rtype: dict
:returns: Table reference as represented as an API resource
"""
return {
'projectId': self._project,
'datasetId': self._dataset_id,
'tableId': self._table_id,
}

def _key(self):
"""A tuple key that uniquely describes this field.
Used to compute this instance's hashcode and evaluate equality.
Returns:
tuple: The contents of this :class:`DatasetReference`.
"""
return (
self._project,
self._dataset_id,
self._table_id,
)

def __eq__(self, other):
if not isinstance(other, TableReference):
return NotImplemented
return self._key() == other._key()

def __ne__(self, other):
return not self == other

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

def __repr__(self):
return 'TableReference{}'.format(self._key())


class Table(object):
"""Tables represent a set of rows whose values correspond to a schema.
Expand Down
64 changes: 64 additions & 0 deletions bigquery/tests/unit/test_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,70 @@ def test_table(self):
self.assertEqual(table_ref.project, 'some-project-1')
self.assertEqual(table_ref.table_id, 'table_1')

def test_to_api_repr(self):
dataset = self._make_one('project_1', 'dataset_1')

resource = dataset.to_api_repr()

self.assertEqual(
resource,
{
'projectId': 'project_1',
'datasetId': 'dataset_1',
})

def test_from_api_repr(self):
from google.cloud.bigquery.dataset import DatasetReference
expected = self._make_one('project_1', 'dataset_1')

got = DatasetReference.from_api_repr(
{
'projectId': 'project_1',
'datasetId': 'dataset_1',
})

self.assertEqual(expected, got)

def test___eq___wrong_type(self):
dataset = self._make_one('project_1', 'dataset_1')
other = object()
self.assertNotEqual(dataset, other)
self.assertEqual(dataset, mock.ANY)

def test___eq___project_mismatch(self):
dataset = self._make_one('project_1', 'dataset_1')
other = self._make_one('project_2', 'dataset_1')
self.assertNotEqual(dataset, other)

def test___eq___dataset_mismatch(self):
dataset = self._make_one('project_1', 'dataset_1')
other = self._make_one('project_1', 'dataset_2')
self.assertNotEqual(dataset, other)

def test___eq___equality(self):
dataset = self._make_one('project_1', 'dataset_1')
other = self._make_one('project_1', 'dataset_1')
self.assertEqual(dataset, other)

def test___hash__set_equality(self):
dataset1 = self._make_one('project_1', 'dataset_1')
dataset2 = self._make_one('project_1', 'dataset_2')
set_one = {dataset1, dataset2}
set_two = {dataset1, dataset2}
self.assertEqual(set_one, set_two)

def test___hash__not_equals(self):
dataset1 = self._make_one('project_1', 'dataset_1')
dataset2 = self._make_one('project_1', 'dataset_2')
set_one = {dataset1}
set_two = {dataset2}
self.assertNotEqual(set_one, set_two)

def test___repr__(self):
dataset = self._make_one('project1', 'dataset1')
expected = "DatasetReference('project1', 'dataset1')"
self.assertEqual(repr(dataset), expected)


class TestDataset(unittest.TestCase):
from google.cloud.bigquery.dataset import DatasetReference
Expand Down
92 changes: 92 additions & 0 deletions bigquery/tests/unit/test_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,98 @@ def test_ctor_defaults(self):
self.assertEqual(table_ref.dataset_id, dataset_ref.dataset_id)
self.assertEqual(table_ref.table_id, 'table_1')

def test_to_api_repr(self):
from google.cloud.bigquery.dataset import DatasetReference
dataset_ref = DatasetReference('project_1', 'dataset_1')
table_ref = self._make_one(dataset_ref, 'table_1')

resource = table_ref.to_api_repr()

self.assertEqual(
resource,
{
'projectId': 'project_1',
'datasetId': 'dataset_1',
'tableId': 'table_1',
})

def test_from_api_repr(self):
from google.cloud.bigquery.dataset import DatasetReference
from google.cloud.bigquery.table import TableReference
dataset_ref = DatasetReference('project_1', 'dataset_1')
expected = self._make_one(dataset_ref, 'table_1')

got = TableReference.from_api_repr(
{
'projectId': 'project_1',
'datasetId': 'dataset_1',
'tableId': 'table_1',
})

self.assertEqual(expected, got)

def test___eq___wrong_type(self):
from google.cloud.bigquery.dataset import DatasetReference
dataset_ref = DatasetReference('project_1', 'dataset_1')
table = self._make_one(dataset_ref, 'table_1')
other = object()
self.assertNotEqual(table, other)
self.assertEqual(table, mock.ANY)

def test___eq___project_mismatch(self):
from google.cloud.bigquery.dataset import DatasetReference
dataset = DatasetReference('project_1', 'dataset_1')
other_dataset = DatasetReference('project_2', 'dataset_1')
table = self._make_one(dataset, 'table_1')
other = self._make_one(other_dataset, 'table_1')
self.assertNotEqual(table, other)

def test___eq___dataset_mismatch(self):
from google.cloud.bigquery.dataset import DatasetReference
dataset = DatasetReference('project_1', 'dataset_1')
other_dataset = DatasetReference('project_1', 'dataset_2')
table = self._make_one(dataset, 'table_1')
other = self._make_one(other_dataset, 'table_1')
self.assertNotEqual(table, other)

def test___eq___table_mismatch(self):
from google.cloud.bigquery.dataset import DatasetReference
dataset = DatasetReference('project_1', 'dataset_1')
table = self._make_one(dataset, 'table_1')
other = self._make_one(dataset, 'table_2')
self.assertNotEqual(table, other)

def test___eq___equality(self):
from google.cloud.bigquery.dataset import DatasetReference
dataset = DatasetReference('project_1', 'dataset_1')
table = self._make_one(dataset, 'table_1')
other = self._make_one(dataset, 'table_1')
self.assertEqual(table, other)

def test___hash__set_equality(self):
from google.cloud.bigquery.dataset import DatasetReference
dataset = DatasetReference('project_1', 'dataset_1')
table1 = self._make_one(dataset, 'table1')
table2 = self._make_one(dataset, 'table2')
set_one = {table1, table2}
set_two = {table1, table2}
self.assertEqual(set_one, set_two)

def test___hash__not_equals(self):
from google.cloud.bigquery.dataset import DatasetReference
dataset = DatasetReference('project_1', 'dataset_1')
table1 = self._make_one(dataset, 'table1')
table2 = self._make_one(dataset, 'table2')
set_one = {table1}
set_two = {table2}
self.assertNotEqual(set_one, set_two)

def test___repr__(self):
dataset = DatasetReference('project1', 'dataset1')
table1 = self._make_one(dataset, 'table1')
expected = "TableReference('project1', 'dataset1', 'table1')"
self.assertEqual(repr(table1), expected)


class TestTable(unittest.TestCase, _SchemaBase):

Expand Down

0 comments on commit f3fa77f

Please sign in to comment.