docker-tidy 4.2.0__tar.gz → 4.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {docker_tidy-4.2.0 → docker_tidy-4.2.2}/PKG-INFO +7 -7
- docker_tidy-4.2.2/dockertidy/__init__.py +8 -0
- {docker_tidy-4.2.0 → docker_tidy-4.2.2}/dockertidy/config.py +20 -16
- docker_tidy-4.2.2/dockertidy/logger.py +126 -0
- docker_tidy-4.2.2/dockertidy/utils.py +26 -0
- {docker_tidy-4.2.0 → docker_tidy-4.2.2}/pyproject.toml +11 -11
- docker_tidy-4.2.0/dockertidy/__init__.py +0 -3
- docker_tidy-4.2.0/dockertidy/logger.py +0 -201
- docker_tidy-4.2.0/dockertidy/utils.py +0 -50
- {docker_tidy-4.2.0 → docker_tidy-4.2.2}/LICENSE +0 -0
- {docker_tidy-4.2.0 → docker_tidy-4.2.2}/README.md +0 -0
- {docker_tidy-4.2.0 → docker_tidy-4.2.2}/dockertidy/autostop.py +0 -0
- {docker_tidy-4.2.0 → docker_tidy-4.2.2}/dockertidy/cli.py +0 -0
- {docker_tidy-4.2.0 → docker_tidy-4.2.2}/dockertidy/exception.py +0 -0
- {docker_tidy-4.2.0 → docker_tidy-4.2.2}/dockertidy/garbage_collector.py +0 -0
- {docker_tidy-4.2.0 → docker_tidy-4.2.2}/dockertidy/parser.py +0 -0
- {docker_tidy-4.2.0 → docker_tidy-4.2.2}/dockertidy/test/__init__.py +0 -0
- {docker_tidy-4.2.0 → docker_tidy-4.2.2}/dockertidy/test/fixtures/__init__.py +0 -0
- {docker_tidy-4.2.0 → docker_tidy-4.2.2}/dockertidy/test/fixtures/fixtures.py +0 -0
- {docker_tidy-4.2.0 → docker_tidy-4.2.2}/dockertidy/test/unit/test_autostop.py +0 -0
- {docker_tidy-4.2.0 → docker_tidy-4.2.2}/dockertidy/test/unit/test_garbagecollector.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docker-tidy
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.2
|
|
4
4
|
Summary: Keep docker hosts tidy.
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -24,24 +24,24 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
24
24
|
Classifier: Topic :: Software Development
|
|
25
25
|
Classifier: Topic :: System :: Systems Administration
|
|
26
26
|
Classifier: Topic :: Utilities
|
|
27
|
-
Requires-Dist: anyconfig (==0.
|
|
27
|
+
Requires-Dist: anyconfig (==0.16.0)
|
|
28
28
|
Requires-Dist: appdirs (==1.4.4)
|
|
29
29
|
Requires-Dist: certifi (==2026.7.22)
|
|
30
30
|
Requires-Dist: colorama (==0.4.6)
|
|
31
|
-
Requires-Dist: dateparser (==1.4.
|
|
31
|
+
Requires-Dist: dateparser (==1.4.3)
|
|
32
32
|
Requires-Dist: docker (==7.2.0)
|
|
33
33
|
Requires-Dist: docker-pycreds (==0.4.0)
|
|
34
|
-
Requires-Dist: environs (==15.
|
|
35
|
-
Requires-Dist: idna (==3.
|
|
34
|
+
Requires-Dist: environs (==15.2.0)
|
|
35
|
+
Requires-Dist: idna (==3.19)
|
|
36
36
|
Requires-Dist: ipaddress (==1.0.23)
|
|
37
37
|
Requires-Dist: jsonschema (==4.26.0)
|
|
38
38
|
Requires-Dist: nested-lookup (==0.2.25)
|
|
39
39
|
Requires-Dist: pathspec (==1.1.1)
|
|
40
40
|
Requires-Dist: python-dateutil (==2.9.0.post0)
|
|
41
|
-
Requires-Dist: python-json-logger (==4.
|
|
41
|
+
Requires-Dist: python-json-logger (==4.2.0)
|
|
42
42
|
Requires-Dist: requests (==2.34.2)
|
|
43
43
|
Requires-Dist: ruamel.yaml (==0.19.1)
|
|
44
|
-
Requires-Dist: websocket_client (==1.9.
|
|
44
|
+
Requires-Dist: websocket_client (==1.9.2)
|
|
45
45
|
Requires-Dist: zipp (==4.1.0)
|
|
46
46
|
Project-URL: Documentation, https://docker-tidy.geekdocs.de/
|
|
47
47
|
Project-URL: Homepage, https://docker-tidy.geekdocs.de/
|
|
@@ -9,7 +9,7 @@ import environs
|
|
|
9
9
|
import jsonschema.exceptions
|
|
10
10
|
import ruamel.yaml
|
|
11
11
|
from appdirs import AppDirs
|
|
12
|
-
from
|
|
12
|
+
from environs.exceptions import EnvError, EnvNotSetError
|
|
13
13
|
|
|
14
14
|
import dockertidy.exception
|
|
15
15
|
import dockertidy.parser
|
|
@@ -167,20 +167,23 @@ class Config:
|
|
|
167
167
|
|
|
168
168
|
def _get_envs(self) -> dict[str, Any]:
|
|
169
169
|
normalized: dict[str, Any] = {}
|
|
170
|
+
prefix = "TIDY_"
|
|
170
171
|
for key, item in self.SETTINGS.items():
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
172
|
+
env = item.get("env")
|
|
173
|
+
if not env:
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
env_name = prefix + env
|
|
177
|
+
try:
|
|
178
|
+
value = item["type"](env_name)
|
|
179
|
+
except EnvNotSetError:
|
|
180
|
+
continue
|
|
181
|
+
except EnvError as e:
|
|
182
|
+
raise dockertidy.exception.ConfigError(
|
|
183
|
+
"Unable to read environment variable", str(e)
|
|
184
|
+
) from e
|
|
185
|
+
|
|
186
|
+
normalized = self._add_dict_branch(normalized, key.split("."), value)
|
|
184
187
|
|
|
185
188
|
return normalized
|
|
186
189
|
|
|
@@ -249,8 +252,9 @@ class Config:
|
|
|
249
252
|
try:
|
|
250
253
|
anyconfig.validate(config, self.schema, ac_schema_safe=False)
|
|
251
254
|
except jsonschema.exceptions.ValidationError as e:
|
|
252
|
-
|
|
253
|
-
|
|
255
|
+
schema_error = (
|
|
256
|
+
f"Failed validating '{e.validator}' in schema {e.json_path}\n{e.message}"
|
|
257
|
+
)
|
|
254
258
|
raise dockertidy.exception.ConfigError("Configuration error", schema_error) from e
|
|
255
259
|
|
|
256
260
|
return True
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Global utility methods and classes."""
|
|
3
|
+
|
|
4
|
+
import copy
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Any, NoReturn
|
|
9
|
+
|
|
10
|
+
import colorama
|
|
11
|
+
from pythonjsonlogger.json import JsonFormatter
|
|
12
|
+
|
|
13
|
+
from dockertidy.utils import Singleton
|
|
14
|
+
|
|
15
|
+
JSON_FORMAT = "%(asctime)s %(levelname)s %(message)s"
|
|
16
|
+
|
|
17
|
+
_LEVEL_COLORS: dict[int, tuple[str, str]] = {
|
|
18
|
+
logging.DEBUG: (colorama.Fore.BLUE, colorama.Style.BRIGHT),
|
|
19
|
+
logging.INFO: (colorama.Fore.CYAN, colorama.Style.BRIGHT),
|
|
20
|
+
logging.WARNING: (colorama.Fore.YELLOW, colorama.Style.BRIGHT),
|
|
21
|
+
logging.ERROR: (colorama.Fore.RED, colorama.Style.BRIGHT),
|
|
22
|
+
logging.CRITICAL: (colorama.Fore.RED, colorama.Style.BRIGHT),
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_STDOUT_LEVELS = {logging.INFO, logging.WARNING}
|
|
26
|
+
_STDERR_LEVELS = {logging.DEBUG, logging.ERROR, logging.CRITICAL}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _should_do_markup() -> bool:
|
|
30
|
+
py_colors = os.environ.get("PY_COLORS")
|
|
31
|
+
if py_colors is not None:
|
|
32
|
+
return py_colors.strip().lower() in {"1", "true", "yes", "on", "y", "t"}
|
|
33
|
+
|
|
34
|
+
return sys.stdout.isatty() and os.environ.get("TERM") != "dumb"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
colorama.init(autoreset=True, strip=not _should_do_markup())
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class LevelFilter(logging.Filter):
|
|
41
|
+
"""Keep only records whose level belongs to the configured level set."""
|
|
42
|
+
|
|
43
|
+
def __init__(self, levels: set[int]) -> None:
|
|
44
|
+
super().__init__()
|
|
45
|
+
self._levels = levels
|
|
46
|
+
|
|
47
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
48
|
+
return record.levelno in self._levels
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ConsoleFormatter(logging.Formatter):
|
|
52
|
+
"""Colorize the level prefix and reset the color after line breaks."""
|
|
53
|
+
|
|
54
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
55
|
+
color, style = _LEVEL_COLORS.get(record.levelno, (colorama.Fore.WHITE, ""))
|
|
56
|
+
reset = colorama.Style.RESET_ALL
|
|
57
|
+
message = record.getMessage().replace("\n", f"\n{reset}... ")
|
|
58
|
+
output = f"{color}{style}[{record.levelname}]{reset} {message}"
|
|
59
|
+
|
|
60
|
+
if record.exc_info and not record.exc_text:
|
|
61
|
+
record.exc_text = self.formatException(record.exc_info)
|
|
62
|
+
if record.exc_text:
|
|
63
|
+
if output[-1:] != "\n":
|
|
64
|
+
output += "\n"
|
|
65
|
+
output += record.exc_text
|
|
66
|
+
|
|
67
|
+
return output
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class MultilineJsonFormatter(JsonFormatter):
|
|
71
|
+
"""JSON formatter that replaces line breaks in messages with spaces."""
|
|
72
|
+
|
|
73
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
74
|
+
record = copy.copy(record)
|
|
75
|
+
record.msg = record.msg.replace("\n", " ")
|
|
76
|
+
return JsonFormatter.format(self, record)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class Log:
|
|
80
|
+
"""Base logging object."""
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self, level: int = logging.WARNING, name: str = "dockertidy", json: bool = False
|
|
84
|
+
) -> None:
|
|
85
|
+
self.logger = logging.getLogger(name)
|
|
86
|
+
self.logger.propagate = False
|
|
87
|
+
self.logger.setLevel(level)
|
|
88
|
+
self._configure(json)
|
|
89
|
+
|
|
90
|
+
def _configure(self, json: bool = False) -> None:
|
|
91
|
+
for handler in self.logger.handlers[:]:
|
|
92
|
+
self.logger.removeHandler(handler)
|
|
93
|
+
|
|
94
|
+
self.logger.addHandler(self._get_handler(sys.stdout, _STDOUT_LEVELS, json))
|
|
95
|
+
self.logger.addHandler(self._get_handler(sys.stderr, _STDERR_LEVELS, json))
|
|
96
|
+
|
|
97
|
+
def _get_handler(self, stream: Any, levels: set[int], json: bool = False) -> logging.Handler:
|
|
98
|
+
handler = logging.StreamHandler(stream)
|
|
99
|
+
handler.setLevel(logging.DEBUG)
|
|
100
|
+
handler.addFilter(LevelFilter(levels))
|
|
101
|
+
handler.setFormatter(self._get_formatter(json))
|
|
102
|
+
return handler
|
|
103
|
+
|
|
104
|
+
def _get_formatter(self, json: bool = False) -> logging.Formatter:
|
|
105
|
+
if json:
|
|
106
|
+
return MultilineJsonFormatter(JSON_FORMAT)
|
|
107
|
+
return ConsoleFormatter()
|
|
108
|
+
|
|
109
|
+
def set_level(self, s: str | int) -> None:
|
|
110
|
+
"""Set log level."""
|
|
111
|
+
self.logger.setLevel(s)
|
|
112
|
+
|
|
113
|
+
def sysexit(self, code: int = 1) -> NoReturn:
|
|
114
|
+
"""Exit running program with given exit code."""
|
|
115
|
+
sys.exit(code)
|
|
116
|
+
|
|
117
|
+
def sysexit_with_message(self, msg: Any, code: int = 1) -> NoReturn:
|
|
118
|
+
"""Exit running program with given exit code and message."""
|
|
119
|
+
self.logger.critical(str(msg))
|
|
120
|
+
self.sysexit(code)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class SingleLog(Log, metaclass=Singleton):
|
|
124
|
+
"""Singleton logger object."""
|
|
125
|
+
|
|
126
|
+
pass
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Global utility methods and classes."""
|
|
3
|
+
|
|
4
|
+
import threading
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def dict_intersect(d1: dict[str, Any], d2: dict[str, Any]) -> dict[str, Any]:
|
|
9
|
+
return {
|
|
10
|
+
k: dict_intersect(d1[k], d2[k]) if isinstance(d1[k], dict) else d1[k]
|
|
11
|
+
for k in d1.keys() & d2.keys()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Singleton(type):
|
|
16
|
+
"""Thread-safe singleton metaclass."""
|
|
17
|
+
|
|
18
|
+
_instances: dict[Any, Any] = {}
|
|
19
|
+
_lock = threading.Lock()
|
|
20
|
+
|
|
21
|
+
def __call__(cls, *args: Any, **kwargs: Any) -> Any:
|
|
22
|
+
if cls not in cls._instances:
|
|
23
|
+
with cls._lock:
|
|
24
|
+
if cls not in cls._instances:
|
|
25
|
+
cls._instances[cls] = super().__call__(*args, **kwargs)
|
|
26
|
+
return cls._instances[cls]
|
|
@@ -28,44 +28,44 @@ name = "docker-tidy"
|
|
|
28
28
|
packages = [{ include = "dockertidy", format = ["sdist", "wheel"] }]
|
|
29
29
|
readme = "README.md"
|
|
30
30
|
repository = "https://github.com/thegeeklab/docker-tidy/"
|
|
31
|
-
version = "4.2.
|
|
31
|
+
version = "4.2.2"
|
|
32
32
|
|
|
33
33
|
[tool.poetry.dependencies]
|
|
34
|
-
anyconfig = "0.
|
|
34
|
+
anyconfig = "0.16.0"
|
|
35
35
|
appdirs = "1.4.4"
|
|
36
36
|
certifi = "2026.7.22"
|
|
37
37
|
colorama = "0.4.6"
|
|
38
|
-
dateparser = "1.4.
|
|
38
|
+
dateparser = "1.4.3"
|
|
39
39
|
docker = "7.2.0"
|
|
40
40
|
docker-pycreds = "0.4.0"
|
|
41
|
-
environs = "15.
|
|
42
|
-
idna = "3.
|
|
41
|
+
environs = "15.2.0"
|
|
42
|
+
idna = "3.19"
|
|
43
43
|
ipaddress = "1.0.23"
|
|
44
44
|
jsonschema = "4.26.0"
|
|
45
45
|
nested-lookup = "0.2.25"
|
|
46
46
|
pathspec = "1.1.1"
|
|
47
47
|
python = "^3.11.0"
|
|
48
48
|
python-dateutil = "2.9.0.post0"
|
|
49
|
-
python-json-logger = "4.
|
|
49
|
+
python-json-logger = "4.2.0"
|
|
50
50
|
requests = "2.34.2"
|
|
51
51
|
"ruamel.yaml" = "0.19.1"
|
|
52
|
-
websocket_client = "1.9.
|
|
52
|
+
websocket_client = "1.9.2"
|
|
53
53
|
zipp = "4.1.0"
|
|
54
54
|
|
|
55
55
|
[tool.poetry.scripts]
|
|
56
56
|
docker-tidy = "dockertidy.cli:main"
|
|
57
57
|
|
|
58
58
|
[tool.poetry.group.dev.dependencies]
|
|
59
|
-
ruff = "0.16.
|
|
59
|
+
ruff = "0.16.7"
|
|
60
60
|
pytest = "9.1.1"
|
|
61
61
|
pytest-mock = "3.15.1"
|
|
62
62
|
pytest-cov = "7.1.0"
|
|
63
63
|
toml = "0.10.2"
|
|
64
64
|
types-appdirs = "1.4.3.5"
|
|
65
|
-
types-docker = "7.2.0.
|
|
66
|
-
mypy = "2.3.
|
|
65
|
+
types-docker = "7.2.0.20260827"
|
|
66
|
+
mypy = "2.3.1"
|
|
67
67
|
types-python-dateutil = "2.9.0.20260807"
|
|
68
|
-
types-dateparser = "1.4.
|
|
68
|
+
types-dateparser = "1.4.3.20260907"
|
|
69
69
|
types-jsonschema = "4.26.0.20260518"
|
|
70
70
|
types-colorama = "0.4.15.20260508"
|
|
71
71
|
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Global utility methods and classes."""
|
|
3
|
-
|
|
4
|
-
import logging
|
|
5
|
-
import os
|
|
6
|
-
import sys
|
|
7
|
-
from typing import Any, NoReturn
|
|
8
|
-
|
|
9
|
-
import colorama
|
|
10
|
-
from pythonjsonlogger.json import JsonFormatter
|
|
11
|
-
|
|
12
|
-
from dockertidy.utils import Singleton, to_bool
|
|
13
|
-
|
|
14
|
-
CONSOLE_FORMAT = "{}[%(levelname)s]{} %(message)s"
|
|
15
|
-
JSON_FORMAT = "%(asctime)s %(levelname)s %(message)s"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def _should_do_markup() -> bool:
|
|
19
|
-
py_colors = os.environ.get("PY_COLORS", None)
|
|
20
|
-
if py_colors is not None:
|
|
21
|
-
return to_bool(py_colors)
|
|
22
|
-
|
|
23
|
-
return sys.stdout.isatty() and os.environ.get("TERM") != "dumb"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
colorama.init(autoreset=True, strip=not _should_do_markup())
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class LogFilter:
|
|
30
|
-
"""A custom log filter which excludes log messages above the logged level."""
|
|
31
|
-
|
|
32
|
-
def __init__(self, level: int) -> None:
|
|
33
|
-
"""
|
|
34
|
-
Initialize a new custom log filter.
|
|
35
|
-
|
|
36
|
-
:param level: Log level limit
|
|
37
|
-
:returns: None
|
|
38
|
-
|
|
39
|
-
"""
|
|
40
|
-
self.__level = level
|
|
41
|
-
|
|
42
|
-
def filter(self, logRecord: logging.LogRecord) -> bool: # noqa
|
|
43
|
-
# https://docs.python.org/3/library/logging.html#logrecord-attributes
|
|
44
|
-
return logRecord.levelno <= self.__level
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class MultilineFormatter(logging.Formatter):
|
|
48
|
-
"""Logging Formatter to reset color after newline characters."""
|
|
49
|
-
|
|
50
|
-
def format(self, record: logging.LogRecord) -> str:
|
|
51
|
-
record.msg = record.msg.replace("\n", f"\n{colorama.Style.RESET_ALL}... ")
|
|
52
|
-
return logging.Formatter.format(self, record)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
class MultilineJsonFormatter(JsonFormatter):
|
|
56
|
-
"""Logging Formatter to remove newline characters."""
|
|
57
|
-
|
|
58
|
-
def format(self, record: logging.LogRecord) -> str:
|
|
59
|
-
record.msg = record.msg.replace("\n", " ")
|
|
60
|
-
return JsonFormatter.format(self, record)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class Log:
|
|
64
|
-
"""Base logging object."""
|
|
65
|
-
|
|
66
|
-
def __init__(
|
|
67
|
-
self, level: int = logging.WARNING, name: str = "dockertidy", json: bool = False
|
|
68
|
-
) -> None:
|
|
69
|
-
self.logger = logging.getLogger(name)
|
|
70
|
-
self.logger.setLevel(level)
|
|
71
|
-
self.logger.addHandler(self._get_error_handler(json=json))
|
|
72
|
-
self.logger.addHandler(self._get_warning_handler(json=json))
|
|
73
|
-
self.logger.addHandler(self._get_info_handler(json=json))
|
|
74
|
-
self.logger.addHandler(self._get_critical_handler(json=json))
|
|
75
|
-
self.logger.addHandler(self._get_debug_handler(json=json))
|
|
76
|
-
self.logger.propagate = False
|
|
77
|
-
|
|
78
|
-
def _get_error_handler(self, json: bool = False) -> logging.Handler:
|
|
79
|
-
handler = logging.StreamHandler(sys.stderr)
|
|
80
|
-
handler.setLevel(logging.ERROR)
|
|
81
|
-
handler.addFilter(LogFilter(logging.ERROR))
|
|
82
|
-
handler.setFormatter(
|
|
83
|
-
MultilineFormatter(
|
|
84
|
-
self.error(CONSOLE_FORMAT.format(colorama.Fore.RED, colorama.Style.RESET_ALL))
|
|
85
|
-
)
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
if json:
|
|
89
|
-
handler.setFormatter(MultilineJsonFormatter(JSON_FORMAT))
|
|
90
|
-
|
|
91
|
-
return handler
|
|
92
|
-
|
|
93
|
-
def _get_warning_handler(self, json: bool = False) -> logging.Handler:
|
|
94
|
-
handler = logging.StreamHandler(sys.stdout)
|
|
95
|
-
handler.setLevel(logging.WARNING)
|
|
96
|
-
handler.addFilter(LogFilter(logging.WARNING))
|
|
97
|
-
handler.setFormatter(
|
|
98
|
-
MultilineFormatter(
|
|
99
|
-
self.warning(CONSOLE_FORMAT.format(colorama.Fore.YELLOW, colorama.Style.RESET_ALL))
|
|
100
|
-
)
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
if json:
|
|
104
|
-
handler.setFormatter(MultilineJsonFormatter(JSON_FORMAT))
|
|
105
|
-
|
|
106
|
-
return handler
|
|
107
|
-
|
|
108
|
-
def _get_info_handler(self, json: bool = False) -> logging.Handler:
|
|
109
|
-
handler = logging.StreamHandler(sys.stdout)
|
|
110
|
-
handler.setLevel(logging.INFO)
|
|
111
|
-
handler.addFilter(LogFilter(logging.INFO))
|
|
112
|
-
handler.setFormatter(
|
|
113
|
-
MultilineFormatter(
|
|
114
|
-
self.info(CONSOLE_FORMAT.format(colorama.Fore.CYAN, colorama.Style.RESET_ALL))
|
|
115
|
-
)
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
if json:
|
|
119
|
-
handler.setFormatter(MultilineJsonFormatter(JSON_FORMAT))
|
|
120
|
-
|
|
121
|
-
return handler
|
|
122
|
-
|
|
123
|
-
def _get_critical_handler(self, json: bool = False) -> logging.Handler:
|
|
124
|
-
handler = logging.StreamHandler(sys.stderr)
|
|
125
|
-
handler.setLevel(logging.CRITICAL)
|
|
126
|
-
handler.addFilter(LogFilter(logging.CRITICAL))
|
|
127
|
-
handler.setFormatter(
|
|
128
|
-
MultilineFormatter(
|
|
129
|
-
self.critical(CONSOLE_FORMAT.format(colorama.Fore.RED, colorama.Style.RESET_ALL))
|
|
130
|
-
)
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
if json:
|
|
134
|
-
handler.setFormatter(MultilineJsonFormatter(JSON_FORMAT))
|
|
135
|
-
|
|
136
|
-
return handler
|
|
137
|
-
|
|
138
|
-
def _get_debug_handler(self, json: bool = False) -> logging.Handler:
|
|
139
|
-
handler = logging.StreamHandler(sys.stderr)
|
|
140
|
-
handler.setLevel(logging.DEBUG)
|
|
141
|
-
handler.addFilter(LogFilter(logging.DEBUG))
|
|
142
|
-
handler.setFormatter(
|
|
143
|
-
MultilineFormatter(
|
|
144
|
-
self.critical(CONSOLE_FORMAT.format(colorama.Fore.BLUE, colorama.Style.RESET_ALL))
|
|
145
|
-
)
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
if json:
|
|
149
|
-
handler.setFormatter(MultilineJsonFormatter(JSON_FORMAT))
|
|
150
|
-
|
|
151
|
-
return handler
|
|
152
|
-
|
|
153
|
-
def set_level(self, s: str | int) -> None:
|
|
154
|
-
"""Set log level."""
|
|
155
|
-
self.logger.setLevel(s)
|
|
156
|
-
|
|
157
|
-
def debug(self, msg: str) -> str:
|
|
158
|
-
"""Format info messages and return string."""
|
|
159
|
-
return msg
|
|
160
|
-
|
|
161
|
-
def critical(self, msg: str) -> str:
|
|
162
|
-
"""Format critical messages and return string."""
|
|
163
|
-
return msg
|
|
164
|
-
|
|
165
|
-
def error(self, msg: str) -> str:
|
|
166
|
-
"""Format error messages and return string."""
|
|
167
|
-
return msg
|
|
168
|
-
|
|
169
|
-
def warning(self, msg: str) -> str:
|
|
170
|
-
"""Format warning messages and return string."""
|
|
171
|
-
return msg
|
|
172
|
-
|
|
173
|
-
def info(self, msg: str) -> str:
|
|
174
|
-
"""Format info messages and return string."""
|
|
175
|
-
return msg
|
|
176
|
-
|
|
177
|
-
def _color_text(self, color: str, msg: str) -> str:
|
|
178
|
-
"""
|
|
179
|
-
Colorize strings.
|
|
180
|
-
|
|
181
|
-
:param color: colorama color settings
|
|
182
|
-
:param msg: string to colorize
|
|
183
|
-
:returns: string
|
|
184
|
-
|
|
185
|
-
"""
|
|
186
|
-
return f"{color}{msg}{colorama.Style.RESET_ALL}"
|
|
187
|
-
|
|
188
|
-
def sysexit(self, code: int = 1) -> NoReturn:
|
|
189
|
-
"""Exit running program with given exit code."""
|
|
190
|
-
sys.exit(code)
|
|
191
|
-
|
|
192
|
-
def sysexit_with_message(self, msg: Any, code: int = 1) -> NoReturn:
|
|
193
|
-
"""Exit running program with given exit code and message."""
|
|
194
|
-
self.logger.critical(str(msg))
|
|
195
|
-
self.sysexit(code)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
class SingleLog(Log, metaclass=Singleton):
|
|
199
|
-
"""Singleton logger object."""
|
|
200
|
-
|
|
201
|
-
pass
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Global utility methods and classes."""
|
|
3
|
-
|
|
4
|
-
from typing import Any
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def strtobool(value: str) -> bool:
|
|
8
|
-
"""Convert a string representation of truth to true or false."""
|
|
9
|
-
|
|
10
|
-
_map = {
|
|
11
|
-
"y": True,
|
|
12
|
-
"yes": True,
|
|
13
|
-
"t": True,
|
|
14
|
-
"true": True,
|
|
15
|
-
"on": True,
|
|
16
|
-
"1": True,
|
|
17
|
-
"n": False,
|
|
18
|
-
"no": False,
|
|
19
|
-
"f": False,
|
|
20
|
-
"false": False,
|
|
21
|
-
"off": False,
|
|
22
|
-
"0": False,
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
try:
|
|
26
|
-
return _map[str(value).lower()]
|
|
27
|
-
except KeyError as err:
|
|
28
|
-
raise ValueError(f'"{value}" is not a valid bool value') from err
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def to_bool(string: str | bool) -> bool:
|
|
32
|
-
return bool(strtobool(str(string)))
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def dict_intersect(d1: dict[str, Any], d2: dict[str, Any]) -> dict[str, Any]:
|
|
36
|
-
return {
|
|
37
|
-
k: dict_intersect(d1[k], d2[k]) if isinstance(d1[k], dict) else d1[k]
|
|
38
|
-
for k in d1.keys() & d2.keys()
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class Singleton(type):
|
|
43
|
-
"""Singleton metaclass."""
|
|
44
|
-
|
|
45
|
-
_instances: dict[Any, Any] = {}
|
|
46
|
-
|
|
47
|
-
def __call__(cls, *args: Any, **kwargs: Any) -> Any:
|
|
48
|
-
if cls not in cls._instances:
|
|
49
|
-
cls._instances[cls] = super().__call__(*args, **kwargs)
|
|
50
|
-
return cls._instances[cls]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|