Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BigQuery: add destination table properties to 'LoadJobConfig'. #6202

Merged
merged 26 commits into from
Oct 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ac176a8
Add explicit test coverage for 'LoadJobConfig.allow_jagged_rows'.
tseaver Oct 12, 2018
7f050e9
Add explicit test coverage for 'LoadJobConfig.allow_quoted_newlines'.
tseaver Oct 12, 2018
5a0ab18
Add explicit test coverage for 'LoadJobConfig.autodetect'.
tseaver Oct 12, 2018
b37caba
Add explicit test coverage for 'LoadJobConfig.create_disposition'.
tseaver Oct 12, 2018
29f4e6b
Add explicit test coverage for 'LoadJobConfig.encoding'.
tseaver Oct 12, 2018
b6c37da
Add explicit test coverage for 'LoadJobConfig.field_delimiter'.
tseaver Oct 12, 2018
5e8cae2
Add explicit test coverage for 'LoadJobConfig.ignore_unknown_values'.
tseaver Oct 12, 2018
7a199fa
Add explicit test coverage for 'LoadJobConfig.max_bad_records'.
tseaver Oct 12, 2018
68fdf67
Add explicit test coverage for 'LoadJobConfig.null_marker'.
tseaver Oct 12, 2018
adf2710
Add explicit test coverage for 'LoadJobConfig.quote_character'.
tseaver Oct 12, 2018
51aade2
Add explicit test coverage for 'LoadJobConfig.skip_leading_rows'.
tseaver Oct 12, 2018
a345f58
Add explicit test coverage for 'LoadJobConfig.source_format'.
tseaver Oct 12, 2018
c739c10
Add explicit test coverage for 'LoadJobConfig.write_disposition'.
tseaver Oct 12, 2018
465aa61
Add/improve explicit test coverage for 'LoadJobConfig.schema'.
tseaver Oct 12, 2018
dbe59e7
Make EncryptionConfiguration instances comparable / hashable.
tseaver Oct 12, 2018
4d66817
Add/improve explicit test coverage for 'LoadJobConfig.destination_enc…
tseaver Oct 12, 2018
77e908d
Make 'TimePartitioning' comparable / hashable.
tseaver Oct 12, 2018
a131731
Add/improve explicit test coverage for 'LoadJobConfig.time_partitioni…
tseaver Oct 12, 2018
b945c3c
Fix 'LoadJobConfig.time_partitioning' setter w/ None.
tseaver Oct 12, 2018
268e334
Add/improve explicit test coverage for 'LoadJobConfig.clustering_fiel…
tseaver Oct 12, 2018
c191f2f
Add explicit test coverage for 'LoadJobConfig.schema_update_options'.
tseaver Oct 12, 2018
3fafd08
Reorder 'LoadJobConfig' properties alphabetically.
tseaver Oct 12, 2018
7c76fe3
Remove spurious test of superclass 'to_api_repr'/'from_api_repr' from…
tseaver Oct 12, 2018
8a1d4fa
Add 'LoadTableConfig.destination_table_description' property.
tseaver Oct 12, 2018
1240481
Add 'LoadTableConfig.destination_table_friendly_name' property.
tseaver Oct 12, 2018
2a920d1
Remove 'XXX' comments after resolving related question in review.
tseaver Oct 17, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 135 additions & 93 deletions bigquery/google/cloud/bigquery/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,34 @@ def autodetect(self):
def autodetect(self, value):
self._set_sub_prop('autodetect', value)

@property
def clustering_fields(self):
"""Union[List[str], None]: Fields defining clustering for the table

(Defaults to :data:`None`).

Clustering fields are immutable after table creation.

.. note::

As of 2018-06-29, clustering fields cannot be set on a table
which does not also have time partioning defined.
"""
prop = self._get_sub_prop('clustering')
if prop is not None:
return list(prop.get('fields', ()))

@clustering_fields.setter
def clustering_fields(self, value):
"""Union[List[str], None]: Fields defining clustering for the table

(Defaults to :data:`None`).
"""
if value is not None:
self._set_sub_prop('clustering', {'fields': value})
else:
self._del_sub_prop('clustering')

