gemini-code-assist[bot] commented on code in PR #39087:
URL: https://github.com/apache/beam/pull/39087#discussion_r3579520403


##########
sdks/python/apache_beam/io/gcp/bigquery_tools.py:
##########
@@ -646,53 +630,62 @@ def _start_query_job(
       dry_run=False,
       kms_key=None,
       job_labels=None):
-    reference = bigquery.JobReference(jobId=job_id, projectId=project_id)
-    request = bigquery.BigqueryJobsInsertRequest(
-        projectId=project_id,
-        job=bigquery.Job(
-            configuration=bigquery.JobConfiguration(
-                dryRun=dry_run,
-                query=bigquery.JobConfigurationQuery(
-                    query=query,
-                    useLegacySql=use_legacy_sql,
-                    allowLargeResults=not dry_run,
-                    destinationTable=self._get_temp_table(
-                        self._get_temp_table_project(project_id))
-                    if not dry_run else None,
-                    flattenResults=flatten_results,
-                    priority=priority,
-                    destinationEncryptionConfiguration=bigquery.
-                    EncryptionConfiguration(kmsKeyName=kms_key)),
-                labels=_build_job_labels(job_labels),
-            ),
-            jobReference=reference))
-
-    return self._start_job(request)
-
-  def wait_for_bq_job(self, job_reference, sleep_duration_sec=5, 
max_retries=0):
-    """Poll job until it is DONE.
+    job_config = gcp_bigquery.QueryJobConfig(
+        use_legacy_sql=use_legacy_sql,
+        flatten_results=flatten_results,
+        priority=priority,
+        dry_run=dry_run,
+        labels=job_labels or {},
+        allow_large_results=not dry_run,
+    )
+    if kms_key:
+      job_config.destination_encryption_configuration = 
gcp_bigquery.EncryptionConfiguration(
+          kms_key_name=kms_key)
+    if not dry_run:
+      job_config.destination = self._get_temp_table(
+          self._get_temp_table_project(project_id))
 
-    Args:
-      job_reference: bigquery.JobReference instance.
-      sleep_duration_sec: Specifies the delay in seconds between retries.
-      max_retries: The total number of times to retry. If equals to 0,
-        the function waits forever.
+    try:
+      job = self.client.query(
+          query, job_config=job_config, job_id=job_id, project=project_id)
+      return BeamJobReference(
+          projectId=job.project,
+          jobId=job.job_id,
+          location=job.location,
+          totalBytesProcessed=getattr(job, 'total_bytes_processed', None))
+    except google_api_core_exceptions.Conflict as exn:
+      _LOGGER.info("BigQuery job %s already exists", job_id)
+      job_location = self._parse_location_from_exc(exn.message, job_id)
+      return BeamJobReference(
+          projectId=project_id, jobId=job_id, location=job_location)
 
-    Raises:
-      `RuntimeError`: If the job is FAILED or the number of retries has been
-        reached.
-    """
+  def wait_for_bq_job(self, job_reference, sleep_duration_sec=5, 
max_retries=0):
     retry = 0
+    project_id = getattr(
+        job_reference, 'project', getattr(job_reference, 'projectId', None))
+    job_id = getattr(
+        job_reference, 'job_id', getattr(job_reference, 'jobId', None))
+    location = getattr(job_reference, 'location', None)
     while True:
       retry += 1
-      job = self.get_job(
-          job_reference.projectId, job_reference.jobId, job_reference.location)
-      _LOGGER.info('Job %s status: %s', job.id, job.status.state)
-      if job.status.state == 'DONE' and job.status.errorResult:
+      job = self.get_job(project_id, job_id, location)
+
+      if hasattr(job, 'status') and hasattr(job.status, 'state'):
+        state = job.status.state
+        error_result = job.status.errorResult
+        job_id = getattr(job, 'id', getattr(job_reference, 'jobId', ''))
+      else:
+        state = job.state
+        error_result = job.error_result
+        job_id = job.job_id if hasattr(job, 'job_id') else getattr(
+            job_reference, 'jobId', '')
+
+      _LOGGER.info('Job %s status: %s', job_id, state)
+      if state == 'DONE' and error_result:
         raise RuntimeError(
             'BigQuery job {} failed. Error Result: {}'.format(
-                job_reference.jobId, job.status.errorResult))
-      elif job.status.state == 'DONE':
+                job_reference.jobId, error_result))

