blob: 3ad76bbcc1d497a1c6d16ac30942ec410ad1b6ce [file]
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Compatibility shims and legacy client emulation for BigQuery.
This module contains temporary compatibility models, monkey patches, and
helpers designed to ease migration away from the deprecated apitools BigQuery
client to modern ``google-cloud-bigquery``.
.. warning::
Importing this module is disabled by default and requires explicit opt-in.
To enable these compatibility shims, set the environment variable
``BEAM_USE_BIGQUERY_COMPAT_SHIMS=1``.
.. note::
This module is intended to be removed in a future Beam release once the
apitools client dependency is completely removed.
**Future Removal Guidance**:
* Compatibility models (e.g. ``_TableReferenceCompat``,
``_DatasetReferenceCompat``, ``_TableSchemaCompat``) and monkey-patches
will be dropped. Code using ``TableReference``, ``DatasetReference``,
etc. should import directly from ``google.cloud.bigquery``.
* Input normalization helpers (``_to_gcp_table_ref``,
``_to_gcp_dataset_ref``, ``_to_gcp_schema``, ``_extract_dict_labels``,
``_to_table_schema``) that convert string specs or dicts to modern
``google.cloud.bigquery`` instances are actively used across pipeline
code paths and should be preserved in ``bigquery_tools.py`` when this
compat module is excised.
"""
# pytype: skip-file
import logging
import os
import warnings
from apache_beam.utils.annotations import BeamDeprecationWarning
_LOGGER = logging.getLogger(__name__)
BIGQUERY_COMPAT_ENV_VAR = "BEAM_USE_BIGQUERY_COMPAT_SHIMS"
def _check_compat_opt_in():
"""Checks whether BigQuery compatibility shims are enabled via environment variable.
Raises:
ImportError: If the environment variable is not set to an enabled value.
"""
val = os.environ.get(BIGQUERY_COMPAT_ENV_VAR, "").strip().lower()
if val not in ("1", "true", "yes", "enabled", "on"):
raise ImportError(
f"BigQuery compatibility shims in {__name__} are disabled by default "
f"and require explicit opt-in. Set the environment variable "
f"{BIGQUERY_COMPAT_ENV_VAR}=1 to enable them.")
warnings.warn(
f"BigQuery compatibility shims in {__name__} are deprecated and will be "
"removed in a future release of Apache Beam. Please migrate to using "
"google-cloud-bigquery directly.",
BeamDeprecationWarning,
stacklevel=2)
_check_compat_opt_in()
try:
from google.cloud import bigquery as gcp_bigquery
from google.cloud.bigquery import job as gcp_job
except ImportError:
gcp_bigquery = None
gcp_job = None
try:
from apache_beam.io.gcp.internal.clients import bigquery as apitools_bigquery
except ImportError:
apitools_bigquery = None
try:
from apitools.base.protorpclite import messages as _protorpclite_messages
except ImportError:
_protorpclite_messages = None
_LOGGER = logging.getLogger(__name__)
# -----------------------------------------------------------------------------
# Compatibility Models for TableReference, DatasetReference, Schema, and Jobs.
#
# These classes and monkey patches bridge between legacy apitools structures
# and modern google.cloud.bigquery objects, providing camelCase attribute access
# (e.g. projectId, datasetId, tableId, tableReference) for backwards
# compatibility across pipelines, transforms, and test suites.
# -----------------------------------------------------------------------------
class _DatasetReferenceCompat(object):
"""Compatibility model for BigQuery DatasetReference when google-cloud-bigquery is unavailable.
Supports both camelCase (projectId, datasetId) and snake_case (project, dataset_id, project_id).
"""
def __init__(
self,
project=None,
dataset_id=None,
projectId=None,
datasetId=None,
project_id=None):
p = (
projectId if projectId is not None else
(project_id if project_id is not None else project))
d = datasetId if datasetId is not None else dataset_id
self._project = p or ""
self._dataset_id = d or ""
@classmethod
def from_string(cls, dataset_ref, default_project=None):
last_sep = max(dataset_ref.rfind("."), dataset_ref.rfind(":"))
if last_sep != -1:
p = dataset_ref[:last_sep]
d = dataset_ref[last_sep + 1:]
else:
p = default_project or "default"
d = dataset_ref
return cls(project=p, dataset_id=d)
@property
def projectId(self):
return self._project
@projectId.setter
def projectId(self, val):
self._project = val
@property
def project(self):
return self._project
@project.setter
def project(self, val):
self._project = val
@property
def project_id(self):
return self._project
@project_id.setter
def project_id(self, val):
self._project = val
@property
def datasetId(self):
return self._dataset_id
@datasetId.setter
def datasetId(self, val):
self._dataset_id = val
@property
def dataset_id(self):
return self._dataset_id
@dataset_id.setter
def dataset_id(self, val):
self._dataset_id = val
def __repr__(self):
return f"DatasetReference('{self.project}', '{self.dataset_id}')"
def __eq__(self, other):
if other is None:
return False
if not hasattr(other, "project") and not hasattr(other, "projectId"):
return NotImplemented
other_p = getattr(other, "projectId", None) or getattr(
other, "project", None)
other_d = getattr(other, "datasetId", None) or getattr(
other, "dataset_id", None)
return (self.projectId, self.datasetId) == (other_p, other_d)
def __hash__(self):
return hash((self.projectId, self.datasetId))
class _TableReferenceCompat(object):
"""Compatibility model for BigQuery TableReference when google-cloud-bigquery is unavailable.
Supports both camelCase (projectId, datasetId, tableId) and snake_case
(project, dataset_id, table_id, project_id).
"""
def __init__(
self,
dataset_ref=None,
table_id=None,
projectId=None,
datasetId=None,
tableId=None,
project=None,
dataset_id=None,
project_id=None):
p = (
projectId if projectId is not None else
(project_id if project_id is not None else project))
d = datasetId if datasetId is not None else dataset_id
t = tableId if tableId is not None else table_id
if p is not None or d is not None or t is not None:
self._project = p
self._dataset_id = d
self._table_id = t
elif dataset_ref is not None:
self._project = getattr(dataset_ref, "projectId", None) or getattr(
dataset_ref, "project", None)
self._dataset_id = getattr(dataset_ref, "datasetId", None) or getattr(
dataset_ref, "dataset_id", None)
self._table_id = table_id or ""
else:
self._project = None
self._dataset_id = None
self._table_id = None
@classmethod
def from_string(cls, table_ref, default_project=None):
from apache_beam.io.gcp.bigquery_tools import parse_table_reference
parsed = parse_table_reference(table_ref, project=default_project)
return cls(
projectId=parsed.projectId or default_project,
datasetId=parsed.datasetId,
tableId=parsed.tableId)
@property
def projectId(self):
return self._project
@projectId.setter
def projectId(self, val):
self._project = val
@property
def project(self):
return self._project
@project.setter
def project(self, val):
self._project = val
@property
def project_id(self):
return self._project
@project_id.setter
def project_id(self, val):
self._project = val
@property
def datasetId(self):
return self._dataset_id
@datasetId.setter
def datasetId(self, val):
self._dataset_id = val
@property
def dataset_id(self):
return self._dataset_id
@dataset_id.setter
def dataset_id(self, val):
self._dataset_id = val
@property
def tableId(self):
return self._table_id
@tableId.setter
def tableId(self, val):
self._table_id = val
@property
def table_id(self):
return self._table_id
@table_id.setter
def table_id(self, val):
self._table_id = val
@property
def dataset_reference(self):
return _DatasetReferenceCompat(
projectId=self.projectId, datasetId=self.datasetId)
@property
def datasetReference(self):
return self.dataset_reference
def __repr__(self):
return (
f"TableReference(projectId='{self.projectId}', "
f"datasetId='{self.datasetId}', tableId='{self.tableId}')")
def __eq__(self, other):
if other is None:
return False
if not hasattr(other, "tableId") and not hasattr(other, "table_id"):
return NotImplemented
other_p = getattr(other, "projectId", None) or getattr(
other, "project", None)
other_d = getattr(other, "datasetId", None) or getattr(
other, "dataset_id", None)
other_t = getattr(other, "tableId", None) or getattr(
other, "table_id", None)
return (self.projectId, self.datasetId,
self.tableId) == (other_p, other_d, other_t)
def __hash__(self):
return hash((self.projectId, self.datasetId, self.tableId))
class _TableFieldSchemaCompat(object):
def __init__(
self,
name="",
type="STRING",
mode="NULLABLE",
description=None,
fields=(),
field_type=None,
**kwargs):
ft = type or field_type or "STRING"
self.name = name
self.field_type = ft
self.mode = mode or "NULLABLE"
self.description = description
self.fields = list(fields) if fields else []
@property
def type(self):
return self.field_type
@type.setter
def type(self, val):
self.field_type = val
class _TableSchemaCompat(list):
def __init__(self, fields=None):
if fields:
super().__init__(fields)
else:
super().__init__()
@property
def fields(self):
return self
@fields.setter
def fields(self, value):
self.clear()
if value:
self.extend(value)
class _TableCellCompat(object):
def __init__(self, v=None):
self.v = v
class _TableRowCompat(object):
def __init__(self, f=None):
self.f = f or []
# Check if an underlying type is available because the internal client silently
# passes on an import error if any dependencies (mainly apitools) are missing.
if apitools_bigquery is not None and hasattr(apitools_bigquery,
"TableReference"):
TableReference = apitools_bigquery.TableReference
DatasetReference = apitools_bigquery.DatasetReference
TableFieldSchema = apitools_bigquery.TableFieldSchema
TableSchema = apitools_bigquery.TableSchema
TableRow = apitools_bigquery.TableRow
TableCell = apitools_bigquery.TableCell
Table = apitools_bigquery.Table
Dataset = apitools_bigquery.Dataset
Job = apitools_bigquery.Job
JobConfiguration = apitools_bigquery.JobConfiguration
JobConfigurationLoad = apitools_bigquery.JobConfigurationLoad
JobConfigurationQuery = apitools_bigquery.JobConfigurationQuery
JobConfigurationExtract = apitools_bigquery.JobConfigurationExtract
JobConfigurationTableCopy = apitools_bigquery.JobConfigurationTableCopy
JobStatistics = apitools_bigquery.JobStatistics
JobStatistics2 = apitools_bigquery.JobStatistics2
JobStatistics4 = apitools_bigquery.JobStatistics4
ErrorProto = apitools_bigquery.ErrorProto
else:
TableReference = _TableReferenceCompat
DatasetReference = _DatasetReferenceCompat
TableFieldSchema = _TableFieldSchemaCompat
TableSchema = _TableSchemaCompat
TableRow = _TableRowCompat
TableCell = _TableCellCompat
Table = None
Dataset = None
Job = None
JobConfiguration = None
JobConfigurationLoad = None
JobConfigurationQuery = None
JobConfigurationExtract = None
JobConfigurationTableCopy = None
JobStatistics = None
JobStatistics2 = None
JobStatistics4 = None
ErrorProto = None
class JobReference(object):
"""Compatibility model for BigQuery JobReference.
Supports both camelCase (jobId, projectId) and snake_case (job_id, project, project_id)
initialization and attribute access.
"""
def __init__(
self,
jobId=None,
projectId=None,
location=None,
job_id=None,
project=None,
project_id=None):
self.jobId = jobId if jobId is not None else job_id
self.projectId = (
projectId if projectId is not None else
(project if project is not None else project_id))
self.location = location
@property
def job_id(self):
return self.jobId
@job_id.setter
def job_id(self, val):
self.jobId = val
@property
def project(self):
return self.projectId
@project.setter
def project(self, val):
self.projectId = val
@property
def project_id(self):
return self.projectId
@project_id.setter
def project_id(self, val):
self.projectId = val
def __eq__(self, other):
if other is None:
return False
if isinstance(other, JobReference):
return (
self.jobId == other.jobId and self.projectId == other.projectId and
self.location == other.location)
if apitools_bigquery and hasattr(apitools_bigquery,
"JobReference") and isinstance(
other, apitools_bigquery.JobReference):
return (
self.jobId == getattr(other, "jobId", None) and
self.projectId == getattr(other, "projectId", None) and
self.location == getattr(other, "location", None))
return NotImplemented
def __hash__(self):
return hash((self.jobId, self.projectId, self.location))
def __repr__(self):
return (
f"JobReference(jobId={self.jobId!r}, "
f"projectId={self.projectId!r}, "
f"location={self.location!r})")
_PATCHED_PROTORPCLITE = False
def _patch_protorpclite_equality():
global _PATCHED_PROTORPCLITE
if _PATCHED_PROTORPCLITE:
return
if _protorpclite_messages is not None and hasattr(_protorpclite_messages,
"Message"):
_PATCHED_PROTORPCLITE = True
_orig_message_eq = _protorpclite_messages.Message.__eq__
def _message_compat_eq(self, other):
if isinstance(other, JobReference) and apitools_bigquery and hasattr(
apitools_bigquery, "JobReference") and isinstance(
self, apitools_bigquery.JobReference):
return (
getattr(self, "jobId", None) == other.jobId and
getattr(self, "projectId", None) == other.projectId and
getattr(self, "location", None) == other.location)
if isinstance(other, TableReference) and apitools_bigquery and hasattr(
apitools_bigquery, "TableReference") and isinstance(
self, apitools_bigquery.TableReference):
return (
getattr(self, "projectId", None) == other.projectId and
getattr(self, "datasetId", None) == other.datasetId and
getattr(self, "tableId", None) == other.tableId)
if isinstance(other, DatasetReference) and apitools_bigquery and hasattr(
apitools_bigquery, "DatasetReference") and isinstance(
self, apitools_bigquery.DatasetReference):
return (
getattr(self, "projectId", None) == other.projectId and
getattr(self, "datasetId", None) == other.datasetId)
return _orig_message_eq(self, other)
_protorpclite_messages.Message.__eq__ = _message_compat_eq
def _set_table_ref_prop(ref, prop, val):
if hasattr(ref, "_properties") and isinstance(ref._properties, dict):
ref._properties[prop] = val
if prop == "projectId":
setattr(ref, "_project", val)
elif prop == "datasetId":
setattr(ref, "_dataset_id", val)
elif prop == "tableId":
setattr(ref, "_table_id", val)
def _extract_field(obj, *field_names):
"""Extracts the first matching non-None field from a dict or object."""
if obj is None:
return None
for name in field_names:
if isinstance(obj, dict):
if name in obj and obj[name] is not None:
return obj[name]
elif hasattr(obj, name):
val = getattr(obj, name)
if val is not None and not hasattr(val, "_mock_methods"):
return val
return None
class _ClusteringCompat(dict):
"""Compatibility model for Table clustering.
Supports dict access, json serialization, and attribute access to fields.
Note: Mutations to .fields in-place do not re-sync back to the parent table.
"""
def __init__(self, fields=None):
fields_list = list(fields) if fields else []
super().__init__(fields=fields_list)
@property
def fields(self):
return self.get("fields", [])
@fields.setter
def fields(self, val):
self["fields"] = list(val) if val else []
def _to_gcp_time_partitioning(tp):
"""Converts a dict, apitools TimePartitioning, string, or google.cloud.bigquery.TimePartitioning."""
if tp is None:
return None
if hasattr(tp, "_mock_methods") or hasattr(tp, "_mock_children"):
return tp
if gcp_bigquery is not None and isinstance(
tp, getattr(gcp_bigquery, "TimePartitioning", ())):
return tp
if isinstance(tp, str):
if gcp_bigquery is not None and hasattr(gcp_bigquery, "TimePartitioning"):
return gcp_bigquery.TimePartitioning(type_=tp)
return tp
tp_field = _extract_field(tp, "field")
tp_type = _extract_field(tp, "type", "type_")
tp_exp = _extract_field(tp, "expirationMs", "expiration_ms")
tp_req = _extract_field(
tp, "requirePartitionFilter", "require_partition_filter")
try:
exp_ms = int(tp_exp) if tp_exp is not None else None
except (ValueError, TypeError):
exp_ms = None
if gcp_bigquery is not None and hasattr(gcp_bigquery, "TimePartitioning"):
return gcp_bigquery.TimePartitioning(
type_=tp_type,
field=tp_field,
expiration_ms=exp_ms,
require_partition_filter=tp_req)
return tp
def _to_gcp_range_partitioning(rp):
"""Converts a dict, apitools RangePartitioning, or google.cloud.bigquery.RangePartitioning."""
if rp is None:
return None
if hasattr(rp, "_mock_methods") or hasattr(rp, "_mock_children"):
return rp
if gcp_bigquery is not None and isinstance(
rp, getattr(gcp_bigquery, "RangePartitioning", ())):
return rp
rp_field = _extract_field(rp, "field")
rp_range = _extract_field(rp, "range", "range_")
if rp_range is not None and gcp_bigquery is not None and hasattr(
gcp_bigquery, "PartitionRange"):
if not isinstance(rp_range, getattr(gcp_bigquery, "PartitionRange", ())):
start = _extract_field(rp_range, "start")
end = _extract_field(rp_range, "end")
interval = _extract_field(rp_range, "interval")
try:
start = int(start) if start is not None else None
except (ValueError, TypeError):
start = None
try:
end = int(end) if end is not None else None
except (ValueError, TypeError):
end = None
try:
interval = int(interval) if interval is not None else None
except (ValueError, TypeError):
interval = None
rp_range = gcp_bigquery.PartitionRange(
start=start, end=end, interval=interval)
if gcp_bigquery is not None and hasattr(gcp_bigquery, "RangePartitioning"):
return gcp_bigquery.RangePartitioning(field=rp_field, range_=rp_range)
return rp
def _to_gcp_clustering_fields(clustering):
"""Extracts clustering field list from list, dict, or apitools Clustering."""
if clustering is None:
return None
if hasattr(clustering, "_mock_methods") or hasattr(clustering,
"_mock_children"):
return clustering
if isinstance(clustering, (list, tuple)):
return list(clustering)
fields = _extract_field(clustering, "fields")
if isinstance(fields, (list, tuple)):
return list(fields)
return None
# -----------------------------------------------------------------------------
# Compatibility Monkey-Patching for google.cloud.bigquery Classes
# -----------------------------------------------------------------------------
_PATCHED_GCP_BIGQUERY = False
def _patch_gcp_bigquery():
global _PATCHED_GCP_BIGQUERY
if not gcp_bigquery or _PATCHED_GCP_BIGQUERY:
return
_PATCHED_GCP_BIGQUERY = True
if not hasattr(gcp_bigquery.TableReference, "projectId"):
gcp_bigquery.TableReference.projectId = property(
lambda self: self.project,
lambda self, val: _set_table_ref_prop(self, "projectId", val))
gcp_bigquery.TableReference.datasetId = property(
lambda self: self.dataset_id,
lambda self, val: _set_table_ref_prop(self, "datasetId", val))
gcp_bigquery.TableReference.tableId = property(
lambda self: self.table_id,
lambda self, val: _set_table_ref_prop(self, "tableId", val))
if not hasattr(gcp_bigquery.DatasetReference, "projectId"):
gcp_bigquery.DatasetReference.projectId = property(
lambda self: self.project,
lambda self, val: setattr(self, "_project", val))
gcp_bigquery.DatasetReference.datasetId = property(
lambda self: self.dataset_id,
lambda self, val: setattr(self, "_dataset_id", val))
if not hasattr(gcp_bigquery.SchemaField, "type"):
gcp_bigquery.SchemaField.type = property(
lambda self: self.field_type,
lambda self, val: setattr(self, "_field_type", val))
if not hasattr(gcp_bigquery.Table, "tableReference"):
gcp_bigquery.Table.tableReference = property(lambda self: self.reference)
gcp_bigquery.Table.numRows = property(lambda self: self.num_rows)
gcp_bigquery.Table.numBytes = property(lambda self: self.num_bytes)
gcp_bigquery.Table.timePartitioning = property(
lambda self: self.time_partitioning, lambda self, val: setattr(
self, "time_partitioning", _to_gcp_time_partitioning(val)))
gcp_bigquery.Table.rangePartitioning = property(
lambda self: self.range_partitioning, lambda self, val: setattr(
self, "range_partitioning", _to_gcp_range_partitioning(val)))
if hasattr(gcp_bigquery.Table, "time_partitioning"):
orig_tp_setter = gcp_bigquery.Table.time_partitioning.fset
if orig_tp_setter is not None:
gcp_bigquery.Table.time_partitioning = property(
gcp_bigquery.Table.time_partitioning.fget,
lambda self, val: orig_tp_setter(
self, _to_gcp_time_partitioning(val)))
if hasattr(gcp_bigquery.Table, "range_partitioning"):
orig_rp_setter = gcp_bigquery.Table.range_partitioning.fset
if orig_rp_setter is not None:
gcp_bigquery.Table.range_partitioning = property(
gcp_bigquery.Table.range_partitioning.fget,
lambda self, val: orig_rp_setter(
self, _to_gcp_range_partitioning(val)))
if not hasattr(gcp_bigquery.Table, "clustering"):
gcp_bigquery.Table.clustering = property(
lambda self: _ClusteringCompat(self.clustering_fields)
if self.clustering_fields else None, lambda self, val: setattr(
self, "clustering_fields", _to_gcp_clustering_fields(val)))
if hasattr(gcp_bigquery.Table, "clustering_fields"):
orig_cf_setter = gcp_bigquery.Table.clustering_fields.fset
if orig_cf_setter is not None:
gcp_bigquery.Table.clustering_fields = property(
gcp_bigquery.Table.clustering_fields.fget,
lambda self, val: orig_cf_setter(
self, _to_gcp_clustering_fields(val)))
if hasattr(gcp_bigquery, "TimePartitioning"):
if not hasattr(gcp_bigquery.TimePartitioning, "type"):
gcp_bigquery.TimePartitioning.type = property(
lambda self: self.type_,
lambda self, val: setattr(self, "type_", val))
if not hasattr(gcp_bigquery.TimePartitioning, "expirationMs"):
gcp_bigquery.TimePartitioning.expirationMs = property(
lambda self: self.expiration_ms,
lambda self, val: setattr(self, "expiration_ms", val))
if not hasattr(gcp_bigquery.TimePartitioning, "requirePartitionFilter"):
gcp_bigquery.TimePartitioning.requirePartitionFilter = property(
lambda self: self.require_partition_filter,
lambda self, val: setattr(self, "require_partition_filter", val))
if hasattr(gcp_bigquery, "RangePartitioning"):
if not hasattr(gcp_bigquery.RangePartitioning, "range"):
gcp_bigquery.RangePartitioning.range = property(
lambda self: self.range_,
lambda self, val: setattr(self, "range_", val))
if not hasattr(gcp_bigquery.Dataset, "datasetReference"):
gcp_bigquery.Dataset.datasetReference = property(
lambda self: self.reference)
gcp_bigquery.Dataset.defaultTableExpirationMs = property(
lambda self: self.default_table_expiration_ms,
lambda self, val: setattr(self, "default_table_expiration_ms", val))
if hasattr(gcp_bigquery, "LoadJobConfig"):
if not hasattr(gcp_bigquery.LoadJobConfig, "schemaUpdateOptions"):
gcp_bigquery.LoadJobConfig.schemaUpdateOptions = property(
lambda self: self.schema_update_options,
lambda self, val: setattr(self, "schema_update_options", val))
if not hasattr(gcp_bigquery.LoadJobConfig, "ignoreUnknownValues"):
gcp_bigquery.LoadJobConfig.ignoreUnknownValues = property(
lambda self: self.ignore_unknown_values,
lambda self, val: setattr(self, "ignore_unknown_values", val))
if not hasattr(gcp_bigquery.LoadJobConfig, "maxBadRecords"):
gcp_bigquery.LoadJobConfig.maxBadRecords = property(
lambda self: self.max_bad_records,
lambda self, val: setattr(self, "max_bad_records", val))
if not hasattr(gcp_bigquery.LoadJobConfig, "nullMarker"):
gcp_bigquery.LoadJobConfig.nullMarker = property(
lambda self: self.null_marker,
lambda self, val: setattr(self, "null_marker", val))
if not hasattr(gcp_bigquery.LoadJobConfig, "fieldDelimiter"):
gcp_bigquery.LoadJobConfig.fieldDelimiter = property(
lambda self: self.field_delimiter,
lambda self, val: setattr(self, "field_delimiter", val))
if not hasattr(gcp_bigquery.LoadJobConfig, "skipLeadingRows"):
gcp_bigquery.LoadJobConfig.skipLeadingRows = property(
lambda self: self.skip_leading_rows,
lambda self, val: setattr(self, "skip_leading_rows", val))
if not hasattr(gcp_bigquery.LoadJobConfig, "allowJaggedRows"):
gcp_bigquery.LoadJobConfig.allowJaggedRows = property(
lambda self: self.allow_jagged_rows,
lambda self, val: setattr(self, "allow_jagged_rows", val))
if not hasattr(gcp_bigquery.LoadJobConfig, "allowQuotedNewlines"):
gcp_bigquery.LoadJobConfig.allowQuotedNewlines = property(
lambda self: self.allow_quoted_newlines,
lambda self, val: setattr(self, "allow_quoted_newlines", val))
if not hasattr(gcp_bigquery.LoadJobConfig, "decimalTargetTypes"):
gcp_bigquery.LoadJobConfig.decimalTargetTypes = property(
lambda self: self.decimal_target_types,
lambda self, val: setattr(self, "decimal_target_types", val))
if not hasattr(gcp_bigquery.LoadJobConfig, "useAvroLogicalTypes"):
gcp_bigquery.LoadJobConfig.useAvroLogicalTypes = property(
lambda self: self.use_avro_logical_types,
lambda self, val: setattr(self, "use_avro_logical_types", val))
if hasattr(gcp_bigquery, "QueryJobConfig"):
if not hasattr(gcp_bigquery.QueryJobConfig, "schemaUpdateOptions"):
gcp_bigquery.QueryJobConfig.schemaUpdateOptions = property(
lambda self: self.schema_update_options,
lambda self, val: setattr(self, "schema_update_options", val))
if not hasattr(gcp_bigquery.QueryJobConfig, "useLegacySql"):
gcp_bigquery.QueryJobConfig.useLegacySql = property(
lambda self: self.use_legacy_sql,
lambda self, val: setattr(self, "use_legacy_sql", val))
if not hasattr(gcp_bigquery.QueryJobConfig, "flattenResults"):
gcp_bigquery.QueryJobConfig.flattenResults = property(
lambda self: self.flatten_results,
lambda self, val: setattr(self, "flatten_results", val))
if not hasattr(gcp_bigquery.QueryJobConfig, "allowLargeResults"):
gcp_bigquery.QueryJobConfig.allowLargeResults = property(
lambda self: self.allow_large_results,
lambda self, val: setattr(self, "allow_large_results", val))
if not hasattr(gcp_bigquery.QueryJobConfig, "maximumBytesBilled"):
gcp_bigquery.QueryJobConfig.maximumBytesBilled = property(
lambda self: self.maximum_bytes_billed,
lambda self, val: setattr(self, "maximum_bytes_billed", val))
if hasattr(gcp_bigquery, "Table") and hasattr(gcp_bigquery.Table, "labels"):
_orig_tbl_labels_setter = gcp_bigquery.Table.labels.fset
if _orig_tbl_labels_setter:
def _safe_tbl_labels_setter(self, value):
if value is None:
value = {}
elif not isinstance(value, dict) and hasattr(value,
"additionalProperties"):
from apitools.base.py import encoding
value = encoding.MessageToDict(value)
_orig_tbl_labels_setter(self, value)
gcp_bigquery.Table.labels = gcp_bigquery.Table.labels.setter(
_safe_tbl_labels_setter)
if hasattr(gcp_bigquery, "Dataset") and hasattr(gcp_bigquery.Dataset,
"labels"):
_orig_ds_labels_setter = gcp_bigquery.Dataset.labels.fset
if _orig_ds_labels_setter:
def _safe_ds_labels_setter(self, value):
if value is None:
value = {}
elif not isinstance(value, dict) and hasattr(value,
"additionalProperties"):
from apitools.base.py import encoding
value = encoding.MessageToDict(value)
_orig_ds_labels_setter(self, value)
gcp_bigquery.Dataset.labels = gcp_bigquery.Dataset.labels.setter(
_safe_ds_labels_setter)
try:
from google.cloud.bigquery.job.base import _JobConfig as _GcpJobConfig
if hasattr(_GcpJobConfig, "labels") and hasattr(_GcpJobConfig.labels,
"fset"):
_orig_job_labels_setter = _GcpJobConfig.labels.fset
if _orig_job_labels_setter:
def _safe_job_labels_setter(self, value):
if value is None:
value = {}
elif not isinstance(value, dict) and hasattr(value,
"additionalProperties"):
from apitools.base.py import encoding
value = encoding.MessageToDict(value)
_orig_job_labels_setter(self, value)
_GcpJobConfig.labels = _GcpJobConfig.labels.setter(
_safe_job_labels_setter)
except ImportError:
pass
if hasattr(gcp_job,
"_AsyncJob") and not hasattr(gcp_job._AsyncJob, "jobReference"):
gcp_job._AsyncJob.jobReference = property(
lambda self: JobReference(
job_id=self.job_id, project=self.project, location=self.location))
gcp_job._AsyncJob.status = property(lambda self: _JobStatusCompat(self))
gcp_job._AsyncJob.statistics = property(lambda self: _JobStatsCompat(self))
if not hasattr(gcp_bigquery.Client, "tables"):
gcp_bigquery.Client.tables = property(
lambda self: _ClientTablesCompat(self))
if not hasattr(gcp_bigquery.Client, "datasets"):
gcp_bigquery.Client.datasets = property(
lambda self: _ClientDatasetsCompat(self))
if not hasattr(gcp_bigquery.Client, "jobs"):
gcp_bigquery.Client.jobs = property(lambda self: _ClientJobsCompat(self))
# -----------------------------------------------------------------------------
# Compatibility Helpers
# -----------------------------------------------------------------------------
def _to_json_compatible(obj):
"""Converts an object or nested structure to JSON/API-compatible dicts/types."""
if obj is None:
return None
if isinstance(obj, (str, int, float, bool)):
return obj
if isinstance(obj, (list, tuple, set)):
return [_to_json_compatible(item) for item in obj]
if isinstance(obj, dict):
return {k: _to_json_compatible(v) for k, v in obj.items()}
if hasattr(obj, "to_api_repr") and callable(obj.to_api_repr):
return obj.to_api_repr()
if _protorpclite_messages is not None and hasattr(
_protorpclite_messages, "Message") and isinstance(
obj, _protorpclite_messages.Message):
try:
from apitools.base.py import encoding
return encoding.MessageToDict(obj)
except Exception:
pass
return obj
def _extract_dict_labels(labels):
"""Converts labels to a non-empty dictionary or returns None."""
if not labels:
return None
labels = _to_json_compatible(labels)
if isinstance(labels, dict) and labels:
return labels
return None
def _to_gcp_table_ref(table_ref, default_project=None):
"""Converts a TableReference or string into a google.cloud.bigquery.TableReference."""
if table_ref is None:
return None
if gcp_bigquery is not None and isinstance(
table_ref, getattr(gcp_bigquery, "TableReference", ())):
return table_ref
if isinstance(table_ref, str):
from apache_beam.io.gcp.bigquery_tools import parse_table_reference
table_ref = parse_table_reference(table_ref, project=default_project)
proj = getattr(table_ref, "projectId", None) or getattr(
table_ref, "project", None) or getattr(
table_ref, "project_id", None) or default_project or "default"
dataset_id = getattr(table_ref, "datasetId", None) or getattr(
table_ref, "dataset_id", None) or getattr(table_ref, "dataset", None)
table_id = getattr(table_ref, "tableId", None) or getattr(
table_ref, "table_id", None) or getattr(table_ref, "table", None)
if dataset_id and table_id:
if gcp_bigquery is not None and hasattr(
gcp_bigquery, "TableReference") and hasattr(gcp_bigquery,
"DatasetReference"):
return gcp_bigquery.TableReference(
gcp_bigquery.DatasetReference(proj, dataset_id), table_id)
return _TableReferenceCompat(
projectId=proj, datasetId=dataset_id, tableId=table_id)
return table_ref
def _to_gcp_dataset_ref(dataset_ref, project=None):
"""Converts a DatasetReference or string into a google.cloud.bigquery.DatasetReference."""
if dataset_ref is None:
return None
if gcp_bigquery is not None and isinstance(
dataset_ref, getattr(gcp_bigquery, "DatasetReference", ())):
return dataset_ref
if isinstance(dataset_ref, str):
last_sep = max(dataset_ref.rfind("."), dataset_ref.rfind(":"))
if last_sep != -1:
proj = dataset_ref[:last_sep]
ds_id = dataset_ref[last_sep + 1:]
if gcp_bigquery is not None and hasattr(gcp_bigquery, "DatasetReference"):
return gcp_bigquery.DatasetReference(proj, ds_id)
return _DatasetReferenceCompat(projectId=proj, datasetId=ds_id)
proj = project or "default"
if gcp_bigquery is not None and hasattr(gcp_bigquery, "DatasetReference"):
return gcp_bigquery.DatasetReference(proj, dataset_ref)
return _DatasetReferenceCompat(projectId=proj, datasetId=dataset_ref)
if hasattr(dataset_ref, "projectId") or hasattr(dataset_ref, "project"):
proj = getattr(dataset_ref, "projectId", None) or getattr(
dataset_ref, "project", None) or getattr(
dataset_ref, "project_id", None) or project or "default"
ds_id = getattr(dataset_ref, "datasetId", None) or getattr(
dataset_ref, "dataset_id", None)
if gcp_bigquery is not None and hasattr(gcp_bigquery, "DatasetReference"):
return gcp_bigquery.DatasetReference(proj, ds_id)
return _DatasetReferenceCompat(projectId=proj, datasetId=ds_id)
return dataset_ref
def _table_field_to_dict(field):
"""Create a dictionary representation of a table field for serialization."""
if isinstance(field, dict):
return field
result = {}
result['name'] = getattr(field, 'name', '')
result['type'] = getattr(field, 'type', None) or getattr(
field, 'field_type', None) or 'STRING'
result['mode'] = getattr(field, 'mode', 'NULLABLE') or 'NULLABLE'
if hasattr(field, 'description') and field.description is not None:
result['description'] = field.description
if hasattr(field, 'fields') and field.fields:
result['fields'] = [_table_field_to_dict(f) for f in field.fields]
return result
def _to_gcp_schema(schema):
"""Converts a TableSchema, list of fields, dict, or string into a list of google.cloud.bigquery.SchemaField."""
if schema is None:
return None
if isinstance(schema, (list, tuple)):
fields = []
for f in schema:
if gcp_bigquery is not None and isinstance(
f, getattr(gcp_bigquery, "SchemaField", ())):
fields.append(f)
elif isinstance(f, dict) and gcp_bigquery is not None:
fields.append(gcp_bigquery.SchemaField.from_api_repr(f))
elif hasattr(f, "name") and gcp_bigquery is not None:
dict_field = _table_field_to_dict(f)
if isinstance(dict_field, dict):
fields.append(gcp_bigquery.SchemaField.from_api_repr(dict_field))
else:
fields.append(f)
else:
fields.append(f)
return fields
if isinstance(schema, TableSchema) or hasattr(schema, "fields"):
from apache_beam.io.gcp.bigquery_tools import get_dict_table_schema
dict_schema = get_dict_table_schema(schema)
if isinstance(dict_schema, dict) and gcp_bigquery is not None:
return [
gcp_bigquery.SchemaField.from_api_repr(f)
for f in dict_schema.get("fields", [])
]
if hasattr(schema, "fields") and schema.fields is not None:
return list(schema.fields)
if isinstance(schema, dict):
if gcp_bigquery is not None:
return [
gcp_bigquery.SchemaField.from_api_repr(f)
for f in schema.get("fields", [])
]
return schema.get("fields", [])
if isinstance(schema, str):
from apache_beam.io.gcp.bigquery_tools import get_dict_table_schema
return _to_gcp_schema(get_dict_table_schema(schema))
return schema
def _to_table_schema(schema):
"""Converts a list of google.cloud.bigquery.SchemaField, dict, or TableSchema into a TableSchema."""
if schema is None:
return TableSchema()
if isinstance(schema, TableSchema):
return schema
if isinstance(schema, dict):
return _to_table_schema(schema.get("fields", []))
if hasattr(schema, "fields") and not isinstance(schema, (list, tuple)):
return _to_table_schema(schema.fields)
def _to_field_schema(f):
if isinstance(f, TableFieldSchema):
return f
if isinstance(f, dict):
f_dict = f
elif hasattr(f, "to_api_repr"):
f_dict = f.to_api_repr()
else:
f_dict = None
if f_dict is not None:
name = f_dict.get("name", "")
field_type = f_dict.get("type") or f_dict.get("type_") or "STRING"
mode = f_dict.get("mode", "NULLABLE")
description = f_dict.get("description", None)
sub_fields = [_to_field_schema(sf) for sf in f_dict.get("fields", [])]
return TableFieldSchema(
name=name,
type=field_type,
mode=mode,
description=description,
fields=sub_fields)
name = getattr(f, "name", "")
field_type = getattr(f, "field_type", None) or getattr(
f, "type", None) or "STRING"
mode = getattr(f, "mode", "NULLABLE")
description = getattr(f, "description", None)
sub = getattr(f, "fields", ())
sub_fields = [_to_field_schema(sf) for sf in sub] if sub else ()
return TableFieldSchema(
name=name,
type=field_type,
mode=mode,
description=description,
fields=sub_fields)
if isinstance(schema, (list, tuple)):
return TableSchema(fields=[_to_field_schema(f) for f in schema])
return TableSchema()
# -----------------------------------------------------------------------------
# Emulated Client Compatibility Interfaces
# -----------------------------------------------------------------------------
class _JobStatusCompat:
def __init__(self, job):
self._job = job
@property
def state(self):
return self._job.state
@property
def errorResult(self):
return self._job.error_result
@property
def errors(self):
return self._job.errors
class _JobStatsCompat:
def __init__(self, job):
self._job = job
@property
def query(self):
return self
@property
def totalBytesBilled(self):
return getattr(self._job, "total_bytes_billed", None)
@property
def totalBytesProcessed(self):
return getattr(self._job, "total_bytes_processed", None)
@property
def referencedTables(self):
tables = getattr(self._job, "referenced_tables", None)
if tables is not None:
return [
TableReference(
projectId=t.project, datasetId=t.dataset_id, tableId=t.table_id)
for t in tables
]
return None
class _ClientTablesCompat:
def __init__(self, client):
self._client = client
def Get(self, request):
proj = getattr(request, "projectId", None)
ds_id = getattr(request, "datasetId", None)
tbl_id = getattr(request, "tableId", None)
if ds_id and tbl_id:
table_ref = gcp_bigquery.TableReference(
gcp_bigquery.DatasetReference(
proj or getattr(self._client, "project", None) or "default",
ds_id),
tbl_id)
else:
t_ref = getattr(request, "tableReference", None) or getattr(
request, "tableId", None) or request
table_ref = _to_gcp_table_ref(
t_ref, default_project=proj or getattr(self._client, "project", None))
return self._client.get_table(table_ref)
def Insert(self, request):
table = getattr(request, "table", None)
if table is not None:
t_ref = getattr(table, "tableReference", None)
proj = getattr(t_ref, "projectId", None) or getattr(
request, "projectId", None)
ds_id = getattr(t_ref, "datasetId", None) or getattr(
request, "datasetId", None)
tbl_id = getattr(t_ref, "tableId", None)
schema = getattr(table, "schema", None)
else:
proj = getattr(request, "projectId", None)
ds_id = getattr(request, "datasetId", None)
tbl_id = getattr(request, "tableId", None)
schema = getattr(request, "schema", None)
gcp_tbl_ref = gcp_bigquery.TableReference(
gcp_bigquery.DatasetReference(
proj or getattr(self._client, "project", None) or "default", ds_id),
tbl_id)
gcp_table = gcp_bigquery.Table(gcp_tbl_ref, schema=_to_gcp_schema(schema))
if table is not None:
tp = _extract_field(table, "timePartitioning", "time_partitioning")
if tp is not None:
gcp_table.time_partitioning = _to_gcp_time_partitioning(tp)
rp = _extract_field(table, "rangePartitioning", "range_partitioning")
if rp is not None:
gcp_table.range_partitioning = _to_gcp_range_partitioning(rp)
clustering = _extract_field(table, "clustering", "clustering_fields")
if clustering is not None:
cl_fields = _to_gcp_clustering_fields(clustering)
if cl_fields:
gcp_table.clustering_fields = cl_fields
desc = _extract_field(table, "description")
if desc is not None:
gcp_table.description = desc
fname = _extract_field(table, "friendlyName", "friendly_name")
if fname is not None:
gcp_table.friendly_name = fname
dict_labels = _extract_dict_labels(getattr(table, "labels", None))
if dict_labels:
gcp_table.labels = dict_labels
enc = _extract_field(
table, "encryptionConfiguration", "encryption_configuration")
kms = _extract_field(
enc, "kmsKeyName", "kms_key_name") if enc is not None else None
if kms:
gcp_table.encryption_configuration = (
gcp_bigquery.EncryptionConfiguration(kms_key_name=kms))
return self._client.create_table(gcp_table, exists_ok=True)
def Delete(self, request):
t_ref = getattr(request, "tableReference", None)
proj = getattr(t_ref, "projectId", None) or getattr(
request, "projectId", None)
ds_id = getattr(t_ref, "datasetId", None) or getattr(
request, "datasetId", None)
tbl_id = getattr(t_ref, "tableId", None) or getattr(
request, "tableId", None)
gcp_tbl_ref = gcp_bigquery.TableReference(
gcp_bigquery.DatasetReference(
proj or getattr(self._client, "project", None) or "default", ds_id),
tbl_id)
return self._client.delete_table(gcp_tbl_ref, not_found_ok=True)
def List(self, request):
proj = getattr(request, "projectId", None)
ds_id = getattr(request, "datasetId", None)
ds_ref = gcp_bigquery.DatasetReference(
proj or getattr(self._client, "project", None) or "default", ds_id)
return self._client.list_tables(ds_ref)
def Patch(self, request):
table = getattr(request, "table", None)
proj = getattr(request, "projectId", None)
ds_id = getattr(request, "datasetId", None)
tbl_id = getattr(request, "tableId", None)
gcp_tbl_ref = gcp_bigquery.TableReference(
gcp_bigquery.DatasetReference(
proj or getattr(self._client, "project", None) or "default", ds_id),
tbl_id)
gcp_table = gcp_bigquery.Table(gcp_tbl_ref)
if table and getattr(table, "schema", None):
gcp_table.schema = _to_gcp_schema(table.schema)
return self._client.update_table(gcp_table, ["schema"])
def Update(self, request):
return self.Patch(request)
class _ClientDatasetsCompat:
def __init__(self, client):
self._client = client
def Get(self, request):
proj = getattr(request, "projectId", None)
ds_id = getattr(request, "datasetId", None)
ds_ref = gcp_bigquery.DatasetReference(
proj or getattr(self._client, "project", None) or "default", ds_id)
return self._client.get_dataset(ds_ref)
def Insert(self, request):
dataset = getattr(request, "dataset", None)
ds_ref_raw = getattr(dataset, "datasetReference", None) if dataset else None
proj = getattr(ds_ref_raw, "projectId", None) or getattr(
request, "projectId", None)
ds_id = getattr(ds_ref_raw, "datasetId", None) or getattr(
request, "datasetId", None)
ds_ref = gcp_bigquery.DatasetReference(
proj or getattr(self._client, "project", None) or "default", ds_id)
gcp_ds = gcp_bigquery.Dataset(ds_ref)
if dataset:
if getattr(dataset, "location", None):
gcp_ds.location = dataset.location
if getattr(dataset, "defaultTableExpirationMs", None):
gcp_ds.default_table_expiration_ms = dataset.defaultTableExpirationMs
return self._client.create_dataset(gcp_ds, exists_ok=True)
def Delete(self, request):
proj = getattr(request, "projectId", None)
ds_id = getattr(request, "datasetId", None)
delete_contents = getattr(request, "deleteContents", True)
ds_ref = gcp_bigquery.DatasetReference(
proj or getattr(self._client, "project", None) or "default", ds_id)
return self._client.delete_dataset(
ds_ref, delete_contents=delete_contents, not_found_ok=True)
def List(self, request):
proj = getattr(request, "projectId", None) or getattr(
self._client, "project", None)
return self._client.list_datasets(project=proj)
def Patch(self, request):
dataset = getattr(request, "dataset", None)
proj = getattr(request, "projectId", None)
ds_id = getattr(request, "datasetId", None)
ds_ref = gcp_bigquery.DatasetReference(
proj or getattr(self._client, "project", None) or "default", ds_id)
gcp_ds = gcp_bigquery.Dataset(ds_ref)
fields_to_update = []
if dataset:
if getattr(dataset, "defaultTableExpirationMs", None):
gcp_ds.default_table_expiration_ms = dataset.defaultTableExpirationMs
fields_to_update.append("default_table_expiration_ms")
return self._client.update_dataset(gcp_ds, fields_to_update)
def Update(self, request):
return self.Patch(request)
class _ClientJobsCompat:
def __init__(self, client):
self._client = client
def Get(self, request):
proj = getattr(request, "projectId", None)
job_id = getattr(request, "jobId", None)
loc = getattr(request, "location", None)
return self._client.get_job(job_id, project=proj, location=loc)
def GetQueryResults(self, request):
proj = getattr(request, "projectId", None)
job_id = getattr(request, "jobId", None)
loc = getattr(request, "location", None)
page_token = getattr(request, "pageToken", None)
max_results = getattr(request, "maxResults", None)
job = self._client.get_job(job_id, project=proj, location=loc)
if page_token is not None:
return self._client.list_rows(
job, page_token=page_token, max_results=max_results)
return job.result(max_results=max_results)
def Insert(self, request, upload=None):
job_obj = getattr(request, "job", None)
job_ref = (
getattr(job_obj, "jobReference", None) if job_obj else getattr(
request, "jobReference", None))
job_id = getattr(job_ref, "jobId", None) or getattr(job_ref, "job_id", None)
proj = (
getattr(request, "projectId", None) or
getattr(job_ref, "projectId", None) or
getattr(job_ref, "project", None))
config = getattr(job_obj, "configuration", None) if job_obj else None
dict_labels = _extract_dict_labels(
getattr(config, "labels", None)) if config else None
if config and getattr(config, "query", None):
q = config.query
dest = None
if getattr(q, "destinationTable", None):
dest = _to_gcp_table_ref(q.destinationTable, default_project=proj)
job_config = gcp_bigquery.QueryJobConfig(
dry_run=getattr(q, "dryRun", False),
use_legacy_sql=getattr(q, "useLegacySql", False) if getattr(
q, "useLegacySql", None) is not None else False,
flatten_results=getattr(q, "flattenResults", None),
priority=getattr(q, "priority", "INTERACTIVE"),
destination=dest,
)
if dict_labels:
job_config.labels = dict_labels
kms = getattr(
getattr(q, "destinationEncryptionConfiguration", None),
"kmsKeyName",
None)
if kms:
job_config.destination_encryption_configuration = (
gcp_bigquery.EncryptionConfiguration(kms_key_name=kms))
return self._client.query(
q.query,
job_config=job_config,
job_id=job_id,
project=proj,
job_retry=None,
)
elif config and getattr(config, "load", None):
ld = config.load
dest = _to_gcp_table_ref(
getattr(ld, "destinationTable", None), default_project=proj)
uris = list(getattr(ld, "sourceUris", []))
if uris:
job_config = gcp_bigquery.LoadJobConfig()
if dict_labels:
job_config.labels = dict_labels
if getattr(ld, "schema", None):
job_config.schema = _to_gcp_schema(ld.schema)
if getattr(ld, "writeDisposition", None):
job_config.write_disposition = ld.writeDisposition
if getattr(ld, "createDisposition", None):
job_config.create_disposition = ld.createDisposition
return self._client.load_table_from_uri(
uris, dest, job_config=job_config, job_id=job_id, project=proj)
elif config and getattr(config, "copy", None):
cp = config.copy
sources = [
_to_gcp_table_ref(s, default_project=proj)
for s in getattr(cp, "sourceTables", [])
]
dest = _to_gcp_table_ref(
getattr(cp, "destinationTable", None), default_project=proj)
job_config = gcp_bigquery.CopyJobConfig()
if dict_labels:
job_config.labels = dict_labels
if getattr(cp, "writeDisposition", None):
job_config.write_disposition = cp.writeDisposition
if getattr(cp, "createDisposition", None):
job_config.create_disposition = cp.createDisposition
return self._client.copy_table(
sources, dest, job_config=job_config, job_id=job_id, project=proj)
elif config and getattr(config, "extract", None):
ex = config.extract
src = _to_gcp_table_ref(
getattr(ex, "sourceTable", None), default_project=proj)
uris = list(getattr(ex, "destinationUris", []))
job_config = gcp_bigquery.ExtractJobConfig()
if dict_labels:
job_config.labels = dict_labels
if getattr(ex, "destinationFormat", None):
job_config.destination_format = ex.destinationFormat
return self._client.extract_table(
src, uris, job_config=job_config, job_id=job_id, project=proj)
return self._client.get_job(job_id, project=proj)
# Automatically execute compatibility setup and monkey-patching upon import
_patch_protorpclite_equality()
_patch_gcp_bigquery()