Skip to content

decorators

dev_tool.decorators

F = TypeVar('F', bound=(Callable[..., Any])) module-attribute

log = logging.getLogger(__name__) module-attribute

is_developer_token

A decorator that checks if the developer token file exists and contains a token.

Parameters:

  • func (F) –

    The function to decorate.

Returns:

  • F

    The decorated function.

Raises:

  • FileNotFoundError

    If the token file does not exist or is empty.

Source code in dev_tool/decorators.py
def is_developer_token(func: F) -> F:
    """
    A decorator that checks if the developer token file exists and contains a token.

    :param func: The function to decorate.
    :return: The decorated function.
    :raises FileNotFoundError: If the token file does not exist or is empty.
    """

    @wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        if not DEVELOPER_TOKEN.exists():
            message = f'The developer token file not found at: {DEVELOPER_TOKEN}'
            CONTEXT.notification.error_banner(message)

            log.exception(message)
            raise FileNotFoundError(message)

        token = CONTEXT.configuration.get_developer_token()

        if not token:
            message = f'The developer token file is empty: {DEVELOPER_TOKEN}'
            CONTEXT.notification.error_banner(message)

            log.exception(message)
            raise FileNotFoundError(message)

        return func(*args, **kwargs)

    return cast('F', wrapper)

is_django_environment_variables

A decorator that checks if required Django environment variables are set.

Parameters:

  • func (F) –

    The function to decorate.

Returns:

  • F

    The decorated function.

Raises:

  • MissingDjangoEnvironmentVariableError

    If any required variables are missing.

Source code in dev_tool/decorators.py
def is_django_environment_variables(func: F) -> F:
    """
    A decorator that checks if required Django environment variables are set.

    :param func: The function to decorate.
    :return: The decorated function.
    :raises MissingDjangoEnvironmentVariableError: If any required variables are missing.
    """

    @wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        required: tuple[str, ...] = (
            'DJANGO_SETTINGS_MODULE',
            'DJANGO_DEBUG',
            'DATABASE_HOST',
            'DATABASE_PORT',
            'DATABASE_NAME',
            'DATABASE_USER',
            'DATABASE_PASSWORD',
        )

        is_set = is_in_env_variables(required)

        if not is_set:
            message = (
                f'You are missing the following '
                f'environment variables: {required}'
            )

            CONTEXT.notification.error_banner(message)

            log.exception(message)
            raise MissingDjangoEnvironmentVariableError(message)

        return func(*args, **kwargs)

    return cast('F', wrapper)

is_postgres_environment_variables

A decorator that checks if required PostgreSQL environment variables are set.

Parameters:

  • func (F) –

    The function to decorate.

Returns:

  • F

    The decorated function.

Raises:

  • MissingPostgresEnvironmentVariableError

    If any required variables are missing.

Source code in dev_tool/decorators.py
def is_postgres_environment_variables(func: F) -> F:
    """
    A decorator that checks if required PostgreSQL environment variables are set.

    :param func: The function to decorate.
    :return: The decorated function.
    :raises MissingPostgresEnvironmentVariableError: If any required variables are missing.
    """

    @wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        required: tuple[str, ...] = (
            'DATABASE_PORT',
            'DATABASE_USER',
            'DATABASE_PASSWORD',
        )

        is_set = is_in_env_variables(required)

        if not is_set:
            message = (
                f'You are missing the following '
                f'environment variables: {required}'
            )

            CONTEXT.notification.error_banner(message)

            log.exception(message)
            raise MissingPostgresEnvironmentVariableError(message)

        return func(*args, **kwargs)

    return cast('F', wrapper)

is_pyproject

A decorator that checks if the pyproject.toml file exists.

Parameters:

  • func (F) –

    The function to decorate.

Returns:

  • F

    The decorated function.

Raises:

  • FileNotFoundError

    If the pyproject.toml file does not exist.

