cxppython 0.0.1__tar.gz → 0.0.3__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.
- {cxppython-0.0.1 → cxppython-0.0.3}/PKG-INFO +8 -1
- cxppython-0.0.3/README.md +8 -0
- cxppython-0.0.3/cxppython/__init__.py +3 -0
- cxppython-0.0.3/cxppython/core/__init__.py +0 -0
- cxppython-0.0.3/cxppython/core/config.py +248 -0
- {cxppython-0.0.1 → cxppython-0.0.3}/cxppython/main.py +2 -1
- cxppython-0.0.3/cxppython/utils/__init__.py +0 -0
- cxppython-0.0.3/cxppython/utils/btlogging/__init__.py +11 -0
- cxppython-0.0.3/cxppython/utils/btlogging/console.py +73 -0
- cxppython-0.0.3/cxppython/utils/btlogging/defines.py +11 -0
- cxppython-0.0.3/cxppython/utils/btlogging/format.py +214 -0
- cxppython-0.0.3/cxppython/utils/btlogging/helpers.py +71 -0
- cxppython-0.0.3/cxppython/utils/btlogging/loggingmachine.py +664 -0
- cxppython-0.0.3/cxppython/utils/database/__init__.py +2 -0
- cxppython-0.0.3/cxppython/utils/database/mysql.py +103 -0
- cxppython-0.0.3/cxppython/utils/easy_imports.py +37 -0
- {cxppython-0.0.1 → cxppython-0.0.3}/cxppython.egg-info/PKG-INFO +8 -1
- cxppython-0.0.3/cxppython.egg-info/SOURCES.txt +22 -0
- {cxppython-0.0.1 → cxppython-0.0.3}/setup.py +2 -2
- cxppython-0.0.1/README.md +0 -1
- cxppython-0.0.1/cxppython/__init__.py +0 -1
- cxppython-0.0.1/cxppython.egg-info/SOURCES.txt +0 -10
- {cxppython-0.0.1 → cxppython-0.0.3}/LICENSE +0 -0
- {cxppython-0.0.1 → cxppython-0.0.3}/cxppython.egg-info/dependency_links.txt +0 -0
- {cxppython-0.0.1 → cxppython-0.0.3}/cxppython.egg-info/requires.txt +0 -0
- {cxppython-0.0.1 → cxppython-0.0.3}/cxppython.egg-info/top_level.txt +0 -0
- {cxppython-0.0.1 → cxppython-0.0.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: cxppython
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: A python utils package
|
|
5
5
|
Home-page: https://github.com/yourusername/my_package
|
|
6
6
|
Author: cxp
|
|
@@ -25,3 +25,10 @@ Dynamic: requires-python
|
|
|
25
25
|
Dynamic: summary
|
|
26
26
|
|
|
27
27
|
# cxppython
|
|
28
|
+
### install build
|
|
29
|
+
- python -m pip install --upgrade pip setuptools wheel twine
|
|
30
|
+
-
|
|
31
|
+
### build
|
|
32
|
+
- python setup.py sdist bdist_wheel
|
|
33
|
+
### upload
|
|
34
|
+
- twine upload dist/*
|
|
File without changes
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Implementation of the config class, which manages the configuration of different Bittensor modules.
|
|
2
|
+
|
|
3
|
+
Example:
|
|
4
|
+
import argparse
|
|
5
|
+
import bittensor as bt
|
|
6
|
+
|
|
7
|
+
parser = argparse.ArgumentParser('Miner')
|
|
8
|
+
bt.Axon.add_args(parser)
|
|
9
|
+
bt.Subtensor.add_args(parser)
|
|
10
|
+
bt.Async_subtensor.add_args(parser)
|
|
11
|
+
bt.Wallet.add_args(parser)
|
|
12
|
+
bt.logging.add_args(parser)
|
|
13
|
+
bt.PriorityThreadPoolExecutor.add_args(parser)
|
|
14
|
+
config = bt.config(parser)
|
|
15
|
+
|
|
16
|
+
print(config)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
from copy import deepcopy
|
|
23
|
+
from typing import Any, TypeVar, Type, Optional
|
|
24
|
+
|
|
25
|
+
import yaml
|
|
26
|
+
from munch import DefaultMunch
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _filter_keys(obj):
|
|
30
|
+
"""Filters keys from an object, excluding private and certain internal properties."""
|
|
31
|
+
if isinstance(obj, dict):
|
|
32
|
+
return {
|
|
33
|
+
k: _filter_keys(v)
|
|
34
|
+
for k, v in obj.items()
|
|
35
|
+
if not k.startswith("__") and not k.startswith("_Config__is_set")
|
|
36
|
+
}
|
|
37
|
+
elif isinstance(obj, (Config, DefaultMunch)):
|
|
38
|
+
return _filter_keys(obj.toDict())
|
|
39
|
+
return obj
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class InvalidConfigFile(Exception):
|
|
43
|
+
"""Raised when there's an error loading the config file."""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Config(DefaultMunch):
|
|
47
|
+
"""Manages configuration for Bittensor modules with nested namespace support."""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
parser: argparse.ArgumentParser = None,
|
|
52
|
+
args: Optional[list[str]] = None,
|
|
53
|
+
strict: bool = False,
|
|
54
|
+
default: Any = None,
|
|
55
|
+
) -> None:
|
|
56
|
+
super().__init__(default)
|
|
57
|
+
self.__is_set = {}
|
|
58
|
+
|
|
59
|
+
if parser is None:
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
self._add_default_arguments(parser)
|
|
63
|
+
args = args or sys.argv[1:]
|
|
64
|
+
self._validate_required_args(parser, args)
|
|
65
|
+
|
|
66
|
+
config_params = self._parse_args(args, parser, strict=False)
|
|
67
|
+
config_path = self._get_config_path(config_params)
|
|
68
|
+
strict = strict or getattr(config_params, "strict", False)
|
|
69
|
+
|
|
70
|
+
if config_path:
|
|
71
|
+
self._load_config_file(parser, config_path)
|
|
72
|
+
|
|
73
|
+
params = self._parse_args(args, parser, strict)
|
|
74
|
+
self._build_config_tree(params)
|
|
75
|
+
self._detect_set_parameters(parser, args)
|
|
76
|
+
|
|
77
|
+
def __str__(self) -> str:
|
|
78
|
+
"""String representation without private keys, optimized to avoid deepcopy."""
|
|
79
|
+
cleaned = _filter_keys(self.toDict())
|
|
80
|
+
return "\n" + yaml.dump(cleaned, sort_keys=False, default_flow_style=False)
|
|
81
|
+
|
|
82
|
+
def __repr__(self) -> str:
|
|
83
|
+
"""String representation of the Config."""
|
|
84
|
+
return self.__str__()
|
|
85
|
+
|
|
86
|
+
def _validate_required_args(
|
|
87
|
+
self, parser: argparse.ArgumentParser, args: list[str]
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Validates required arguments are present."""
|
|
90
|
+
missing = self._find_missing_required_args(parser, args)
|
|
91
|
+
if missing:
|
|
92
|
+
raise ValueError(f"Missing required arguments: {', '.join(missing)}")
|
|
93
|
+
|
|
94
|
+
def _find_missing_required_args(
|
|
95
|
+
self, parser: argparse.ArgumentParser, args: list[str]
|
|
96
|
+
) -> list[str]:
|
|
97
|
+
"""Identifies missing required arguments."""
|
|
98
|
+
required = {a.dest for a in parser._actions if a.required}
|
|
99
|
+
provided = {a.split("=")[0].lstrip("-") for a in args if a.startswith("-")}
|
|
100
|
+
return list(required - provided)
|
|
101
|
+
|
|
102
|
+
def _get_config_path(self, params: DefaultMunch) -> Optional[str]:
|
|
103
|
+
"""Gets Config path from parameters."""
|
|
104
|
+
return getattr(params, "config", None)
|
|
105
|
+
|
|
106
|
+
def _load_config_file(self, parser: argparse.ArgumentParser, path: str) -> None:
|
|
107
|
+
"""Loads Config from YAML file."""
|
|
108
|
+
try:
|
|
109
|
+
with open(os.path.expanduser(path)) as f:
|
|
110
|
+
config = yaml.safe_load(f)
|
|
111
|
+
print(f"Loading config from: {path}")
|
|
112
|
+
parser.set_defaults(**config)
|
|
113
|
+
except Exception as e:
|
|
114
|
+
raise InvalidConfigFile(f"Error loading config: {e}") from e
|
|
115
|
+
|
|
116
|
+
def _build_config_tree(self, params: DefaultMunch) -> None:
|
|
117
|
+
"""Builds nested Config structure."""
|
|
118
|
+
for key, value in params.items():
|
|
119
|
+
if key in ["__is_set"]:
|
|
120
|
+
continue
|
|
121
|
+
current = self
|
|
122
|
+
parts = key.split(".")
|
|
123
|
+
for part in parts[:-1]:
|
|
124
|
+
current = current.setdefault(part, Config())
|
|
125
|
+
current[parts[-1]] = value
|
|
126
|
+
|
|
127
|
+
def _detect_set_parameters(
|
|
128
|
+
self, parser: argparse.ArgumentParser, args: list[str]
|
|
129
|
+
) -> None:
|
|
130
|
+
"""Detects which parameters were explicitly set."""
|
|
131
|
+
temp_parser = self._create_non_default_parser(parser)
|
|
132
|
+
detected = self._parse_args(args, temp_parser, strict=False)
|
|
133
|
+
self.__is_set = DefaultMunch(**{k: True for k in detected.keys()})
|
|
134
|
+
|
|
135
|
+
def _create_non_default_parser(
|
|
136
|
+
self, original: argparse.ArgumentParser
|
|
137
|
+
) -> argparse.ArgumentParser:
|
|
138
|
+
"""Creates a parser that ignores default values."""
|
|
139
|
+
parser = deepcopy(original)
|
|
140
|
+
for action in parser._actions:
|
|
141
|
+
action.default = argparse.SUPPRESS
|
|
142
|
+
return parser
|
|
143
|
+
|
|
144
|
+
@staticmethod
|
|
145
|
+
def _parse_args(
|
|
146
|
+
args: list[str], parser: argparse.ArgumentParser, strict: bool
|
|
147
|
+
) -> DefaultMunch:
|
|
148
|
+
"""Parses args with error handling."""
|
|
149
|
+
try:
|
|
150
|
+
if strict:
|
|
151
|
+
result = parser.parse_args(args)
|
|
152
|
+
return DefaultMunch.fromDict(vars(result))
|
|
153
|
+
|
|
154
|
+
result, unknown = parser.parse_known_args(args)
|
|
155
|
+
for arg in unknown:
|
|
156
|
+
if arg.startswith("--") and (name := arg[2:]) in vars(result):
|
|
157
|
+
setattr(result, name, True)
|
|
158
|
+
return DefaultMunch.fromDict(vars(result))
|
|
159
|
+
except Exception:
|
|
160
|
+
raise ValueError("Invalid arguments provided.")
|
|
161
|
+
|
|
162
|
+
def __deepcopy__(self, memo) -> "Config":
|
|
163
|
+
"""Creates a deep copy that maintains Config type."""
|
|
164
|
+
new_config = Config()
|
|
165
|
+
memo[id(self)] = new_config
|
|
166
|
+
|
|
167
|
+
for key, value in self.items():
|
|
168
|
+
new_config[key] = deepcopy(value, memo)
|
|
169
|
+
|
|
170
|
+
new_config.__is_set = deepcopy(self.__is_set, memo)
|
|
171
|
+
return new_config
|
|
172
|
+
|
|
173
|
+
def merge(self, other: "Config") -> None:
|
|
174
|
+
"""Merges another Config into this one."""
|
|
175
|
+
self.update(self._merge_dicts(self, other))
|
|
176
|
+
self.__is_set.update(other.__is_set)
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def _merge_dicts(a: DefaultMunch, b: DefaultMunch) -> DefaultMunch:
|
|
180
|
+
"""Recursively merges two Config objects."""
|
|
181
|
+
result = deepcopy(a)
|
|
182
|
+
for key, value in b.items():
|
|
183
|
+
if key in result:
|
|
184
|
+
if isinstance(result[key], DefaultMunch) and isinstance(
|
|
185
|
+
value, DefaultMunch
|
|
186
|
+
):
|
|
187
|
+
result[key] = Config._merge_dicts(result[key], value)
|
|
188
|
+
else:
|
|
189
|
+
result[key] = deepcopy(value)
|
|
190
|
+
else:
|
|
191
|
+
result[key] = deepcopy(value)
|
|
192
|
+
return result
|
|
193
|
+
|
|
194
|
+
def is_set(self, param_name: str) -> bool:
|
|
195
|
+
"""Checks if a parameter was explicitly set."""
|
|
196
|
+
return self.__is_set.get(param_name, False)
|
|
197
|
+
|
|
198
|
+
def to_dict(self) -> dict:
|
|
199
|
+
"""Returns the configuration as a dictionary."""
|
|
200
|
+
return self.toDict()
|
|
201
|
+
|
|
202
|
+
def _add_default_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
203
|
+
"""Adds default arguments to the Config parser."""
|
|
204
|
+
arguments = [
|
|
205
|
+
(
|
|
206
|
+
"--config",
|
|
207
|
+
{
|
|
208
|
+
"type": str,
|
|
209
|
+
"help": "If set, defaults are overridden by passed file.",
|
|
210
|
+
"default": False,
|
|
211
|
+
},
|
|
212
|
+
),
|
|
213
|
+
(
|
|
214
|
+
"--strict",
|
|
215
|
+
{
|
|
216
|
+
"action": "store_true",
|
|
217
|
+
"help": "If flagged, config will check that only exact arguments have been set.",
|
|
218
|
+
"default": False,
|
|
219
|
+
},
|
|
220
|
+
),
|
|
221
|
+
(
|
|
222
|
+
"--no_version_checking",
|
|
223
|
+
{
|
|
224
|
+
"action": "store_true",
|
|
225
|
+
"help": "Set `true to stop cli version checking.",
|
|
226
|
+
"default": False,
|
|
227
|
+
},
|
|
228
|
+
),
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
for arg_name, kwargs in arguments:
|
|
232
|
+
try:
|
|
233
|
+
parser.add_argument(arg_name, **kwargs)
|
|
234
|
+
except argparse.ArgumentError:
|
|
235
|
+
# this can fail if argument has already been added.
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
T = TypeVar("T", bound="DefaultConfig")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class DefaultConfig(Config):
|
|
243
|
+
"""A Config with a set of default values."""
|
|
244
|
+
|
|
245
|
+
@classmethod
|
|
246
|
+
def default(cls: Type[T]) -> T:
|
|
247
|
+
"""Get default config."""
|
|
248
|
+
raise NotImplementedError("Function default is not implemented.")
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
btlogging sub-package standardized logging for Bittensor.
|
|
3
|
+
|
|
4
|
+
This module provides logging functionality for the Bittensor package. It includes custom loggers, handlers, and
|
|
5
|
+
formatters to ensure consistent logging throughout the project.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .loggingmachine import LoggingMachine
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
logging = LoggingMachine(LoggingMachine.config())
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BittensorConsole class gives the ability to log messages to the terminal without changing Bittensor logging level.
|
|
3
|
+
|
|
4
|
+
Example:
|
|
5
|
+
from bittensor import logging
|
|
6
|
+
|
|
7
|
+
# will be logged
|
|
8
|
+
logging.console.info("info message")
|
|
9
|
+
logging.console.error("error message")
|
|
10
|
+
logging.console.success("success message")
|
|
11
|
+
logging.console.warning("warning message")
|
|
12
|
+
logging.console.critical("critical message")
|
|
13
|
+
|
|
14
|
+
# will not be logged
|
|
15
|
+
logging.info("test info")
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from functools import wraps
|
|
19
|
+
from typing import Callable, TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
from .helpers import all_loggers
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from .loggingmachine import LoggingMachine
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _print_wrapper(func: "Callable"):
|
|
28
|
+
@wraps(func)
|
|
29
|
+
def wrapper(self: "BittensorConsole", *args, **kwargs):
|
|
30
|
+
"""A wrapper function to temporarily set the logger level to debug."""
|
|
31
|
+
old_logger_level = self.logger.get_level()
|
|
32
|
+
self.logger.set_console()
|
|
33
|
+
func(self, *args, **kwargs)
|
|
34
|
+
|
|
35
|
+
for logger in all_loggers():
|
|
36
|
+
logger.setLevel(old_logger_level)
|
|
37
|
+
|
|
38
|
+
return wrapper
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class BittensorConsole:
|
|
42
|
+
def __init__(self, logger: "LoggingMachine"):
|
|
43
|
+
self.logger = logger
|
|
44
|
+
|
|
45
|
+
@_print_wrapper
|
|
46
|
+
def debug(self, message: str):
|
|
47
|
+
"""Logs a DEBUG message to the console."""
|
|
48
|
+
self.logger.debug(message)
|
|
49
|
+
|
|
50
|
+
@_print_wrapper
|
|
51
|
+
def info(self, message: str):
|
|
52
|
+
"""Logs a INFO message to the console."""
|
|
53
|
+
self.logger.info(message)
|
|
54
|
+
|
|
55
|
+
@_print_wrapper
|
|
56
|
+
def success(self, message: str):
|
|
57
|
+
"""Logs a SUCCESS message to the console."""
|
|
58
|
+
self.logger.success(message)
|
|
59
|
+
|
|
60
|
+
@_print_wrapper
|
|
61
|
+
def warning(self, message: str):
|
|
62
|
+
"""Logs a WARNING message to the console."""
|
|
63
|
+
self.logger.warning(message)
|
|
64
|
+
|
|
65
|
+
@_print_wrapper
|
|
66
|
+
def error(self, message: str):
|
|
67
|
+
"""Logs a ERROR message to the console."""
|
|
68
|
+
self.logger.error(message)
|
|
69
|
+
|
|
70
|
+
@_print_wrapper
|
|
71
|
+
def critical(self, message: str):
|
|
72
|
+
"""Logs a CRITICAL message to the console."""
|
|
73
|
+
self.logger.critical(message)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Btlogging constant definition module."""
|
|
2
|
+
|
|
3
|
+
BASE_LOG_FORMAT = "%(asctime)s | %(levelname)s | %(message)s"
|
|
4
|
+
TRACE_LOG_FORMAT = (
|
|
5
|
+
f"%(asctime)s | %(levelname)s | %(name)s:%(filename)s:%(lineno)s | %(message)s"
|
|
6
|
+
)
|
|
7
|
+
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
|
8
|
+
BITTENSOR_LOGGER_NAME = "cxppy"
|
|
9
|
+
DEFAULT_LOG_FILE_NAME = "cxppy.log"
|
|
10
|
+
DEFAULT_MAX_ROTATING_LOG_FILE_SIZE = 25 * 1024 * 1024
|
|
11
|
+
DEFAULT_LOG_BACKUP_COUNT = 10
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
btlogging.format module
|
|
3
|
+
|
|
4
|
+
This module defines custom logging formatters for the Bittensor project.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import time
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from colorama import init, Fore, Back, Style
|
|
11
|
+
|
|
12
|
+
init(wrap=False)
|
|
13
|
+
|
|
14
|
+
TRACE_LEVEL_NUM: int = 5
|
|
15
|
+
SUCCESS_LEVEL_NUM: int = 21
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _trace(self, message: str, *args, **kws):
|
|
19
|
+
if self.isEnabledFor(TRACE_LEVEL_NUM):
|
|
20
|
+
self._log(TRACE_LEVEL_NUM, message, args, **kws)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _success(self, message: str, *args, **kws):
|
|
24
|
+
if self.isEnabledFor(SUCCESS_LEVEL_NUM):
|
|
25
|
+
self._log(SUCCESS_LEVEL_NUM, message, args, **kws)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
logging.SUCCESS = SUCCESS_LEVEL_NUM
|
|
29
|
+
logging.addLevelName(SUCCESS_LEVEL_NUM, "SUCCESS")
|
|
30
|
+
logging.Logger.success = _success
|
|
31
|
+
|
|
32
|
+
logging.TRACE = TRACE_LEVEL_NUM
|
|
33
|
+
logging.addLevelName(TRACE_LEVEL_NUM, "TRACE")
|
|
34
|
+
logging.Logger.trace = _trace
|
|
35
|
+
|
|
36
|
+
emoji_map: dict[str, str] = {
|
|
37
|
+
":white_heavy_check_mark:": "✅",
|
|
38
|
+
":cross_mark:": "❌",
|
|
39
|
+
":satellite:": "🛰️",
|
|
40
|
+
":warning:": "⚠️",
|
|
41
|
+
":arrow_right:": "➡️",
|
|
42
|
+
":hourglass:": "⏳",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
color_map: dict[str, str] = {
|
|
47
|
+
"[red]": Fore.RED,
|
|
48
|
+
"[/red]": Style.RESET_ALL,
|
|
49
|
+
"[blue]": Fore.BLUE,
|
|
50
|
+
"[/blue]": Style.RESET_ALL,
|
|
51
|
+
"[green]": Fore.GREEN,
|
|
52
|
+
"[/green]": Style.RESET_ALL,
|
|
53
|
+
"[magenta]": Fore.MAGENTA,
|
|
54
|
+
"[/magenta]": Style.RESET_ALL,
|
|
55
|
+
"[yellow]": Fore.YELLOW,
|
|
56
|
+
"[/yellow]": Style.RESET_ALL,
|
|
57
|
+
"[orange]": Fore.YELLOW,
|
|
58
|
+
"[/orange]": Style.RESET_ALL,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
log_level_color_prefix: dict[int, str] = {
|
|
63
|
+
logging.NOTSET: Fore.RESET,
|
|
64
|
+
logging.TRACE: Fore.MAGENTA,
|
|
65
|
+
logging.DEBUG: Fore.BLUE,
|
|
66
|
+
logging.INFO: Fore.WHITE,
|
|
67
|
+
logging.SUCCESS: Fore.GREEN,
|
|
68
|
+
logging.WARNING: Fore.YELLOW,
|
|
69
|
+
logging.ERROR: Fore.RED,
|
|
70
|
+
logging.CRITICAL: Back.RED,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
LOG_FORMATS: dict[int, str] = {
|
|
75
|
+
level: f"{Fore.BLUE}%(asctime)s{Fore.RESET} | {Style.BRIGHT}{color}%(levelname)s\033[0m | %(message)s"
|
|
76
|
+
for level, color in log_level_color_prefix.items()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
LOG_TRACE_FORMATS: dict[int, str] = {
|
|
80
|
+
level: f"{Fore.BLUE}%(asctime)s{Fore.RESET}"
|
|
81
|
+
f" | {Style.BRIGHT}{color}%(levelname)s{Fore.RESET}{Back.RESET}{Style.RESET_ALL}"
|
|
82
|
+
f" | %(name)s:%(filename)s:%(lineno)s"
|
|
83
|
+
f" | %(message)s"
|
|
84
|
+
for level, color in log_level_color_prefix.items()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
DEFAULT_LOG_FORMAT: str = (
|
|
88
|
+
f"{Fore.BLUE}%(asctime)s{Fore.RESET} | "
|
|
89
|
+
f"{Style.BRIGHT}{Fore.WHITE}%(levelname)s{Style.RESET_ALL} | "
|
|
90
|
+
f"%(name)s:%(filename)s:%(lineno)s | %(message)s"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
DEFAULT_TRACE_FORMAT: str = (
|
|
94
|
+
f"{Fore.BLUE}%(asctime)s{Fore.RESET} | "
|
|
95
|
+
f"{Style.BRIGHT}{Fore.WHITE}%(levelname)s{Style.RESET_ALL} | "
|
|
96
|
+
f"%(name)s:%(filename)s:%(lineno)s | %(message)s"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class BtStreamFormatter(logging.Formatter):
|
|
101
|
+
"""
|
|
102
|
+
A custom logging formatter for the Bittensor project that overrides the time formatting to include milliseconds,
|
|
103
|
+
centers the level name, and applies custom log formats, emojis, and colors.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
def __init__(self, *args, **kwargs):
|
|
107
|
+
super().__init__(*args, **kwargs)
|
|
108
|
+
self.trace = False
|
|
109
|
+
|
|
110
|
+
def formatTime(self, record, datefmt: Optional[str] = None) -> str:
|
|
111
|
+
"""
|
|
112
|
+
Override formatTime to add milliseconds.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
record (logging.LogRecord): The log record.
|
|
116
|
+
datefmt (Optional[str]): The date format string.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
s (str): The formatted time string with milliseconds.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
created = self.converter(record.created)
|
|
123
|
+
if datefmt:
|
|
124
|
+
s = time.strftime(datefmt, created)
|
|
125
|
+
else:
|
|
126
|
+
s = time.strftime("%Y-%m-%d %H:%M:%S", created)
|
|
127
|
+
s += f".{int(record.msecs):03d}"
|
|
128
|
+
return s
|
|
129
|
+
|
|
130
|
+
def format(self, record: "logging.LogRecord") -> str:
|
|
131
|
+
"""
|
|
132
|
+
Override format to apply custom formatting including emojis and colors.
|
|
133
|
+
|
|
134
|
+
This method saves the original format, applies custom formatting based on the log level and trace flag, replaces
|
|
135
|
+
text with emojis and colors, and then returns the formatted log record.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
record (logging.LogRecord): The log record.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
result (str): The formatted log record.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
format_orig = self._style._fmt
|
|
145
|
+
record.levelname = f"{record.levelname:^16}"
|
|
146
|
+
|
|
147
|
+
if record.levelno not in LOG_FORMATS:
|
|
148
|
+
self._style._fmt = (
|
|
149
|
+
DEFAULT_TRACE_FORMAT if self.trace else DEFAULT_LOG_FORMAT
|
|
150
|
+
)
|
|
151
|
+
else:
|
|
152
|
+
if self.trace is True:
|
|
153
|
+
self._style._fmt = LOG_TRACE_FORMATS[record.levelno]
|
|
154
|
+
else:
|
|
155
|
+
self._style._fmt = LOG_FORMATS[record.levelno]
|
|
156
|
+
|
|
157
|
+
for text, emoji in emoji_map.items():
|
|
158
|
+
record.msg = record.msg.replace(text, emoji)
|
|
159
|
+
# Apply color specifiers
|
|
160
|
+
for text, color in color_map.items():
|
|
161
|
+
record.msg = record.msg.replace(text, color)
|
|
162
|
+
|
|
163
|
+
result = super().format(record)
|
|
164
|
+
self._style._fmt = format_orig
|
|
165
|
+
|
|
166
|
+
return result
|
|
167
|
+
|
|
168
|
+
def set_trace(self, state: bool = True):
|
|
169
|
+
"""Change formatter state."""
|
|
170
|
+
self.trace = state
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class BtFileFormatter(logging.Formatter):
|
|
174
|
+
"""
|
|
175
|
+
BtFileFormatter
|
|
176
|
+
|
|
177
|
+
A custom logging formatter for the Bittensor project that overrides the time formatting to include milliseconds and
|
|
178
|
+
centers the level name.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
def formatTime(
|
|
182
|
+
self, record: "logging.LogRecord", datefmt: Optional[str] = None
|
|
183
|
+
) -> str:
|
|
184
|
+
"""
|
|
185
|
+
Override formatTime to add milliseconds.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
record (logging.LogRecord): The log record.
|
|
189
|
+
datefmt (Optional[str]): The date format string.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
s (str): The formatted time string with milliseconds.
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
created = self.converter(record.created)
|
|
196
|
+
if datefmt:
|
|
197
|
+
s = time.strftime(datefmt, created)
|
|
198
|
+
else:
|
|
199
|
+
s = time.strftime("%Y-%m-%d %H:%M:%S", created)
|
|
200
|
+
s += f".{int(record.msecs):03d}"
|
|
201
|
+
return s
|
|
202
|
+
|
|
203
|
+
def format(self, record: "logging.LogRecord") -> str:
|
|
204
|
+
"""
|
|
205
|
+
Override format to center the level name.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
record (logging.LogRecord): The log record.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
formatted record (str): The formatted log record.
|
|
212
|
+
"""
|
|
213
|
+
record.levelname = f"{record.levelname:^10}"
|
|
214
|
+
return super().format(record)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
btlogging.helpers module provides helper functions for the Bittensor logging system.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Generator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def all_loggers() -> Generator["logging.Logger", None, None]:
|
|
10
|
+
"""Generator that yields all logger instances in the application.
|
|
11
|
+
|
|
12
|
+
Iterates through the logging root manager's logger dictionary and yields all active `Logger` instances. It skips
|
|
13
|
+
placeholders and other types that are not instances of `Logger`.
|
|
14
|
+
|
|
15
|
+
Yields:
|
|
16
|
+
logger (logging.Logger): An active logger instance.
|
|
17
|
+
"""
|
|
18
|
+
for logger in logging.root.manager.loggerDict.values():
|
|
19
|
+
if isinstance(logger, logging.PlaceHolder):
|
|
20
|
+
continue
|
|
21
|
+
# In some versions of Python, the values in loggerDict might be
|
|
22
|
+
# LoggerAdapter instances instead of Logger instances.
|
|
23
|
+
# We check for Logger instances specifically.
|
|
24
|
+
if isinstance(logger, logging.Logger):
|
|
25
|
+
yield logger
|
|
26
|
+
else:
|
|
27
|
+
# If it's not a Logger instance, it could be a LoggerAdapter or
|
|
28
|
+
# another form that doesn't directly offer logging methods.
|
|
29
|
+
# This branch can be extended to handle such cases as needed.
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def all_logger_names() -> Generator[str, None, None]:
|
|
34
|
+
"""
|
|
35
|
+
Generate the names of all active loggers.
|
|
36
|
+
|
|
37
|
+
This function iterates through the logging root manager's logger dictionary and yields the names of all active
|
|
38
|
+
`Logger` instances. It skips placeholders and other types that are not instances of `Logger`.
|
|
39
|
+
|
|
40
|
+
Yields:
|
|
41
|
+
name (str): The name of an active logger.
|
|
42
|
+
"""
|
|
43
|
+
for name, logger in logging.root.manager.loggerDict.items():
|
|
44
|
+
if isinstance(logger, logging.PlaceHolder):
|
|
45
|
+
continue
|
|
46
|
+
# In some versions of Python, the values in loggerDict might be
|
|
47
|
+
# LoggerAdapter instances instead of Logger instances.
|
|
48
|
+
# We check for Logger instances specifically.
|
|
49
|
+
if isinstance(logger, logging.Logger):
|
|
50
|
+
yield name
|
|
51
|
+
else:
|
|
52
|
+
# If it's not a Logger instance, it could be a LoggerAdapter or
|
|
53
|
+
# another form that doesn't directly offer logging methods.
|
|
54
|
+
# This branch can be extended to handle such cases as needed.
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_max_logger_name_length() -> int:
|
|
59
|
+
"""
|
|
60
|
+
Calculate and return the length of the longest logger name.
|
|
61
|
+
|
|
62
|
+
This function iterates through all active logger names and determines the length of the longest name.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
max_length (int): The length of the longest logger name.
|
|
66
|
+
"""
|
|
67
|
+
max_length = 0
|
|
68
|
+
for name in all_logger_names():
|
|
69
|
+
if len(name) > max_length:
|
|
70
|
+
max_length = len(name)
|
|
71
|
+
return max_length
|