Skip to content

Commit

Permalink
chore: Improve terminal output (#335)
Browse files Browse the repository at this point in the history
Implementing the following changes:
1. Add debug log level (colored cyan)
2. Make error messages print to stderr instead of stdout
3. include "[seCureLI] [\<log level\>] " prefix to messages
4. Update default log level from ERROR to WARN
5. Move log level enum to separate class and use more consistently
  • Loading branch information
tdurk93 authored Dec 11, 2023
1 parent 57aea2d commit a986da5
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 44 deletions.
70 changes: 47 additions & 23 deletions secureli/abstractions/echo.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from abc import ABC, abstractmethod
from enum import Enum
from typing import Optional
from typing import IO, Optional

import sys
import typer

from secureli.utilities.logging import EchoLevel


class Color(str, Enum):
BLACK = "black"
Expand All @@ -25,18 +28,31 @@ class EchoAbstraction(ABC):
"""

def __init__(self, level: str):
self.print_enabled = level != "OFF"
self.info_enabled = level in ["DEBUG", "INFO"]
self.warn_enabled = level in ["DEBUG", "INFO", "WARN"]
self.error_enabled = level in ["DEBUG", "INFO", "WARN", "ERROR"]
self.print_enabled = level != EchoLevel.off
self.debug_enabled = level == EchoLevel.debug
self.info_enabled = level in [EchoLevel.debug, EchoLevel.info]
self.warn_enabled = level in [EchoLevel.debug, EchoLevel.info, EchoLevel.warn]
self.error_enabled = level in [
EchoLevel.debug,
EchoLevel.info,
EchoLevel.warn,
EchoLevel.error,
]

@abstractmethod
def _echo(self, message: str, color: Optional[Color] = None, bold: bool = False):
def _echo(
self,
message: str,
color: Optional[Color] = None,
bold: bool = False,
fd: IO = sys.stdout,
):
"""
Print the provided message to the terminal with the associated color and weight
:param message: The message to print
:param color: The color to use
:param bold: Whether to make this message appear bold or not
:param fd: A file descriptor, defaults to stdout
"""
pass

Expand All @@ -59,10 +75,16 @@ def print(self, message: str, color: Optional[Color] = None, bold: bool = False)
:param color: The color to use
:param bold: Whether to make this message appear bold or not
"""
if not self.print_enabled:
return
if self.print_enabled:
self._echo(message, color, bold)

self._echo(message, color, bold)
def debug(self, message: str) -> None:
"""
Prints the message to the terminal in light blue and bold
:param message: The debug message to print
"""
if self.debug_enabled:
self._echo(f"[DEBUG] {message}", color=Color.CYAN, bold=True)

def info(self, message: str, color: Optional[Color] = None, bold: bool = False):
"""
Expand All @@ -71,42 +93,44 @@ def info(self, message: str, color: Optional[Color] = None, bold: bool = False):
:param color: The color to use
:param bold: Whether to make this message appear bold or not
"""
if not self.info_enabled:
return

self._echo(message, color, bold)
if self.info_enabled:
self._echo(f"[INFO] {message}", color, bold)

def error(self, message: str):
"""
Prints the provided message to the terminal in red and bold
:param message: The error message to print
"""
if not self.error_enabled:
return
self._echo(message, color=Color.RED, bold=True)
if self.error_enabled:
self._echo(f"[ERROR] {message}", color=Color.RED, bold=True, fd=sys.stderr)

def warning(self, message: str):
"""
Prints the provided message to the terminal in red and bold
:param message: The error message to print
"""
if not self.warn_enabled:
return
self._echo(message, color=Color.YELLOW, bold=False)
if self.warn_enabled:
self._echo(f"[WARN] {message}", color=Color.YELLOW, bold=False)


class TyperEcho(EchoAbstraction):
"""
Encapsulates the Typer dependency for printing purposes, allows us to render stuff to the screen.
"""

def __init__(self, level: str):
def __init__(self, level: str) -> None:
super().__init__(level)

def _echo(self, message: str, color: Optional[Color] = None, bold: bool = False):
def _echo(
self,
message: str,
color: Optional[Color] = None,
bold: bool = False,
fd: IO = sys.stdout,
) -> None:
fg = color.value if color else None
message = typer.style(message, fg=fg, bold=bold)
typer.echo(message)
message = typer.style(f"[seCureLI] {message}", fg=fg, bold=bold)
typer.echo(message, file=fd)

def confirm(self, message: str, default_response: Optional[bool] = False) -> bool:
return typer.confirm(message, default=default_response, show_default=True)
20 changes: 4 additions & 16 deletions secureli/repositories/settings.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from enum import Enum
from pathlib import Path
from typing import Optional
from pydantic import BaseModel, BaseSettings, Field
from secureli.utilities.logging import EchoLevel

import yaml
from pydantic import BaseModel, BaseSettings, Field


default_ignored_extensions = [
# Images
Expand Down Expand Up @@ -68,25 +69,12 @@ class RepoFilesSettings(BaseSettings):
exclude_file_patterns: list[str] = Field(default=[])


class EchoLevel(str, Enum):
debug = "DEBUG"
info = "INFO"
warn = "WARN"
error = "ERROR"

def __str__(self) -> str:
return self.value

def __repr__(self) -> str:
return self.__str__()


class EchoSettings(BaseSettings):
"""
Settings that affect how seCureLI provides information to the user.
"""

level: EchoLevel = Field(default=EchoLevel.error)
level: EchoLevel = Field(default=EchoLevel.warn)


class LanguageSupportSettings(BaseSettings):
Expand Down
15 changes: 15 additions & 0 deletions secureli/utilities/logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from enum import Enum


class EchoLevel(str, Enum):
debug = "DEBUG"
info = "INFO"
warn = "WARN"
error = "ERROR"
off = "OFF"

def __str__(self) -> str:
return self.value

def __repr__(self) -> str:
return self.__str__()
9 changes: 4 additions & 5 deletions tests/abstractions/test_typer_echo.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from pytest_mock import MockerFixture
from secureli.abstractions.echo import TyperEcho, Color
from unittest.mock import MagicMock, ANY

import pytest
from pytest_mock import MockerFixture

from secureli.abstractions.echo import TyperEcho, Color


@pytest.fixture()
Expand Down Expand Up @@ -55,7 +54,7 @@ def test_that_typer_echo_stylizes_message(
typer_echo.info(mock_echo_text)

mock_typer_style.assert_called_once()
mock_typer_echo.assert_called_once_with(mock_echo_text)
mock_typer_echo.assert_called_once_with(mock_echo_text, file=ANY)


def test_that_typer_echo_stylizes_message_when_printing(
Expand All @@ -67,7 +66,7 @@ def test_that_typer_echo_stylizes_message_when_printing(
typer_echo.print(mock_echo_text)

mock_typer_style.assert_called_once()
mock_typer_echo.assert_called_once_with(mock_echo_text)
mock_typer_echo.assert_called_once_with(mock_echo_text, file=ANY)


def test_that_typer_echo_does_not_even_print_when_off(
Expand Down

0 comments on commit a986da5

Please sign in to comment.