composer_local_dev/errors.py (108 lines of code) (raw):
# Copyright 2022 Google LLC
#
# Licensed 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.
import functools
from typing import Optional, Tuple
import click
from google.auth import exceptions as auth_exception
from composer_local_dev import constants
class ComposerCliError(click.ClickException):
"""
An exception that occurred in Composer CLI and can be handled and shown to
the user.
"""
def __init__(self, msg):
msg += constants.ADD_DEBUG_ON_ERROR_INFO
super().__init__(msg)
class ComposerCliFatalError(Exception):
"""
Fatal exception that occurred in Composer CLI indicating unrecoverable
state.
"""
pass
class ImageNotFoundError(ComposerCliError):
"""Composer image not found."""
def __init__(self, image_version):
msg = constants.IMAGE_TAG_DOES_NOT_EXIST_ERROR.format(
image_tag=image_version
)
super().__init__(msg)
class EnvironmentAlreadyRunningError(ComposerCliError):
"""Environment with the same name is already running."""
def __init__(self, name):
msg = constants.ENVIRONMENT_ALREADY_RUNNING.format(name=name)
super().__init__(msg)
class EnvironmentNotRunningError(ComposerCliError):
"""Composer environment is not running."""
def __init__(self):
msg = constants.ENV_NOT_RUNNING
super().__init__(msg)
class EnvironmentNotFoundError(EnvironmentNotRunningError):
"""Composer environment docker container was not found."""
pass
class EnvironmentStartError(ComposerCliError):
"""Composer environment failed to start."""
def __init__(self, msg: Optional[str] = None):
if msg is None:
msg = constants.ENVIRONMENT_FAILED_TO_START_ERROR
super().__init__(msg)
class EnvironmentStartTimeoutError(EnvironmentStartError):
"""Composer environment start timed out."""
def __init__(self):
msg = constants.ENV_DID_NOT_START_TIMEOUT_ERROR.format(
seconds=constants.OPERATION_TIMEOUT_SECONDS
)
super().__init__(msg)
class InvalidConfigurationError(ComposerCliError):
"""Composer environment configuration is not valid."""
class MissingRequiredParameterError(InvalidConfigurationError):
"""Missing required parameter in configuration file."""
def __init__(self, param):
msg = constants.MISSING_REQUIRED_PARAM_ERROR.format(param=param)
super().__init__(msg)
class FailedToParseConfigParamIntError(InvalidConfigurationError):
def __init__(self, param_name: str, value: str):
msg = constants.INVALID_INT_VALUE_ERROR.format(
param_name=param_name, value=value
)
super().__init__(msg)
class FailedToParseConfigParamIntRangeError(InvalidConfigurationError):
def __init__(
self,
param_name: str,
value: int,
int_range: Tuple[int,],
):
if len(int_range) == 1:
allowed_range = f"x>={int_range[0]}"
else:
allowed_range = f"{int_range[0]}<=x<={int_range[1]}"
msg = constants.INVALID_INT_RANGE_VALUE_ERROR.format(
param_name=param_name, value=value, allowed_range=allowed_range
)
super().__init__(msg)
class FailedToParseConfigError(InvalidConfigurationError):
def __init__(self, config_path, err):
msg = constants.INVALID_CONFIGURATION_FILE_ERROR.format(
config_path=config_path, error=err
)
super().__init__(msg)
class FailedToParseVariablesError(InvalidConfigurationError):
def __init__(self, env_file_path, invalid_line):
msg = constants.INVALID_ENV_VARIABLES_FILE_ERROR.format(
env_file_path=env_file_path, line=invalid_line
)
super().__init__(msg)
class DockerNotAvailableError(ComposerCliError):
def __init__(self, err):
super().__init__(constants.DOCKER_NOT_AVAILABLE_ERROR.format(error=err))
class InvalidAuthError(ComposerCliError):
"""Gcloud authentication is invalid or missing."""
def __init__(self, err):
error_str = str(err)
# we are normalizing error, so it does not end up with two dots
if error_str.endswith("."):
error_str = error_str[:-1]
super().__init__(constants.AUTH_INVALID_ERROR.format(error=error_str))
class DAGPathNotExistError(ComposerCliError):
"""DAG path does not exist or is not a directory."""
def __init__(self, dags_path):
super().__init__(
constants.DAGS_PATH_NOT_EXISTS_ERROR.format(dags_path=dags_path)
)
def catch_exceptions(func=None):
"""
Catch exceptions and print user friendly message for common issues.
"""
if not func:
return functools.partial(catch_exceptions)
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except (
click.ClickException,
click.Abort,
):
raise
except auth_exception.DefaultCredentialsError as err:
raise InvalidAuthError(str(err))
except Exception:
message = "\nFatal exception occurred. Please report at https://github.com/GoogleCloudPlatform/composer-local-dev/issues"
raise ComposerCliFatalError(message)
return wrapper