2022-03-24 12:37:18 +00:00
|
|
|
from typing import TYPE_CHECKING, List, Callable
|
2022-03-19 22:11:53 +00:00
|
|
|
from functools import wraps
|
|
|
|
|
import sys
|
|
|
|
|
import os
|
|
|
|
|
|
2022-03-24 09:13:49 +00:00
|
|
|
if TYPE_CHECKING:
|
2022-03-24 16:07:16 +00:00
|
|
|
from .values import Value # pragma: no cover
|
2022-03-19 22:11:53 +00:00
|
|
|
|
2022-03-24 09:13:49 +00:00
|
|
|
|
|
|
|
|
class TermStyles:
|
|
|
|
|
BOLD = "\033[1m" if os.isatty(sys.stderr.fileno()) else ""
|
|
|
|
|
RED = "\033[91m" if os.isatty(sys.stderr.fileno()) else ""
|
|
|
|
|
END = "\033[0m" if os.isatty(sys.stderr.fileno()) else ""
|
|
|
|
|
|
|
|
|
|
|
2022-03-24 10:03:13 +00:00
|
|
|
def extract_explanation_lines_from_value(value_instance: 'Value') -> List[str]:
|
|
|
|
|
result = []
|
|
|
|
|
|
|
|
|
|
if value_instance.help_text is not None:
|
|
|
|
|
result.append(f"Help: {value_instance.help_text}")
|
|
|
|
|
|
2022-03-24 10:05:20 +00:00
|
|
|
if value_instance.help_reference is not None:
|
|
|
|
|
result.append(f"Reference: {value_instance.help_reference}")
|
|
|
|
|
|
2022-03-24 10:03:13 +00:00
|
|
|
if value_instance.destination_name is not None:
|
|
|
|
|
result.append(f"{value_instance.destination_name} is taken from the environment variable "
|
|
|
|
|
f"{value_instance.full_environ_name} as a {type(value_instance).__name__}")
|
|
|
|
|
|
2022-03-24 13:25:28 +00:00
|
|
|
if value_instance.example_generator is not None:
|
|
|
|
|
result.append(f"Example value: '{value_instance.example_generator()}'")
|
|
|
|
|
|
2022-03-24 10:03:13 +00:00
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
2022-03-24 09:42:06 +00:00
|
|
|
class SetupError(Exception):
|
|
|
|
|
"""
|
|
|
|
|
Exception that gets raised when a configuration class cannot be set up by the importer
|
|
|
|
|
"""
|
2022-03-24 10:03:13 +00:00
|
|
|
|
2022-03-24 09:42:06 +00:00
|
|
|
def __init__(self, msg: str, child_errors: List['ConfigurationError'] = None) -> None:
|
|
|
|
|
"""
|
|
|
|
|
:param step_verb: Which step the importer tried to perform (e.g. import, setup)
|
|
|
|
|
:param configuration_path: The full module path of the configuration that was supposed to be set up
|
|
|
|
|
:param child_errors: Optional child configuration errors that caused this error
|
|
|
|
|
"""
|
|
|
|
|
super().__init__(msg)
|
|
|
|
|
self.child_errors = child_errors or []
|
|
|
|
|
|
|
|
|
|
|
2022-03-24 09:13:49 +00:00
|
|
|
class ConfigurationError(ValueError):
|
2022-03-19 22:11:53 +00:00
|
|
|
"""
|
2022-03-24 09:13:49 +00:00
|
|
|
Base error class that is used to indicate that something went wrong during configuration.
|
|
|
|
|
|
|
|
|
|
This error type (and subclasses) is caught and pretty-printed by django-configurations so that an end-user does not
|
|
|
|
|
see an unwieldy traceback but instead a helpful error message.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, main_error_msg: str, explanation_lines: List[str]) -> None:
|
|
|
|
|
"""
|
|
|
|
|
:param main_error_msg: Main message that describes the error.
|
|
|
|
|
This will be displayed before all *explanation_lines* and in the traceback (although tracebacks are normally
|
|
|
|
|
not rendered)
|
|
|
|
|
:param explanation_lines: Additional lines of explanations which further describe the error or give hints on
|
|
|
|
|
how to fix it.
|
|
|
|
|
"""
|
|
|
|
|
super().__init__(main_error_msg)
|
|
|
|
|
self.main_error_msg = main_error_msg
|
|
|
|
|
self.explanation_lines = explanation_lines
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ValueRetrievalError(ConfigurationError):
|
|
|
|
|
"""
|
|
|
|
|
Exception that is raised when errors occur during the retrieval of a Value by one of the `Value` classes.
|
2022-03-19 22:11:53 +00:00
|
|
|
This can happen when the environment variable corresponding to the value is not defined.
|
|
|
|
|
"""
|
|
|
|
|
|
2022-03-24 09:13:49 +00:00
|
|
|
def __init__(self, value_instance: "Value", *extra_explanation_lines: str):
|
|
|
|
|
"""
|
|
|
|
|
:param value_instance: The `Value` instance which caused the generation of this error
|
|
|
|
|
:param extra_explanation_lines: Extra lines that will be appended to `ConfigurationError.explanation_lines`
|
|
|
|
|
in addition the ones automatically generated from the provided *value_instance*.
|
|
|
|
|
"""
|
|
|
|
|
super().__init__(
|
|
|
|
|
f"Value of {value_instance.destination_name} could not be retrieved from environment",
|
2022-03-24 10:03:13 +00:00
|
|
|
list(extra_explanation_lines) + extract_explanation_lines_from_value(value_instance)
|
2022-03-24 09:13:49 +00:00
|
|
|
)
|
2022-03-19 22:11:53 +00:00
|
|
|
|
2022-03-24 09:13:49 +00:00
|
|
|
|
|
|
|
|
class ValueProcessingError(ConfigurationError):
|
2022-03-19 22:11:53 +00:00
|
|
|
"""
|
2022-03-24 09:13:49 +00:00
|
|
|
Exception that is raised when a dynamic Value failed to be processed by one of the `Value` classes after retrieval.
|
|
|
|
|
|
|
|
|
|
Processing could be i.e. converting from string to a native datatype or validation.
|
2022-03-19 22:11:53 +00:00
|
|
|
"""
|
|
|
|
|
|
2022-03-24 09:13:49 +00:00
|
|
|
def __init__(self, value_instance: "Value", raw_value: str, *extra_explanation_lines: str):
|
|
|
|
|
"""
|
|
|
|
|
:param value_instance: The `Value` instance which caused the generation of this error
|
|
|
|
|
:param raw_value: The raw value that was retrieved from the environment and which could not be processed further
|
|
|
|
|
:param extra_explanation_lines: Extra lines that will be prepended to `ConfigurationError.explanation_lines`
|
|
|
|
|
in addition the ones automatically generated from the provided *value_instance*.
|
|
|
|
|
"""
|
|
|
|
|
error = f"{value_instance.destination_name} was given an invalid value"
|
|
|
|
|
if hasattr(value_instance, "message"):
|
|
|
|
|
error += ": " + value_instance.message.format(raw_value)
|
2022-03-19 22:11:53 +00:00
|
|
|
|
2022-03-24 10:03:13 +00:00
|
|
|
explanation_lines = list(extra_explanation_lines) + extract_explanation_lines_from_value(value_instance)
|
2022-03-24 09:13:49 +00:00
|
|
|
explanation_lines.append(f"'{raw_value}' was received but that is invalid")
|
|
|
|
|
|
|
|
|
|
super().__init__(error, explanation_lines)
|
2022-03-19 22:11:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def with_error_handler(callee: Callable) -> Callable:
|
|
|
|
|
"""
|
|
|
|
|
A decorator which is designed to wrap django entry points with an error handler so that django-configuration
|
|
|
|
|
originated errors can be caught and rendered to the user in a readable format.
|
|
|
|
|
"""
|
2022-03-24 09:13:49 +00:00
|
|
|
|
2022-03-19 22:11:53 +00:00
|
|
|
@wraps(callee)
|
|
|
|
|
def wrapper(*args, **kwargs):
|
|
|
|
|
try:
|
|
|
|
|
return callee(*args, **kwargs)
|
2022-03-24 09:42:06 +00:00
|
|
|
except SetupError as e:
|
|
|
|
|
msg = f"{str(e)}"
|
|
|
|
|
for child_error in e.child_errors:
|
|
|
|
|
msg += f"\n * {child_error.main_error_msg}"
|
|
|
|
|
for explanation_line in child_error.explanation_lines:
|
|
|
|
|
msg += f"\n - {explanation_line}"
|
|
|
|
|
|
2022-03-19 22:11:53 +00:00
|
|
|
print(msg, file=sys.stderr)
|
|
|
|
|
|
|
|
|
|
return wrapper
|