Skip to content

Commit cf806f2

Browse files
authored
BigQuery: add destination table properties to 'LoadJobConfig'. (#6202)
Add 'LoadTableConfig.destination_table_description' property. Add 'LoadTableConfig.destination_table_friendly_name' property. Closes #5093.
1 parent c8d4f5d commit cf806f2

File tree

4 files changed

+889
-181
lines changed

4 files changed

+889
-181
lines changed

bigquery/google/cloud/bigquery/job.py

+135-93
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,34 @@ def autodetect(self):
920920
def autodetect(self, value):
921921
self._set_sub_prop('autodetect', value)
922922

923+
@property
924+
def clustering_fields(self):
925+
"""Union[List[str], None]: Fields defining clustering for the table
926+
927+
(Defaults to :data:`None`).
928+
929+
Clustering fields are immutable after table creation.
930+
931+
.. note::
932+
933+
As of 2018-06-29, clustering fields cannot be set on a table
934+
which does not also have time partioning defined.
935+
"""
936+
prop = self._get_sub_prop('clustering')
937+
if prop is not None:
938+
return list(prop.get('fields', ()))
939+
940+
@clustering_fields.setter
941+
def clustering_fields(self, value):
942+
"""Union[List[str], None]: Fields defining clustering for the table
943+
944+
(Defaults to :data:`None`).
945+
"""
946+
if value is not None:
947+
self._set_sub_prop('clustering', {'fields': value})
948+
else:
949+
self._del_sub_prop('clustering')
950+
923951
@property
924952
def create_disposition(self):
925953
"""google.cloud.bigquery.job.CreateDisposition: Specifies behavior
@@ -934,6 +962,69 @@ def create_disposition(self):
934962
def create_disposition(self, value):
935963
self._set_sub_prop('createDisposition', value)
936964

965+
@property
966+
def destination_encryption_configuration(self):
967+
"""google.cloud.bigquery.table.EncryptionConfiguration: Custom
968+
encryption configuration for the destination table.
969+
970+
Custom encryption configuration (e.g., Cloud KMS keys) or ``None``
971+
if using default encryption.
972+
973+
See
974+
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.destinationEncryptionConfiguration
975+
"""
976+
prop = self._get_sub_prop('destinationEncryptionConfiguration')
977+
if prop is not None:
978+
prop = EncryptionConfiguration.from_api_repr(prop)
979+
return prop
980+
981+
@destination_encryption_configuration.setter
982+
def destination_encryption_configuration(self, value):
983+
api_repr = value
984+
if value is not None:
985+
api_repr = value.to_api_repr()
986+
self._set_sub_prop('destinationEncryptionConfiguration', api_repr)
987+
else:
988+
self._del_sub_prop('destinationEncryptionConfiguration')
989+
990+
@property
991+
def destination_table_description(self):
992+
"""Union[str, None] name given to destination table.
993+
994+
See:
995+
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.destinationTableProperties.description
996+
"""
997+
prop = self._get_sub_prop('destinationTableProperties')
998+
if prop is not None:
999+
return prop['description']
1000+
1001+
@destination_table_description.setter
1002+
def destination_table_description(self, value):
1003+
keys = [self._job_type, 'destinationTableProperties', 'description']
1004+
if value is not None:
1005+
_helpers._set_sub_prop(self._properties, keys, value)
1006+
else:
1007+
_helpers._del_sub_prop(self._properties, keys)
1008+
1009+
@property
1010+
def destination_table_friendly_name(self):
1011+
"""Union[str, None] name given to destination table.
1012+
1013+
See:
1014+
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.destinationTableProperties.friendlyName
1015+
"""
1016+
prop = self._get_sub_prop('destinationTableProperties')
1017+
if prop is not None:
1018+
return prop['friendlyName']
1019+
1020+
@destination_table_friendly_name.setter
1021+
def destination_table_friendly_name(self, value):
1022+
keys = [self._job_type, 'destinationTableProperties', 'friendlyName']
1023+
if value is not None:
1024+
_helpers._set_sub_prop(self._properties, keys, value)
1025+
else:
1026+
_helpers._del_sub_prop(self._properties, keys)
1027+
9371028
@property
9381029
def encoding(self):
9391030
"""google.cloud.bigquery.job.Encoding: The character encoding of the
@@ -981,7 +1072,7 @@ def max_bad_records(self):
9811072
See
9821073
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.maxBadRecords
9831074
"""
984-
return self._get_sub_prop('maxBadRecords')
1075+
return _helpers._int_or_none(self._get_sub_prop('maxBadRecords'))
9851076

9861077
@max_bad_records.setter
9871078
def max_bad_records(self, value):
@@ -1013,46 +1104,6 @@ def quote_character(self):
10131104
def quote_character(self, value):
10141105
self._set_sub_prop('quote', value)
10151106

1016-
@property
1017-
def skip_leading_rows(self):
1018-
"""int: Number of rows to skip when reading data (CSV only).
1019-
1020-
See
1021-
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.skipLeadingRows
1022-
"""
1023-
return _helpers._int_or_none(self._get_sub_prop('skipLeadingRows'))
1024-
1025-
@skip_leading_rows.setter
1026-
def skip_leading_rows(self, value):
1027-
self._set_sub_prop('skipLeadingRows', str(value))
1028-
1029-
@property
1030-
def source_format(self):
1031-
"""google.cloud.bigquery.job.SourceFormat: File format of the data.
1032-
1033-
See
1034-
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.sourceFormat
1035-
"""
1036-
return self._get_sub_prop('sourceFormat')
1037-
1038-
@source_format.setter
1039-
def source_format(self, value):
1040-
self._set_sub_prop('sourceFormat', value)
1041-
1042-
@property
1043-
def write_disposition(self):
1044-
"""google.cloud.bigquery.job.WriteDisposition: Action that occurs if
1045-
the destination table already exists.
1046-
1047-
See
1048-
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.writeDisposition
1049-
"""
1050-
return self._get_sub_prop('writeDisposition')
1051-
1052-
@write_disposition.setter
1053-
def write_disposition(self, value):
1054-
self._set_sub_prop('writeDisposition', value)
1055-
10561107
@property
10571108
def schema(self):
10581109
"""List[google.cloud.bigquery.schema.SchemaField]: Schema of the
@@ -1077,27 +1128,42 @@ def schema(self, value):
10771128
[field.to_api_repr() for field in value])
10781129

