googleapis/python-bigquery

dbapi: Chain `DatabaseError` from `GoogleCloudError`

r1b opened this issue · 2 comments

r1b commented

Is your feature request related to a problem? Please describe.
GoogleCloudError can be raised when a query job has failed. When this happens, we get the query_job attached to the exception.

Ref:

However, in the dbapi, we lose the original exception, instead throwing a DatabaseError with a string representation of the original error.

Ref:

except google.cloud.exceptions.GoogleCloudError as exc:
raise exceptions.DatabaseError(exc)

Describe the solution you'd like

I'd like to be able to pull the query_job from a DatabaseError. One possible solution would be to chain the exceptions here:

        except google.cloud.exceptions.GoogleCloudError as exc:
            raise exceptions.DatabaseError(exc) from exc

This would allow me to reference exc.__cause__.query_job.

Describe alternatives you've considered
I work on a system that emits audit logs for failed BigQuery jobs run via DBAPI. Currently the only way to do this is to use regex to pull out the Job ID.

Additional context
N/A

Hi @r1b, thanks for making the request! I think regardless of adding from exc or not, the DatabaseError is wrapped inside. Right now, you can access it from exc.exception.args, similar to the following test:

  def test_execute_raises_if_result_raises(self):
      import google.cloud.exceptions

      from google.cloud.bigquery import client
      from google.cloud.bigquery.dbapi import connect
      from google.cloud.bigquery.dbapi import exceptions

      client = mock.create_autospec(client.Client)
      cloud_error = google.cloud.exceptions.GoogleCloudError("cloud error message")
      cloud_error.query_job = "query_job_info"
      client.query_and_wait.side_effect = cloud_error

      
      connection = connect(client)
      cursor = connection.cursor()

      with self.assertRaises(exceptions.DatabaseError) as exc:
          cursor.execute("SELECT 1")

      assert exc.exception.args[0] == cloud_error

Is there a specific case where the inner exception isn't accessible? If that's the case, please let me know.

r1b commented

@Linchin Apologies, this was my mistake. I'm accessing the dbapi via sqlalchemy, which rewrites the exception to its own implementation also named DatabaseError.

I was able to access the query job from the sqlalchemy exception this way:

exc.orig.args[0].query_job