Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc7e56106e | ||
|
|
90e5179980 | ||
|
|
9db39003c4 | ||
|
|
4ffe372434 | ||
|
|
a00c27c465 | ||
|
|
1f7f4b8d53 | ||
|
|
baca79ce82 | ||
|
|
4265be6430 | ||
|
|
c16b086467 | ||
|
|
48a98c0206 | ||
|
|
f1788f057f | ||
|
|
0ad8883809 | ||
|
|
51e9b1ce7c | ||
|
|
0d3104f60a | ||
|
|
d29f827fc9 |
@@ -1,19 +1,20 @@
|
||||
# MARK: Project info
|
||||
[project]
|
||||
name = "corelibs"
|
||||
version = "0.37.0"
|
||||
version = "0.42.0"
|
||||
description = "Collection of utils for Python scripts"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"corelibs-datetime>=1.0.1",
|
||||
"corelibs-enum-base>=1.0.0",
|
||||
"corelibs-text-colors>=1.0.0",
|
||||
"corelibs-var>=1.0.0",
|
||||
"cryptography>=46.0.3",
|
||||
"jmespath>=1.0.1",
|
||||
"jsonpath-ng>=1.7.0",
|
||||
"psutil>=7.0.0",
|
||||
"requests>=2.32.4",
|
||||
"requests[proxy]>=2.32.4",
|
||||
]
|
||||
|
||||
# MARK: build system
|
||||
@@ -33,12 +34,14 @@ publish-url = "https://git.egplusww.jp/api/packages/PyPI/pypi"
|
||||
corelibs-enum-base = { index = "opj-pypi" }
|
||||
corelibs-datetime = { index = "opj-pypi" }
|
||||
corelibs-var = { index = "opj-pypi" }
|
||||
corelibs-text-colors = { index = "opj-pypi" }
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"deepdiff>=8.6.1",
|
||||
"pytest>=8.4.1",
|
||||
"pytest-cov>=6.2.1",
|
||||
"typing-extensions>=4.15.0",
|
||||
]
|
||||
|
||||
# MARK: Python linting
|
||||
|
||||
@@ -93,6 +93,8 @@ class SettingsLoader:
|
||||
entry_split_char: dict[str, str] = {}
|
||||
# entries that should be converted
|
||||
entry_convert: dict[str, str] = {}
|
||||
# no args to set
|
||||
args_overrride: list[str] = []
|
||||
# all the settings for the config id given
|
||||
settings: dict[str, dict[str, Any]] = {
|
||||
config_id: {},
|
||||
@@ -162,6 +164,8 @@ class SettingsLoader:
|
||||
f"[!] In [{config_id}] the split character setup for entry failed: {check}: {e}",
|
||||
'CRITICAL'
|
||||
)) from e
|
||||
if check == "args_override:yes":
|
||||
args_overrride.append(key)
|
||||
if skip:
|
||||
continue
|
||||
settings[config_id][key] = [
|
||||
@@ -185,9 +189,16 @@ class SettingsLoader:
|
||||
error: bool = False
|
||||
for entry, validate in config_validate.items():
|
||||
# if we have command line option set, this one overrides config
|
||||
if self.__get_arg(entry):
|
||||
if (args_entry := self.__get_arg(entry)) is not None:
|
||||
self.__print(f"[*] Command line option override for: {entry}", 'WARNING')
|
||||
settings[config_id][entry] = self.args.get(entry)
|
||||
if (
|
||||
# only set if flagged as allowed override from args
|
||||
entry in args_overrride and
|
||||
(isinstance(args_entry, list) and entry_split_char.get(entry)) or
|
||||
(not isinstance(args_entry, list) and not entry_split_char.get(entry))
|
||||
):
|
||||
# args is list, but entry has not split, do not set
|
||||
settings[config_id][entry] = args_entry
|
||||
# validate checks
|
||||
for check in validate:
|
||||
# CHECKS
|
||||
@@ -277,10 +288,8 @@ class SettingsLoader:
|
||||
elif convert_type in ["float", "any"] and is_float(settings[config_id][entry]):
|
||||
settings[config_id][entry] = float(settings[config_id][entry])
|
||||
elif convert_type in ["bool", "any"] and (
|
||||
settings[config_id][entry] == "true" or
|
||||
settings[config_id][entry] == "True" or
|
||||
settings[config_id][entry] == "false" or
|
||||
settings[config_id][entry] == "False"
|
||||
settings[config_id][entry].lower() == "true" or
|
||||
settings[config_id][entry].lower() == "false"
|
||||
):
|
||||
try:
|
||||
settings[config_id][entry] = str_to_bool(settings[config_id][entry])
|
||||
|
||||
@@ -13,8 +13,8 @@ from pathlib import Path
|
||||
import atexit
|
||||
from enum import Flag, auto
|
||||
from typing import MutableMapping, TextIO, TypedDict, Any, TYPE_CHECKING, cast
|
||||
from corelibs_text_colors.text_colors import Colors
|
||||
from corelibs.logging_handling.logging_level_handling.logging_level import LoggingLevel
|
||||
from corelibs.string_handling.text_colors import Colors
|
||||
from corelibs.debug_handling.debug_helpers import call_stack, exception_stack
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -32,6 +32,7 @@ class ConsoleFormat(Flag):
|
||||
FILE = auto()
|
||||
FUNCTION = auto()
|
||||
LINENO = auto()
|
||||
LEVEL = auto()
|
||||
|
||||
|
||||
class ConsoleFormatSettings:
|
||||
@@ -43,14 +44,17 @@ class ConsoleFormatSettings:
|
||||
ConsoleFormat.NAME |
|
||||
ConsoleFormat.FILE |
|
||||
ConsoleFormat.FUNCTION |
|
||||
ConsoleFormat.LINENO
|
||||
ConsoleFormat.LINENO |
|
||||
ConsoleFormat.LEVEL
|
||||
)
|
||||
# show time with no time zone, file and line
|
||||
CONDENSED = ConsoleFormat.TIME | ConsoleFormat.FILE | ConsoleFormat.LINENO
|
||||
# only time
|
||||
MINIMAL = ConsoleFormat.TIME
|
||||
# show time with no time zone, file, line and level
|
||||
CONDENSED = ConsoleFormat.TIME | ConsoleFormat.FILE | ConsoleFormat.LINENO | ConsoleFormat.LEVEL
|
||||
# only time and level
|
||||
MINIMAL = ConsoleFormat.TIME | ConsoleFormat.LEVEL
|
||||
# only level
|
||||
BARE = ConsoleFormat.LEVEL
|
||||
# only message
|
||||
BARE = ConsoleFormat(0)
|
||||
NONE = ConsoleFormat(0)
|
||||
|
||||
@staticmethod
|
||||
def from_string(setting_str: str, default: ConsoleFormat | None = None) -> ConsoleFormat | None:
|
||||
@@ -445,6 +449,9 @@ class Log(LogParent):
|
||||
logger setup
|
||||
"""
|
||||
|
||||
CONSOLE_HANDLER: str = 'stream_handler'
|
||||
FILE_HANDLER: str = 'file_handler'
|
||||
|
||||
# spacer lenght characters and the character
|
||||
SPACER_CHAR: str = '='
|
||||
SPACER_LENGTH: int = 32
|
||||
@@ -510,13 +517,13 @@ class Log(LogParent):
|
||||
# in the file writer too, for the ones where color is set BEFORE the format
|
||||
# Any is logging.StreamHandler, logging.FileHandler and all logging.handlers.*
|
||||
self.handlers: dict[str, Any] = {}
|
||||
self.add_handler('file_handler', self.__create_file_handler(
|
||||
'file_handler', self.log_settings['log_level_file'], log_path)
|
||||
self.add_handler(self.FILE_HANDLER, self.__create_file_handler(
|
||||
self.FILE_HANDLER, self.log_settings['log_level_file'], log_path)
|
||||
)
|
||||
if self.log_settings['console_enabled']:
|
||||
# console
|
||||
self.add_handler('stream_handler', self.__create_console_handler(
|
||||
'stream_handler',
|
||||
self.add_handler(self.CONSOLE_HANDLER, self.__create_console_handler(
|
||||
self.CONSOLE_HANDLER,
|
||||
self.log_settings['log_level_console'],
|
||||
console_format_type=self.log_settings['console_format_type'],
|
||||
))
|
||||
@@ -613,19 +620,17 @@ class Log(LogParent):
|
||||
self.handlers[handler_name] = handler
|
||||
return True
|
||||
|
||||
# MARK: console handler
|
||||
def __create_console_handler(
|
||||
self, handler_name: str,
|
||||
log_level_console: LoggingLevel = LoggingLevel.WARNING,
|
||||
filter_exceptions: bool = True,
|
||||
console_format_type: ConsoleFormat = ConsoleFormatSettings.ALL,
|
||||
) -> logging.StreamHandler[TextIO]:
|
||||
# console logger
|
||||
if not self.validate_log_level(log_level_console):
|
||||
log_level_console = self.DEFAULT_LOG_LEVEL_CONSOLE
|
||||
console_handler = logging.StreamHandler()
|
||||
print(f"Console format type: {console_format_type}")
|
||||
# build the format string based on what flags are set
|
||||
# MARK: console logger format
|
||||
def __build_console_format_from_string(self, console_format_type: ConsoleFormat) -> str:
|
||||
"""
|
||||
Build console format string from the given console format type
|
||||
|
||||
Arguments:
|
||||
console_format_type {ConsoleFormat} -- _description_
|
||||
|
||||
Returns:
|
||||
str -- _description_
|
||||
"""
|
||||
format_string = ''
|
||||
# time part if any of the times are requested
|
||||
if (
|
||||
@@ -654,17 +659,23 @@ class Log(LogParent):
|
||||
set_group.append('%(lineno)d')
|
||||
format_string += ':'.join(set_group)
|
||||
format_string += '] '
|
||||
# always level + message
|
||||
format_string += '<%(levelname)s> %(message)s'
|
||||
# basic date, but this will be overridden to ISO in formatTime
|
||||
# format_date = "%Y-%m-%d %H:%M:%S"
|
||||
# color or not
|
||||
if self.log_settings['console_color_output_enabled']:
|
||||
# formatter_console = CustomConsoleFormatter(format_string, datefmt=format_date)
|
||||
formatter_console = CustomConsoleFormatter(format_string)
|
||||
else:
|
||||
# formatter_console = logging.Formatter(format_string, datefmt=format_date)
|
||||
formatter_console = logging.Formatter(format_string)
|
||||
# level if wanted
|
||||
if ConsoleFormat.LEVEL in console_format_type:
|
||||
format_string += '<%(levelname)s> '
|
||||
# always message
|
||||
format_string += '%(message)s'
|
||||
return format_string
|
||||
|
||||
def __set_time_format_for_console_formatter(
|
||||
self, formatter_console: CustomConsoleFormatter | logging.Formatter, console_format_type: ConsoleFormat
|
||||
) -> None:
|
||||
"""
|
||||
Format time for a given format handler, this is for console format only
|
||||
|
||||
Arguments:
|
||||
formatter_console {CustomConsoleFormatter | logging.Formatter} -- _description_
|
||||
console_format_type {ConsoleFormat} -- _description_
|
||||
"""
|
||||
# default for TIME is milliseconds
|
||||
# if we have multiple set, the smallest precision wins
|
||||
if ConsoleFormat.TIME_MICROSECONDS in console_format_type:
|
||||
@@ -701,11 +712,80 @@ class Log(LogParent):
|
||||
.fromtimestamp(record.created)
|
||||
.isoformat(sep=" ", timespec=iso_precision)
|
||||
)
|
||||
|
||||
def __set_console_formatter(self, console_format_type: ConsoleFormat) -> CustomConsoleFormatter | logging.Formatter:
|
||||
"""
|
||||
Build the full formatter and return it
|
||||
|
||||
Arguments:
|
||||
console_format_type {ConsoleFormat} -- _description_
|
||||
|
||||
Returns:
|
||||
CustomConsoleFormatter | logging.Formatter -- _description_
|
||||
"""
|
||||
format_string = self.__build_console_format_from_string(console_format_type)
|
||||
if self.log_settings['console_color_output_enabled']:
|
||||
# formatter_console = CustomConsoleFormatter(format_string, datefmt=format_date)
|
||||
formatter_console = CustomConsoleFormatter(format_string)
|
||||
else:
|
||||
# formatter_console = logging.Formatter(format_string, datefmt=format_date)
|
||||
formatter_console = logging.Formatter(format_string)
|
||||
self.__set_time_format_for_console_formatter(formatter_console, console_format_type)
|
||||
self.log_settings['console_format_type'] = console_format_type
|
||||
return formatter_console
|
||||
|
||||
# MARK: console handler update
|
||||
def update_console_formatter(
|
||||
self,
|
||||
console_format_type: ConsoleFormat,
|
||||
):
|
||||
"""
|
||||
Update the console formatter for format layout and time stamp format
|
||||
|
||||
Arguments:
|
||||
console_format_type {ConsoleFormat} -- _description_
|
||||
"""
|
||||
# skip if console not enabled
|
||||
if not self.log_settings['console_enabled']:
|
||||
return
|
||||
# skip if format has not changed
|
||||
if self.log_settings['console_format_type'] == console_format_type:
|
||||
return
|
||||
# update the formatter
|
||||
self.handlers[self.CONSOLE_HANDLER].setFormatter(
|
||||
self.__set_console_formatter(console_format_type)
|
||||
)
|
||||
|
||||
# MARK: console handler
|
||||
def __create_console_handler(
|
||||
self, handler_name: str,
|
||||
log_level_console: LoggingLevel = LoggingLevel.WARNING,
|
||||
filter_exceptions: bool = True,
|
||||
console_format_type: ConsoleFormat = ConsoleFormatSettings.ALL,
|
||||
) -> logging.StreamHandler[TextIO]:
|
||||
# console logger
|
||||
if not self.validate_log_level(log_level_console):
|
||||
log_level_console = self.DEFAULT_LOG_LEVEL_CONSOLE
|
||||
console_handler = logging.StreamHandler()
|
||||
# print(f"Console format type: {console_format_type}")
|
||||
# build the format string based on what flags are set
|
||||
# format_string = self.__build_console_format_from_string(console_format_type)
|
||||
# # basic date, but this will be overridden to ISO in formatTime
|
||||
# # format_date = "%Y-%m-%d %H:%M:%S"
|
||||
# # color or not
|
||||
# if self.log_settings['console_color_output_enabled']:
|
||||
# # formatter_console = CustomConsoleFormatter(format_string, datefmt=format_date)
|
||||
# formatter_console = CustomConsoleFormatter(format_string)
|
||||
# else:
|
||||
# # formatter_console = logging.Formatter(format_string, datefmt=format_date)
|
||||
# formatter_console = logging.Formatter(format_string)
|
||||
# # set the time format
|
||||
# self.__set_time_format_for_console_formatter(formatter_console, console_format_type)
|
||||
console_handler.set_name(handler_name)
|
||||
console_handler.setLevel(log_level_console.name)
|
||||
# do not show exceptions logs on console
|
||||
console_handler.addFilter(CustomHandlerFilter('console', filter_exceptions))
|
||||
console_handler.setFormatter(formatter_console)
|
||||
console_handler.setFormatter(self.__set_console_formatter(console_format_type))
|
||||
return console_handler
|
||||
|
||||
# MARK: file handler
|
||||
|
||||
@@ -5,152 +5,14 @@ Set colors with print(f"something {Colors.yellow}colorful{Colors.end})
|
||||
bold + underline + color combinations are possible.
|
||||
"""
|
||||
|
||||
from warnings import deprecated
|
||||
from corelibs_text_colors.text_colors import Colors as ColorsNew
|
||||
|
||||
class Colors:
|
||||
|
||||
@deprecated("Use src.corelibs_text_colors.text_colors instead")
|
||||
class Colors(ColorsNew):
|
||||
"""
|
||||
ANSI colors defined
|
||||
"""
|
||||
# General sets, these should not be accessd
|
||||
__BOLD = '\033[1m'
|
||||
__UNDERLINE = '\033[4m'
|
||||
__END = '\033[0m'
|
||||
__RESET = '\033[0m'
|
||||
# Define ANSI color codes as class attributes
|
||||
__BLACK = "\033[30m"
|
||||
__RED = "\033[31m"
|
||||
__GREEN = "\033[32m"
|
||||
__YELLOW = "\033[33m"
|
||||
__BLUE = "\033[34m"
|
||||
__MAGENTA = "\033[35m"
|
||||
__CYAN = "\033[36m"
|
||||
__WHITE = "\033[37m"
|
||||
|
||||
# Define bold/bright versions of the colors
|
||||
__BLACK_BOLD = "\033[1;30m"
|
||||
__RED_BOLD = "\033[1;31m"
|
||||
__GREEN_BOLD = "\033[1;32m"
|
||||
__YELLOW_BOLD = "\033[1;33m"
|
||||
__BLUE_BOLD = "\033[1;34m"
|
||||
__MAGENTA_BOLD = "\033[1;35m"
|
||||
__CYAN_BOLD = "\033[1;36m"
|
||||
__WHITE_BOLD = "\033[1;37m"
|
||||
|
||||
# BRIGHT, alternative
|
||||
__BLACK_BRIGHT = '\033[90m'
|
||||
__RED_BRIGHT = '\033[91m'
|
||||
__GREEN_BRIGHT = '\033[92m'
|
||||
__YELLOW_BRIGHT = '\033[93m'
|
||||
__BLUE_BRIGHT = '\033[94m'
|
||||
__MAGENTA_BRIGHT = '\033[95m'
|
||||
__CYAN_BRIGHT = '\033[96m'
|
||||
__WHITE_BRIGHT = '\033[97m'
|
||||
|
||||
# set access vars
|
||||
bold = __BOLD
|
||||
underline = __UNDERLINE
|
||||
end = __END
|
||||
reset = __RESET
|
||||
# normal
|
||||
black = __BLACK
|
||||
red = __RED
|
||||
green = __GREEN
|
||||
yellow = __YELLOW
|
||||
blue = __BLUE
|
||||
magenta = __MAGENTA
|
||||
cyan = __CYAN
|
||||
white = __WHITE
|
||||
# bold
|
||||
black_bold = __BLACK_BOLD
|
||||
red_bold = __RED_BOLD
|
||||
green_bold = __GREEN_BOLD
|
||||
yellow_bold = __YELLOW_BOLD
|
||||
blue_bold = __BLUE_BOLD
|
||||
magenta_bold = __MAGENTA_BOLD
|
||||
cyan_bold = __CYAN_BOLD
|
||||
white_bold = __WHITE_BOLD
|
||||
# bright
|
||||
black_bright = __BLACK_BRIGHT
|
||||
red_bright = __RED_BRIGHT
|
||||
green_bright = __GREEN_BRIGHT
|
||||
yellow_bright = __YELLOW_BRIGHT
|
||||
blue_bright = __BLUE_BRIGHT
|
||||
magenta_bright = __MAGENTA_BRIGHT
|
||||
cyan_bright = __CYAN_BRIGHT
|
||||
white_bright = __WHITE_BRIGHT
|
||||
|
||||
@staticmethod
|
||||
def disable():
|
||||
"""
|
||||
No colors
|
||||
"""
|
||||
Colors.bold = ''
|
||||
Colors.underline = ''
|
||||
Colors.end = ''
|
||||
Colors.reset = ''
|
||||
# normal
|
||||
Colors.black = ''
|
||||
Colors.red = ''
|
||||
Colors.green = ''
|
||||
Colors.yellow = ''
|
||||
Colors.blue = ''
|
||||
Colors.magenta = ''
|
||||
Colors.cyan = ''
|
||||
Colors.white = ''
|
||||
# bold/bright
|
||||
Colors.black_bold = ''
|
||||
Colors.red_bold = ''
|
||||
Colors.green_bold = ''
|
||||
Colors.yellow_bold = ''
|
||||
Colors.blue_bold = ''
|
||||
Colors.magenta_bold = ''
|
||||
Colors.cyan_bold = ''
|
||||
Colors.white_bold = ''
|
||||
# bold/bright alt
|
||||
Colors.black_bright = ''
|
||||
Colors.red_bright = ''
|
||||
Colors.green_bright = ''
|
||||
Colors.yellow_bright = ''
|
||||
Colors.blue_bright = ''
|
||||
Colors.magenta_bright = ''
|
||||
Colors.cyan_bright = ''
|
||||
Colors.white_bright = ''
|
||||
|
||||
@staticmethod
|
||||
def reset_colors():
|
||||
"""
|
||||
reset colors to the original ones
|
||||
"""
|
||||
# set access vars
|
||||
Colors.bold = Colors.__BOLD
|
||||
Colors.underline = Colors.__UNDERLINE
|
||||
Colors.end = Colors.__END
|
||||
Colors.reset = Colors.__RESET
|
||||
# normal
|
||||
Colors.black = Colors.__BLACK
|
||||
Colors.red = Colors.__RED
|
||||
Colors.green = Colors.__GREEN
|
||||
Colors.yellow = Colors.__YELLOW
|
||||
Colors.blue = Colors.__BLUE
|
||||
Colors.magenta = Colors.__MAGENTA
|
||||
Colors.cyan = Colors.__CYAN
|
||||
Colors.white = Colors.__WHITE
|
||||
# bold
|
||||
Colors.black_bold = Colors.__BLACK_BOLD
|
||||
Colors.red_bold = Colors.__RED_BOLD
|
||||
Colors.green_bold = Colors.__GREEN_BOLD
|
||||
Colors.yellow_bold = Colors.__YELLOW_BOLD
|
||||
Colors.blue_bold = Colors.__BLUE_BOLD
|
||||
Colors.magenta_bold = Colors.__MAGENTA_BOLD
|
||||
Colors.cyan_bold = Colors.__CYAN_BOLD
|
||||
Colors.white_bold = Colors.__WHITE_BOLD
|
||||
# bright
|
||||
Colors.black_bright = Colors.__BLACK_BRIGHT
|
||||
Colors.red_bright = Colors.__RED_BRIGHT
|
||||
Colors.green_bright = Colors.__GREEN_BRIGHT
|
||||
Colors.yellow_bright = Colors.__YELLOW_BRIGHT
|
||||
Colors.blue_bright = Colors.__BLUE_BRIGHT
|
||||
Colors.magenta_bright = Colors.__MAGENTA_BRIGHT
|
||||
Colors.cyan_bright = Colors.__CYAN_BRIGHT
|
||||
Colors.white_bright = Colors.__WHITE_BRIGHT
|
||||
|
||||
# __END__
|
||||
|
||||
@@ -2,82 +2,24 @@
|
||||
Enum base classes
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
from warnings import deprecated
|
||||
from typing import Any
|
||||
# from corelibs_enum_base.enum_base import EnumBase as CorelibsEnumBase
|
||||
import warnings
|
||||
from corelibs_enum_base.enum_base import EnumBase as CorelibsEnumBase
|
||||
|
||||
|
||||
class EnumBase(Enum):
|
||||
class EnumBase(CorelibsEnumBase):
|
||||
"""
|
||||
base for enum
|
||||
|
||||
.. deprecated::
|
||||
Use corelibs_enum_base.EnumBase instead
|
||||
DEPRECATED: Use corelibs_enum_base.EnumBase instead
|
||||
|
||||
lookup_any and from_any will return "EnumBase" and the sub class name
|
||||
run the return again to "from_any" to get a clean value, or cast it
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@deprecated("Use corelibs_enum_base.EnumBase instead")
|
||||
def lookup_key(cls, enum_key: str):
|
||||
"""Lookup from key side (must be string)"""
|
||||
# if there is a ":", then this is legacy, replace with ___
|
||||
if ":" in enum_key:
|
||||
enum_key = enum_key.replace(':', '___')
|
||||
try:
|
||||
return cls[enum_key.upper()]
|
||||
except KeyError as e:
|
||||
raise ValueError(f"Invalid key: {enum_key}") from e
|
||||
except AttributeError as e:
|
||||
raise ValueError(f"Invalid key: {enum_key}") from e
|
||||
|
||||
@classmethod
|
||||
@deprecated("Use corelibs_enum_base.EnumBase instead")
|
||||
def lookup_value(cls, enum_value: Any):
|
||||
"""Lookup through value side"""
|
||||
try:
|
||||
return cls(enum_value)
|
||||
except ValueError as e:
|
||||
raise ValueError(f"Invalid value: {enum_value}") from e
|
||||
# At the module level, issue a deprecation warning
|
||||
warnings.warn("Use corelibs_enum_base.EnumBase instead", DeprecationWarning, stacklevel=2)
|
||||
|
||||
@classmethod
|
||||
@deprecated("Use corelibs_enum_base.EnumBase instead")
|
||||
def from_any(cls, enum_any: Any):
|
||||
"""
|
||||
This only works in the following order
|
||||
-> class itself, as is
|
||||
-> str, assume key lookup
|
||||
-> if failed try other
|
||||
|
||||
Arguments:
|
||||
enum_any {Any} -- _description_
|
||||
|
||||
Returns:
|
||||
_type_ -- _description_
|
||||
"""
|
||||
if isinstance(enum_any, cls):
|
||||
return enum_any
|
||||
# try key first if it is string
|
||||
# if failed try value
|
||||
if isinstance(enum_any, str):
|
||||
try:
|
||||
return cls.lookup_key(enum_any)
|
||||
except (ValueError, AttributeError):
|
||||
try:
|
||||
return cls.lookup_value(enum_any)
|
||||
except ValueError as e:
|
||||
raise ValueError(f"Could not find as key or value: {enum_any}") from e
|
||||
return cls.lookup_value(enum_any)
|
||||
|
||||
@deprecated("Use corelibs_enum_base.EnumBase instead")
|
||||
def to_value(self) -> Any:
|
||||
"""Convert to value"""
|
||||
return self.value
|
||||
|
||||
@deprecated("Use corelibs_enum_base.EnumBase instead")
|
||||
def to_lower_case(self) -> str:
|
||||
"""return lower case"""
|
||||
return self.name.lower()
|
||||
|
||||
@deprecated("Use corelibs_enum_base.EnumBase instead")
|
||||
def __str__(self) -> str:
|
||||
"""return [Enum].NAME like it was called with .name"""
|
||||
return self.name
|
||||
# __EMD__
|
||||
|
||||
15
src/corelibs/var_handling/enum_base.pyi
Normal file
15
src/corelibs/var_handling/enum_base.pyi
Normal file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Enum base classes [STPUB]
|
||||
"""
|
||||
|
||||
from typing_extensions import deprecated
|
||||
from corelibs_enum_base.enum_base import EnumBase as CorelibsEnumBase
|
||||
|
||||
|
||||
@deprecated("Use corelibs_enum_base.EnumBase instead")
|
||||
class EnumBase(CorelibsEnumBase):
|
||||
"""
|
||||
base for enum
|
||||
lookup_any and from_any will return "EnumBase" and the sub class name
|
||||
run the return again to "from_any" to get a clean value, or cast it
|
||||
"""
|
||||
@@ -1,7 +1,12 @@
|
||||
[TestA]
|
||||
foo=bar
|
||||
overload_from_args=bar
|
||||
foobar=1
|
||||
bar=st
|
||||
arg_overload=should_not_be_set_because_of_command_line_is_list
|
||||
arg_overload_list=too,be,long
|
||||
arg_overload_not_set=this should not be set because of override flag
|
||||
just_values=too,be,long
|
||||
some_match=foo
|
||||
some_match_list=foo,bar
|
||||
test_list=a,b,c,d f, g h
|
||||
|
||||
@@ -39,7 +39,10 @@ def main():
|
||||
|
||||
sl = SettingsLoader(
|
||||
{
|
||||
'foo': 'OVERLOAD'
|
||||
'overload_from_args': 'OVERLOAD from ARGS',
|
||||
'arg_overload': ['should', 'not', 'be', 'set'],
|
||||
'arg_overload_list': ['overload', 'this', 'list'],
|
||||
'arg_overload_not_set': "DO_NOT_SET",
|
||||
},
|
||||
ROOT_PATH.joinpath(CONFIG_DIR, CONFIG_FILE),
|
||||
log=log
|
||||
@@ -50,9 +53,11 @@ def main():
|
||||
config_load,
|
||||
{
|
||||
# "doesnt": ["split:,"],
|
||||
"foo": ["mandatory:yes"],
|
||||
"overload_from_args": ["args_override:yes", "mandatory:yes"],
|
||||
"foobar": ["check:int"],
|
||||
"bar": ["mandatory:yes"],
|
||||
"arg_overload_list": ["args_override:yes", "split:,",],
|
||||
"arg_overload_not_set": [],
|
||||
"some_match": ["matching:foo|bar"],
|
||||
"some_match_list": ["split:,", "matching:foo|bar"],
|
||||
"test_list": [
|
||||
|
||||
@@ -25,12 +25,13 @@ def main():
|
||||
"log_level_file": 'DEBUG',
|
||||
# "console_color_output_enabled": False,
|
||||
"per_run_log": True,
|
||||
# Set console log type, must be sent as value for ConsoleFormat or bitwise of ConsoleFormatType
|
||||
# "console_format_type": ConsoleFormatSettings.BARE,
|
||||
# "console_format_type": ConsoleFormatSettings.NONE,
|
||||
# "console_format_type": ConsoleFormatSettings.MINIMAL,
|
||||
# "console_format_type": ConsoleFormatType.TIME_MICROSECONDS | ConsoleFormatType.NAME,
|
||||
# "console_format_type": ConsoleFormatType.NAME,
|
||||
"console_format_type": ConsoleFormat.TIME | ConsoleFormat.TIMEZONE | ConsoleFormat.LINENO,
|
||||
"console_format_type": ConsoleFormat.TIME_MICROSECONDS | ConsoleFormat.NAME | ConsoleFormat.LEVEL,
|
||||
# "console_format_type": ConsoleFormat.NAME,
|
||||
# "console_format_type": (
|
||||
# ConsoleFormat.TIME | ConsoleFormat.TIMEZONE | ConsoleFormat.LINENO | ConsoleFormat.LEVEL
|
||||
# ),
|
||||
}
|
||||
)
|
||||
logn = Logger(log.get_logger_settings())
|
||||
@@ -104,10 +105,16 @@ def main():
|
||||
|
||||
for key, handler in log.handlers.items():
|
||||
print(f"Handler (handlers) [{key}] {handler} -> {handler.level} -> {LoggingLevel.from_any(handler.level)}")
|
||||
log.set_log_level('stream_handler', LoggingLevel.ERROR)
|
||||
log.set_log_level(Log.CONSOLE_HANDLER, LoggingLevel.ERROR)
|
||||
log.logger.warning('[NORMAL] Invisible Warning test: %s', log.logger.name)
|
||||
log.logger.error('[NORMAL] Visible Error test: %s', log.logger.name)
|
||||
# log.handlers['stream_handler'].se
|
||||
|
||||
log.set_log_level(Log.CONSOLE_HANDLER, LoggingLevel.DEBUG)
|
||||
log.debug('Current logging format: %s', log.log_settings['console_format_type'])
|
||||
log.update_console_formatter(ConsoleFormat.TIME | ConsoleFormat.LINENO)
|
||||
log.info('Does hit show less')
|
||||
log.update_console_formatter(ConsoleFormat.TIME | ConsoleFormat.LINENO)
|
||||
log.info('Does hit show less B')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -528,6 +528,123 @@ class TestLoadSettings:
|
||||
captured = capsys.readouterr()
|
||||
assert "Command line option override" in captured.out
|
||||
|
||||
def test_load_settings_args_no_flag(self, tmp_path: Path, capsys: CaptureFixture[str]):
|
||||
"""Test default behavior (no args_override:yes) with list argument that has split"""
|
||||
config_file = tmp_path / "test.ini"
|
||||
config_file.write_text("[TestSection]\nvalue=a,b,c\n")
|
||||
|
||||
loader = SettingsLoader(
|
||||
args={"value": ["x", "y", "z"]},
|
||||
config_file=config_file
|
||||
)
|
||||
result = loader.load_settings(
|
||||
"TestSection",
|
||||
{"value": ["split:,"]}
|
||||
)
|
||||
|
||||
# Without args_override:yes flag, should use config value (no override)
|
||||
assert result["value"] == ["a", "b", "c"]
|
||||
captured = capsys.readouterr()
|
||||
# Message is printed but without args_override:yes flag, override doesn't happen
|
||||
assert "Command line option override" in captured.out
|
||||
|
||||
def test_load_settings_args_list_no_split(self, tmp_path: Path, capsys: CaptureFixture[str]):
|
||||
"""Test that list arguments without split entry are skipped"""
|
||||
config_file = tmp_path / "test.ini"
|
||||
config_file.write_text("[TestSection]\nvalue=config_value\n")
|
||||
|
||||
loader = SettingsLoader(
|
||||
args={"value": ["arg1", "arg2", "arg3"]},
|
||||
config_file=config_file
|
||||
)
|
||||
result = loader.load_settings(
|
||||
"TestSection",
|
||||
{"value": []}
|
||||
)
|
||||
|
||||
# Should keep config value since args is list but no split defined
|
||||
assert result["value"] == "config_value"
|
||||
captured = capsys.readouterr()
|
||||
# Message is printed but list without split prevents the override
|
||||
assert "Command line option override" in captured.out
|
||||
|
||||
def test_load_settings_args_list_with_split(self, tmp_path: Path, capsys: CaptureFixture[str]):
|
||||
"""Test that list arguments with split entry and args_override:yes are applied"""
|
||||
config_file = tmp_path / "test.ini"
|
||||
config_file.write_text("[TestSection]\nvalue=a,b,c\n")
|
||||
|
||||
loader = SettingsLoader(
|
||||
args={"value": ["arg1", "arg2", "arg3"]},
|
||||
config_file=config_file
|
||||
)
|
||||
result = loader.load_settings(
|
||||
"TestSection",
|
||||
{"value": ["split:,", "args_override:yes"]}
|
||||
)
|
||||
|
||||
# Should use args value because split is defined AND args_override:yes is set
|
||||
assert result["value"] == ["arg1", "arg2", "arg3"]
|
||||
captured = capsys.readouterr()
|
||||
assert "Command line option override" in captured.out
|
||||
|
||||
def test_load_settings_args_no_with_mandatory(self, tmp_path: Path, capsys: CaptureFixture[str]):
|
||||
"""Test default behavior (no args_override:yes) with mandatory field and list args with split"""
|
||||
config_file = tmp_path / "test.ini"
|
||||
config_file.write_text("[TestSection]\nvalue=config1,config2\n")
|
||||
|
||||
loader = SettingsLoader(
|
||||
args={"value": ["arg1", "arg2"]},
|
||||
config_file=config_file
|
||||
)
|
||||
result = loader.load_settings(
|
||||
"TestSection",
|
||||
{"value": ["mandatory:yes", "split:,"]}
|
||||
)
|
||||
|
||||
# Should use config value because args_override:yes is not set (default: no override)
|
||||
assert result["value"] == ["config1", "config2"]
|
||||
captured = capsys.readouterr()
|
||||
# Message is printed but without args_override:yes flag, override doesn't happen
|
||||
assert "Command line option override" in captured.out
|
||||
|
||||
def test_load_settings_args_no_with_mandatory_valid(self, tmp_path: Path, capsys: CaptureFixture[str]):
|
||||
"""Test default behavior with string args (always overrides due to current logic)"""
|
||||
config_file = tmp_path / "test.ini"
|
||||
config_file.write_text("[TestSection]\nvalue=config_value\n")
|
||||
|
||||
loader = SettingsLoader(
|
||||
args={"value": "arg_value"},
|
||||
config_file=config_file
|
||||
)
|
||||
result = loader.load_settings(
|
||||
"TestSection",
|
||||
{"value": ["mandatory:yes"]}
|
||||
)
|
||||
|
||||
# Current behavior: string args without split always override (regardless of args_override:yes)
|
||||
assert result["value"] == "arg_value"
|
||||
captured = capsys.readouterr()
|
||||
assert "Command line option override" in captured.out
|
||||
|
||||
def test_load_settings_args_string_no_split(self, tmp_path: Path, capsys: CaptureFixture[str]):
|
||||
"""Test that string arguments with args_override:yes work normally"""
|
||||
config_file = tmp_path / "test.ini"
|
||||
config_file.write_text("[TestSection]\nvalue=config_value\n")
|
||||
|
||||
loader = SettingsLoader(
|
||||
args={"value": "arg_value"},
|
||||
config_file=config_file
|
||||
)
|
||||
result = loader.load_settings(
|
||||
"TestSection",
|
||||
{"value": ["args_override:yes"]}
|
||||
)
|
||||
|
||||
# Should use args value for non-list args with args_override:yes
|
||||
assert result["value"] == "arg_value"
|
||||
captured = capsys.readouterr()
|
||||
assert "Command line option override" in captured.out
|
||||
|
||||
def test_load_settings_no_config_file_with_args(self, tmp_path: Path):
|
||||
"""Test loading settings without config file but with mandatory args"""
|
||||
config_file = tmp_path / "missing.ini"
|
||||
@@ -704,5 +821,48 @@ class TestComplexScenarios:
|
||||
assert result["emails"] == "test@example.com"
|
||||
assert result["date"] == "2025-01-15"
|
||||
|
||||
def test_args_no_and_list_skip_combination(self, tmp_path: Path, capsys: CaptureFixture[str]):
|
||||
"""Test combination of args_override:yes flag and list argument skip behavior"""
|
||||
config_file = tmp_path / "test.ini"
|
||||
config_file.write_text(
|
||||
"[Settings]\n"
|
||||
"no_override=a,b,c\n"
|
||||
"list_no_split=config_list\n"
|
||||
"list_with_split=x,y,z\n"
|
||||
"normal=config_normal\n"
|
||||
)
|
||||
|
||||
loader = SettingsLoader(
|
||||
args={
|
||||
"no_override": ["arg1", "arg2"],
|
||||
"list_no_split": ["arg1", "arg2"],
|
||||
"list_with_split": ["p", "q", "r"],
|
||||
"normal": "arg_normal"
|
||||
},
|
||||
config_file=config_file
|
||||
)
|
||||
result = loader.load_settings(
|
||||
"Settings",
|
||||
{
|
||||
"no_override": ["split:,"],
|
||||
"list_no_split": [],
|
||||
"list_with_split": ["split:,", "args_override:yes"],
|
||||
"normal": ["args_override:yes"]
|
||||
}
|
||||
)
|
||||
|
||||
# Should use config value (no args_override:yes flag for list with split)
|
||||
assert result["no_override"] == ["a", "b", "c"]
|
||||
# Should use config value because args is list without split
|
||||
assert result["list_no_split"] == "config_list"
|
||||
# Should use args value because split is defined AND args_override:yes is set
|
||||
assert result["list_with_split"] == ["p", "q", "r"]
|
||||
# Should use args value (args_override:yes set for string arg)
|
||||
assert result["normal"] == "arg_normal"
|
||||
|
||||
captured = capsys.readouterr()
|
||||
# Should see override messages (even though list_no_split prints, it doesn't apply)
|
||||
assert "Command line option override" in captured.out
|
||||
|
||||
|
||||
# __END__
|
||||
|
||||
@@ -153,6 +153,19 @@ class TestLogSettingsParsing:
|
||||
|
||||
assert log.log_settings["console_format_type"] == ConsoleFormatSettings.BARE
|
||||
|
||||
def test_parse_console_format_type_none(self, tmp_log_path: Path):
|
||||
"""Test parsing with console_format_type set to NONE"""
|
||||
settings: dict[str, Any] = {
|
||||
"console_format_type": ConsoleFormatSettings.NONE,
|
||||
}
|
||||
log = Log(
|
||||
log_path=tmp_log_path,
|
||||
log_name="test",
|
||||
log_settings=settings # type: ignore
|
||||
)
|
||||
|
||||
assert log.log_settings["console_format_type"] == ConsoleFormatSettings.NONE
|
||||
|
||||
def test_parse_console_format_type_invalid(self, tmp_log_path: Path):
|
||||
"""Test parsing with invalid console_format_type raises TypeError"""
|
||||
settings: dict[str, Any] = {
|
||||
@@ -207,6 +220,11 @@ class TestConsoleFormatSettingsFromString:
|
||||
result = ConsoleFormatSettings.from_string('BARE')
|
||||
assert result == ConsoleFormatSettings.BARE
|
||||
|
||||
def test_from_string_none(self):
|
||||
"""Test from_string with 'NONE' returns correct format"""
|
||||
result = ConsoleFormatSettings.from_string('NONE')
|
||||
assert result == ConsoleFormatSettings.NONE
|
||||
|
||||
def test_from_string_invalid_returns_none(self):
|
||||
"""Test from_string with invalid string returns None"""
|
||||
result = ConsoleFormatSettings.from_string('INVALID')
|
||||
@@ -234,6 +252,7 @@ class TestConsoleFormatSettingsFromString:
|
||||
("CONDENSED", ConsoleFormatSettings.CONDENSED),
|
||||
("MINIMAL", ConsoleFormatSettings.MINIMAL),
|
||||
("BARE", ConsoleFormatSettings.BARE),
|
||||
("NONE", ConsoleFormatSettings.NONE),
|
||||
])
|
||||
def test_from_string_all_valid_settings(self, setting_name: str, expected: Any):
|
||||
"""Test from_string with all valid setting names"""
|
||||
|
||||
@@ -140,4 +140,172 @@ class TestCustomConsoleFormatter:
|
||||
assert "Critical message" in result
|
||||
assert "CRITICAL" in result
|
||||
|
||||
|
||||
# MARK: Test update_console_formatter
|
||||
class TestUpdateConsoleFormatter:
|
||||
"""Test cases for update_console_formatter method"""
|
||||
|
||||
def test_update_console_formatter_to_minimal(self, log_instance: Log):
|
||||
"""Test updating console formatter to MINIMAL format"""
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.MINIMAL)
|
||||
|
||||
# Get the console handler's formatter
|
||||
console_handler = log_instance.handlers[log_instance.CONSOLE_HANDLER]
|
||||
formatter = console_handler.formatter
|
||||
|
||||
# Verify formatter was updated
|
||||
assert formatter is not None
|
||||
|
||||
def test_update_console_formatter_to_condensed(self, log_instance: Log):
|
||||
"""Test updating console formatter to CONDENSED format"""
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.CONDENSED)
|
||||
|
||||
# Get the console handler's formatter
|
||||
console_handler = log_instance.handlers[log_instance.CONSOLE_HANDLER]
|
||||
formatter = console_handler.formatter
|
||||
|
||||
# Verify formatter was updated
|
||||
assert formatter is not None
|
||||
|
||||
def test_update_console_formatter_to_bare(self, log_instance: Log):
|
||||
"""Test updating console formatter to BARE format"""
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.BARE)
|
||||
|
||||
# Get the console handler's formatter
|
||||
console_handler = log_instance.handlers[log_instance.CONSOLE_HANDLER]
|
||||
formatter = console_handler.formatter
|
||||
|
||||
# Verify formatter was updated
|
||||
assert formatter is not None
|
||||
|
||||
def test_update_console_formatter_to_none(self, log_instance: Log):
|
||||
"""Test updating console formatter to NONE format"""
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.NONE)
|
||||
|
||||
# Get the console handler's formatter
|
||||
console_handler = log_instance.handlers[log_instance.CONSOLE_HANDLER]
|
||||
formatter = console_handler.formatter
|
||||
|
||||
# Verify formatter was updated
|
||||
assert formatter is not None
|
||||
|
||||
def test_update_console_formatter_to_all(self, log_instance: Log):
|
||||
"""Test updating console formatter to ALL format"""
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.ALL)
|
||||
|
||||
# Get the console handler's formatter
|
||||
console_handler = log_instance.handlers[log_instance.CONSOLE_HANDLER]
|
||||
formatter = console_handler.formatter
|
||||
|
||||
# Verify formatter was updated
|
||||
assert formatter is not None
|
||||
|
||||
def test_update_console_formatter_when_disabled(
|
||||
self, tmp_log_path: Path, basic_log_settings: LogSettings
|
||||
):
|
||||
"""Test that update_console_formatter does nothing when console is disabled"""
|
||||
# Disable console
|
||||
basic_log_settings['console_enabled'] = False
|
||||
log = Log(
|
||||
log_path=tmp_log_path,
|
||||
log_name="test_log",
|
||||
log_settings=basic_log_settings
|
||||
)
|
||||
|
||||
# This should not raise an error and should return early
|
||||
log.update_console_formatter(ConsoleFormatSettings.MINIMAL)
|
||||
|
||||
# Verify console handler doesn't exist
|
||||
assert log.CONSOLE_HANDLER not in log.handlers
|
||||
|
||||
def test_update_console_formatter_with_color_enabled(
|
||||
self, tmp_log_path: Path, basic_log_settings: LogSettings
|
||||
):
|
||||
"""Test updating console formatter with color output enabled"""
|
||||
basic_log_settings['console_color_output_enabled'] = True
|
||||
log = Log(
|
||||
log_path=tmp_log_path,
|
||||
log_name="test_log",
|
||||
log_settings=basic_log_settings
|
||||
)
|
||||
|
||||
log.update_console_formatter(ConsoleFormatSettings.MINIMAL)
|
||||
|
||||
# Get the console handler's formatter
|
||||
console_handler = log.handlers[log.CONSOLE_HANDLER]
|
||||
formatter = console_handler.formatter
|
||||
|
||||
# Verify formatter is CustomConsoleFormatter when colors enabled
|
||||
assert isinstance(formatter, CustomConsoleFormatter)
|
||||
|
||||
def test_update_console_formatter_without_color(self, log_instance: Log):
|
||||
"""Test updating console formatter without color output"""
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.MINIMAL)
|
||||
|
||||
# Get the console handler's formatter
|
||||
console_handler = log_instance.handlers[log_instance.CONSOLE_HANDLER]
|
||||
formatter = console_handler.formatter
|
||||
|
||||
# Verify formatter is standard Formatter when colors disabled
|
||||
assert isinstance(formatter, logging.Formatter)
|
||||
# But not the colored version
|
||||
assert not isinstance(formatter, CustomConsoleFormatter)
|
||||
|
||||
def test_update_console_formatter_multiple_times(self, log_instance: Log):
|
||||
"""Test updating console formatter multiple times"""
|
||||
# Update to MINIMAL
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.MINIMAL)
|
||||
console_handler = log_instance.handlers[log_instance.CONSOLE_HANDLER]
|
||||
formatter1 = console_handler.formatter
|
||||
|
||||
# Update to CONDENSED
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.CONDENSED)
|
||||
formatter2 = console_handler.formatter
|
||||
|
||||
# Update to ALL
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.ALL)
|
||||
formatter3 = console_handler.formatter
|
||||
|
||||
# Verify each update created a new formatter
|
||||
assert formatter1 is not formatter2
|
||||
assert formatter2 is not formatter3
|
||||
assert formatter1 is not formatter3
|
||||
|
||||
def test_update_console_formatter_preserves_handler_level(self, log_instance: Log):
|
||||
"""Test that updating formatter preserves the handler's log level"""
|
||||
original_level = log_instance.handlers[log_instance.CONSOLE_HANDLER].level
|
||||
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.MINIMAL)
|
||||
|
||||
new_level = log_instance.handlers[log_instance.CONSOLE_HANDLER].level
|
||||
assert original_level == new_level
|
||||
|
||||
def test_update_console_formatter_format_output(
|
||||
self, log_instance: Log, caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
"""Test that updated formatter actually affects log output"""
|
||||
# Set to BARE format (message only)
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.BARE)
|
||||
|
||||
# Configure caplog to capture at the appropriate level
|
||||
with caplog.at_level(logging.WARNING):
|
||||
log_instance.warning("Test warning message")
|
||||
|
||||
# Verify message was logged
|
||||
assert "Test warning message" in caplog.text
|
||||
|
||||
def test_update_console_formatter_none_format_output(
|
||||
self, log_instance: Log, caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
"""Test that NONE formatter outputs only the message without any formatting"""
|
||||
# Set to NONE format (message only, no level indicator)
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.NONE)
|
||||
|
||||
# Configure caplog to capture at the appropriate level
|
||||
with caplog.at_level(logging.WARNING):
|
||||
log_instance.warning("Test warning message")
|
||||
|
||||
# Verify message was logged
|
||||
assert "Test warning message" in caplog.text
|
||||
|
||||
# __END__
|
||||
|
||||
@@ -12,6 +12,7 @@ from corelibs.logging_handling.log import (
|
||||
LogParent,
|
||||
LogSettings,
|
||||
ConsoleFormatSettings,
|
||||
ConsoleFormat,
|
||||
)
|
||||
from corelibs.logging_handling.logging_level_handling.logging_level import LoggingLevel
|
||||
|
||||
@@ -108,4 +109,101 @@ class TestHandlerManagement:
|
||||
result2 = log.add_handler("test", handler2)
|
||||
assert result2 is False
|
||||
|
||||
def test_change_console_format_to_minimal(self, log_instance: Log):
|
||||
"""Test changing console handler format to MINIMAL"""
|
||||
original_formatter = log_instance.handlers[log_instance.CONSOLE_HANDLER].formatter
|
||||
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.MINIMAL)
|
||||
|
||||
new_formatter = log_instance.handlers[log_instance.CONSOLE_HANDLER].formatter
|
||||
assert new_formatter is not original_formatter
|
||||
assert new_formatter is not None
|
||||
|
||||
def test_change_console_format_to_condensed(self, log_instance: Log):
|
||||
"""Test changing console handler format to CONDENSED"""
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.CONDENSED)
|
||||
|
||||
formatter = log_instance.handlers[log_instance.CONSOLE_HANDLER].formatter
|
||||
assert formatter is not None
|
||||
|
||||
def test_change_console_format_to_bare(self, log_instance: Log):
|
||||
"""Test changing console handler format to BARE"""
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.BARE)
|
||||
|
||||
formatter = log_instance.handlers[log_instance.CONSOLE_HANDLER].formatter
|
||||
assert formatter is not None
|
||||
|
||||
def test_change_console_format_to_none(self, log_instance: Log):
|
||||
"""Test changing console handler format to NONE"""
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.NONE)
|
||||
|
||||
formatter = log_instance.handlers[log_instance.CONSOLE_HANDLER].formatter
|
||||
assert formatter is not None
|
||||
|
||||
def test_change_console_format_to_all(self, log_instance: Log):
|
||||
"""Test changing console handler format to ALL"""
|
||||
# Start with a different format
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.MINIMAL)
|
||||
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.ALL)
|
||||
|
||||
formatter = log_instance.handlers[log_instance.CONSOLE_HANDLER].formatter
|
||||
assert formatter is not None
|
||||
|
||||
def test_change_console_format_multiple_times(self, log_instance: Log):
|
||||
"""Test changing console handler format multiple times"""
|
||||
formatters: list[logging.Formatter | None] = []
|
||||
|
||||
for format_type in [
|
||||
ConsoleFormatSettings.MINIMAL,
|
||||
ConsoleFormatSettings.CONDENSED,
|
||||
ConsoleFormatSettings.BARE,
|
||||
ConsoleFormatSettings.NONE,
|
||||
ConsoleFormatSettings.ALL,
|
||||
]:
|
||||
log_instance.update_console_formatter(format_type)
|
||||
formatter = log_instance.handlers[log_instance.CONSOLE_HANDLER].formatter
|
||||
formatters.append(formatter)
|
||||
assert formatter is not None
|
||||
|
||||
# Verify each formatter is unique (new instance each time)
|
||||
for i, formatter in enumerate(formatters):
|
||||
for j, other_formatter in enumerate(formatters):
|
||||
if i != j:
|
||||
assert formatter is not other_formatter
|
||||
|
||||
def test_change_console_format_with_disabled_console(
|
||||
self, tmp_log_path: Path, basic_log_settings: LogSettings
|
||||
):
|
||||
"""Test changing console format when console is disabled does nothing"""
|
||||
basic_log_settings['console_enabled'] = False
|
||||
log = Log(
|
||||
log_path=tmp_log_path,
|
||||
log_name="test_log",
|
||||
log_settings=basic_log_settings
|
||||
)
|
||||
|
||||
# Should not raise error, just return early
|
||||
log.update_console_formatter(ConsoleFormatSettings.MINIMAL)
|
||||
|
||||
# Console handler should not exist
|
||||
assert log.CONSOLE_HANDLER not in log.handlers
|
||||
|
||||
@pytest.mark.parametrize("format_type", [
|
||||
ConsoleFormatSettings.ALL,
|
||||
ConsoleFormatSettings.CONDENSED,
|
||||
ConsoleFormatSettings.MINIMAL,
|
||||
ConsoleFormatSettings.BARE,
|
||||
ConsoleFormatSettings.NONE,
|
||||
])
|
||||
def test_change_console_format_parametrized(
|
||||
self, log_instance: Log, format_type: ConsoleFormat # type: ignore
|
||||
):
|
||||
"""Test changing console format with all format types"""
|
||||
log_instance.update_console_formatter(format_type)
|
||||
|
||||
formatter = log_instance.handlers[log_instance.CONSOLE_HANDLER].formatter
|
||||
assert formatter is not None
|
||||
assert isinstance(formatter, logging.Formatter)
|
||||
|
||||
# __END__
|
||||
|
||||
@@ -1,516 +0,0 @@
|
||||
"""
|
||||
PyTest: string_handling/text_colors
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from corelibs.string_handling.text_colors import Colors
|
||||
|
||||
|
||||
class TestColorsInitialState:
|
||||
"""Tests for Colors class initial state"""
|
||||
|
||||
def test_bold_initial_value(self):
|
||||
"""Test that bold has correct ANSI code"""
|
||||
assert Colors.bold == '\033[1m'
|
||||
|
||||
def test_underline_initial_value(self):
|
||||
"""Test that underline has correct ANSI code"""
|
||||
assert Colors.underline == '\033[4m'
|
||||
|
||||
def test_end_initial_value(self):
|
||||
"""Test that end has correct ANSI code"""
|
||||
assert Colors.end == '\033[0m'
|
||||
|
||||
def test_reset_initial_value(self):
|
||||
"""Test that reset has correct ANSI code"""
|
||||
assert Colors.reset == '\033[0m'
|
||||
|
||||
|
||||
class TestColorsNormal:
|
||||
"""Tests for normal color ANSI codes"""
|
||||
|
||||
def test_black_normal(self):
|
||||
"""Test black color code"""
|
||||
assert Colors.black == "\033[30m"
|
||||
|
||||
def test_red_normal(self):
|
||||
"""Test red color code"""
|
||||
assert Colors.red == "\033[31m"
|
||||
|
||||
def test_green_normal(self):
|
||||
"""Test green color code"""
|
||||
assert Colors.green == "\033[32m"
|
||||
|
||||
def test_yellow_normal(self):
|
||||
"""Test yellow color code"""
|
||||
assert Colors.yellow == "\033[33m"
|
||||
|
||||
def test_blue_normal(self):
|
||||
"""Test blue color code"""
|
||||
assert Colors.blue == "\033[34m"
|
||||
|
||||
def test_magenta_normal(self):
|
||||
"""Test magenta color code"""
|
||||
assert Colors.magenta == "\033[35m"
|
||||
|
||||
def test_cyan_normal(self):
|
||||
"""Test cyan color code"""
|
||||
assert Colors.cyan == "\033[36m"
|
||||
|
||||
def test_white_normal(self):
|
||||
"""Test white color code"""
|
||||
assert Colors.white == "\033[37m"
|
||||
|
||||
|
||||
class TestColorsBold:
|
||||
"""Tests for bold color ANSI codes"""
|
||||
|
||||
def test_black_bold(self):
|
||||
"""Test black bold color code"""
|
||||
assert Colors.black_bold == "\033[1;30m"
|
||||
|
||||
def test_red_bold(self):
|
||||
"""Test red bold color code"""
|
||||
assert Colors.red_bold == "\033[1;31m"
|
||||
|
||||
def test_green_bold(self):
|
||||
"""Test green bold color code"""
|
||||
assert Colors.green_bold == "\033[1;32m"
|
||||
|
||||
def test_yellow_bold(self):
|
||||
"""Test yellow bold color code"""
|
||||
assert Colors.yellow_bold == "\033[1;33m"
|
||||
|
||||
def test_blue_bold(self):
|
||||
"""Test blue bold color code"""
|
||||
assert Colors.blue_bold == "\033[1;34m"
|
||||
|
||||
def test_magenta_bold(self):
|
||||
"""Test magenta bold color code"""
|
||||
assert Colors.magenta_bold == "\033[1;35m"
|
||||
|
||||
def test_cyan_bold(self):
|
||||
"""Test cyan bold color code"""
|
||||
assert Colors.cyan_bold == "\033[1;36m"
|
||||
|
||||
def test_white_bold(self):
|
||||
"""Test white bold color code"""
|
||||
assert Colors.white_bold == "\033[1;37m"
|
||||
|
||||
|
||||
class TestColorsBright:
|
||||
"""Tests for bright color ANSI codes"""
|
||||
|
||||
def test_black_bright(self):
|
||||
"""Test black bright color code"""
|
||||
assert Colors.black_bright == '\033[90m'
|
||||
|
||||
def test_red_bright(self):
|
||||
"""Test red bright color code"""
|
||||
assert Colors.red_bright == '\033[91m'
|
||||
|
||||
def test_green_bright(self):
|
||||
"""Test green bright color code"""
|
||||
assert Colors.green_bright == '\033[92m'
|
||||
|
||||
def test_yellow_bright(self):
|
||||
"""Test yellow bright color code"""
|
||||
assert Colors.yellow_bright == '\033[93m'
|
||||
|
||||
def test_blue_bright(self):
|
||||
"""Test blue bright color code"""
|
||||
assert Colors.blue_bright == '\033[94m'
|
||||
|
||||
def test_magenta_bright(self):
|
||||
"""Test magenta bright color code"""
|
||||
assert Colors.magenta_bright == '\033[95m'
|
||||
|
||||
def test_cyan_bright(self):
|
||||
"""Test cyan bright color code"""
|
||||
assert Colors.cyan_bright == '\033[96m'
|
||||
|
||||
def test_white_bright(self):
|
||||
"""Test white bright color code"""
|
||||
assert Colors.white_bright == '\033[97m'
|
||||
|
||||
|
||||
class TestColorsDisable:
|
||||
"""Tests for Colors.disable() method"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Reset colors before each test"""
|
||||
Colors.reset_colors()
|
||||
|
||||
def teardown_method(self):
|
||||
"""Reset colors after each test"""
|
||||
Colors.reset_colors()
|
||||
|
||||
def test_disable_bold_and_underline(self):
|
||||
"""Test that disable() sets bold and underline to empty strings"""
|
||||
Colors.disable()
|
||||
assert Colors.bold == ''
|
||||
assert Colors.underline == ''
|
||||
|
||||
def test_disable_end_and_reset(self):
|
||||
"""Test that disable() sets end and reset to empty strings"""
|
||||
Colors.disable()
|
||||
assert Colors.end == ''
|
||||
assert Colors.reset == ''
|
||||
|
||||
def test_disable_normal_colors(self):
|
||||
"""Test that disable() sets all normal colors to empty strings"""
|
||||
Colors.disable()
|
||||
assert Colors.black == ''
|
||||
assert Colors.red == ''
|
||||
assert Colors.green == ''
|
||||
assert Colors.yellow == ''
|
||||
assert Colors.blue == ''
|
||||
assert Colors.magenta == ''
|
||||
assert Colors.cyan == ''
|
||||
assert Colors.white == ''
|
||||
|
||||
def test_disable_bold_colors(self):
|
||||
"""Test that disable() sets all bold colors to empty strings"""
|
||||
Colors.disable()
|
||||
assert Colors.black_bold == ''
|
||||
assert Colors.red_bold == ''
|
||||
assert Colors.green_bold == ''
|
||||
assert Colors.yellow_bold == ''
|
||||
assert Colors.blue_bold == ''
|
||||
assert Colors.magenta_bold == ''
|
||||
assert Colors.cyan_bold == ''
|
||||
assert Colors.white_bold == ''
|
||||
|
||||
def test_disable_bright_colors(self):
|
||||
"""Test that disable() sets all bright colors to empty strings"""
|
||||
Colors.disable()
|
||||
assert Colors.black_bright == ''
|
||||
assert Colors.red_bright == ''
|
||||
assert Colors.green_bright == ''
|
||||
assert Colors.yellow_bright == ''
|
||||
assert Colors.blue_bright == ''
|
||||
assert Colors.magenta_bright == ''
|
||||
assert Colors.cyan_bright == ''
|
||||
assert Colors.white_bright == ''
|
||||
|
||||
def test_disable_all_colors_at_once(self):
|
||||
"""Test that all color attributes are empty after disable()"""
|
||||
Colors.disable()
|
||||
# Check that all public attributes are empty strings
|
||||
for attr in dir(Colors):
|
||||
if not attr.startswith('_') and attr not in ['disable', 'reset_colors']:
|
||||
assert getattr(Colors, attr) == '', f"{attr} should be empty after disable()"
|
||||
|
||||
|
||||
class TestColorsResetColors:
|
||||
"""Tests for Colors.reset_colors() method"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Disable colors before each test"""
|
||||
Colors.disable()
|
||||
|
||||
def teardown_method(self):
|
||||
"""Reset colors after each test"""
|
||||
Colors.reset_colors()
|
||||
|
||||
def test_reset_bold_and_underline(self):
|
||||
"""Test that reset_colors() restores bold and underline"""
|
||||
Colors.reset_colors()
|
||||
assert Colors.bold == '\033[1m'
|
||||
assert Colors.underline == '\033[4m'
|
||||
|
||||
def test_reset_end_and_reset(self):
|
||||
"""Test that reset_colors() restores end and reset"""
|
||||
Colors.reset_colors()
|
||||
assert Colors.end == '\033[0m'
|
||||
assert Colors.reset == '\033[0m'
|
||||
|
||||
def test_reset_normal_colors(self):
|
||||
"""Test that reset_colors() restores all normal colors"""
|
||||
Colors.reset_colors()
|
||||
assert Colors.black == "\033[30m"
|
||||
assert Colors.red == "\033[31m"
|
||||
assert Colors.green == "\033[32m"
|
||||
assert Colors.yellow == "\033[33m"
|
||||
assert Colors.blue == "\033[34m"
|
||||
assert Colors.magenta == "\033[35m"
|
||||
assert Colors.cyan == "\033[36m"
|
||||
assert Colors.white == "\033[37m"
|
||||
|
||||
def test_reset_bold_colors(self):
|
||||
"""Test that reset_colors() restores all bold colors"""
|
||||
Colors.reset_colors()
|
||||
assert Colors.black_bold == "\033[1;30m"
|
||||
assert Colors.red_bold == "\033[1;31m"
|
||||
assert Colors.green_bold == "\033[1;32m"
|
||||
assert Colors.yellow_bold == "\033[1;33m"
|
||||
assert Colors.blue_bold == "\033[1;34m"
|
||||
assert Colors.magenta_bold == "\033[1;35m"
|
||||
assert Colors.cyan_bold == "\033[1;36m"
|
||||
assert Colors.white_bold == "\033[1;37m"
|
||||
|
||||
def test_reset_bright_colors(self):
|
||||
"""Test that reset_colors() restores all bright colors"""
|
||||
Colors.reset_colors()
|
||||
assert Colors.black_bright == '\033[90m'
|
||||
assert Colors.red_bright == '\033[91m'
|
||||
assert Colors.green_bright == '\033[92m'
|
||||
assert Colors.yellow_bright == '\033[93m'
|
||||
assert Colors.blue_bright == '\033[94m'
|
||||
assert Colors.magenta_bright == '\033[95m'
|
||||
assert Colors.cyan_bright == '\033[96m'
|
||||
assert Colors.white_bright == '\033[97m'
|
||||
|
||||
|
||||
class TestColorsDisableAndReset:
|
||||
"""Tests for disable and reset cycle"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Reset colors before each test"""
|
||||
Colors.reset_colors()
|
||||
|
||||
def teardown_method(self):
|
||||
"""Reset colors after each test"""
|
||||
Colors.reset_colors()
|
||||
|
||||
def test_disable_then_reset_cycle(self):
|
||||
"""Test that colors can be disabled and then reset multiple times"""
|
||||
# Initial state
|
||||
original_red = Colors.red
|
||||
|
||||
# Disable
|
||||
Colors.disable()
|
||||
assert Colors.red == ''
|
||||
|
||||
# Reset
|
||||
Colors.reset_colors()
|
||||
assert Colors.red == original_red
|
||||
|
||||
# Disable again
|
||||
Colors.disable()
|
||||
assert Colors.red == ''
|
||||
|
||||
# Reset again
|
||||
Colors.reset_colors()
|
||||
assert Colors.red == original_red
|
||||
|
||||
def test_multiple_disables(self):
|
||||
"""Test that calling disable() multiple times is safe"""
|
||||
Colors.disable()
|
||||
Colors.disable()
|
||||
Colors.disable()
|
||||
assert Colors.red == ''
|
||||
assert Colors.blue == ''
|
||||
|
||||
def test_multiple_resets(self):
|
||||
"""Test that calling reset_colors() multiple times is safe"""
|
||||
Colors.reset_colors()
|
||||
Colors.reset_colors()
|
||||
Colors.reset_colors()
|
||||
assert Colors.red == "\033[31m"
|
||||
assert Colors.blue == "\033[34m"
|
||||
|
||||
|
||||
class TestColorsUsage:
|
||||
"""Tests for practical usage of Colors class"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Reset colors before each test"""
|
||||
Colors.reset_colors()
|
||||
|
||||
def teardown_method(self):
|
||||
"""Reset colors after each test"""
|
||||
Colors.reset_colors()
|
||||
|
||||
def test_colored_string_with_reset(self):
|
||||
"""Test creating a colored string with reset"""
|
||||
result = f"{Colors.red}Error{Colors.end}"
|
||||
assert result == "\033[31mError\033[0m"
|
||||
|
||||
def test_bold_colored_string(self):
|
||||
"""Test creating a bold colored string"""
|
||||
result = f"{Colors.bold}{Colors.yellow}Warning{Colors.end}"
|
||||
assert result == "\033[1m\033[33mWarning\033[0m"
|
||||
|
||||
def test_underline_colored_string(self):
|
||||
"""Test creating an underlined colored string"""
|
||||
result = f"{Colors.underline}{Colors.blue}Info{Colors.end}"
|
||||
assert result == "\033[4m\033[34mInfo\033[0m"
|
||||
|
||||
def test_bold_underline_colored_string(self):
|
||||
"""Test creating a bold and underlined colored string"""
|
||||
result = f"{Colors.bold}{Colors.underline}{Colors.green}Success{Colors.end}"
|
||||
assert result == "\033[1m\033[4m\033[32mSuccess\033[0m"
|
||||
|
||||
def test_multiple_colors_in_string(self):
|
||||
"""Test using multiple colors in one string"""
|
||||
result = f"{Colors.red}Red{Colors.end} {Colors.blue}Blue{Colors.end}"
|
||||
assert result == "\033[31mRed\033[0m \033[34mBlue\033[0m"
|
||||
|
||||
def test_bright_color_usage(self):
|
||||
"""Test using bright color variants"""
|
||||
result = f"{Colors.cyan_bright}Bright Cyan{Colors.end}"
|
||||
assert result == "\033[96mBright Cyan\033[0m"
|
||||
|
||||
def test_bold_color_shortcut(self):
|
||||
"""Test using bold color shortcuts"""
|
||||
result = f"{Colors.red_bold}Bold Red{Colors.end}"
|
||||
assert result == "\033[1;31mBold Red\033[0m"
|
||||
|
||||
def test_disabled_colors_produce_plain_text(self):
|
||||
"""Test that disabled colors produce plain text without ANSI codes"""
|
||||
Colors.disable()
|
||||
result = f"{Colors.red}Error{Colors.end}"
|
||||
assert result == "Error"
|
||||
assert "\033[" not in result
|
||||
|
||||
def test_disabled_bold_underline_produce_plain_text(self):
|
||||
"""Test that disabled formatting produces plain text"""
|
||||
Colors.disable()
|
||||
result = f"{Colors.bold}{Colors.underline}{Colors.green}Success{Colors.end}"
|
||||
assert result == "Success"
|
||||
assert "\033[" not in result
|
||||
|
||||
|
||||
class TestColorsPrivateAttributes:
|
||||
"""Tests to ensure private attributes are not directly accessible"""
|
||||
|
||||
def test_private_bold_not_accessible(self):
|
||||
"""Test that __BOLD is private"""
|
||||
with pytest.raises(AttributeError):
|
||||
_ = Colors.__BOLD
|
||||
|
||||
def test_private_colors_not_accessible(self):
|
||||
"""Test that private color attributes are not accessible"""
|
||||
with pytest.raises(AttributeError):
|
||||
_ = Colors.__RED
|
||||
with pytest.raises(AttributeError):
|
||||
_ = Colors.__GREEN
|
||||
|
||||
|
||||
# Parametrized tests
|
||||
@pytest.mark.parametrize("color_attr,expected_code", [
|
||||
("black", "\033[30m"),
|
||||
("red", "\033[31m"),
|
||||
("green", "\033[32m"),
|
||||
("yellow", "\033[33m"),
|
||||
("blue", "\033[34m"),
|
||||
("magenta", "\033[35m"),
|
||||
("cyan", "\033[36m"),
|
||||
("white", "\033[37m"),
|
||||
])
|
||||
def test_normal_colors_parametrized(color_attr: str, expected_code: str):
|
||||
"""Parametrized test for normal colors"""
|
||||
Colors.reset_colors()
|
||||
assert getattr(Colors, color_attr) == expected_code
|
||||
|
||||
|
||||
@pytest.mark.parametrize("color_attr,expected_code", [
|
||||
("black_bold", "\033[1;30m"),
|
||||
("red_bold", "\033[1;31m"),
|
||||
("green_bold", "\033[1;32m"),
|
||||
("yellow_bold", "\033[1;33m"),
|
||||
("blue_bold", "\033[1;34m"),
|
||||
("magenta_bold", "\033[1;35m"),
|
||||
("cyan_bold", "\033[1;36m"),
|
||||
("white_bold", "\033[1;37m"),
|
||||
])
|
||||
def test_bold_colors_parametrized(color_attr: str, expected_code: str):
|
||||
"""Parametrized test for bold colors"""
|
||||
Colors.reset_colors()
|
||||
assert getattr(Colors, color_attr) == expected_code
|
||||
|
||||
|
||||
@pytest.mark.parametrize("color_attr,expected_code", [
|
||||
("black_bright", '\033[90m'),
|
||||
("red_bright", '\033[91m'),
|
||||
("green_bright", '\033[92m'),
|
||||
("yellow_bright", '\033[93m'),
|
||||
("blue_bright", '\033[94m'),
|
||||
("magenta_bright", '\033[95m'),
|
||||
("cyan_bright", '\033[96m'),
|
||||
("white_bright", '\033[97m'),
|
||||
])
|
||||
def test_bright_colors_parametrized(color_attr: str, expected_code: str):
|
||||
"""Parametrized test for bright colors"""
|
||||
Colors.reset_colors()
|
||||
assert getattr(Colors, color_attr) == expected_code
|
||||
|
||||
|
||||
@pytest.mark.parametrize("color_attr", [
|
||||
"bold", "underline", "end", "reset",
|
||||
"black", "red", "green", "yellow", "blue", "magenta", "cyan", "white",
|
||||
"black_bold", "red_bold", "green_bold", "yellow_bold",
|
||||
"blue_bold", "magenta_bold", "cyan_bold", "white_bold",
|
||||
"black_bright", "red_bright", "green_bright", "yellow_bright",
|
||||
"blue_bright", "magenta_bright", "cyan_bright", "white_bright",
|
||||
])
|
||||
def test_disable_all_attributes_parametrized(color_attr: str):
|
||||
"""Parametrized test that all color attributes are disabled"""
|
||||
Colors.reset_colors()
|
||||
Colors.disable()
|
||||
assert getattr(Colors, color_attr) == ''
|
||||
|
||||
|
||||
@pytest.mark.parametrize("color_attr", [
|
||||
"bold", "underline", "end", "reset",
|
||||
"black", "red", "green", "yellow", "blue", "magenta", "cyan", "white",
|
||||
"black_bold", "red_bold", "green_bold", "yellow_bold",
|
||||
"blue_bold", "magenta_bold", "cyan_bold", "white_bold",
|
||||
"black_bright", "red_bright", "green_bright", "yellow_bright",
|
||||
"blue_bright", "magenta_bright", "cyan_bright", "white_bright",
|
||||
])
|
||||
def test_reset_all_attributes_parametrized(color_attr: str):
|
||||
"""Parametrized test that all color attributes are reset"""
|
||||
Colors.disable()
|
||||
Colors.reset_colors()
|
||||
assert getattr(Colors, color_attr) != ''
|
||||
assert '\033[' in getattr(Colors, color_attr)
|
||||
|
||||
|
||||
# Edge case tests
|
||||
class TestColorsEdgeCases:
|
||||
"""Tests for edge cases and special scenarios"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Reset colors before each test"""
|
||||
Colors.reset_colors()
|
||||
|
||||
def teardown_method(self):
|
||||
"""Reset colors after each test"""
|
||||
Colors.reset_colors()
|
||||
|
||||
def test_colors_class_is_not_instantiable(self):
|
||||
"""Test that Colors class can be instantiated (it's not abstract)"""
|
||||
# The class uses static methods, but can be instantiated
|
||||
instance = Colors()
|
||||
assert isinstance(instance, Colors)
|
||||
|
||||
def test_static_methods_work_on_instance(self):
|
||||
"""Test that static methods work when called on instance"""
|
||||
instance = Colors()
|
||||
instance.disable()
|
||||
assert Colors.red == ''
|
||||
instance.reset_colors()
|
||||
assert Colors.red == "\033[31m"
|
||||
|
||||
def test_concatenation_of_multiple_effects(self):
|
||||
"""Test concatenating multiple color effects"""
|
||||
result = f"{Colors.bold}{Colors.underline}{Colors.red_bright}Test{Colors.reset}"
|
||||
assert "\033[1m" in result # bold
|
||||
assert "\033[4m" in result # underline
|
||||
assert "\033[91m" in result # red bright
|
||||
assert "\033[0m" in result # reset
|
||||
|
||||
def test_empty_string_with_colors(self):
|
||||
"""Test applying colors to empty string"""
|
||||
result = f"{Colors.red}{Colors.end}"
|
||||
assert result == "\033[31m\033[0m"
|
||||
|
||||
def test_nested_color_changes(self):
|
||||
"""Test nested color changes in string"""
|
||||
result = f"{Colors.red}Red {Colors.blue}Blue{Colors.end} Red again{Colors.end}"
|
||||
assert result == "\033[31mRed \033[34mBlue\033[0m Red again\033[0m"
|
||||
|
||||
|
||||
# __END__
|
||||
26
uv.lock
generated
26
uv.lock
generated
@@ -108,11 +108,12 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "corelibs"
|
||||
version = "0.37.0"
|
||||
version = "0.42.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "corelibs-datetime" },
|
||||
{ name = "corelibs-enum-base" },
|
||||
{ name = "corelibs-text-colors" },
|
||||
{ name = "corelibs-var" },
|
||||
{ name = "cryptography" },
|
||||
{ name = "jmespath" },
|
||||
@@ -126,18 +127,20 @@ dev = [
|
||||
{ name = "deepdiff" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-cov" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "corelibs-datetime", specifier = ">=1.0.1", index = "https://git.egplusww.jp/api/packages/PyPI/pypi/simple/" },
|
||||
{ name = "corelibs-enum-base", specifier = ">=1.0.0", index = "https://git.egplusww.jp/api/packages/PyPI/pypi/simple/" },
|
||||
{ name = "corelibs-text-colors", specifier = ">=1.0.0", index = "https://git.egplusww.jp/api/packages/PyPI/pypi/simple/" },
|
||||
{ name = "corelibs-var", specifier = ">=1.0.0", index = "https://git.egplusww.jp/api/packages/PyPI/pypi/simple/" },
|
||||
{ name = "cryptography", specifier = ">=46.0.3" },
|
||||
{ name = "jmespath", specifier = ">=1.0.1" },
|
||||
{ name = "jsonpath-ng", specifier = ">=1.7.0" },
|
||||
{ name = "psutil", specifier = ">=7.0.0" },
|
||||
{ name = "requests", specifier = ">=2.32.4" },
|
||||
{ name = "requests", extras = ["proxy"], specifier = ">=2.32.4" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
@@ -145,6 +148,7 @@ dev = [
|
||||
{ name = "deepdiff", specifier = ">=8.6.1" },
|
||||
{ name = "pytest", specifier = ">=8.4.1" },
|
||||
{ name = "pytest-cov", specifier = ">=6.2.1" },
|
||||
{ name = "typing-extensions", specifier = ">=4.15.0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -168,6 +172,15 @@ wheels = [
|
||||
{ url = "https://git.egplusww.jp/api/packages/PyPI/pypi/files/corelibs-enum-base/1.0.0/corelibs_enum_base-1.0.0-py3-none-any.whl", hash = "sha256:c305d4063c69021aaf9ef75fbcce961039dae3c3de7820febeac7082c998a1f8" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "corelibs-text-colors"
|
||||
version = "1.0.0"
|
||||
source = { registry = "https://git.egplusww.jp/api/packages/PyPI/pypi/simple/" }
|
||||
sdist = { url = "https://git.egplusww.jp/api/packages/PyPI/pypi/files/corelibs-text-colors/1.0.0/corelibs_text_colors-1.0.0.tar.gz", hash = "sha256:482fee54eb94d6b8acff091b531213ea312fac866836951dc06b26994fa52161" }
|
||||
wheels = [
|
||||
{ url = "https://git.egplusww.jp/api/packages/PyPI/pypi/files/corelibs-text-colors/1.0.0/corelibs_text_colors-1.0.0-py3-none-any.whl", hash = "sha256:ed0ba1799ce437e3fec67c94f0d9adfddde26094143d6a019e05d7557fecb03c" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "corelibs-var"
|
||||
version = "1.0.0"
|
||||
@@ -470,6 +483,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.5.0"
|
||||
|
||||
Reference in New Issue
Block a user