turintech_logging 2.0.1__py3-none-any.whl

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.
@@ -0,0 +1,23 @@
1
+ # ───────────────────────────────────────────────────── imports ────────────────────────────────────────────────────── #
2
+ from core_logging.logger_conf import EnableLoggerConf, FileLoggerConf, LoggerConf
3
+ from core_logging.logger_handler import InterceptHandler
4
+ from core_logging.logger_service import LoggerService
5
+ from core_logging.logger_utils import LoggerType, get_logger
6
+
7
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
8
+ # Defines the public interface of the module that will be imported when using 'from package import *'. #
9
+ # This helps control what is exposed to the global namespace, limiting imports to only those listed in __all__. #
10
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
11
+
12
+ __all__ = [
13
+ "EnableLoggerConf",
14
+ "FileLoggerConf",
15
+ "LoggerConf",
16
+ "InterceptHandler",
17
+ "LoggerService",
18
+ "get_logger",
19
+ "LoggerType",
20
+ "logger",
21
+ ]
22
+
23
+ logger = get_logger()
@@ -0,0 +1,184 @@
1
+ # ───────────────────────────────────────────────────── imports ────────────────────────────────────────────────────── #
2
+ import sys
3
+ from datetime import time, timedelta
4
+ from pathlib import Path
5
+ from typing import Any, Callable, Dict, Optional, Union
6
+
7
+ from pydantic import Field, field_validator
8
+
9
+ from core_common_configuration.base_conf_env import BaseConfEnv
10
+
11
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
12
+ # specifies all modules that shall be loaded and imported into the #
13
+ # current namespace when we use 'from package import *' #
14
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
15
+
16
+ __all__ = [
17
+ "EnableLoggerConf",
18
+ "LoggerConf",
19
+ "FileLoggerConf",
20
+ "logger_conf_factory",
21
+ "file_logger_conf_factory",
22
+ "enable_logger_conf_factory",
23
+ ]
24
+
25
+
26
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
27
+ # Logging Configuration #
28
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
29
+
30
+
31
+ class EnableLoggerConf(BaseConfEnv):
32
+ """Configuration to enable or disable different types of logging."""
33
+
34
+ enable: bool = Field(
35
+ True,
36
+ description=(
37
+ "Flag indicating whether the logging configuration should be initialized (True) or if this configuration "
38
+ "is already initialized (False)."
39
+ ),
40
+ )
41
+ enable_file: bool = Field(True, description="Flag to enable (True) or disable (False) logging to a file.")
42
+ enable_stderr: bool = Field(False, description="Flag to enable (True) or disable (False) logging to stderr output.")
43
+
44
+
45
+ class LoggerConf(BaseConfEnv):
46
+ """Configuration attributes for application logs.
47
+
48
+ Attributes:
49
+ sink: An object responsible for receiving formatted logging messages and propagating them
50
+ to an appropriate endpoint.
51
+ level: The minimum severity level from which logged messages should be sent to the sink.
52
+ format: The template used to format logged messages before being sent to the sink.
53
+ colorize: Whether the color markups contained in the formatted message should be converted
54
+ to ANSI codes for terminal coloration, or stripped otherwise.
55
+ serialize: Whether the logged message and its records should be first converted to a JSON string
56
+ before being sent to the sink.
57
+ backtrace: Whether the exception trace formatted should be extended upward, beyond the catching point,
58
+ to show the full stack trace which generated the error.
59
+ diagnose: Whether the exception trace should display the variables' values to ease debugging.
60
+ This should be set to False in production to avoid leaking sensitive data.
61
+ enqueue: Whether the messages to be logged should first pass through a multiprocess-safe queue
62
+ before reaching the sink. This is useful while logging to a file through multiple processes
63
+ and also has the advantage of making logging calls non-blocking.
64
+ """
65
+
66
+ sink: Union[Path, object]
67
+
68
+ level: str = "INFO"
69
+ format: Union[str, Callable] = (
70
+ "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green>"
71
+ " <r>-</r> <level>{level: <8}</level>"
72
+ " <r>-</r> <cyan>{name}</cyan>.<cyan>{function}</cyan>:<cyan>{line}</cyan>"
73
+ " <r>-</r> <level>{message}</level>"
74
+ )
75
+ colorize: Optional[bool] = True
76
+ serialize: Optional[bool] = False
77
+ backtrace: Optional[bool] = True
78
+ diagnose: Optional[bool] = False
79
+ enqueue: Optional[bool] = True
80
+
81
+ @field_validator("level")
82
+ def upper_validator(cls, value: str) -> str:
83
+ return value.upper() if value else value
84
+
85
+ @field_validator("sink", mode="before")
86
+ def sink_validator(cls, value: str):
87
+ return {
88
+ "sys.stdout": sys.stdout,
89
+ "sys.stderr": sys.stderr,
90
+ }.get(value, value)
91
+
92
+ def model_dump(self, **kwargs) -> dict[str, Any]:
93
+ """Override Pydantic's `model_dump` to exclude the `sink` field when dumping.
94
+
95
+ After obtaining the clean dump, re-attach the actual `sink` object (e.g., sys.stdout, file path,
96
+ logging handler) so that `logger.add()` receives a supported sink type.
97
+ """
98
+ dump = super().model_dump(**kwargs, exclude={"sink"})
99
+ return {**dump, "sink": self.sink}
100
+
101
+
102
+ class FileLoggerConf(LoggerConf):
103
+ """Configuration attributes for application file logs."""
104
+
105
+ colorize: Optional[bool] = False
106
+ rotation: Union[str, int, time, timedelta] = "12:00" # New file is created each day at noon
107
+ retention: Union[str, int, time, timedelta] = "1 month"
108
+ compression: Optional[str] = "zip"
109
+ delay: bool = True
110
+
111
+
112
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
113
+ # Logging Configuration Factory #
114
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
115
+
116
+
117
+ def enable_logger_conf_factory(
118
+ _env_file: Optional[str] = None, prefix: Optional[str] = None, defaults: Optional[Dict] = None, **kwargs
119
+ ) -> EnableLoggerConf:
120
+ """This is a factory generating a EnableLoggerConf class specific to a service, loading every value from a generic
121
+ .env file storing variables in uppercase with a service prefix.
122
+
123
+ example .env:
124
+ PREFIX_ENABLE=true
125
+ ...
126
+
127
+ Args:
128
+ _env_file (str, optional): Configuration file of the environment variables from where to load the values.
129
+ prefix (str, optional): Prefix that the class attributes must have in the environment variables.
130
+ defaults (Dict, optional): New values to override the default field values for the configuration model.
131
+ kwargs (**Dict): Arguments passed to the Settings class initializer.
132
+
133
+ Returns:
134
+ conf (EnableLoggerConf): Object of the required configuration class
135
+
136
+ """
137
+ return EnableLoggerConf.with_defaults(env_file=_env_file, env_prefix=prefix, defaults=defaults, **kwargs)
138
+
139
+
140
+ def logger_conf_factory(
141
+ _env_file: Optional[str] = None, prefix: Optional[str] = None, defaults: Optional[Dict] = None, **kwargs
142
+ ) -> LoggerConf:
143
+ """This is a factory generating a LoggerConf class specific to a service, loading every value from a generic .env
144
+ file storing variables in uppercase with a service prefix.
145
+
146
+ example .env:
147
+ PREFIX_LEVEL=INFO
148
+ ...
149
+
150
+ Args:
151
+ _env_file (str, optional): Configuration file of the environment variables from where to load the values.
152
+ prefix (str, optional): Prefix that the class attributes must have in the environment variables.
153
+ defaults (Dict, optional): New values to override the default field values for the configuration model.
154
+ kwargs (**Dict): Arguments passed to the Settings class initializer.
155
+
156
+ Returns:
157
+ conf (LoggerConf): Object of the required configuration class
158
+
159
+ """
160
+ return LoggerConf.with_defaults(env_file=_env_file, env_prefix=prefix, defaults=defaults, **kwargs)
161
+
162
+
163
+ def file_logger_conf_factory(
164
+ _env_file: Optional[str] = None, prefix: Optional[str] = None, defaults: Optional[Dict] = None, **kwargs
165
+ ) -> FileLoggerConf:
166
+ """This is a factory generating a FileLoggerConf class specific to a service, loading every value from a generic
167
+ .env file storing variables in uppercase with a service prefix.
168
+
169
+ example .env:
170
+ PREFIX_LEVEL=INFO
171
+ PREFIX_COLORIZE=true
172
+ ...
173
+
174
+ Args:
175
+ _env_file (str, optional): Configuration file of the environment variables from where to load the values.
176
+ prefix (str, optional): Prefix that the class attributes must have in the environment variables.
177
+ defaults (Dict, optional): New values to override the default field values for the configuration model.
178
+ kwargs (**Dict): Arguments passed to the Settings class initializer.
179
+
180
+ Returns:
181
+ conf (FileLoggerConf): Object of the required configuration class
182
+
183
+ """
184
+ return FileLoggerConf.with_defaults(env_file=_env_file, env_prefix=prefix, defaults=defaults, **kwargs)
@@ -0,0 +1,54 @@
1
+ # ───────────────────────────────────────────────────── imports ────────────────────────────────────────────────────── #
2
+ import logging
3
+ from types import FrameType
4
+ from typing import Optional
5
+
6
+ from core_logging.logger_utils import LoggerType
7
+
8
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
9
+ # Defines the public interface of the module that will be imported when using 'from package import *'. #
10
+ # This helps control what is exposed to the global namespace, limiting imports to only those listed in __all__. #
11
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
12
+
13
+ __all__ = ["InterceptHandler"]
14
+
15
+
16
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
17
+ # Module Implementation #
18
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
19
+
20
+
21
+ class InterceptHandler(logging.Handler):
22
+ """A logging handler that redirects standard logging messages to a structured logger."""
23
+
24
+ def __init__(self, logger: LoggerType, name: Optional[str] = None):
25
+ """Initializes the handler with a structured logger.
26
+
27
+ Args:
28
+ logger (LoggerType): The logger instance to intercept log messages.
29
+ name (str, optional): The name of the logging handler. Defaults to None.
30
+ """
31
+ super().__init__()
32
+ self.logger: LoggerType = logger
33
+ if name:
34
+ self.set_name(name)
35
+
36
+ def emit(self, record: logging.LogRecord):
37
+ """Processes and forwards log records to the structured logger.
38
+
39
+ Args:
40
+ record (logging.LogRecord): The log record to be handled.
41
+ """
42
+ try:
43
+ level = self.logger.level(record.levelname).name
44
+ except ValueError:
45
+ level = record.levelno # type: ignore
46
+
47
+ # Find caller from where originated the logged message
48
+ frame: Optional[FrameType] = logging.currentframe()
49
+ depth: int = 2
50
+ while frame and frame.f_code.co_filename == logging.__file__:
51
+ frame = frame.f_back
52
+ depth += 1
53
+
54
+ self.logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
@@ -0,0 +1,203 @@
1
+ # ───────────────────────────────────────────────────── imports ────────────────────────────────────────────────────── #
2
+ import logging
3
+ import sys
4
+ from os import PathLike
5
+ from typing import Any, Optional
6
+
7
+ from core_common_configuration import BaseConfManager
8
+ from core_logging.logger_conf import EnableLoggerConf, FileLoggerConf, LoggerConf
9
+ from core_logging.logger_handler import InterceptHandler
10
+ from core_logging.logger_utils import LoggerType, get_logger
11
+
12
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
13
+ # Defines the public interface of the module that will be imported when using 'from package import *'. #
14
+ # This helps control what is exposed to the global namespace, limiting imports to only those listed in __all__. #
15
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
16
+
17
+ __all__ = ["LoggerService"]
18
+
19
+
20
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
21
+ # Service implementation #
22
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
23
+
24
+
25
+ class LoggerService:
26
+ """Manages logging configurations and initialisation.
27
+
28
+ This class handles the configuration of logging settings, including enabling or disabling
29
+
30
+ Attributes:
31
+ _env_prefix (str): Prefix for environment variable names.
32
+ _conf_mgr (BaseConfManager): Configuration manager for handling environment variables.
33
+ _defaults_enable_logger_conf (Optional[dict[str, Any]]): Default configurations for enabling logging.
34
+ _defaults_logger_conf (Optional[dict[str, Any]]): Default configurations for logger settings.
35
+ _defaults_file_logger_conf (Optional[dict[str, Any]]): Default configurations for file logger settings.
36
+ _extra (Optional[dict[str, str]]): Additional parameters for the core logger.
37
+ _mandatory_logger_conf (dict[str, dict[str, Any]]): Mandatory logger configurations.
38
+ """
39
+
40
+ _env_prefix: str
41
+ _conf_mgr: BaseConfManager
42
+ _defaults_enable_logger_conf: Optional[dict[str, Any]]
43
+ _defaults_logger_conf: Optional[dict[str, Any]]
44
+ _defaults_file_logger_conf: Optional[dict[str, Any]]
45
+ _extra: Optional[dict[str, str]]
46
+ _mandatory_logger_conf: dict[str, dict[str, Any]]
47
+
48
+ def __init__(
49
+ self,
50
+ env_prefix: str = "LOGGER_",
51
+ env_file: Optional[PathLike] = None,
52
+ mandatory_logger_conf: Optional[dict[str, dict[str, Any]]] = None,
53
+ extra: Optional[dict[str, str]] = None,
54
+ **kwargs,
55
+ ):
56
+ """Initialises the LoggerService with specified configurations.
57
+
58
+ Args:
59
+ env_prefix (str): Prefix for environment variable names.
60
+ env_file (str | Path, optional): Path to the environment variable configuration file.
61
+ mandatory_logger_conf (dict[str, dict[str, Any]], optional): Mandatory logger configurations.
62
+ extra (dict[str, str], optional): Additional parameters for the core logger.
63
+ **kwargs: Additional keyword arguments, including:
64
+ - defaults_enable_logger_conf (dict[str, Any], optional): Default configurations for enabling logging.
65
+ - defaults_logger_conf (dict[str, Any], optional): Default configurations for logger settings.
66
+ - defaults_file_logger_conf (dict[str, Any], optional): Default configurations for file logger settings.
67
+ """
68
+ self._env_prefix = kwargs.get("prefix", env_prefix)
69
+ self._conf_mgr = BaseConfManager(env_file=env_file)
70
+ self._defaults_enable_logger_conf = kwargs.get("defaults_enable_logger_conf")
71
+ self._defaults_logger_conf = kwargs.get("defaults_logger_conf")
72
+ self._defaults_file_logger_conf = kwargs.get("defaults_file_logger_conf")
73
+ self._extra = extra
74
+ self._mandatory_logger_conf = (
75
+ mandatory_logger_conf
76
+ if mandatory_logger_conf is not None
77
+ else {"stdout": {"sink": sys.stdout}, "stderr": {"sink": sys.stderr, "level": "ERROR"}}
78
+ )
79
+
80
+ # ------------------------------------------------------------------------------------
81
+ # --- Configuration
82
+ # ------------------------------------------------------------------------------------
83
+
84
+ @property
85
+ def enable_logger_conf(self) -> EnableLoggerConf:
86
+ """Retrieve the configuration for enabling or disabling logging."""
87
+ return self._conf_mgr.get_conf(
88
+ conf_type=EnableLoggerConf,
89
+ conf_name="ENABLE_LOGGER_CONF",
90
+ env_prefix=self._env_prefix,
91
+ defaults=self._defaults_enable_logger_conf,
92
+ )
93
+
94
+ @property
95
+ def logger_conf(self) -> dict[str, LoggerConf]:
96
+ """Retrieve the logger configurations."""
97
+ return {
98
+ key: self._conf_mgr.get_conf(
99
+ conf_type=LoggerConf,
100
+ conf_name=f"LOGGER_CONF_{key.upper()}",
101
+ env_prefix=self._env_prefix,
102
+ defaults=self._defaults_logger_conf,
103
+ **mandatory,
104
+ )
105
+ for key, mandatory in self._mandatory_logger_conf.items()
106
+ }
107
+
108
+ @property
109
+ def file_logger_conf(self) -> FileLoggerConf:
110
+ """Retrieve the file logger configuration."""
111
+ return self._conf_mgr.get_conf(
112
+ conf_type=FileLoggerConf,
113
+ conf_name="FILE_LOGGER_CONF",
114
+ env_prefix=self._env_prefix,
115
+ defaults=self._defaults_file_logger_conf,
116
+ )
117
+
118
+ def get_configuration(self, mode: str = "json") -> dict[str, Any]:
119
+ """Returns the current logging configuration.
120
+
121
+ Args:
122
+ mode (str): Serialization mode (default: "json").
123
+
124
+ Returns:
125
+ dict[str, Any]: Full logging configuration.
126
+ """
127
+
128
+ def _serialized_conf(conf: Optional[dict[str, Any]]) -> dict[str, str]:
129
+ _conf = conf or {}
130
+ if mode == "json":
131
+ return {key: str(value) for key, value in _conf.items()}
132
+ return _conf
133
+
134
+ return {
135
+ "env_prefix": self._env_prefix,
136
+ "env_file": self._conf_mgr.env_file,
137
+ "defaults_enable_logger_conf": _serialized_conf(self._defaults_enable_logger_conf),
138
+ "defaults_file_logger_conf": _serialized_conf(self._defaults_file_logger_conf),
139
+ "mandatory_logger_conf": _serialized_conf(self._mandatory_logger_conf),
140
+ "extra": self._extra,
141
+ **{key: value.model_dump(mode=mode) for key, value in self._conf_mgr.config_map.items()},
142
+ }
143
+
144
+ # ------------------------------------------------------------------------------------
145
+ # --- Logging Initialisation
146
+ # ------------------------------------------------------------------------------------
147
+
148
+ def init_logging(self, logger: LoggerType, loggers_to_intercept: Optional[list[str]] = None) -> None:
149
+ """Initialises logging configuration and optionally intercepts specified loggers.
150
+
151
+ Args:
152
+ logger (LoggerType): The logger instance to configure.
153
+ loggers_to_intercept (list[str], optional): Optional list of loggers to intercept.
154
+ """
155
+ if loggers_to_intercept:
156
+ self.init_logging_handlers(logger, loggers_to_intercept)
157
+ self.init_logging_conf(logger)
158
+
159
+ def init_logging_conf(self, logger: Optional[LoggerType] = None) -> None:
160
+ """Configures the logging settings.
161
+
162
+ Args:
163
+ logger (LoggerType, optional): The logger instance to configure.
164
+ If not provided, the default logger obtained from the `get_logger` function will be used.
165
+ """
166
+ _logger: LoggerType = get_logger() if not logger else logger
167
+ enable_logger_conf: EnableLoggerConf = self.enable_logger_conf
168
+ if enable_logger_conf.enable:
169
+ _logger.configure(extra=self._extra)
170
+ _logger.remove()
171
+ _logger._core.handlers_count = 0
172
+
173
+ for key, config in self.logger_conf.items():
174
+ if key == "stdout" or (enable_logger_conf.enable_stderr and key == "stderr"):
175
+ _logger.add(**config.model_dump())
176
+
177
+ if enable_logger_conf.enable_file:
178
+ _logger.add(**self.file_logger_conf.model_dump())
179
+
180
+ _logger.debug(f"Logger configured: {self.get_configuration()}")
181
+
182
+ @classmethod
183
+ def init_logging_handlers(cls, logger: LoggerType, loggers_to_intercept: list[str]) -> None:
184
+ """Intercepts and redirects specified loggers to a structured logger.
185
+
186
+ WARNING: If called in the startup event, logs before application start will use the old format.
187
+
188
+ Args:
189
+ logger (LoggerType): The structured logger instance.
190
+ loggers_to_intercept (list[str]): List of loggers to intercept.
191
+ """
192
+
193
+ for logger_name in loggers_to_intercept:
194
+ for intercepted_logger in (
195
+ logging.getLogger(name)
196
+ for name in logging.root.manager.loggerDict # pylint:disable=no-member
197
+ if name.startswith(f"{logger_name}.")
198
+ ):
199
+ intercepted_logger.handlers = [InterceptHandler(logger, intercepted_logger.name)]
200
+ intercepted_logger.propagate = False # Prevent duplicate log messages
201
+ log = logging.getLogger(logger_name)
202
+ log.handlers = [InterceptHandler(logger, logger_name)]
203
+ log.propagate = False # Prevent duplicate log messages
@@ -0,0 +1,178 @@
1
+ # ───────────────────────────────────────────────────── imports ────────────────────────────────────────────────────── #
2
+ import logging
3
+ from contextlib import contextmanager
4
+ from typing import Any
5
+
6
+ from typing_extensions import TypeAlias
7
+
8
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
9
+ # Defines the public interface of the module that will be imported when using 'from package import *'. #
10
+ # This helps control what is exposed to the global namespace, limiting imports to only those listed in __all__. #
11
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
12
+
13
+ __all__ = [
14
+ "LoggerType",
15
+ "get_logger",
16
+ "get_logging_backup",
17
+ "set_logging_buckup",
18
+ "get_loguru_backup",
19
+ "set_loguru_buckup",
20
+ "preserve_logging_state",
21
+ ]
22
+
23
+
24
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
25
+ # Module Implementation #
26
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #
27
+
28
+
29
+ def logger_type() -> type:
30
+ """Returns the Logger class from the specified logging module.
31
+
32
+ The import of the loguru.Logger class is performed inside this function
33
+ to avoid potential serialization issues.
34
+
35
+ Returns:
36
+ type: The Logger class from the specified logging module.
37
+ """
38
+ from loguru._logger import Logger # pylint: disable=import-outside-toplevel
39
+
40
+ return Logger
41
+
42
+
43
+ LoggerType: TypeAlias = logger_type() # type: ignore
44
+
45
+
46
+ def get_logger(**bind_args) -> LoggerType:
47
+ """Returns a logger instance, optionally binding extra context.
48
+
49
+ The import of the loguru logger instance is done inside this function to prevent
50
+ serialization issues.
51
+
52
+ Args:
53
+ **bind_args: The arguments to bind additional context to the logger.
54
+
55
+ Returns:
56
+ LoggerType: A logger instance with the provided context bound to it.
57
+ """
58
+ from loguru import logger # pylint: disable=import-outside-toplevel
59
+
60
+ return logger.bind(**bind_args)
61
+
62
+
63
+ # ------------------------------------------------------------------------------------
64
+ # --- Preserve and restore the state of loggers
65
+ # ------------------------------------------------------------------------------------
66
+
67
+
68
+ def get_logging_backup() -> dict[str, dict[str, Any]]:
69
+ """Returns a dictionary containing the state of the logging module.
70
+
71
+ Returns:
72
+ dict[str, dict[str, Union[bool, list[logging.Handler]]]]: A dictionary containing the state
73
+ of the logging module.
74
+ """
75
+ return {
76
+ name: {
77
+ "handlers": list(logger.handlers),
78
+ "propagate": logger.propagate,
79
+ }
80
+ for name, logger in [(logging.root.name, logging.root.handlers)] + list(logging.root.manager.loggerDict.items())
81
+ if isinstance(logger, logging.Logger)
82
+ }
83
+
84
+
85
+ def set_logging_buckup(logging_backup: dict[str, dict[str, Any]]) -> None:
86
+ """Restore the state of the logging module from a backup.
87
+
88
+ Args:
89
+ logging_backup (dict[str, dict[str, Union[bool, list[logging.Handler]]]]): The backup dictionary
90
+ created by get_logging_backup().
91
+ """
92
+ for name, logger in [(logging.root.name, logging.root.handlers)] + list(logging.root.manager.loggerDict.items()):
93
+ if isinstance(logger, logging.Logger):
94
+ if state := logging_backup.get(name):
95
+ logger.handlers = state["handlers"]
96
+ logger.propagate = state["propagate"]
97
+ else:
98
+ logging.root.manager.loggerDict.pop(name, None)
99
+
100
+
101
+ def get_loguru_backup() -> dict[str, Any]:
102
+ """Returns a dictionary containing the state of the loguru logger.
103
+
104
+ Returns:
105
+ dict[str, Any]: A dictionary containing the state of the loguru logger.
106
+ """
107
+ from loguru import logger
108
+ from loguru._logger import Core
109
+
110
+ _core: Core = logger._core # pyright: ignore[reportAttributeAccessIssue]
111
+ return {
112
+ **_core.__getstate__(),
113
+ "enabled": _core.enabled.copy(),
114
+ "handlers": _core.handlers.copy(),
115
+ }
116
+
117
+
118
+ def set_loguru_buckup(loguru_backup: dict[str, Any]) -> None:
119
+ """Restore the state of loguru logger from a backup.
120
+
121
+ Args:
122
+ loguru_backup (dict[str, Any]): The backup dictionary created by get_loguru_backup().
123
+
124
+ Warning:
125
+ The colorize argument is not restored, so the color of the logger might not be the same as before.
126
+ """
127
+ from loguru import logger
128
+ from loguru._file_sink import FileSink
129
+ from loguru._logger import Core
130
+ from loguru._simple_sinks import StreamSink
131
+
132
+ _core: Core = logger._core # pyright: ignore[reportAttributeAccessIssue]
133
+
134
+ # Remove all handlers currently added to the logger
135
+ logger.remove()
136
+ _core.handlers_count = 0
137
+
138
+ # Restore the internal state of the logger (except handlers)
139
+ handlers_backup = loguru_backup.get("handlers", {})
140
+ _core.__setstate__(
141
+ {key: value for key, value in loguru_backup.items() if key not in ["handlers", "handlers_count"]}
142
+ )
143
+
144
+ # Add each handler again using logger.add()
145
+ for _id, handler in handlers_backup.items():
146
+ fmt_with_exc = handler._formatter.strip()
147
+ fmt = fmt_with_exc[: -len("\n{exception}")] if fmt_with_exc.endswith("\n{exception}") else fmt_with_exc
148
+
149
+ sink = (
150
+ handler._sink._stream
151
+ if isinstance(handler._sink, StreamSink)
152
+ else handler._sink._path
153
+ if isinstance(handler._sink, FileSink)
154
+ else handler._sink
155
+ )
156
+
157
+ logger.add(
158
+ sink=sink,
159
+ level=handler._levelno,
160
+ format=fmt,
161
+ filter=handler._filter,
162
+ serialize=handler._serialize,
163
+ enqueue=handler._enqueue,
164
+ context=handler._multiprocessing_context,
165
+ catch=handler._error_interceptor._should_catch,
166
+ )
167
+
168
+
169
+ @contextmanager
170
+ def preserve_logging_state():
171
+ """Context manager to temporarily preserve and restore the state of loggers."""
172
+ logging_backup = get_logging_backup()
173
+ loguru_backup = get_loguru_backup()
174
+ try:
175
+ yield
176
+ finally:
177
+ set_logging_buckup(logging_backup)
178
+ set_loguru_buckup(loguru_backup)
core_logging/py.typed ADDED
File without changes
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: turintech_logging
3
+ Version: 2.0.1
4
+ Summary: Core Libs files logging
5
+ License-Expression: LicenseRef-Turing-Intelligence-Technology-EULA
6
+ License-File: LICENSE
7
+ Classifier: Development Status :: 5 - Production/Stable
8
+ Classifier: License :: Other/Proprietary License
9
+ Requires-Python: <3.14,>=3.8
10
+ Requires-Dist: loguru<1.0.0,>=0.7.0
11
+ Requires-Dist: pydantic<3.0.0,>=2
12
+ Requires-Dist: python-dotenv<1.0.0,>=0.21.0
13
+ Requires-Dist: turintech-configuration<3.0.0,>=2.0.0a0
14
+ Requires-Dist: typing-extensions<5.0.0,>=4.5.0
@@ -0,0 +1,10 @@
1
+ core_logging/__init__.py,sha256=fAXIgbY-RYCFiZUpp16dQjBwevIN_4xunjzVbNF4MEs,1735
2
+ core_logging/logger_conf.py,sha256=8aYTJbK-ybN9Nz4xFsbxVxuepAJVwP49mUF6FgTampo,10098
3
+ core_logging/logger_handler.py,sha256=h1ngFW5v5VSqVCFqXxeiLaNEZlCgzbLQocqUtaVt5Z4,3584
4
+ core_logging/logger_service.py,sha256=_RXGm4W6IYIO_w1YPeddSikB487zggCziwXr_PE-WzA,10790
5
+ core_logging/logger_utils.py,sha256=L-q97M5o2PvVwCp3EONtDYlPKC4n5EEwUoTj8fr1LKc,7545
6
+ core_logging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ turintech_logging-2.0.1.dist-info/METADATA,sha256=nngnLOiR1G7EYTZhxUzsUqkW_7sxNH1VLnRPW8Cn83g,533
8
+ turintech_logging-2.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
+ turintech_logging-2.0.1.dist-info/licenses/LICENSE,sha256=1oiCRxRmKyfqDkVZDDGPl8W09ACBwaCJNUDYsW1aJB0,14103
10
+ turintech_logging-2.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,146 @@
1
+ TURING INTELLIGENCE TECHNOLOGY LIMITED END-USER LICENCE
2
+
3
+ THIS LICENCE AGREEMENT (Licence) is made BETWEEN you (Licensee) and TURING INTELLIGENCE TECHNOLOGY LIMITED (business address at 1 Finsbury Avenue, London, EC2M 2PF, United Kingdom) (the Licensor), for TurinTech Artifacts(Code).
4
+
5
+ The Licensor licenses the use of the Code to the Licensee on the basis of this Licence. The Licensor does not sell the Code to the Licensee. The Licensor remain the owner of the Code at all times, except for any Open Source third-party libraries (excluding any modifications or derivatives to that software, which shall be owned by the Licensor to the extent permitted under the terms of the open source software licence) (Open Source Software) are licensed on the applicable open source software licence.
6
+
7
+ 1. GRANT AND SCOPE OF LICENCE
8
+
9
+ 1.1. In consideration of the Licensee agreeing to abide by the terms of this Licence, the Licensor grants to Licensee a non-exclusive, non-transferable licence to use the Code on the terms of this Licence.
10
+
11
+ 1.2. The Licensee may:
12
+
13
+ (a) download and use the Code for the Licensee's internal business purposes only in connection with the use of the "evoML" and / or "Artemis" software that the Licensor has made available to the Licensee under the terms of evoML and / or Artemis End-User License; and
14
+
15
+ (b) provided the Licensee complies with the provisions in clause 2 *(Restrictions),* make up [1 (one)] copy of the Code for back-up purposes only.
16
+
17
+ 2. RESTRICTIONS
18
+
19
+ 2.1. Except as expressly set out in this Licence or as permitted by applicable law, the Licensee undertakes:
20
+
21
+ (a) to comply with the applicable terms in respect of the Open Source Software;
22
+
23
+ (b) not to copy the Code except where such copying is incidental to normal use of the Code, or where it is necessary for the purpose of back-up or operational security;
24
+
25
+ (c) not to rent, lease, sub-license, loan, translate, merge, adapt, vary or modify the Code;
26
+
27
+ (d) not to make alterations to, or modifications of, the whole or any part of the Code, nor permit the Code or any part of it to be combined with, or become incorporated in, any other programs;
28
+
29
+ (e) not to disassemble, decompile, reverse-engineer or create derivative works based on the whole or any part of the Code nor attempt to do any such thing except to the extent that (by virtue of section 296A of the Copyright, Designs and Patents Act 1988) such actions cannot be prohibited because they are essential for the purpose of achieving inter-operability of the Code with another software program, and provided that the information obtained by the Licensee during such activities:
30
+
31
+ (i) is used only for the purpose of achieving inter-operability of the Code with another software program; and
32
+
33
+ (ii) is not unnecessarily disclosed or communicated without the Licensor's prior written consent to any third party; and
34
+
35
+ (iii) is not used to create any software which is substantially similar to the Code;
36
+
37
+ (f) to keep all copies of the Code secure and to maintain accurate and up-to-date records of the number and locations of all copies of the Code;
38
+
39
+ (g) to supervise and control use of the Code and ensure that the Code is used by the Licensee, and its Personnel in accordance with the terms of this Licence;
40
+
41
+ (h) to include the Licensor's copyright notice on all entire and partial copies the Licensee make of the Code on any medium;
42
+
43
+ (i) not to provide or otherwise make available the Code in whole or in part, in any form to any person other than Licensee's Personnel without prior written consent from the Licensor.
44
+
45
+ 3. INTELLECTUAL PROPERTY RIGHTS
46
+
47
+ 3.1. The Licensee acknowledges that all Intellectual Property Rights in the Code anywhere in the world belong to the Licensor (except the Open Source Software but excluding any modifications or derivatives of the Open Source Software, which shall be owned by the Licensor to the extent permitted under the terms of the applicable Open Source Software licence), that rights in the Code are licensed (not sold) to the Licensee, and that the Licensee have no rights in, or to, the Code other than the right to use them in accordance with the terms of this Licence.
48
+
49
+ 3.2. The Licensee acknowledges that any output obtained using the Code is the sole responsibility of the Licensee. The Licensee is responsible for ensuring that the output obtained using the Code and any results and insights derived from such output are accurate, unbiased, and does not violate any applicable laws and regulations and / or the terms of this agreement.
50
+
51
+ 4. LIMITATION OF LIABILITY
52
+
53
+ 4.1. The Licensee acknowledge that the Code has not been developed to meet the Licensee's individual requirements, and that it is therefore the Licensee's responsibility to ensure that the facilities and functions of the Code as described in relevant user documentation meet the Licensee's requirements.
54
+
55
+ 4.2. The Licensor only supplies the Code for internal use by the Licensee's business, and the Licensee agrees not to use the Code for any re-sale purposes.
56
+
57
+ 4.3. Nothing in this Licence limits or excludes a Party's liability:
58
+
59
+ (a) to the extent that it cannot be legally limited or excluded by law (including liability for fraud or fraudulent statements); or
60
+
61
+ (b) for death or personal injury arising out of its negligence or that of its Personnel.
62
+ 4.4. Subject to clause 4.3, neither Party shall have any liability to the other Party, whether for breach of contract, in tort (including negligence), for breach of statutory duty, or otherwise, arising under or in connection with this Licence for:
63
+
64
+ (a) loss of profits, sales, business, or revenue;
65
+
66
+ (b) business interruption;
67
+
68
+ (c) loss of anticipated savings;
69
+
70
+ (d) wasted expenditure;
71
+
72
+ (e) loss or corruption of data or information;
73
+
74
+ (f) loss of business opportunity, goodwill or reputation; or
75
+
76
+ (g) any indirect or consequential Losses.
77
+ 4.5. Subject to clause 4.3, the Licensor's total liability to the Licensee, whether for breach of contract, in tort (including negligence), for breach of statutory duty, or otherwise, arising under or in connection with this Licence for Losses incurred or suffered by the Licensee shall be limited for all claims in aggregate to £10,000.
78
+
79
+ 4.6. This Licence sets out the full extent of the Licensor's obligations and liabilities in respect of the supply of the Code. Except as expressly stated in this Licence, there are no conditions, warranties, representations or other terms, express or implied, that are binding on the Licensor. Any condition, warranty, representation or other term concerning the supply of the Code which might otherwise be implied into, or incorporated in, this Licence whether by statute, common law or otherwise, is excluded to the fullest extent permitted by law.
80
+
81
+ 5. TERMINATION
82
+
83
+ 5.1. The Licensor may terminate this Licence immediately by written notice to the Licensee either for (a) convenience, or (b) if the Licensee commit a material or persistent breach of this Licence which the Licensee fails to remedy (if remediable) within 14 days after the service of written notice requiring the Licensee to do so.
84
+
85
+ 5.2. On termination of this Licence for any reason:
86
+
87
+ (a) all rights granted to the Licensee under this Licence shall cease;
88
+
89
+ (b) the Licensee must immediately cease all activities authorised by this Licence; and
90
+
91
+ (c) the Licensee must immediately and permanently delete or remove the Code from all computer equipment in its possession, and certify to the Licensor that the Licensee has done so.
92
+
93
+ 6. NOTICES
94
+
95
+ 6.1. Any notice:
96
+
97
+ (a) given by the Licensor to the Licensee will be deemed received and properly served 24 hours after it is first posted on the Licensor's website, 24 hours after an email is sent to the Licensee, or three days after the date of posting of any letter; and
98
+
99
+ (b) given by Licensee to the Licensor will be deemed received and properly served 24 hours after an email is sent to `legal@turintech.ai`, or three days after the date of posting of any letter.
100
+ 6.2. In proving the service of any notice, it will be sufficient to prove, in the case of posting on the Licensor's website, that the website was generally accessible to the public for a period of 24 hours after the first posting of the notice; in the case of a letter, that such letter was properly addressed, stamped and placed in the post to the address of the recipient given for these purposes; and, in the case of an email, that such email was sent to the email address of the recipient given for these purposes.
101
+
102
+ 7. FORCE MAJEURE
103
+
104
+ 7.1. Provided that it has promptly notify the other Party of the occurrence of a Force Majeure Event affecting it in connection with this Licence, if a Party is prevented from, hindered or delayed in performing any of its obligations under this Licence by a Force Majeure Event, it shall not be in breach of this Licence for any such failure or delay in performing such obligations.
105
+
106
+ 7.2. If a Force Majeure Event prevents the Licensor from making available or providing any of the Code for more than 30 (thirty) days, the Licensor may terminate this Licence immediately by notice to the Licensee.
107
+
108
+ 8. GENERAL
109
+
110
+ 8.1. A person who is not a Party to this Licence shall have no rights pursuant to the Contracts (Rights of Third Parties) Act 1999 to enforce rights or benefits under this Licence.
111
+
112
+ 8.2. The Licensee shall not assign, novate, transfer, mortgage, charge, subcontract, delegate or otherwise dispose of any or all of its rights and obligations under this Licence without the prior written consent of the Licensor. The Licensor may do all such things without the Licensee's consent.
113
+
114
+ 8.3. This Licence shall be binding on, and shall ensure for the benefit of, the successors and permitted assigns of a Party.
115
+
116
+ 8.4. Except as may otherwise be specifically provided for in this Licence, no right, power, privilege or remedy conferred by any provision of this Licence is intended to be exclusive of any other right, power, privilege or remedy (whether under any other provision of this Licence, at common law, equity, under statute or otherwise).
117
+
118
+ 8.5. A waiver of any right or remedy under this Licence or at law is only effective if given by notice. No failure or delay by a Party to exercise any right or remedy (nor a partial exercise of such right or remedy) provided under this Licence or at law constitutes a waiver of that or any other right or remedy, nor shall it prevent or restrict the further exercise of that or any other right or remedy.
119
+
120
+ 8.6. Nothing in this Licence shall be construed as constituting a partnership between the Parties, and nor shall it constitute a Party the agent of the other Party.
121
+
122
+ 8.7. If any provision of this Licence is invalid, illegal or unenforceable in connection with its performance, such provision shall be deemed deleted to the minimum extent necessary.
123
+
124
+ 8.8. The Licensor may update the terms of this Licence at any time on notice to Licensee. If the Licensee continues to use the Code following the deemed receipt and service of the notice under clause 6 (Notices) shall constitute the Licensee's acceptance to the terms of this Licence, as varied. If the Licensee does not wish to accept the terms of the Licence (as varied), the Licensee must immediately stop using and accessing the Code on the deemed receipt and service of the notice.
125
+
126
+ 8.9. For the purposes of this Licence:
127
+
128
+ (a) **Force Majeure Event** means any event beyond a Party's reasonable control;
129
+
130
+ (b) **Intellectual Property Rights** means rights in patents (including utility models), designs (whether or not capable of registration), semi-conductor topography rights, copyright, moral rights, database rights, trademarks, service marks, trade and business names, rights to sue for passing off, rights in the nature of unfair competition rights, trade secret, confidentiality and other proprietary rights including rights to know-how and other technical information, applications to register any of the foregoing, rights to take action for past infringements in respect of any of the foregoing, and all rights in the nature of any of the foregoing anywhere in the world;
131
+
132
+ (c) **Losses** mean all losses, liabilities, damages, costs, charges, and expenses (including reputational loss or damage, management time, legal fees on a solicitor and own client basis, other professional advisers' fees, and costs and disbursements of investigation, litigation, settlement, judgment, interest, fines, penalties and remedial actions);
133
+
134
+ (d) **Party** means a party to this Licence, and **Parties** means both of them;
135
+
136
+ (e) **Personnel** means employees, agents, consultants, contractors and subcontractors.
137
+
138
+ 9. ENTIRE AGREEMENT
139
+
140
+ 9.1. This Licence constitutes the entire agreement between the Parties in relation to its subject matter. Nothing else has been agreed between the Parties in relation to that subject matter, and any earlier agreement or understanding between the Parties in relation to that subject matter has been superseded by this Licence. When entering into this Licence, no Party has relied on anything written, said or done by any person except to the extent that it is an express term of this Licence. No Party will have any non-contractual claim against any other Party for any representation made in connection with this Licence except to the extent that such claim cannot be excluded by contract. If a representation has become an express term of this Licence, it will only be enforceable as a contractual term in accordance with the terms of this Licence.
141
+
142
+ 10. GOVERNING LAW AND JURISDICTION
143
+
144
+ 10.1. This Licence, and any non-contractual obligations connected with it, shall be governed by the laws of England and Wales. The Parties irrevocably agree that all disputes, regardless of whether the dispute shall be regarded as contractual or not, shall be exclusively governed by and determined only in accordance with the laws of England and Wales.
145
+
146
+ 10.2. The Parties irrevocably agree that the courts of England and Wales have exclusive jurisdiction to determine any dispute and to grant interim remedies, or other provisional or protective relief.