From 2e1daa811142d864eb862127fe14a9ffd67a47f5 Mon Sep 17 00:00:00 2001 From: Linchin Date: Thu, 20 Nov 2025 22:55:18 +0000 Subject: [PATCH 01/30] feat: support timestamp_precision in table schema --- google/cloud/bigquery/schema.py | 31 ++++++++++++++++++++-- tests/unit/test_schema.py | 47 ++++++++++++++++++++++----------- 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index 456730b00..956c23d9d 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -196,6 +196,16 @@ class SchemaField(object): Only valid for top-level schema fields (not nested fields). If the type is FOREIGN, this field is required. + + timestamp_precision: Optional[int] + Precision (maximum number of total digits in base 10) for seconds + of TIMESTAMP type. + + Possible values include: + + - 6 (Default, for TIMESTAMP type with microsecond precision) + + - 12 (For TIMESTAMP type with picosecond precision) """ def __init__( @@ -213,6 +223,7 @@ def __init__( range_element_type: Union[FieldElementType, str, None] = None, rounding_mode: Union[enums.RoundingMode, str, None] = None, foreign_type_definition: Optional[str] = None, + timestamp_precision: Optional[int] = None, ): self._properties: Dict[str, Any] = { "name": name, @@ -237,6 +248,8 @@ def __init__( if isinstance(policy_tags, PolicyTagList) else None ) + if timestamp_precision is not None: + self._properties["timestampPrecision"] = timestamp_precision if isinstance(range_element_type, str): self._properties["rangeElementType"] = {"type": range_element_type} if isinstance(range_element_type, FieldElementType): @@ -373,6 +386,19 @@ def policy_tags(self): """ resource = self._properties.get("policyTags") return PolicyTagList.from_api_repr(resource) if resource is not None else None + + @property + def timestamp_precision(self): + """Optional[int]: Subfields contained in this field. + + Must be empty unset if ``field_type`` is not 'RECORD'. + """ + return _helpers._int_or_none(self._properties.get("timestampPrecision")) + + @timestamp_precision.setter + def timestamp_precision(self, value: Optional[int]): + value = _helpers._isinstance_or_raise(value, int, none_allowed=True) + self._properties["timestampPrecision"] = value def to_api_repr(self) -> dict: """Return a dictionary representing this schema field. @@ -417,6 +443,7 @@ def _key(self): self.description, self.fields, policy_tags, + self._properties.get("timestampPrecision"), ) def to_standard_sql(self) -> standard_sql.StandardSqlField: @@ -468,9 +495,9 @@ def __hash__(self): def __repr__(self): key = self._key() - policy_tags = key[-1] + policy_tags = key[-2] policy_tags_inst = None if policy_tags is None else PolicyTagList(policy_tags) - adjusted_key = key[:-1] + (policy_tags_inst,) + adjusted_key = key[:-2] + (policy_tags_inst,) + (key[-1],) return f"{self.__class__.__name__}{adjusted_key}" diff --git a/tests/unit/test_schema.py b/tests/unit/test_schema.py index c63a8312c..dec3170f6 100644 --- a/tests/unit/test_schema.py +++ b/tests/unit/test_schema.py @@ -69,6 +69,7 @@ def test_constructor_explicit(self): default_value_expression=FIELD_DEFAULT_VALUE_EXPRESSION, rounding_mode=enums.RoundingMode.ROUNDING_MODE_UNSPECIFIED, foreign_type_definition="INTEGER", + timestamp_precision=3, ) self.assertEqual(field.name, "test") self.assertEqual(field.field_type, "STRING") @@ -87,6 +88,7 @@ def test_constructor_explicit(self): ) self.assertEqual(field.rounding_mode, "ROUNDING_MODE_UNSPECIFIED") self.assertEqual(field.foreign_type_definition, "INTEGER") + self.assertEqual(field._properties["timestampPrecision"], 3) def test_constructor_explicit_none(self): field = self._make_one("test", "STRING", description=None, policy_tags=None) @@ -175,20 +177,22 @@ def test_to_api_repr_omits_unset_properties(self): self.assertNotIn("description", resource) self.assertNotIn("policyTags", resource) - def test_to_api_repr_with_subfield(self): - for record_type in ("RECORD", "STRUCT"): - subfield = self._make_one("bar", "INTEGER", "NULLABLE") - field = self._make_one("foo", record_type, "REQUIRED", fields=(subfield,)) - self.assertEqual( - field.to_api_repr(), - { - "fields": [{"mode": "NULLABLE", "name": "bar", "type": "INTEGER"}], - "mode": "REQUIRED", - "name": "foo", - "type": record_type, - }, - ) - + def test_to_api_repr_w_timestamp_precision(self): + field = self._make_one( + "foo", + "TIMESTAMP", + "NULLABLE", + timestamp_precision=3, + ) + self.assertEqual( + field.to_api_repr(), + { + "mode": "NULLABLE", + "name": "foo", + "type": "TIMESTAMP", + "timestampPrecision": 3, + }, + ) def test_from_api_repr(self): field = self._get_target_class().from_api_repr( { @@ -198,6 +202,7 @@ def test_from_api_repr(self): "name": "foo", "type": "record", "roundingMode": "ROUNDING_MODE_UNSPECIFIED", + "timestampPrecision": 3, } ) self.assertEqual(field.name, "foo") @@ -210,6 +215,7 @@ def test_from_api_repr(self): self.assertEqual(field.fields[0].mode, "NULLABLE") self.assertEqual(field.range_element_type, None) self.assertEqual(field.rounding_mode, "ROUNDING_MODE_UNSPECIFIED") + self.assertEqual(field._properties["timestampPrecision"], 3) def test_from_api_repr_policy(self): field = self._get_target_class().from_api_repr( @@ -323,6 +329,15 @@ def test_foreign_type_definition_property_str(self): schema_field._properties["foreignTypeDefinition"] = FOREIGN_TYPE_DEFINITION self.assertEqual(schema_field.foreign_type_definition, FOREIGN_TYPE_DEFINITION) + def test_timestamp_precision_property(self): + TIMESTAMP_PRECISION = 3 + schema_field = self._make_one( + "test", "TIMESTAMP", timestamp_precision=TIMESTAMP_PRECISION + ) + self.assertEqual( + schema_field._properties["timestampPrecision"], TIMESTAMP_PRECISION + ) + def test_to_standard_sql_simple_type(self): examples = ( # a few legacy types @@ -637,7 +652,9 @@ def test___hash__not_equals(self): def test___repr__(self): field1 = self._make_one("field1", "STRING") - expected = "SchemaField('field1', 'STRING', 'NULLABLE', None, None, (), None)" + expected = ( + "SchemaField('field1', 'STRING', 'NULLABLE', None, None, (), None, None)" + ) self.assertEqual(repr(field1), expected) def test___repr__evaluable_no_policy_tags(self): From 253ac1fafdb49ab000de1dafd5878b163599c410 Mon Sep 17 00:00:00 2001 From: Linchin Date: Thu, 20 Nov 2025 22:58:39 +0000 Subject: [PATCH 02/30] undelete test_to_api_repr_with_subfield --- tests/unit/test_schema.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/unit/test_schema.py b/tests/unit/test_schema.py index dec3170f6..f3fc6b5c2 100644 --- a/tests/unit/test_schema.py +++ b/tests/unit/test_schema.py @@ -177,6 +177,20 @@ def test_to_api_repr_omits_unset_properties(self): self.assertNotIn("description", resource) self.assertNotIn("policyTags", resource) + def test_to_api_repr_with_subfield(self): + for record_type in ("RECORD", "STRUCT"): + subfield = self._make_one("bar", "INTEGER", "NULLABLE") + field = self._make_one("foo", record_type, "REQUIRED", fields=(subfield,)) + self.assertEqual( + field.to_api_repr(), + { + "fields": [{"mode": "NULLABLE", "name": "bar", "type": "INTEGER"}], + "mode": "REQUIRED", + "name": "foo", + "type": record_type, + }, + ) + def test_to_api_repr_w_timestamp_precision(self): field = self._make_one( "foo", From dc3c498f4a975bc293b44aad576053f2ea36e29a Mon Sep 17 00:00:00 2001 From: Linchin Date: Thu, 20 Nov 2025 23:00:30 +0000 Subject: [PATCH 03/30] lint --- google/cloud/bigquery/schema.py | 4 ++-- tests/unit/test_schema.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index 956c23d9d..b3b9cc6df 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -200,7 +200,7 @@ class SchemaField(object): timestamp_precision: Optional[int] Precision (maximum number of total digits in base 10) for seconds of TIMESTAMP type. - + Possible values include: - 6 (Default, for TIMESTAMP type with microsecond precision) @@ -386,7 +386,7 @@ def policy_tags(self): """ resource = self._properties.get("policyTags") return PolicyTagList.from_api_repr(resource) if resource is not None else None - + @property def timestamp_precision(self): """Optional[int]: Subfields contained in this field. diff --git a/tests/unit/test_schema.py b/tests/unit/test_schema.py index f3fc6b5c2..77f29b914 100644 --- a/tests/unit/test_schema.py +++ b/tests/unit/test_schema.py @@ -207,6 +207,7 @@ def test_to_api_repr_w_timestamp_precision(self): "timestampPrecision": 3, }, ) + def test_from_api_repr(self): field = self._get_target_class().from_api_repr( { From 234a3fd056a87d0689147d5a37ef03351f3c2855 Mon Sep 17 00:00:00 2001 From: Linchin Date: Mon, 24 Nov 2025 22:14:29 +0000 Subject: [PATCH 04/30] remove property setter as it is read only --- google/cloud/bigquery/schema.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index b3b9cc6df..966e25d37 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -395,11 +395,6 @@ def timestamp_precision(self): """ return _helpers._int_or_none(self._properties.get("timestampPrecision")) - @timestamp_precision.setter - def timestamp_precision(self, value: Optional[int]): - value = _helpers._isinstance_or_raise(value, int, none_allowed=True) - self._properties["timestampPrecision"] = value - def to_api_repr(self) -> dict: """Return a dictionary representing this schema field. From b20159b7cc36c029398dcb0b02828e87bc44066d Mon Sep 17 00:00:00 2001 From: Linchin Date: Mon, 24 Nov 2025 22:16:31 +0000 Subject: [PATCH 05/30] docstring --- google/cloud/bigquery/schema.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index 966e25d37..3a0b16bda 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -389,9 +389,14 @@ def policy_tags(self): @property def timestamp_precision(self): - """Optional[int]: Subfields contained in this field. + """Optional[int]: Precision (maximum number of total digits in base 10) + for seconds of TIMESTAMP type. - Must be empty unset if ``field_type`` is not 'RECORD'. + Possible values include: + + - 6 (Default, for TIMESTAMP type with microsecond precision) + + - 12 (For TIMESTAMP type with picosecond precision) """ return _helpers._int_or_none(self._properties.get("timestampPrecision")) From a1bc2cbbe6da191f57740378708c19079fac80af Mon Sep 17 00:00:00 2001 From: Linchin Date: Mon, 24 Nov 2025 22:24:52 +0000 Subject: [PATCH 06/30] update unit test --- tests/unit/test_schema.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/unit/test_schema.py b/tests/unit/test_schema.py index 77f29b914..d808f35ac 100644 --- a/tests/unit/test_schema.py +++ b/tests/unit/test_schema.py @@ -346,12 +346,9 @@ def test_foreign_type_definition_property_str(self): def test_timestamp_precision_property(self): TIMESTAMP_PRECISION = 3 - schema_field = self._make_one( - "test", "TIMESTAMP", timestamp_precision=TIMESTAMP_PRECISION - ) - self.assertEqual( - schema_field._properties["timestampPrecision"], TIMESTAMP_PRECISION - ) + schema_field = self._make_one("test", "TIMESTAMP") + schema_field._properties["timestampPrecision"] = TIMESTAMP_PRECISION + self.assertEqual(schema_field.timestamp_precision, TIMESTAMP_PRECISION) def test_to_standard_sql_simple_type(self): examples = ( From bc6dcda006f5609888359f5423d4244e1765edb9 Mon Sep 17 00:00:00 2001 From: Linchin Date: Mon, 24 Nov 2025 22:28:39 +0000 Subject: [PATCH 07/30] unit test --- tests/unit/test_schema.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_schema.py b/tests/unit/test_schema.py index d808f35ac..fce61264e 100644 --- a/tests/unit/test_schema.py +++ b/tests/unit/test_schema.py @@ -196,7 +196,7 @@ def test_to_api_repr_w_timestamp_precision(self): "foo", "TIMESTAMP", "NULLABLE", - timestamp_precision=3, + timestamp_precision=6, ) self.assertEqual( field.to_api_repr(), @@ -204,7 +204,7 @@ def test_to_api_repr_w_timestamp_precision(self): "mode": "NULLABLE", "name": "foo", "type": "TIMESTAMP", - "timestampPrecision": 3, + "timestampPrecision": 6, }, ) From 518a12c4750ea84666af8ae7c62679dc332d9079 Mon Sep 17 00:00:00 2001 From: Linchin Date: Mon, 24 Nov 2025 22:59:56 +0000 Subject: [PATCH 08/30] add create_table system test --- google/cloud/bigquery/schema.py | 2 +- tests/system/test_client.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index 3a0b16bda..a048e5242 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -443,7 +443,7 @@ def _key(self): self.description, self.fields, policy_tags, - self._properties.get("timestampPrecision"), + self.timestamp_precision, ) def to_standard_sql(self) -> standard_sql.StandardSqlField: diff --git a/tests/system/test_client.py b/tests/system/test_client.py index 6584ca03c..73923fe90 100644 --- a/tests/system/test_client.py +++ b/tests/system/test_client.py @@ -74,6 +74,11 @@ bigquery.SchemaField("full_name", "STRING", mode="REQUIRED"), bigquery.SchemaField("age", "INTEGER", mode="REQUIRED"), ] +SCHEMA_PICOSECOND = [ + bigquery.SchemaField("full_name", "STRING", mode="REQUIRED"), + bigquery.SchemaField("age", "INTEGER", mode="REQUIRED"), + bigquery.SchemaField("time_pico", "TIMESTAMP", mode="REQUIRED", timestamp_precision=12), +] CLUSTERING_SCHEMA = [ bigquery.SchemaField("full_name", "STRING", mode="REQUIRED"), bigquery.SchemaField("age", "INTEGER", mode="REQUIRED"), @@ -631,6 +636,20 @@ def test_create_table_w_time_partitioning_w_clustering_fields(self): self.assertEqual(time_partitioning.field, "transaction_time") self.assertEqual(table.clustering_fields, ["user_email", "store_code"]) + def test_create_tabl_w_picosecond_timestamp(self): + dataset = self.temp_dataset(_make_dataset_id("create_table")) + table_id = "test_table" + table_arg = Table(dataset.table(table_id), schema=SCHEMA_PICOSECOND) + self.assertFalse(_table_exists(table_arg)) + + table = helpers.retry_403(Config.CLIENT.create_table)(table_arg) + self.to_delete.insert(0, table) + + self.assertTrue(_table_exists(table)) + self.assertEqual(table.table_id, table_id) + # breakpoint() + self.assertEqual(table.schema, SCHEMA_PICOSECOND) + def test_delete_dataset_with_string(self): dataset_id = _make_dataset_id("delete_table_true_with_string") project = Config.CLIENT.project From a8d5f5c7cf82a4948ce648ffe4b52adb3dc1b451 Mon Sep 17 00:00:00 2001 From: Linchin Date: Mon, 24 Nov 2025 23:03:08 +0000 Subject: [PATCH 09/30] typo --- tests/system/test_client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/system/test_client.py b/tests/system/test_client.py index 73923fe90..3ca6685a5 100644 --- a/tests/system/test_client.py +++ b/tests/system/test_client.py @@ -647,7 +647,6 @@ def test_create_tabl_w_picosecond_timestamp(self): self.assertTrue(_table_exists(table)) self.assertEqual(table.table_id, table_id) - # breakpoint() self.assertEqual(table.schema, SCHEMA_PICOSECOND) def test_delete_dataset_with_string(self): From 0567adf603c77b09a81bf78836eb055431f4feb7 Mon Sep 17 00:00:00 2001 From: Linchin Date: Tue, 25 Nov 2025 00:08:54 +0000 Subject: [PATCH 10/30] add query system test --- tests/system/test_client.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/system/test_client.py b/tests/system/test_client.py index 3ca6685a5..1c03d5fc7 100644 --- a/tests/system/test_client.py +++ b/tests/system/test_client.py @@ -1945,6 +1945,16 @@ def test_dml_statistics(self): assert query_job.dml_stats.updated_row_count == 0 assert query_job.dml_stats.deleted_row_count == 3 + def test_query_w_picosecond_timestamp(self): + query_job = Config.CLIENT.query( + "SELECT CAST(\"2025-10-20\" AS TIMESTAMP(12)) as time_pico;", + ) + expected_schema = [ + bigquery.SchemaField("time_pico", "TIMESTAMP", mode="NULLABLE", timestamp_precision=12), + ] + iterator = query_job.result() + self.assertEqual(iterator.schema, expected_schema) + def test_transaction_info(self): table_schema = ( bigquery.SchemaField("foo", "STRING"), From 1268c455d225ea0512c6d3b43e30142d06e07394 Mon Sep 17 00:00:00 2001 From: Linchin Date: Tue, 25 Nov 2025 00:23:51 +0000 Subject: [PATCH 11/30] remove query system test --- tests/system/test_client.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/system/test_client.py b/tests/system/test_client.py index 1c03d5fc7..3ca6685a5 100644 --- a/tests/system/test_client.py +++ b/tests/system/test_client.py @@ -1945,16 +1945,6 @@ def test_dml_statistics(self): assert query_job.dml_stats.updated_row_count == 0 assert query_job.dml_stats.deleted_row_count == 3 - def test_query_w_picosecond_timestamp(self): - query_job = Config.CLIENT.query( - "SELECT CAST(\"2025-10-20\" AS TIMESTAMP(12)) as time_pico;", - ) - expected_schema = [ - bigquery.SchemaField("time_pico", "TIMESTAMP", mode="NULLABLE", timestamp_precision=12), - ] - iterator = query_job.result() - self.assertEqual(iterator.schema, expected_schema) - def test_transaction_info(self): table_schema = ( bigquery.SchemaField("foo", "STRING"), From 8603973492780e9b7453b5261b86df9f26f7e9c2 Mon Sep 17 00:00:00 2001 From: Linchin Date: Tue, 25 Nov 2025 00:25:11 +0000 Subject: [PATCH 12/30] unit test --- tests/unit/test_schema.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_schema.py b/tests/unit/test_schema.py index fce61264e..d3fc57b45 100644 --- a/tests/unit/test_schema.py +++ b/tests/unit/test_schema.py @@ -69,7 +69,7 @@ def test_constructor_explicit(self): default_value_expression=FIELD_DEFAULT_VALUE_EXPRESSION, rounding_mode=enums.RoundingMode.ROUNDING_MODE_UNSPECIFIED, foreign_type_definition="INTEGER", - timestamp_precision=3, + timestamp_precision=6, ) self.assertEqual(field.name, "test") self.assertEqual(field.field_type, "STRING") @@ -88,7 +88,7 @@ def test_constructor_explicit(self): ) self.assertEqual(field.rounding_mode, "ROUNDING_MODE_UNSPECIFIED") self.assertEqual(field.foreign_type_definition, "INTEGER") - self.assertEqual(field._properties["timestampPrecision"], 3) + self.assertEqual(field._properties["timestampPrecision"], 6) def test_constructor_explicit_none(self): field = self._make_one("test", "STRING", description=None, policy_tags=None) From cb9f818f131020b0d8d37336be6c68bba4b23aa8 Mon Sep 17 00:00:00 2001 From: Linchin Date: Tue, 25 Nov 2025 00:25:56 +0000 Subject: [PATCH 13/30] unit test --- tests/unit/test_schema.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_schema.py b/tests/unit/test_schema.py index d3fc57b45..518236066 100644 --- a/tests/unit/test_schema.py +++ b/tests/unit/test_schema.py @@ -217,7 +217,7 @@ def test_from_api_repr(self): "name": "foo", "type": "record", "roundingMode": "ROUNDING_MODE_UNSPECIFIED", - "timestampPrecision": 3, + "timestampPrecision": 6, } ) self.assertEqual(field.name, "foo") @@ -230,7 +230,7 @@ def test_from_api_repr(self): self.assertEqual(field.fields[0].mode, "NULLABLE") self.assertEqual(field.range_element_type, None) self.assertEqual(field.rounding_mode, "ROUNDING_MODE_UNSPECIFIED") - self.assertEqual(field._properties["timestampPrecision"], 3) + self.assertEqual(field._properties["timestampPrecision"], 6) def test_from_api_repr_policy(self): field = self._get_target_class().from_api_repr( From 696dfffcd597cef4151a1ed7a97798651ccb3d11 Mon Sep 17 00:00:00 2001 From: Linchin Date: Tue, 25 Nov 2025 00:26:36 +0000 Subject: [PATCH 14/30] unit test --- tests/unit/test_schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_schema.py b/tests/unit/test_schema.py index 518236066..e6246e85a 100644 --- a/tests/unit/test_schema.py +++ b/tests/unit/test_schema.py @@ -345,7 +345,7 @@ def test_foreign_type_definition_property_str(self): self.assertEqual(schema_field.foreign_type_definition, FOREIGN_TYPE_DEFINITION) def test_timestamp_precision_property(self): - TIMESTAMP_PRECISION = 3 + TIMESTAMP_PRECISION = 6 schema_field = self._make_one("test", "TIMESTAMP") schema_field._properties["timestampPrecision"] = TIMESTAMP_PRECISION self.assertEqual(schema_field.timestamp_precision, TIMESTAMP_PRECISION) From d24df7df54fbdd502f58b72e9a5597f0af4ac5e2 Mon Sep 17 00:00:00 2001 From: Linchin Date: Tue, 25 Nov 2025 19:29:51 +0000 Subject: [PATCH 15/30] docstring --- google/cloud/bigquery/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index a048e5242..a36bad9f9 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -389,7 +389,7 @@ def policy_tags(self): @property def timestamp_precision(self): - """Optional[int]: Precision (maximum number of total digits in base 10) + """Precision (maximum number of total digits in base 10) for seconds of TIMESTAMP type. Possible values include: From 873bff6028aa4e2e9fc60df3b627d79ba9df380a Mon Sep 17 00:00:00 2001 From: Linchin Date: Tue, 25 Nov 2025 22:15:11 +0000 Subject: [PATCH 16/30] docstring --- google/cloud/bigquery/schema.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index a36bad9f9..7e87264d3 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -389,12 +389,12 @@ def policy_tags(self): @property def timestamp_precision(self): - """Precision (maximum number of total digits in base 10) - for seconds of TIMESTAMP type. + """Precision (maximum number of total digits in base 10) for seconds of + TIMESTAMP type. Possible values include: - - 6 (Default, for TIMESTAMP type with microsecond precision) + - None (Default, for TIMESTAMP type with microsecond precision) - 12 (For TIMESTAMP type with picosecond precision) """ From 6a93c2634972615e62709505d4a7de264b379527 Mon Sep 17 00:00:00 2001 From: Linchin Date: Tue, 25 Nov 2025 22:17:37 +0000 Subject: [PATCH 17/30] docstring --- google/cloud/bigquery/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index 7e87264d3..be2613447 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -203,7 +203,7 @@ class SchemaField(object): Possible values include: - - 6 (Default, for TIMESTAMP type with microsecond precision) + - None (Default, for TIMESTAMP type with microsecond precision) - 12 (For TIMESTAMP type with picosecond precision) """ From 9a4f72f9a3cf6ee54a5462fbd2edc6a73bf82e7d Mon Sep 17 00:00:00 2001 From: Linchin Date: Tue, 25 Nov 2025 23:35:15 +0000 Subject: [PATCH 18/30] improve __repr__() --- google/cloud/bigquery/schema.py | 5 ++--- tests/system/test_client.py | 4 +++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index be2613447..7231605b5 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -494,10 +494,9 @@ def __hash__(self): return hash(self._key()) def __repr__(self): - key = self._key() - policy_tags = key[-2] + *initial_tags, policy_tags, timestamp_precision_tag = self._key() policy_tags_inst = None if policy_tags is None else PolicyTagList(policy_tags) - adjusted_key = key[:-2] + (policy_tags_inst,) + (key[-1],) + adjusted_key = (*initial_tags, policy_tags_inst, timestamp_precision_tag) return f"{self.__class__.__name__}{adjusted_key}" diff --git a/tests/system/test_client.py b/tests/system/test_client.py index 3ca6685a5..0ae3fc9f0 100644 --- a/tests/system/test_client.py +++ b/tests/system/test_client.py @@ -77,7 +77,9 @@ SCHEMA_PICOSECOND = [ bigquery.SchemaField("full_name", "STRING", mode="REQUIRED"), bigquery.SchemaField("age", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("time_pico", "TIMESTAMP", mode="REQUIRED", timestamp_precision=12), + bigquery.SchemaField( + "time_pico", "TIMESTAMP", mode="REQUIRED", timestamp_precision=12 + ), ] CLUSTERING_SCHEMA = [ bigquery.SchemaField("full_name", "STRING", mode="REQUIRED"), From fc085330bc9380df8ee5934b4162c98f7594721e Mon Sep 17 00:00:00 2001 From: Linchin Date: Mon, 8 Dec 2025 21:55:35 +0000 Subject: [PATCH 19/30] use enum for timestamp_precision --- google/cloud/bigquery/enums.py | 15 ++++ google/cloud/bigquery/schema.py | 22 +++--- timeout_value_2.txt | 128 ++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 13 deletions(-) create mode 100644 timeout_value_2.txt diff --git a/google/cloud/bigquery/enums.py b/google/cloud/bigquery/enums.py index 1b1eb241a..dc67f9674 100644 --- a/google/cloud/bigquery/enums.py +++ b/google/cloud/bigquery/enums.py @@ -480,3 +480,18 @@ class SourceColumnMatch(str, enum.Enum): NAME = "NAME" """Matches by name. This reads the header row as column names and reorders columns to match the field names in the schema.""" + + +class TimestampPrecision(enum.Enum): + """Precision (maximum number of total digits in base 10) for seconds of + TIMESTAMP type.""" + + MICROSECOND = None + """ + Default, for TIMESTAMP type with microsecond precision. + """ + + PICOSECOND = 12 + """ + For TIMESTAMP type with picosecond precision. + """ diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index 7231605b5..e7267dc3a 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -197,15 +197,13 @@ class SchemaField(object): Only valid for top-level schema fields (not nested fields). If the type is FOREIGN, this field is required. - timestamp_precision: Optional[int] + timestamp_precision: Union[enums.TimestampPrecision, int, None] Precision (maximum number of total digits in base 10) for seconds of TIMESTAMP type. - Possible values include: - - - None (Default, for TIMESTAMP type with microsecond precision) - - - 12 (For TIMESTAMP type with picosecond precision) + Defaults to `enums.TimestampPrecision.MICROSECOND` (`None`) for + microsecond precision. Use `enums.TimestampPrecision.PICOSECOND` + (`12`) for picosecond precision. """ def __init__( @@ -389,14 +387,12 @@ def policy_tags(self): @property def timestamp_precision(self): - """Precision (maximum number of total digits in base 10) for seconds of - TIMESTAMP type. - - Possible values include: - - - None (Default, for TIMESTAMP type with microsecond precision) + """Union[enums.TimestampPrecision, int, None]: Precision (maximum number + of total digits in base 10) for seconds of TIMESTAMP type. - - 12 (For TIMESTAMP type with picosecond precision) + Defaults to `enums.TimestampPrecision.MICROSECOND` (`None`) for + microsecond precision. Use `enums.TimestampPrecision.PICOSECOND` (`12`) + for picosecond precision. """ return _helpers._int_or_none(self._properties.get("timestampPrecision")) diff --git a/timeout_value_2.txt b/timeout_value_2.txt new file mode 100644 index 000000000..4311ca4c1 --- /dev/null +++ b/timeout_value_2.txt @@ -0,0 +1,128 @@ +timeout: 1, deadline: 978355.762395545, next_sleep: 0.8387038236498067, exc: +timeout: 0.1, deadline: 978355.7105118759, next_sleep: 0.00027264356095121965, exc: 404 not normally retriable +timeout: 0.1, deadline: 978355.7112843869, next_sleep: 0.0005221385459112642, exc: 404 not normally retriable +timeout: 120.0, deadline: 978475.969267207, next_sleep: 0.7932984313855296, exc: 429 retriable exception +timeout: 0.1, deadline: 978357.982180721, next_sleep: 0.0006448068700835767, exc: 404 not normally retriable +timeout: 0.1, deadline: 978357.983290251, next_sleep: 0.0009674171869215179, exc: 404 not normally retriable +timeout: 0.1, deadline: 978357.984610041, next_sleep: 0.00032156163308249565, exc: 404 not normally retriable +timeout: 0.1, deadline: 978357.9852889209, next_sleep: 0.0006800204055350386, exc: 404 not normally retriable +timeout: 0.1, deadline: 978357.986294671, next_sleep: 0.0008018167599456671, exc: 404 not normally retriable +timeout: 0.1, deadline: 978357.987476401, next_sleep: 0.0009311538867737943, exc: 404 not normally retriable +timeout: 200.0, deadline: 1765230971.410979, next_sleep: 0.8314432558305912, exc: 502 retry me +timeout: 600.0, deadline: 1765231271.410979, next_sleep: 0.5105814189614761, exc: 500 job_retry me +timeout: 200.0, deadline: 1765231271.410979, next_sleep: 0.5603073877844243, exc: 502 retry me +timeout: 200.0, deadline: 1765230973.336951, next_sleep: 0.4857907625648802, exc: 502 retry me +timeout: 400.0, deadline: 1765231073.336951, next_sleep: 0.4899948554904009, exc: 500 job_retry me +timeout: 200.0, deadline: 1765231273.336951, next_sleep: 0.84194091877862, exc: 502 retry me +timeout: 1, deadline: 978366.098796235, next_sleep: 0.32226121592037205, exc: +timeout: 1, deadline: 978366.423546346, next_sleep: 0.8499486419579728, exc: +timeout: 1, deadline: 978367.434991377, next_sleep: 0.3836059281992953, exc: 403 +timeout: 600.0, deadline: 978967.151953309, next_sleep: 0.5414093753904822, exc: None +timeout: 2400.0, deadline: 980768.947447572, next_sleep: 0.6292297054830006, exc: 500 first try +timeout: 1, deadline: 978370.675553254, next_sleep: 0.28618424769449136, exc: +timeout: 1, deadline: 978370.964251014, next_sleep: 0.4420708319625928, exc: +timeout: 200.0, deadline: 978570.414356535, next_sleep: 0.6529561134170622, exc: 429 this is retriable by default +timeout: 200.0, deadline: 978570.414356535, next_sleep: 0.7171308816872937, exc: 404 we lost your job +timeout: 200.0, deadline: 978570.414356535, next_sleep: 2.5594975651343663, exc: 404 we lost your job again, sorry +timeout: 600.0, deadline: 1735690200.0, next_sleep: 0.13071235830082906, exc: Timeout of 1.0s exceeded, last exception: 404 we lost your job again, sorry +timeout: 200.0, deadline: 978574.510162103, next_sleep: 0.36823427621471116, exc: 500 get job failed +timeout: 2400.0, deadline: 980780.340589964, next_sleep: 0.09143499366790109, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 980780.340589964, next_sleep: 0.2704121721587227, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 980780.340589964, next_sleep: 2.5408614910246823, exc: 429 ; reason: rateLimitExceeded +timeout: 120.0, deadline: 978500.354624824, next_sleep: 0.6668468464990291, exc: 404 ; reason: notFound +timeout: 120.0, deadline: 978500.354624824, next_sleep: 0.4483941657220438, exc: 404 ; reason: notFound +timeout: 120.0, deadline: 978500.354624824, next_sleep: 3.474247648759312, exc: 404 ; reason: notFound +timeout: 120.0, deadline: 978500.367416034, next_sleep: 0.7838332629184905, exc: 404 ; reason: notFound +timeout: 120.0, deadline: 978500.367416034, next_sleep: 0.0483348255406868, exc: 404 ; reason: notFound +timeout: 120.0, deadline: 978500.367416034, next_sleep: 1.81585407781535, exc: 404 ; reason: notFound +timeout: 120.0, deadline: 978500.380200564, next_sleep: 0.4542454333228608, exc: 404 ; reason: notFound +timeout: 120.0, deadline: 978500.380200564, next_sleep: 0.8572586031169229, exc: 404 ; reason: notFound +timeout: 120.0, deadline: 978500.380200564, next_sleep: 3.4443489441828774, exc: 404 ; reason: notFound +timeout: 600.0, deadline: 1704069004.0, next_sleep: 0.6681220723303167, exc: +timeout: 2400.0, deadline: 1704070202.0, next_sleep: 0.38587841208832285, exc: Timeout of 600.0s exceeded, last exception: +timeout: 600.0, deadline: 1765232496.338881, next_sleep: 0.45277264118720517, exc: 500 Job failed just because... +timeout: 2400.0, deadline: 1765233694.338881, next_sleep: 0.16416303634366414, exc: Timeout of 600.0s exceeded, last exception: 500 Job failed just because... +timeout: 2400.0, deadline: 1765233694.338881, next_sleep: 0.6190694710580775, exc: 500 +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 0.5702043720061895, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 1.7841078387837406, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 2.832865470870173, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 5.057365346584958, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 4.407732102407159, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 7.107147395368749, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 52.014271271999206, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 56.793332831654865, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 57.438834165382055, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 22.61611471989796, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 42.19167925076571, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 33.63920826261272, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 16.096826961197213, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 18.2617548205308, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 16.15168873734663, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 1.0150750159006394, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 6.512429543279927, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 50.90609063000533, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 7.340511726154881, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 58.52049711777617, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 43.981235964136175, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 56.40664859541352, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 28.627186704881225, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 45.75155591404954, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 19.63472010240566, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 52.28741011732236, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 31.320218874737044, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 52.130539652296605, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 31.512008734105173, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 55.037713367049015, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 51.03287372330287, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 44.79625136820908, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 19.10881845743206, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 55.13801940498344, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 3.8887774173749, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 51.398882099725824, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 51.01142692789878, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 23.595159319953243, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 26.047665816356226, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 49.306770881216835, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 25.1846112406392, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 45.055834473562356, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 17.883118716785585, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 11.896556313416141, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 40.34191107753058, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 48.24534707391338, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 18.904896450063703, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 26.008314274214626, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 13.176187276969207, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 7.3761482151139095, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 54.69229790318889, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 54.05171795192, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 45.32685064128777, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 14.353633598756772, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 7.98453577041105, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 53.459155430349334, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 20.287295620145734, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 34.43598073347521, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 6.522487221283226, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 54.07790146595999, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 59.156002169545, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 23.340948639587165, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 59.330803756151575, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 58.87002561303058, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 31.4282125155591, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 41.0987742268019, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 57.104824622740395, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 29.1136608417132, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 47.41112072816565, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 57.328008095558715, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 51.70080769914592, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704069600.0, next_sleep: 30.297137368813033, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704071998.716728, next_sleep: 0.20143152103008766, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704071998.716728, next_sleep: 1.3083122545651673, exc: 500 ; reason: backendError +timeout: 2400.0, deadline: 1704071998.716728, next_sleep: 0.591697258360937, exc: 429 ; reason: rateLimitExceeded +timeout: 2400.0, deadline: 1704071998.716728, next_sleep: 5.714972037288209, exc: 500 ; reason: backendError +timeout: 2400.0, deadline: 980782.802375289, next_sleep: 0.4004304538795789, exc: 500 job_retry me + +Location: response-location +Job ID: abc + +timeout: 600.0, deadline: 978983.21294691, next_sleep: 0.5048321061993695, exc: 503 retry me +timeout: 10.0, deadline: 978393.726221531, next_sleep: 0.14514349787731606, exc: 503 retry me From 0b743f37b10719509fe0df73a726ebea31a4f4c6 Mon Sep 17 00:00:00 2001 From: Linchin Date: Mon, 8 Dec 2025 21:56:49 +0000 Subject: [PATCH 20/30] delete file --- timeout_value_2.txt | 128 -------------------------------------------- 1 file changed, 128 deletions(-) delete mode 100644 timeout_value_2.txt diff --git a/timeout_value_2.txt b/timeout_value_2.txt deleted file mode 100644 index 4311ca4c1..000000000 --- a/timeout_value_2.txt +++ /dev/null @@ -1,128 +0,0 @@ -timeout: 1, deadline: 978355.762395545, next_sleep: 0.8387038236498067, exc: -timeout: 0.1, deadline: 978355.7105118759, next_sleep: 0.00027264356095121965, exc: 404 not normally retriable -timeout: 0.1, deadline: 978355.7112843869, next_sleep: 0.0005221385459112642, exc: 404 not normally retriable -timeout: 120.0, deadline: 978475.969267207, next_sleep: 0.7932984313855296, exc: 429 retriable exception -timeout: 0.1, deadline: 978357.982180721, next_sleep: 0.0006448068700835767, exc: 404 not normally retriable -timeout: 0.1, deadline: 978357.983290251, next_sleep: 0.0009674171869215179, exc: 404 not normally retriable -timeout: 0.1, deadline: 978357.984610041, next_sleep: 0.00032156163308249565, exc: 404 not normally retriable -timeout: 0.1, deadline: 978357.9852889209, next_sleep: 0.0006800204055350386, exc: 404 not normally retriable -timeout: 0.1, deadline: 978357.986294671, next_sleep: 0.0008018167599456671, exc: 404 not normally retriable -timeout: 0.1, deadline: 978357.987476401, next_sleep: 0.0009311538867737943, exc: 404 not normally retriable -timeout: 200.0, deadline: 1765230971.410979, next_sleep: 0.8314432558305912, exc: 502 retry me -timeout: 600.0, deadline: 1765231271.410979, next_sleep: 0.5105814189614761, exc: 500 job_retry me -timeout: 200.0, deadline: 1765231271.410979, next_sleep: 0.5603073877844243, exc: 502 retry me -timeout: 200.0, deadline: 1765230973.336951, next_sleep: 0.4857907625648802, exc: 502 retry me -timeout: 400.0, deadline: 1765231073.336951, next_sleep: 0.4899948554904009, exc: 500 job_retry me -timeout: 200.0, deadline: 1765231273.336951, next_sleep: 0.84194091877862, exc: 502 retry me -timeout: 1, deadline: 978366.098796235, next_sleep: 0.32226121592037205, exc: -timeout: 1, deadline: 978366.423546346, next_sleep: 0.8499486419579728, exc: -timeout: 1, deadline: 978367.434991377, next_sleep: 0.3836059281992953, exc: 403 -timeout: 600.0, deadline: 978967.151953309, next_sleep: 0.5414093753904822, exc: None -timeout: 2400.0, deadline: 980768.947447572, next_sleep: 0.6292297054830006, exc: 500 first try -timeout: 1, deadline: 978370.675553254, next_sleep: 0.28618424769449136, exc: -timeout: 1, deadline: 978370.964251014, next_sleep: 0.4420708319625928, exc: -timeout: 200.0, deadline: 978570.414356535, next_sleep: 0.6529561134170622, exc: 429 this is retriable by default -timeout: 200.0, deadline: 978570.414356535, next_sleep: 0.7171308816872937, exc: 404 we lost your job -timeout: 200.0, deadline: 978570.414356535, next_sleep: 2.5594975651343663, exc: 404 we lost your job again, sorry -timeout: 600.0, deadline: 1735690200.0, next_sleep: 0.13071235830082906, exc: Timeout of 1.0s exceeded, last exception: 404 we lost your job again, sorry -timeout: 200.0, deadline: 978574.510162103, next_sleep: 0.36823427621471116, exc: 500 get job failed -timeout: 2400.0, deadline: 980780.340589964, next_sleep: 0.09143499366790109, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 980780.340589964, next_sleep: 0.2704121721587227, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 980780.340589964, next_sleep: 2.5408614910246823, exc: 429 ; reason: rateLimitExceeded -timeout: 120.0, deadline: 978500.354624824, next_sleep: 0.6668468464990291, exc: 404 ; reason: notFound -timeout: 120.0, deadline: 978500.354624824, next_sleep: 0.4483941657220438, exc: 404 ; reason: notFound -timeout: 120.0, deadline: 978500.354624824, next_sleep: 3.474247648759312, exc: 404 ; reason: notFound -timeout: 120.0, deadline: 978500.367416034, next_sleep: 0.7838332629184905, exc: 404 ; reason: notFound -timeout: 120.0, deadline: 978500.367416034, next_sleep: 0.0483348255406868, exc: 404 ; reason: notFound -timeout: 120.0, deadline: 978500.367416034, next_sleep: 1.81585407781535, exc: 404 ; reason: notFound -timeout: 120.0, deadline: 978500.380200564, next_sleep: 0.4542454333228608, exc: 404 ; reason: notFound -timeout: 120.0, deadline: 978500.380200564, next_sleep: 0.8572586031169229, exc: 404 ; reason: notFound -timeout: 120.0, deadline: 978500.380200564, next_sleep: 3.4443489441828774, exc: 404 ; reason: notFound -timeout: 600.0, deadline: 1704069004.0, next_sleep: 0.6681220723303167, exc: -timeout: 2400.0, deadline: 1704070202.0, next_sleep: 0.38587841208832285, exc: Timeout of 600.0s exceeded, last exception: -timeout: 600.0, deadline: 1765232496.338881, next_sleep: 0.45277264118720517, exc: 500 Job failed just because... -timeout: 2400.0, deadline: 1765233694.338881, next_sleep: 0.16416303634366414, exc: Timeout of 600.0s exceeded, last exception: 500 Job failed just because... -timeout: 2400.0, deadline: 1765233694.338881, next_sleep: 0.6190694710580775, exc: 500 -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 0.5702043720061895, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 1.7841078387837406, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 2.832865470870173, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 5.057365346584958, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 4.407732102407159, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 7.107147395368749, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 52.014271271999206, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 56.793332831654865, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 57.438834165382055, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 22.61611471989796, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 42.19167925076571, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 33.63920826261272, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 16.096826961197213, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 18.2617548205308, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 16.15168873734663, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 1.0150750159006394, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 6.512429543279927, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 50.90609063000533, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 7.340511726154881, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 58.52049711777617, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 43.981235964136175, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 56.40664859541352, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 28.627186704881225, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 45.75155591404954, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 19.63472010240566, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 52.28741011732236, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 31.320218874737044, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 52.130539652296605, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 31.512008734105173, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 55.037713367049015, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 51.03287372330287, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 44.79625136820908, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 19.10881845743206, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 55.13801940498344, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 3.8887774173749, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 51.398882099725824, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 51.01142692789878, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 23.595159319953243, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 26.047665816356226, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 49.306770881216835, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 25.1846112406392, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 45.055834473562356, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 17.883118716785585, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 11.896556313416141, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 40.34191107753058, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 48.24534707391338, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 18.904896450063703, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 26.008314274214626, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 13.176187276969207, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 7.3761482151139095, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 54.69229790318889, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 54.05171795192, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 45.32685064128777, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 14.353633598756772, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 7.98453577041105, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 53.459155430349334, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 20.287295620145734, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 34.43598073347521, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 6.522487221283226, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 54.07790146595999, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 59.156002169545, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 23.340948639587165, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 59.330803756151575, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 58.87002561303058, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 31.4282125155591, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 41.0987742268019, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 57.104824622740395, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 29.1136608417132, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 47.41112072816565, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 57.328008095558715, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 51.70080769914592, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704069600.0, next_sleep: 30.297137368813033, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704071998.716728, next_sleep: 0.20143152103008766, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704071998.716728, next_sleep: 1.3083122545651673, exc: 500 ; reason: backendError -timeout: 2400.0, deadline: 1704071998.716728, next_sleep: 0.591697258360937, exc: 429 ; reason: rateLimitExceeded -timeout: 2400.0, deadline: 1704071998.716728, next_sleep: 5.714972037288209, exc: 500 ; reason: backendError -timeout: 2400.0, deadline: 980782.802375289, next_sleep: 0.4004304538795789, exc: 500 job_retry me - -Location: response-location -Job ID: abc - -timeout: 600.0, deadline: 978983.21294691, next_sleep: 0.5048321061993695, exc: 503 retry me -timeout: 10.0, deadline: 978393.726221531, next_sleep: 0.14514349787731606, exc: 503 retry me From 2a81ef9a228a609f730df38edcead41c0e3ff7a1 Mon Sep 17 00:00:00 2001 From: Linchin Date: Mon, 8 Dec 2025 22:02:50 +0000 Subject: [PATCH 21/30] docstring --- google/cloud/bigquery/schema.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index e7267dc3a..789b19fd3 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -389,10 +389,6 @@ def policy_tags(self): def timestamp_precision(self): """Union[enums.TimestampPrecision, int, None]: Precision (maximum number of total digits in base 10) for seconds of TIMESTAMP type. - - Defaults to `enums.TimestampPrecision.MICROSECOND` (`None`) for - microsecond precision. Use `enums.TimestampPrecision.PICOSECOND` (`12`) - for picosecond precision. """ return _helpers._int_or_none(self._properties.get("timestampPrecision")) From e131b6d860c14ebb3c10946c61f81eb9d102d5d1 Mon Sep 17 00:00:00 2001 From: Linchin Date: Mon, 8 Dec 2025 22:15:15 +0000 Subject: [PATCH 22/30] update test --- google/cloud/bigquery/schema.py | 2 +- tests/system/test_client.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index 789b19fd3..9e827f387 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -221,7 +221,7 @@ def __init__( range_element_type: Union[FieldElementType, str, None] = None, rounding_mode: Union[enums.RoundingMode, str, None] = None, foreign_type_definition: Optional[str] = None, - timestamp_precision: Optional[int] = None, + timestamp_precision: Union[enums.TimestampPrecision, int, None] = None, ): self._properties: Dict[str, Any] = { "name": name, diff --git a/tests/system/test_client.py b/tests/system/test_client.py index 0ae3fc9f0..420f82010 100644 --- a/tests/system/test_client.py +++ b/tests/system/test_client.py @@ -78,7 +78,10 @@ bigquery.SchemaField("full_name", "STRING", mode="REQUIRED"), bigquery.SchemaField("age", "INTEGER", mode="REQUIRED"), bigquery.SchemaField( - "time_pico", "TIMESTAMP", mode="REQUIRED", timestamp_precision=12 + "time_pico", + "TIMESTAMP", + mode="REQUIRED", + timestamp_precision=enums.TimestampPrecision.PICOSECOND, ), ] CLUSTERING_SCHEMA = [ From 769353797c15868e2a276c8c1abf8633a0155432 Mon Sep 17 00:00:00 2001 From: Linchin Date: Mon, 8 Dec 2025 22:28:01 +0000 Subject: [PATCH 23/30] improve unit test --- google/cloud/bigquery/schema.py | 2 +- tests/unit/test_schema.py | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index 9e827f387..973e219e0 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -390,7 +390,7 @@ def timestamp_precision(self): """Union[enums.TimestampPrecision, int, None]: Precision (maximum number of total digits in base 10) for seconds of TIMESTAMP type. """ - return _helpers._int_or_none(self._properties.get("timestampPrecision")) + return self._properties.get("timestampPrecision") def to_api_repr(self) -> dict: """Return a dictionary representing this schema field. diff --git a/tests/unit/test_schema.py b/tests/unit/test_schema.py index e6246e85a..72301e454 100644 --- a/tests/unit/test_schema.py +++ b/tests/unit/test_schema.py @@ -69,7 +69,7 @@ def test_constructor_explicit(self): default_value_expression=FIELD_DEFAULT_VALUE_EXPRESSION, rounding_mode=enums.RoundingMode.ROUNDING_MODE_UNSPECIFIED, foreign_type_definition="INTEGER", - timestamp_precision=6, + timestamp_precision=enums.TimestampPrecision.PICOSECOND, ) self.assertEqual(field.name, "test") self.assertEqual(field.field_type, "STRING") @@ -88,7 +88,10 @@ def test_constructor_explicit(self): ) self.assertEqual(field.rounding_mode, "ROUNDING_MODE_UNSPECIFIED") self.assertEqual(field.foreign_type_definition, "INTEGER") - self.assertEqual(field._properties["timestampPrecision"], 6) + self.assertEqual( + field._properties["timestampPrecision"], + enums.TimestampPrecision.PICOSECOND, + ) def test_constructor_explicit_none(self): field = self._make_one("test", "STRING", description=None, policy_tags=None) @@ -196,7 +199,7 @@ def test_to_api_repr_w_timestamp_precision(self): "foo", "TIMESTAMP", "NULLABLE", - timestamp_precision=6, + timestamp_precision=enums.TimestampPrecision.PICOSECOND, ) self.assertEqual( field.to_api_repr(), @@ -204,7 +207,7 @@ def test_to_api_repr_w_timestamp_precision(self): "mode": "NULLABLE", "name": "foo", "type": "TIMESTAMP", - "timestampPrecision": 6, + "timestampPrecision": enums.TimestampPrecision.PICOSECOND, }, ) @@ -217,7 +220,7 @@ def test_from_api_repr(self): "name": "foo", "type": "record", "roundingMode": "ROUNDING_MODE_UNSPECIFIED", - "timestampPrecision": 6, + "timestampPrecision": enums.TimestampPrecision.PICOSECOND, } ) self.assertEqual(field.name, "foo") @@ -230,7 +233,10 @@ def test_from_api_repr(self): self.assertEqual(field.fields[0].mode, "NULLABLE") self.assertEqual(field.range_element_type, None) self.assertEqual(field.rounding_mode, "ROUNDING_MODE_UNSPECIFIED") - self.assertEqual(field._properties["timestampPrecision"], 6) + self.assertEqual( + field._properties["timestampPrecision"], + enums.TimestampPrecision.PICOSECOND, + ) def test_from_api_repr_policy(self): field = self._get_target_class().from_api_repr( @@ -345,7 +351,7 @@ def test_foreign_type_definition_property_str(self): self.assertEqual(schema_field.foreign_type_definition, FOREIGN_TYPE_DEFINITION) def test_timestamp_precision_property(self): - TIMESTAMP_PRECISION = 6 + TIMESTAMP_PRECISION = enums.TimestampPrecision.PICOSECOND schema_field = self._make_one("test", "TIMESTAMP") schema_field._properties["timestampPrecision"] = TIMESTAMP_PRECISION self.assertEqual(schema_field.timestamp_precision, TIMESTAMP_PRECISION) From 5d2fbf0f5c5c0ce952887da9a46daaef816154b7 Mon Sep 17 00:00:00 2001 From: Linchin Date: Mon, 8 Dec 2025 23:27:35 +0000 Subject: [PATCH 24/30] fix system test --- google/cloud/bigquery/enums.py | 2 +- google/cloud/bigquery/schema.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/google/cloud/bigquery/enums.py b/google/cloud/bigquery/enums.py index dc67f9674..4be951d85 100644 --- a/google/cloud/bigquery/enums.py +++ b/google/cloud/bigquery/enums.py @@ -482,7 +482,7 @@ class SourceColumnMatch(str, enum.Enum): columns to match the field names in the schema.""" -class TimestampPrecision(enum.Enum): +class TimestampPrecision(object): """Precision (maximum number of total digits in base 10) for seconds of TIMESTAMP type.""" diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index 973e219e0..9e827f387 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -390,7 +390,7 @@ def timestamp_precision(self): """Union[enums.TimestampPrecision, int, None]: Precision (maximum number of total digits in base 10) for seconds of TIMESTAMP type. """ - return self._properties.get("timestampPrecision") + return _helpers._int_or_none(self._properties.get("timestampPrecision")) def to_api_repr(self) -> dict: """Return a dictionary representing this schema field. From c7c2b47295db85f725efcfd16eb50fd06d70a796 Mon Sep 17 00:00:00 2001 From: Lingqing Gan Date: Tue, 9 Dec 2025 14:28:07 -0800 Subject: [PATCH 25/30] Update tests/system/test_client.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- tests/system/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/test_client.py b/tests/system/test_client.py index 420f82010..3d32a3634 100644 --- a/tests/system/test_client.py +++ b/tests/system/test_client.py @@ -641,7 +641,7 @@ def test_create_table_w_time_partitioning_w_clustering_fields(self): self.assertEqual(time_partitioning.field, "transaction_time") self.assertEqual(table.clustering_fields, ["user_email", "store_code"]) - def test_create_tabl_w_picosecond_timestamp(self): + def test_create_table_w_picosecond_timestamp(self): dataset = self.temp_dataset(_make_dataset_id("create_table")) table_id = "test_table" table_arg = Table(dataset.table(table_id), schema=SCHEMA_PICOSECOND) From f87b618b8b8d76ca0b68ba03caa3835012eca6ba Mon Sep 17 00:00:00 2001 From: Linchin Date: Thu, 11 Dec 2025 00:32:20 +0000 Subject: [PATCH 26/30] only allow enums values --- google/cloud/bigquery/enums.py | 2 +- google/cloud/bigquery/schema.py | 25 +++++++++++++++++-------- tests/unit/test_schema.py | 7 +++++-- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/google/cloud/bigquery/enums.py b/google/cloud/bigquery/enums.py index 4be951d85..dc67f9674 100644 --- a/google/cloud/bigquery/enums.py +++ b/google/cloud/bigquery/enums.py @@ -482,7 +482,7 @@ class SourceColumnMatch(str, enum.Enum): columns to match the field names in the schema.""" -class TimestampPrecision(object): +class TimestampPrecision(enum.Enum): """Precision (maximum number of total digits in base 10) for seconds of TIMESTAMP type.""" diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index 9e827f387..a533f937e 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -221,7 +221,7 @@ def __init__( range_element_type: Union[FieldElementType, str, None] = None, rounding_mode: Union[enums.RoundingMode, str, None] = None, foreign_type_definition: Optional[str] = None, - timestamp_precision: Union[enums.TimestampPrecision, int, None] = None, + timestamp_precision: Optional[enums.TimestampPrecision] = None, ): self._properties: Dict[str, Any] = { "name": name, @@ -246,8 +246,13 @@ def __init__( if isinstance(policy_tags, PolicyTagList) else None ) - if timestamp_precision is not None: - self._properties["timestampPrecision"] = timestamp_precision + if isinstance(timestamp_precision, enums.TimestampPrecision): + self._properties["timestampPrecision"] = timestamp_precision.value + elif timestamp_precision is not None: + raise ValueError( + "timestamp_precision must be class enums.TimestampPrecision " + f"or None, got {type(timestamp_precision)} instead." + ) if isinstance(range_element_type, str): self._properties["rangeElementType"] = {"type": range_element_type} if isinstance(range_element_type, FieldElementType): @@ -390,7 +395,7 @@ def timestamp_precision(self): """Union[enums.TimestampPrecision, int, None]: Precision (maximum number of total digits in base 10) for seconds of TIMESTAMP type. """ - return _helpers._int_or_none(self._properties.get("timestampPrecision")) + return enums.TimestampPrecision(self._properties.get("timestampPrecision")) def to_api_repr(self) -> dict: """Return a dictionary representing this schema field. @@ -426,6 +431,8 @@ def _key(self): None if self.policy_tags is None else tuple(sorted(self.policy_tags.names)) ) + timestamp_precision = self._properties.get("timestampPrecision") + return ( self.name, field_type, @@ -435,7 +442,7 @@ def _key(self): self.description, self.fields, policy_tags, - self.timestamp_precision, + timestamp_precision, ) def to_standard_sql(self) -> standard_sql.StandardSqlField: @@ -548,9 +555,11 @@ def _to_schema_fields(schema): if isinstance(schema, Sequence): # Input is a Sequence (e.g. a list): Process and return a list of SchemaFields return [ - field - if isinstance(field, SchemaField) - else SchemaField.from_api_repr(field) + ( + field + if isinstance(field, SchemaField) + else SchemaField.from_api_repr(field) + ) for field in schema ] diff --git a/tests/unit/test_schema.py b/tests/unit/test_schema.py index 72301e454..ceec653f0 100644 --- a/tests/unit/test_schema.py +++ b/tests/unit/test_schema.py @@ -52,6 +52,9 @@ def test_constructor_defaults(self): self.assertIsNone(field.default_value_expression) self.assertEqual(field.rounding_mode, None) self.assertEqual(field.foreign_type_definition, None) + self.assertEqual( + field.timestamp_precision, enums.TimestampPrecision.MICROSECOND + ) def test_constructor_explicit(self): FIELD_DEFAULT_VALUE_EXPRESSION = "This is the default value for this field" @@ -89,7 +92,7 @@ def test_constructor_explicit(self): self.assertEqual(field.rounding_mode, "ROUNDING_MODE_UNSPECIFIED") self.assertEqual(field.foreign_type_definition, "INTEGER") self.assertEqual( - field._properties["timestampPrecision"], + field.timestamp_precision, enums.TimestampPrecision.PICOSECOND, ) @@ -207,7 +210,7 @@ def test_to_api_repr_w_timestamp_precision(self): "mode": "NULLABLE", "name": "foo", "type": "TIMESTAMP", - "timestampPrecision": enums.TimestampPrecision.PICOSECOND, + "timestampPrecision": 12, }, ) From c0e4595a3e4fec91569bceebd207dff77d3e5495 Mon Sep 17 00:00:00 2001 From: Linchin Date: Thu, 11 Dec 2025 01:08:53 +0000 Subject: [PATCH 27/30] docstring and tests --- google/cloud/bigquery/schema.py | 11 +++++++---- tests/unit/test_schema.py | 8 +++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index a533f937e..752c9d470 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -197,7 +197,7 @@ class SchemaField(object): Only valid for top-level schema fields (not nested fields). If the type is FOREIGN, this field is required. - timestamp_precision: Union[enums.TimestampPrecision, int, None] + timestamp_precision: Optional[enums.TimestampPrecision] Precision (maximum number of total digits in base 10) for seconds of TIMESTAMP type. @@ -391,9 +391,12 @@ def policy_tags(self): return PolicyTagList.from_api_repr(resource) if resource is not None else None @property - def timestamp_precision(self): - """Union[enums.TimestampPrecision, int, None]: Precision (maximum number - of total digits in base 10) for seconds of TIMESTAMP type. + def timestamp_precision(self) -> enums.TimestampPrecision: + """Precision (maximum number of total digits in base 10) for seconds of + TIMESTAMP type. + + Returns: + enums.TimestampPrecision: value of TimestampPrecision. """ return enums.TimestampPrecision(self._properties.get("timestampPrecision")) diff --git a/tests/unit/test_schema.py b/tests/unit/test_schema.py index ceec653f0..f31913b03 100644 --- a/tests/unit/test_schema.py +++ b/tests/unit/test_schema.py @@ -223,7 +223,7 @@ def test_from_api_repr(self): "name": "foo", "type": "record", "roundingMode": "ROUNDING_MODE_UNSPECIFIED", - "timestampPrecision": enums.TimestampPrecision.PICOSECOND, + "timestampPrecision": 12, } ) self.assertEqual(field.name, "foo") @@ -237,7 +237,7 @@ def test_from_api_repr(self): self.assertEqual(field.range_element_type, None) self.assertEqual(field.rounding_mode, "ROUNDING_MODE_UNSPECIFIED") self.assertEqual( - field._properties["timestampPrecision"], + field.timestamp_precision, enums.TimestampPrecision.PICOSECOND, ) @@ -356,7 +356,9 @@ def test_foreign_type_definition_property_str(self): def test_timestamp_precision_property(self): TIMESTAMP_PRECISION = enums.TimestampPrecision.PICOSECOND schema_field = self._make_one("test", "TIMESTAMP") - schema_field._properties["timestampPrecision"] = TIMESTAMP_PRECISION + schema_field._properties[ + "timestampPrecision" + ] = enums.TimestampPrecision.PICOSECOND.value self.assertEqual(schema_field.timestamp_precision, TIMESTAMP_PRECISION) def test_to_standard_sql_simple_type(self): From 04c5f598ae38ef35ac3c8677c78ea81a19494eb9 Mon Sep 17 00:00:00 2001 From: Linchin Date: Thu, 11 Dec 2025 01:39:43 +0000 Subject: [PATCH 28/30] handle server inconsistency and unit tests --- google/cloud/bigquery/schema.py | 9 +++++++++ tests/unit/test_schema.py | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index 752c9d470..1c3bad501 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -279,6 +279,15 @@ def from_api_repr(cls, api_repr: dict) -> "SchemaField": """ placeholder = cls("this_will_be_replaced", "PLACEHOLDER") + # The API would return a string despite we send an integer. To ensure + # success of resending received schema, we convert string to integer + # to ensure consistency. + if ( + isinstance(api_repr, dict) + and type(api_repr.get("timestampPrecision")) is str + ): + api_repr["timestampPrecision"] = int(api_repr["timestampPrecision"]) + # Note: we don't make a copy of api_repr because this can cause # unnecessary slowdowns, especially on deeply nested STRUCT / RECORD # fields. See https://github.com/googleapis/python-bigquery/issues/6 diff --git a/tests/unit/test_schema.py b/tests/unit/test_schema.py index f31913b03..f61b22035 100644 --- a/tests/unit/test_schema.py +++ b/tests/unit/test_schema.py @@ -294,6 +294,17 @@ def test_from_api_repr_defaults(self): self.assertNotIn("policyTags", field._properties) self.assertNotIn("rangeElementType", field._properties) + def test_from_api_repr_timestamp_precision_str(self): + # The backend would return timestampPrecision field as a string, even + # if we send over an integer. This test verifies we manually converted + # it into integer to ensure resending could succeed. + field = self._get_target_class().from_api_repr( + { + "timestampPrecision": "12", + } + ) + self.assertEqual(field._properties["timestampPrecision"], 12) + def test_name_property(self): name = "lemon-ness" schema_field = self._make_one(name, "INTEGER") @@ -353,6 +364,14 @@ def test_foreign_type_definition_property_str(self): schema_field._properties["foreignTypeDefinition"] = FOREIGN_TYPE_DEFINITION self.assertEqual(schema_field.foreign_type_definition, FOREIGN_TYPE_DEFINITION) + def test_timestamp_precision_unsupported_type(self): + with pytest.raises(ValueError) as e: + self._make_one("test", "TIMESTAMP", timestamp_precision=12) + + assert "timestamp_precision must be class enums.TimestampPrecision" in str( + e.value + ) + def test_timestamp_precision_property(self): TIMESTAMP_PRECISION = enums.TimestampPrecision.PICOSECOND schema_field = self._make_one("test", "TIMESTAMP") From 4cd3df4338b3708a03f6f7f0ad4322ef03e7752a Mon Sep 17 00:00:00 2001 From: Linchin Date: Fri, 19 Dec 2025 01:31:07 +0000 Subject: [PATCH 29/30] improve code --- google/cloud/bigquery/schema.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index 1c3bad501..4ac9a4e30 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -270,9 +270,8 @@ def from_api_repr(cls, api_repr: dict) -> "SchemaField": """Return a ``SchemaField`` object deserialized from a dictionary. Args: - api_repr (Mapping[str, str]): The serialized representation - of the SchemaField, such as what is output by - :meth:`to_api_repr`. + api_repr (dict): The serialized representation of the SchemaField, + such as what is output by :meth:`to_api_repr`. Returns: google.cloud.bigquery.schema.SchemaField: The ``SchemaField`` object. @@ -282,11 +281,10 @@ def from_api_repr(cls, api_repr: dict) -> "SchemaField": # The API would return a string despite we send an integer. To ensure # success of resending received schema, we convert string to integer # to ensure consistency. - if ( - isinstance(api_repr, dict) - and type(api_repr.get("timestampPrecision")) is str - ): + try: api_repr["timestampPrecision"] = int(api_repr["timestampPrecision"]) + except (TypeError, KeyError): + pass # Note: we don't make a copy of api_repr because this can cause # unnecessary slowdowns, especially on deeply nested STRUCT / RECORD From e6a3f8b9ec298be9f982bdcf1d7dfd7758f649ed Mon Sep 17 00:00:00 2001 From: Linchin Date: Fri, 19 Dec 2025 18:02:32 +0000 Subject: [PATCH 30/30] docstring --- google/cloud/bigquery/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/cloud/bigquery/schema.py b/google/cloud/bigquery/schema.py index 4ac9a4e30..1809df21f 100644 --- a/google/cloud/bigquery/schema.py +++ b/google/cloud/bigquery/schema.py @@ -271,7 +271,7 @@ def from_api_repr(cls, api_repr: dict) -> "SchemaField": Args: api_repr (dict): The serialized representation of the SchemaField, - such as what is output by :meth:`to_api_repr`. + such as what is output by :meth:`to_api_repr`. Returns: google.cloud.bigquery.schema.SchemaField: The ``SchemaField`` object.