196 lines
6.2 KiB
Python
196 lines
6.2 KiB
Python
#!/usr/bin/env python
|
|
"""Utility functions and classes for BQ CLI errors."""
|
|
|
|
import textwrap
|
|
from typing import Dict, List, Optional
|
|
|
|
import bq_flags
|
|
from utils import bq_logging
|
|
|
|
|
|
P12_DEPRECATION_MESSAGE = (
|
|
'BQ CLI no longer supports the deprecated P12 format by default. To migrate'
|
|
' to the new JSON service account key format, follow the steps in'
|
|
' https://cloud.google.com/iam/docs/keys-create-delete#creating. To force'
|
|
' BQ CLI to recognize P12 keys, re-run the command with'
|
|
' --nouse_google_auth.'
|
|
)
|
|
|
|
|
|
class BigqueryError(Exception):
|
|
"""Class to represent a BigQuery error."""
|
|
|
|
|
|
class BigqueryTypeError(BigqueryError):
|
|
"""A BQ CLI type error that should not show a stack trace."""
|
|
|
|
|
|
class BigqueryCommunicationError(BigqueryError):
|
|
"""Error communicating with the server."""
|
|
|
|
|
|
class BigqueryInterfaceError(BigqueryError):
|
|
"""Response from server missing required fields."""
|
|
|
|
|
|
class BigqueryServiceError(BigqueryError):
|
|
"""Base class of Bigquery-specific error responses.
|
|
|
|
The BigQuery server received request and returned an error.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
error: Dict[str, str],
|
|
error_list: List[Dict[str, str]],
|
|
job_ref: Optional[str] = None,
|
|
*args,
|
|
**kwds,
|
|
):
|
|
# pylint: disable=g-doc-args
|
|
# pylint: disable=keyword-arg-before-vararg
|
|
"""Initializes a BigqueryServiceError.
|
|
|
|
Args:
|
|
message: A user-facing error message.
|
|
error: The error dictionary, code may inspect the 'reason' key.
|
|
error_list: A list of additional entries, for example a load job may
|
|
contain multiple errors here for each error encountered during
|
|
processing.
|
|
job_ref: Optional JobReference string, if this error was encountered while
|
|
processing a job.
|
|
"""
|
|
super().__init__(message, *args, **kwds)
|
|
self.error = error
|
|
self.error_list = error_list
|
|
self.job_ref = job_ref
|
|
|
|
def __repr__(self):
|
|
return '%s: error=%s, error_list=%s, job_ref=%s' % (
|
|
self.__class__.__name__,
|
|
self.error,
|
|
self.error_list,
|
|
self.job_ref,
|
|
)
|
|
|
|
|
|
class BigqueryNotFoundError(BigqueryServiceError):
|
|
"""The requested resource or identifier was not found."""
|
|
|
|
|
|
class BigqueryDuplicateError(BigqueryServiceError):
|
|
"""The requested resource or identifier already exists."""
|
|
|
|
|
|
class BigqueryAccessDeniedError(BigqueryServiceError):
|
|
"""The user does not have access to the requested resource."""
|
|
|
|
|
|
class BigqueryInvalidQueryError(BigqueryServiceError):
|
|
"""The SQL statement is invalid."""
|
|
|
|
|
|
class BigqueryTermsOfServiceError(BigqueryAccessDeniedError):
|
|
"""User has not ACK'd ToS."""
|
|
|
|
|
|
class BigqueryBackendError(BigqueryServiceError):
|
|
"""A backend error typically corresponding to retriable HTTP 5xx failures."""
|
|
|
|
|
|
class BigqueryClientError(BigqueryError):
|
|
"""Invalid use of BigqueryClient."""
|
|
|
|
|
|
class BigqueryClientConfigurationError(BigqueryClientError):
|
|
"""Invalid configuration of BigqueryClient."""
|
|
|
|
|
|
class BigquerySchemaError(BigqueryClientError):
|
|
"""Error in locating or parsing the schema."""
|
|
|
|
|
|
class BigqueryTableConstraintsError(BigqueryClientError):
|
|
"""Error in locating or parsing the table constraints."""
|
|
|
|
|
|
def CreateBigqueryError(
|
|
error: Dict[str, str],
|
|
server_error: Dict[str, str],
|
|
error_ls: List[Dict[str, str]],
|
|
job_ref: Optional[str] = None,
|
|
session_id: Optional[str] = None,
|
|
) -> BigqueryError:
|
|
"""Returns a BigqueryError for json error embedded in server_error.
|
|
|
|
If error_ls contains any errors other than the given one, those
|
|
are also included in the returned message.
|
|
|
|
Args:
|
|
error: The primary error to convert.
|
|
server_error: The error returned by the server. (This is only used in the
|
|
case that error is malformed.)
|
|
error_ls: Additional errors to include in the error message.
|
|
job_ref: String representation a JobReference, if this is an error
|
|
associated with a job.
|
|
session_id: Id of the session if the job is part of one.
|
|
|
|
Returns:
|
|
BigqueryError representing error.
|
|
"""
|
|
reason = error.get('reason')
|
|
if job_ref:
|
|
message = f"Error processing job '{job_ref}': {error.get('message')}"
|
|
else:
|
|
message = error.get('message', '')
|
|
# We don't want to repeat the "main" error message.
|
|
new_errors = [err for err in error_ls if err != error]
|
|
if new_errors:
|
|
message += '\nFailure details:\n'
|
|
wrap_error_message = True
|
|
new_error_messages = [
|
|
': '.join(filter(None, [err.get('location'), err.get('message')]))
|
|
for err in new_errors
|
|
]
|
|
if wrap_error_message:
|
|
message += '\n'.join(
|
|
textwrap.fill(msg, initial_indent=' - ', subsequent_indent=' ')
|
|
for msg in new_error_messages
|
|
)
|
|
else:
|
|
error_message = '\n'.join(new_error_messages)
|
|
if error_message:
|
|
message += '- ' + error_message
|
|
if session_id:
|
|
message += '\nIn session: %s' % session_id
|
|
|
|
# Sometimes we will have type(message) being <type 'unicode'>, for example
|
|
# from an invalid query containing a non-English string. Reduce this
|
|
# to <type 'string'> now -- otherwise it's a trap for any code that
|
|
# tries to %s-format the exception later: str() uses 'ascii' codec.
|
|
# And the message is for display only, so this shouldn't confuse other code.
|
|
message = bq_logging.EncodeForPrinting(message)
|
|
|
|
if not reason or not message:
|
|
return BigqueryInterfaceError(
|
|
'Error reported by server with missing error fields. '
|
|
'Server returned: %s' % (str(server_error),)
|
|
)
|
|
if reason == 'notFound':
|
|
return BigqueryNotFoundError(message, error, error_ls, job_ref=job_ref)
|
|
if reason == 'duplicate':
|
|
return BigqueryDuplicateError(message, error, error_ls, job_ref=job_ref)
|
|
if reason == 'accessDenied':
|
|
return BigqueryAccessDeniedError(message, error, error_ls, job_ref=job_ref)
|
|
if reason == 'invalidQuery':
|
|
return BigqueryInvalidQueryError(message, error, error_ls, job_ref=job_ref)
|
|
if reason == 'termsOfServiceNotAccepted':
|
|
return BigqueryTermsOfServiceError(
|
|
message, error, error_ls, job_ref=job_ref
|
|
)
|
|
if reason == 'backendError':
|
|
return BigqueryBackendError(message, error, error_ls, job_ref=job_ref)
|
|
# We map the remaining errors to BigqueryServiceError.
|
|
return BigqueryServiceError(message, error, error_ls, job_ref=job_ref)
|