@property
def create_disposition(self):
"""google.cloud.bigquery.job.CreateDisposition: Specifies behavior
Expand All @@ -934,6 +962,69 @@ def create_disposition(self):
def create_disposition(self, value):
self._set_sub_prop('createDisposition', value)

@property
def destination_encryption_configuration(self):
"""google.cloud.bigquery.table.EncryptionConfiguration: Custom
encryption configuration for the destination table.

Custom encryption configuration (e.g., Cloud KMS keys) or ``None``
if using default encryption.

See
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.destinationEncryptionConfiguration
"""
prop = self._get_sub_prop('destinationEncryptionConfiguration')
if prop is not None:
prop = EncryptionConfiguration.from_api_repr(prop)
return prop

@destination_encryption_configuration.setter
def destination_encryption_configuration(self, value):
api_repr = value
if value is not None:
api_repr = value.to_api_repr()
self._set_sub_prop('destinationEncryptionConfiguration', api_repr)
else:
self._del_sub_prop('destinationEncryptionConfiguration')

@property
def destination_table_description(self):
"""Union[str, None] name given to destination table.

See:
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.destinationTableProperties.description
"""
prop = self._get_sub_prop('destinationTableProperties')
if prop is not None:
return prop['description']

@destination_table_description.setter
def destination_table_description(self, value):
keys = [self._job_type, 'destinationTableProperties', 'description']
if value is not None:
_helpers._set_sub_prop(self._properties, keys, value)
else:
_helpers._del_sub_prop(self._properties, keys)

@property
def destination_table_friendly_name(self):
"""Union[str, None] name given to destination table.

See:
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.destinationTableProperties.friendlyName
"""
prop = self._get_sub_prop('destinationTableProperties')
if prop is not None:
return prop['friendlyName']

@destination_table_friendly_name.setter
def destination_table_friendly_name(self, value):
keys = [self._job_type, 'destinationTableProperties', 'friendlyName']
if value is not None:
_helpers._set_sub_prop(self._properties, keys, value)
else:
_helpers._del_sub_prop(self._properties, keys)

@property
def encoding(self):
"""google.cloud.bigquery.job.Encoding: The character encoding of the
Expand Down Expand Up @@ -981,7 +1072,7 @@ def max_bad_records(self):
See
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.maxBadRecords
"""
return self._get_sub_prop('maxBadRecords')
return _helpers._int_or_none(self._get_sub_prop('maxBadRecords'))

@max_bad_records.setter
def max_bad_records(self, value):
Expand Down Expand Up @@ -1013,46 +1104,6 @@ def quote_character(self):
def quote_character(self, value):
self._set_sub_prop('quote', value)

@property
def skip_leading_rows(self):
"""int: Number of rows to skip when reading data (CSV only).

See
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.skipLeadingRows
"""
return _helpers._int_or_none(self._get_sub_prop('skipLeadingRows'))

@skip_leading_rows.setter
def skip_leading_rows(self, value):
self._set_sub_prop('skipLeadingRows', str(value))

@property
def source_format(self):
"""google.cloud.bigquery.job.SourceFormat: File format of the data.

See
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.sourceFormat
"""
return self._get_sub_prop('sourceFormat')

@source_format.setter
def source_format(self, value):
self._set_sub_prop('sourceFormat', value)

@property
def write_disposition(self):
"""google.cloud.bigquery.job.WriteDisposition: Action that occurs if
the destination table already exists.

See
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.writeDisposition
"""
return self._get_sub_prop('writeDisposition')

@write_disposition.setter
def write_disposition(self, value):
self._set_sub_prop('writeDisposition', value)

@property
def schema(self):
"""List[google.cloud.bigquery.schema.SchemaField]: Schema of the
Expand All @@ -1077,27 +1128,42 @@ def schema(self, value):
[field.to_api_repr() for field in value])