10791130
@property
1080-
def destination_encryption_configuration(self):
1081-
"""google.cloud.bigquery.table.EncryptionConfiguration: Custom
1082-
encryption configuration for the destination table.
1131+
def schema_update_options(self):
1132+
"""List[google.cloud.bigquery.job.SchemaUpdateOption]: Specifies
1133+
updates to the destination table schema to allow as a side effect of
1134+
the load job.
1135+
"""
1136+
return self._get_sub_prop('schemaUpdateOptions')
10831137

1084-
Custom encryption configuration (e.g., Cloud KMS keys) or ``None``
1085-
if using default encryption.
1138+
@schema_update_options.setter
1139+
def schema_update_options(self, values):
1140+
self._set_sub_prop('schemaUpdateOptions', values)
1141+
1142+
@property
1143+
def skip_leading_rows(self):
1144+
"""int: Number of rows to skip when reading data (CSV only).
10861145
10871146
See
1088-
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.destinationEncryptionConfiguration
1147+
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.skipLeadingRows
10891148
"""
1090-
prop = self._get_sub_prop('destinationEncryptionConfiguration')
1091-
if prop is not None:
1092-
prop = EncryptionConfiguration.from_api_repr(prop)
1093-
return prop
1149+
return _helpers._int_or_none(self._get_sub_prop('skipLeadingRows'))
10941150

1095-
@destination_encryption_configuration.setter
1096-
def destination_encryption_configuration(self, value):
1097-
api_repr = value
1098-
if value is not None:
1099-
api_repr = value.to_api_repr()
1100-
self._set_sub_prop('destinationEncryptionConfiguration', api_repr)
1151+
@skip_leading_rows.setter
1152+
def skip_leading_rows(self, value):
1153+
self._set_sub_prop('skipLeadingRows', str(value))
1154+
1155+
@property
1156+
def source_format(self):
1157+
"""google.cloud.bigquery.job.SourceFormat: File format of the data.
1158+
1159+
See
1160+
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.sourceFormat
1161+
"""
1162+
return self._get_sub_prop('sourceFormat')
1163+
1164+
@source_format.setter
1165+
def source_format(self, value):
1166+
self._set_sub_prop('sourceFormat', value)
11011167

11021168
@property
11031169
def time_partitioning(self):
@@ -1114,47 +1180,23 @@ def time_partitioning(self, value):
11141180
api_repr = value
11151181
if value is not None:
11161182
api_repr = value.to_api_repr()
1117-
self._set_sub_prop('timePartitioning', api_repr)
1118-
1119-
@property
1120-
def clustering_fields(self):
1121-
"""Union[List[str], None]: Fields defining clustering for the table
1122-
1123-
(Defaults to :data:`None`).
1124-
1125-
Clustering fields are immutable after table creation.
1126-
1127-
.. note::
1128-
1129-
As of 2018-06-29, clustering fields cannot be set on a table
1130-
which does not also have time partioning defined.
1131-
"""
1132-
prop = self._get_sub_prop('clustering')
1133-
if prop is not None:
1134-
return list(prop.get('fields', ()))
1135-
1136-
@clustering_fields.setter
1137-
def clustering_fields(self, value):
1138-
"""Union[List[str], None]: Fields defining clustering for the table
1139-
1140-
(Defaults to :data:`None`).
1141-
"""
1142-
if value is not None:
1143-
self._set_sub_prop('clustering', {'fields': value})
1183+
self._set_sub_prop('timePartitioning', api_repr)
11441184
else:
1145-
self._del_sub_prop('clustering')
1185+
self._del_sub_prop('timePartitioning')
11461186

11471187
@property
1148-
def schema_update_options(self):
1149-
"""List[google.cloud.bigquery.job.SchemaUpdateOption]: Specifies
1150-
updates to the destination table schema to allow as a side effect of
1151-
the load job.
1188+
def write_disposition(self):
1189+
"""google.cloud.bigquery.job.WriteDisposition: Action that occurs if
1190+
the destination table already exists.
1191+
1192+
See
1193+
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.writeDisposition
11521194
"""
1153-
return self._get_sub_prop('schemaUpdateOptions')
1195+
return self._get_sub_prop('writeDisposition')
11541196