Review Comment:
   ![high](https://www.gstatic.com/codereviewagent/high-priority.svg)
   
   The `job_reference` object can be a `google.cloud.bigquery.job` instance 
(such as `LoadJob` or `QueryJob`) which has `job_id` but not `jobId`. Accessing 
`job_reference.jobId` will raise an `AttributeError` at runtime. Use the local 
variable `job_id` instead, which was safely extracted using `getattr` above.
   
   ```suggestion
         if state == 'DONE' and error_result:
           raise RuntimeError(
               'BigQuery job {} failed. Error Result: {}'.format(
                   job_id, error_result))
   ```



##########
sdks/python/apache_beam/io/gcp/bigquery_read_internal.py:
##########
@@ -444,15 +469,19 @@ def _to_bytes(value):
 
   @classmethod
   def _convert_to_tuple(cls, table_field_schemas):
-    """Recursively converts the list of TableFieldSchema instances to the
+    """Recursively converts the list of SchemaField instances to the
     list of tuples to prevent errors when pickling and unpickling
-    TableFieldSchema instances.
+    SchemaField instances.
     """
     if not table_field_schemas:
       return []
 
     return [
-        FieldSchema(cls._convert_to_tuple(x.fields), x.mode, x.name, x.type)
+        FieldSchema(
+            cls._convert_to_tuple(x.fields),
+            x.mode,
+            x.name,
+            getattr(x, "field_type", getattr(x, "type", None)))
         for x in table_field_schemas
     ]

Review Comment:
   ![critical](https://www.gstatic.com/codereviewagent/critical.svg)
   
   When `table_schema` is a dictionary, `table_field_schemas` is a list of 
dictionaries. Attempting to access attributes like `x.fields`, `x.mode`, 
`x.name` on `x` will raise an `AttributeError` because `x` is a dictionary. We 
should robustly handle both `SchemaField` objects and `dict` objects.
   
   ```suggestion
       res = []
       for x in table_field_schemas:
         if isinstance(x, dict):
           fields = x.get('fields', [])
           mode = x.get('mode', 'NULLABLE')
           name = x.get('name')
           field_type = x.get('type')
         else:
           fields = getattr(x, 'fields', [])
           mode = getattr(x, 'mode', 'NULLABLE')
           name = getattr(x, 'name')
           field_type = getattr(x, 'field_type', getattr(x, 'type', None))
         res.append(FieldSchema(cls._convert_to_tuple(fields), mode, name, 
field_type))
       return res
   ```



##########
sdks/python/apache_beam/io/gcp/bigquery_tools.py:
##########
@@ -1103,27 +1063,28 @@ def perform_extract_job(
     """Starts a job to export data from BigQuery.
 
     Returns:
-      bigquery.JobReference with the information about the job that was 
started.
+      gcp_bigquery.JobReference with the information about the job that was 
started.
     """
-    job_project = project or table_reference.projectId
-    job_reference = bigquery.JobReference(jobId=job_id, projectId=job_project)
-    request = bigquery.BigqueryJobsInsertRequest(
-        projectId=job_project,
-        job=bigquery.Job(
-            configuration=bigquery.JobConfiguration(
-                extract=bigquery.JobConfigurationExtract(
-                    destinationUris=destination,
-                    sourceTable=table_reference,
-                    printHeader=include_header,
-                    destinationFormat=destination_format,
-                    compression=compression,
-                    useAvroLogicalTypes=use_avro_logical_types,
-                ),
-                labels=_build_job_labels(job_labels),
-            ),
-            jobReference=job_reference,
-        ))
-    return self._start_job(request).jobReference
+    job_project = project or table_reference.project
+    job_config = gcp_bigquery.ExtractJobConfig(
+        print_header=include_header,
+        destination_format=destination_format,
+        compression=compression,
+        use_avro_logical_types=use_avro_logical_types,
+        labels=job_labels or {},
+    )
+    try:
+      job = self.client.extract_table(
+          source=table_reference,
+          destination_uris=destination,
+          job_id=job_id,
+          project=job_project,
+          job_config=job_config)
+      return job
+    except google_api_core_exceptions.Conflict as exn:
+      _LOGGER.info("BigQuery job %s already exists", job_id)
+      job_location = self._parse_location_from_exc(exn.message, job_id)
+      return self.get_job(job_project, job_id, job_location)

Review Comment:
   ![high](https://www.gstatic.com/codereviewagent/high-priority.svg)
   
   To maintain consistency with other job-starting methods in `BigQueryWrapper` 
(such as `_insert_load_job`, `_insert_copy_job`, and `_start_query_job`), 
`perform_extract_job` should return a `BeamJobReference` instead of an 
`ExtractJob` or `Job` object. Callers expecting a job reference with 
`projectId` and `jobId` attributes will fail with `AttributeError` if they 
receive an `ExtractJob` or `Job` object (which only have `project` and 
`job_id`).
   
   ```python
       try:
         job = self.client.extract_table(
             source=table_reference,
             destination_uris=destination,
             job_id=job_id,
             project=job_project,
             job_config=job_config)
         return BeamJobReference(
             projectId=job.project, jobId=job.job_id, location=job.location)
       except google_api_core_exceptions.Conflict as exn:
         _LOGGER.info("BigQuery job %s already exists", job_id)
         job_location = self._parse_location_from_exc(exn.message, job_id)
         return BeamJobReference(
             projectId=job_project, jobId=job_id, location=job_location)
   ```



##########
sdks/python/apache_beam/io/gcp/bigquery_file_loads.py:
##########
@@ -397,17 +397,22 @@ def process(self, element, schema_mod_job_name_prefix):
       return
 
     table_reference = bigquery_tools.parse_table_reference(destination)
-    if table_reference.projectId is None:
-      table_reference.projectId = vp.RuntimeValueProvider.get_value(
+    if table_reference.project is None or table_reference.project == 
bigquery_tools.FALLBACK_PROJECT:
+      from google.cloud.bigquery import DatasetReference
+      from google.cloud.bigquery import TableReference
+      new_project = vp.RuntimeValueProvider.get_value(
           'project', str, '') or self.project
+      table_reference = TableReference(
+          DatasetReference(new_project, table_reference.dataset_id),
+          table_reference.table_id)

Review Comment:
   ![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg)
   
   Avoid inline imports of `DatasetReference` and `TableReference` from 
`google.cloud.bigquery` inside `process`. This can raise `ImportError` if 
`google-cloud-bigquery` is not installed, and is redundant because 
`bigquery_tools` already exports `TableReference` and `DatasetReference` safely 
with fallback dummy classes.
   
   ```python
       if table_reference.project is None or table_reference.project == 
bigquery_tools.FALLBACK_PROJECT:
         new_project = vp.RuntimeValueProvider.get_value(
             'project', str, '') or self.project
         table_reference = bigquery_tools.TableReference(
             bigquery_tools.DatasetReference(new_project, 
table_reference.dataset_id),
             table_reference.table_id)
   ```



##########
sdks/python/apache_beam/io/gcp/bigquery_file_loads.py:
##########
@@ -537,15 +542,26 @@ def process_one(self, element, job_name_prefix):
     destination, job_reference = element
 
     copy_to_reference = bigquery_tools.parse_table_reference(destination)
-    if copy_to_reference.projectId is None:
-      copy_to_reference.projectId = vp.RuntimeValueProvider.get_value(
+    if copy_to_reference.project is None or copy_to_reference.project == 
bigquery_tools.FALLBACK_PROJECT:
+      from google.cloud.bigquery import DatasetReference
+      from google.cloud.bigquery import TableReference
+      new_project = vp.RuntimeValueProvider.get_value(
           'project', str, '') or self.project
+      copy_to_reference = TableReference(
+          DatasetReference(new_project, copy_to_reference.dataset_id),
+          copy_to_reference.table_id)
 
     copy_from_reference = bigquery_tools.parse_table_reference(destination)
-    copy_from_reference.tableId = job_reference.jobId
-    if copy_from_reference.projectId is None:
-      copy_from_reference.projectId = vp.RuntimeValueProvider.get_value(
+    new_table_id = job_reference.jobId
+    new_project = copy_from_reference.project
+    if new_project is None or new_project == bigquery_tools.FALLBACK_PROJECT:
+      new_project = vp.RuntimeValueProvider.get_value(
           'project', str, '') or self.project
+    from google.cloud.bigquery import DatasetReference
+    from google.cloud.bigquery import TableReference
+    copy_from_reference = TableReference(
+        DatasetReference(new_project, copy_from_reference.dataset_id),
+        new_table_id)

Review Comment:
   ![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg)
   
   Avoid inline imports of `DatasetReference` and `TableReference` from 
`google.cloud.bigquery` inside `process_one`. This can raise `ImportError` if 
`google-cloud-bigquery` is not installed, and is redundant because 
`bigquery_tools` already exports `TableReference` and `DatasetReference` safely 
with fallback dummy classes.
   
   ```suggestion
       if copy_to_reference.project is None or copy_to_reference.project == 
bigquery_tools.FALLBACK_PROJECT:
         new_project = vp.RuntimeValueProvider.get_value(
             'project', str, '') or self.project
         copy_to_reference = bigquery_tools.TableReference(
             bigquery_tools.DatasetReference(new_project, 
copy_to_reference.dataset_id),
             copy_to_reference.table_id)
   
       copy_from_reference = bigquery_tools.parse_table_reference(destination)
       new_table_id = job_reference.jobId
       new_project = copy_from_reference.project
       if new_project is None or new_project == bigquery_tools.FALLBACK_PROJECT:
         new_project = vp.RuntimeValueProvider.get_value(
             'project', str, '') or self.project
       copy_from_reference = bigquery_tools.TableReference(
           bigquery_tools.DatasetReference(new_project, 
copy_from_reference.dataset_id),
           new_table_id)
   ```



##########
sdks/python/apache_beam/dataframe/io_it_test.py:
##########
@@ -34,8 +33,11 @@
 
 try:
   from google.api_core.exceptions import GoogleAPICallError
+
+  import apache_beam.io.gcp.bigquery
 except ImportError:
   GoogleAPICallError = None
+  bigquery = None

Review Comment:
   ![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg)
   
   In the `try` block, `import apache_beam.io.gcp.bigquery` is called, but 
`bigquery` is not bound to the local scope (only `apache_beam` is). In the 
`except` block, `bigquery = None` is set. If `bigquery` is accessed directly 
later in the file, it will raise a `NameError` when `ImportError` is not 
raised. Import it as `import apache_beam.io.gcp.bigquery as bigquery` instead.
   
   ```suggestion
     from google.api_core.exceptions import GoogleAPICallError
   
     import apache_beam.io.gcp.bigquery as bigquery
   except ImportError:
     GoogleAPICallError = None
     bigquery = None
   ```



##########
sdks/python/apache_beam/io/gcp/bigquery_tools.py:
##########
@@ -425,13 +443,16 @@ def _get_temp_table_project(self, fallback_project_id):
     Otherwise, returns the fallback_project_id.
     """
     if self.temp_table_ref:
-      return self.temp_table_ref.projectId
+      return getattr(
+          self.temp_table_ref,
+          'projectId',
+          getattr(self.temp_table_ref, 'project', None))
     else:
       return fallback_project_id
 
   def _get_temp_dataset(self):
     if self.temp_table_ref:
-      return self.temp_table_ref.datasetId
+      return self.temp_table_ref.dataset_id
     return BigQueryWrapper.TEMP_DATASET + self._temporary_table_suffix

Review Comment:
   ![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg)
   
   If `self.temp_table_ref` is an older `apitools` `TableReference` or a mock 
object that only has `datasetId` (camelCase), accessing 
`self.temp_table_ref.dataset_id` will raise an `AttributeError`. Since 
`self.temp_dataset_id` is already safely initialized in `__init__` using 
`getattr` to handle both `datasetId` and `dataset_id`, `_get_temp_dataset` 
should simply return `self.temp_dataset_id`.
   
   ```suggestion
     def _get_temp_dataset(self):
       if self.temp_dataset_id:
         return self.temp_dataset_id
       return BigQueryWrapper.TEMP_DATASET + self._temporary_table_suffix
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to