Source code in dev_tool/decorators.py
def is_pyproject(func: F) -> F:
    """
    A decorator that checks if the pyproject.toml file exists.

    :param func: The function to decorate.
    :return: The decorated function.
    :raises FileNotFoundError: If the pyproject.toml file does not exist.
    """

    @wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        if not PYPROJECT.exists():
            message = f'"{PYPROJECT}" does not exist. Please create it.'
            CONTEXT.notification.error_banner(message)

            log.exception(message)
            raise FileNotFoundError(message)

        return func(*args, **kwargs)

    return cast('F', wrapper)

is_settings

A decorator that checks if Django settings are correctly configured.

Parameters:

  • func (F) –

    The function to decorate.

Returns:

  • F

    The decorated function.

Raises:

  • RuntimeError

    If Django settings are not correctly configured.

Source code in dev_tool/decorators.py
def is_settings(func: F) -> F:
    """
    A decorator that checks if Django settings are correctly configured.

    :param func: The function to decorate.
    :return: The decorated function.
    :raises RuntimeError: If Django settings are not correctly configured.
    """

    @wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        errors = []
        settings = kwargs.get('settings')

        if not settings:
            settings = args[0].settings

        base = str(BASE)

        if base not in sys.path:
            sys.path.insert(0, base)

        try:
            importlib.import_module(settings)
        except ModuleNotFoundError as e:
            errors.append(f'Settings module "{settings}" cannot be imported: {e}')

        manage = BASE / 'manage.py'

        if not manage.is_file():
            errors.append(f"'manage.py' not found in the base directory: {BASE}")

        if errors:
            for error in errors:
                CONTEXT.notification.error_banner(error)
                log.exception(error)

            message = 'Please fix the above errors and try again.'
            raise RuntimeError(message)

        return func(*args, **kwargs)

    return cast('F', wrapper)

is_virtual_environment

A decorator that checks if a virtual environment exists.

Parameters:

  • func (F) –

    The function to decorate.

Returns:

  • F

    The decorated function, or None if the virtual environment does not exist.

Source code in dev_tool/decorators.py
def is_virtual_environment(func: F) -> F:
    """
    A decorator that checks if a virtual environment exists.

    :param func: The function to decorate.
    :return: The decorated function, or None if the virtual environment does not exist.
    """

    @wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        if not VENV.exists():
            message = 'The virtual environment does not exist...'
            CONTEXT.notification.error_banner(message)

            log.debug(message)

            return None

        return func(*args, **kwargs)

    return cast('F', wrapper)

retry_on_failure

A decorator that retries a function on failure.

Parameters:

  • maximum_retries (int, default: 10 ) –

    The maximum number of retry attempts.

  • delay (int, default: 2 ) –

    The delay in seconds between retry attempts.

  • exceptions (tuple[type[Exception], ...], default: (OperationalError,) ) –

    The exceptions to catch and retry on.

Returns:

  • Callable[[F], F]

    The decorated function.

Source code in dev_tool/decorators.py
def retry_on_failure(
    maximum_retries: int = 10,
    delay: int = 2,
    exceptions: tuple[type[Exception], ...] = (psycopg2.OperationalError,)
) -> Callable[[F], F]:
    """
    A decorator that retries a function on failure.

    :param maximum_retries: The maximum number of retry attempts.
    :param delay: The delay in seconds between retry attempts.
    :param exceptions: The exceptions to catch and retry on.
    :return: The decorated function.
    """

    def decorator(func: F) -> F:
        @wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            retries = 0

            while retries < maximum_retries:
                try:
                    return func(*args, **kwargs)
                except exceptions:
                    retries = retries + 1

                    message = (
                        f'Attempt {retries}/{maximum_retries}. '
                        f'Retrying in {delay} seconds...'
                    )

                    CONTEXT.notification.warning_text(message)

                    log.debug(message)

                    time.sleep(delay)
                except Exception:
                    raise

            message = f'Failed after {maximum_retries} attempts.'
            CONTEXT.notification.error_banner(message)

            log.exception(message)
            raise RetryExceededError(message)

        return cast('F', wrapper)

    return decorator