diff --git a/bigquery/google/cloud/bigquery/table.py b/bigquery/google/cloud/bigquery/table.py index 46213d5fe8bf..864fff4458b1 100644 --- a/bigquery/google/cloud/bigquery/table.py +++ b/bigquery/google/cloud/bigquery/table.py @@ -1787,7 +1787,7 @@ def type_(self): """google.cloud.bigquery.table.TimePartitioningType: The type of time partitioning to use. """ - return self._properties["type"] + return self._properties.get("type") @type_.setter def type_(self, value): @@ -1849,7 +1849,7 @@ def from_api_repr(cls, api_repr): google.cloud.bigquery.table.TimePartitioning: The ``TimePartitioning`` object. """ - instance = cls(api_repr["type"]) + instance = cls() instance._properties = api_repr return instance diff --git a/bigquery/noxfile.py b/bigquery/noxfile.py index 39e5f4548c0b..a9df7a67cfcc 100644 --- a/bigquery/noxfile.py +++ b/bigquery/noxfile.py @@ -141,8 +141,10 @@ def lint(session): serious code quality issues. """ - session.install("black", "flake8", *LOCAL_DEPS) - session.install(".") + session.install("black", "flake8") + for local_dep in LOCAL_DEPS: + session.install("-e", local_dep) + session.install("-e", ".") session.run("flake8", os.path.join("google", "cloud", "bigquery")) session.run("flake8", "tests") session.run("flake8", os.path.join("docs", "snippets.py")) diff --git a/bigquery/tests/unit/test_table.py b/bigquery/tests/unit/test_table.py index 18ca125e804c..07a625b98825 100644 --- a/bigquery/tests/unit/test_table.py +++ b/bigquery/tests/unit/test_table.py @@ -871,6 +871,54 @@ def test__build_resource_w_custom_field_not_in__properties(self): with self.assertRaises(ValueError): table._build_resource(["bad"]) + def test_time_partitioning_getter(self): + from google.cloud.bigquery.table import TimePartitioning + from google.cloud.bigquery.table import TimePartitioningType + + dataset = DatasetReference(self.PROJECT, self.DS_ID) + table_ref = dataset.table(self.TABLE_NAME) + table = self._make_one(table_ref) + + table._properties["timePartitioning"] = { + "type": "DAY", + "field": "col1", + "expirationMs": "123456", + "requirePartitionFilter": False, + } + self.assertIsInstance(table.time_partitioning, TimePartitioning) + self.assertEqual(table.time_partitioning.type_, TimePartitioningType.DAY) + self.assertEqual(table.time_partitioning.field, "col1") + self.assertEqual(table.time_partitioning.expiration_ms, 123456) + self.assertFalse(table.time_partitioning.require_partition_filter) + + def test_time_partitioning_getter_w_none(self): + dataset = DatasetReference(self.PROJECT, self.DS_ID) + table_ref = dataset.table(self.TABLE_NAME) + table = self._make_one(table_ref) + + table._properties["timePartitioning"] = None + self.assertIsNone(table.time_partitioning) + + del table._properties["timePartitioning"] + self.assertIsNone(table.time_partitioning) + + def test_time_partitioning_getter_w_empty(self): + from google.cloud.bigquery.table import TimePartitioning + + dataset = DatasetReference(self.PROJECT, self.DS_ID) + table_ref = dataset.table(self.TABLE_NAME) + table = self._make_one(table_ref) + + # Even though there are required properties according to the API + # specification, sometimes time partitioning is populated as an empty + # object. See internal bug 131167013. + table._properties["timePartitioning"] = {} + self.assertIsInstance(table.time_partitioning, TimePartitioning) + self.assertIsNone(table.time_partitioning.type_) + self.assertIsNone(table.time_partitioning.field) + self.assertIsNone(table.time_partitioning.expiration_ms) + self.assertIsNone(table.time_partitioning.require_partition_filter) + def test_time_partitioning_setter(self): from google.cloud.bigquery.table import TimePartitioning from google.cloud.bigquery.table import TimePartitioningType @@ -2211,6 +2259,20 @@ def test_constructor_explicit(self): self.assertEqual(time_partitioning.expiration_ms, 10000) self.assertTrue(time_partitioning.require_partition_filter) + def test_from_api_repr_empty(self): + klass = self._get_target_class() + + # Even though there are required properties according to the API + # specification, sometimes time partitioning is populated as an empty + # object. See internal bug 131167013. + api_repr = {} + time_partitioning = klass.from_api_repr(api_repr) + + self.assertIsNone(time_partitioning.type_) + self.assertIsNone(time_partitioning.field) + self.assertIsNone(time_partitioning.expiration_ms) + self.assertIsNone(time_partitioning.require_partition_filter) + def test_from_api_repr_minimal(self): from google.cloud.bigquery.table import TimePartitioningType @@ -2223,6 +2285,12 @@ def test_from_api_repr_minimal(self): self.assertIsNone(time_partitioning.expiration_ms) self.assertIsNone(time_partitioning.require_partition_filter) + def test_from_api_repr_doesnt_override_type(self): + klass = self._get_target_class() + api_repr = {"type": "HOUR"} + time_partitioning = klass.from_api_repr(api_repr) + self.assertEqual(time_partitioning.type_, "HOUR") + def test_from_api_repr_explicit(self): from google.cloud.bigquery.table import TimePartitioningType