@property
def destination_encryption_configuration(self):
"""google.cloud.bigquery.table.EncryptionConfiguration: Custom
encryption configuration for the destination table.
def schema_update_options(self):
"""List[google.cloud.bigquery.job.SchemaUpdateOption]: Specifies
updates to the destination table schema to allow as a side effect of
the load job.
"""
return self._get_sub_prop('schemaUpdateOptions')

Custom encryption configuration (e.g., Cloud KMS keys) or ``None``
if using default encryption.
@schema_update_options.setter
def schema_update_options(self, values):
self._set_sub_prop('schemaUpdateOptions', values)

@property
def skip_leading_rows(self):
"""int: Number of rows to skip when reading data (CSV only).

See
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.destinationEncryptionConfiguration
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.skipLeadingRows
"""
prop = self._get_sub_prop('destinationEncryptionConfiguration')
if prop is not None:
prop = EncryptionConfiguration.from_api_repr(prop)
return prop
return _helpers._int_or_none(self._get_sub_prop('skipLeadingRows'))

@destination_encryption_configuration.setter
def destination_encryption_configuration(self, value):
api_repr = value
if value is not None:
api_repr = value.to_api_repr()
self._set_sub_prop('destinationEncryptionConfiguration', api_repr)
@skip_leading_rows.setter
def skip_leading_rows(self, value):
self._set_sub_prop('skipLeadingRows', str(value))

@property
def source_format(self):
"""google.cloud.bigquery.job.SourceFormat: File format of the data.

See
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.sourceFormat
"""
return self._get_sub_prop('sourceFormat')

@source_format.setter
def source_format(self, value):
self._set_sub_prop('sourceFormat', value)

@property
def time_partitioning(self):
Expand All @@ -1114,47 +1180,23 @@ def time_partitioning(self, value):
api_repr = value
if value is not None:
api_repr = value.to_api_repr()
self._set_sub_prop('timePartitioning', api_repr)

@property
def clustering_fields(self):
"""Union[List[str], None]: Fields defining clustering for the table

(Defaults to :data:`None`).

Clustering fields are immutable after table creation.

.. note::

As of 2018-06-29, clustering fields cannot be set on a table
which does not also have time partioning defined.
"""
prop = self._get_sub_prop('clustering')
if prop is not None:
return list(prop.get('fields', ()))

@clustering_fields.setter
def clustering_fields(self, value):
"""Union[List[str], None]: Fields defining clustering for the table

(Defaults to :data:`None`).
"""
if value is not None:
self._set_sub_prop('clustering', {'fields': value})
self._set_sub_prop('timePartitioning', api_repr)
else:
self._del_sub_prop('clustering')
self._del_sub_prop('timePartitioning')

@property
def schema_update_options(self):
"""List[google.cloud.bigquery.job.SchemaUpdateOption]: Specifies
updates to the destination table schema to allow as a side effect of
the load job.
def write_disposition(self):
"""google.cloud.bigquery.job.WriteDisposition: Action that occurs if
the destination table already exists.

See
https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.load.writeDisposition
"""
return self._get_sub_prop('schemaUpdateOptions')
return self._get_sub_prop('writeDisposition')

@schema_update_options.setter
def schema_update_options(self, values):
self._set_sub_prop('schemaUpdateOptions', values)
@write_disposition.setter
def write_disposition(self, value):
self._set_sub_prop('writeDisposition', value)


class LoadJob(_AsyncJob):
Expand Down
32 changes: 32 additions & 0 deletions bigquery/google/cloud/bigquery/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,20 @@ def to_api_repr(self):
"""
return copy.deepcopy(self._properties)

def __eq__(self, other):
if not isinstance(other, EncryptionConfiguration):
return NotImplemented
return self.kms_key_name == other.kms_key_name

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

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

def __repr__(self):
return 'EncryptionConfiguration({})'.format(self.kms_key_name)


class TableReference(object):
"""TableReferences are pointers to tables.
Expand Down Expand Up @@ -1342,6 +1356,24 @@ def to_api_repr(self):
"""
return self._properties

def _key(self):
return tuple(sorted(self._properties.items()))

def __eq__(self, other):
if not isinstance(other, TimePartitioning):
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):
key_vals = ['{}={}'.format(key, val) for key, val in self._key()]
return 'TimePartitioning({})'.format(','.join(key_vals))


def _item_to_row(iterator, resource):
"""Convert a JSON row to the native object.
Expand Down
Loading