1155-
@schema_update_options.setter
1156-
def schema_update_options(self, values):
1157-
self._set_sub_prop('schemaUpdateOptions', values)
1197+
@write_disposition.setter
1198+
def write_disposition(self, value):
1199+
self._set_sub_prop('writeDisposition', value)
11581200

11591201

11601202
class LoadJob(_AsyncJob):

bigquery/google/cloud/bigquery/table.py

+32
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,20 @@ def to_api_repr(self):
132132
"""
133133
return copy.deepcopy(self._properties)
134134

135+
def __eq__(self, other):
136+
if not isinstance(other, EncryptionConfiguration):
137+
return NotImplemented
138+
return self.kms_key_name == other.kms_key_name
139+
140+
def __ne__(self, other):
141+
return not self == other
142+
143+
def __hash__(self):
144+
return hash(self.kms_key_name)
145+
146+
def __repr__(self):
147+
return 'EncryptionConfiguration({})'.format(self.kms_key_name)
148+
135149

136150
class TableReference(object):
137151
"""TableReferences are pointers to tables.
@@ -1342,6 +1356,24 @@ def to_api_repr(self):
13421356
"""
13431357
return self._properties
13441358

1359+
def _key(self):
1360+
return tuple(sorted(self._properties.items()))
1361+
1362+
def __eq__(self, other):
1363+
if not isinstance(other, TimePartitioning):
1364+
return NotImplemented
1365+
return self._key() == other._key()
1366+
1367+
def __ne__(self, other):
1368+
return not self == other
1369+
1370+
def __hash__(self):
1371+
return hash(self._key())
1372+
1373+
def __repr__(self):
1374+
key_vals = ['{}={}'.format(key, val) for key, val in self._key()]
1375+
return 'TimePartitioning({})'.format(','.join(key_vals))
1376+
13451377

13461378
def _item_to_row(iterator, resource):
13471379
"""Convert a JSON row to the native object.

0 commit comments

Comments
 (0)