inspy-logger 2.2__tar.gz → 3.0.dev2__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.
@@ -1,20 +1,20 @@
1
- ### Do What the Fuck You Want To - Public License (v3.0 3, Sept. 2020)
2
- ----
3
- ----
4
-
5
- Copyright (C) 2020 Taylor-Jayde Blackstone <t.blackstone@inspyre.tech>
6
-
7
- ----
8
-
9
- Everyone is permitted to copy and distribute verbatim or modified
10
- copies of this license document, and changing it is allowed as long
11
- as the name is changed. That's my name, you can't have it.
12
-
13
- DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
14
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
15
-
16
- - You just **DO WHAT THE FUCK YOU WANT TO**.
17
- - You **don't** blame me for **shit**. _I don't care_ how much smoke your hard-drive spewed out.
18
- - If someone else did what the fuck they wanted to and you're _**triggered**_ I'm **not** responsible.
19
-
20
- Contact <dev.null@inspyre.tech> with any complaints.
1
+ ### Do What the Fuck You Want To - Public License (v3.0 3, Sept. 2020)
2
+ ----
3
+ ----
4
+
5
+ Copyright (C) 2020 Taylor-Jayde Blackstone <t.blackstone@inspyre.tech>
6
+
7
+ ----
8
+
9
+ Everyone is permitted to copy and distribute verbatim or modified
10
+ copies of this license document, and changing it is allowed as long
11
+ as the name is changed. That's my name, you can't have it.
12
+
13
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
14
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
15
+
16
+ - You just **DO WHAT THE FUCK YOU WANT TO**.
17
+ - You **don't** blame me for **shit**. _I don't care_ how much smoke your hard-drive spewed out.
18
+ - If someone else did what the fuck they wanted to and you're _**triggered**_ I'm **not** responsible.
19
+
20
+ Contact <dev.null@inspyre.tech> with any complaints.
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.1
2
+ Name: inspy-logger
3
+ Version: 3.0.dev2
4
+ Summary: Colorable, scalable logger for CLI
5
+ Home-page: https://github.com/Inspyre-Softworks/inSPy-Logger
6
+ License: MIT
7
+ Keywords: cli,color,logging,log,terminal,console,colorama,colorlog,rich
8
+ Author: Taylor-Jayde Blackstone
9
+ Author-email: tayjaybabee@gmail.com
10
+ Requires-Python: >=3.7,<4.0
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Natural Language :: English
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.7
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3 :: Only
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Topic :: System :: Logging
25
+ Classifier: Topic :: Utilities
26
+ Requires-Dist: appdirs (>=1.4.4,<2.0.0)
27
+ Requires-Dist: colorama (>=0.4.6,<0.5.0)
28
+ Requires-Dist: colorlog (>=6.7,<7.0)
29
+ Requires-Dist: humanize (>=4.4.0,<5.0.0)
30
+ Requires-Dist: luddite (>=1.0.1,<2.0.0)
31
+ Requires-Dist: packaging (>=21.0,<22.0)
32
+ Requires-Dist: python-box (>=6.1.0,<7.0.0)
33
+ Requires-Dist: requests (>=2.28.1,<3.0.0)
34
+ Requires-Dist: rich (>=13.5.2,<14.0.0)
35
+ Requires-Dist: setuptools-autover (>=1.0.2,<2.0.0)
36
+ Project-URL: Documentation, https://inspy-logger.readthedocs.io/en/latest/
37
+ Project-URL: Repository, https://github.com/Inspyre-Softworks/inSPy-Logger
38
+ Description-Content-Type: text/markdown
39
+
40
+ # inSPy-Logger
41
+ ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/Inspyre-Softworks/inSPy-Logger/CI?label=CI&logo=github&logoColor=9cf&style=for-the-badge)
42
+ ![Codacy grade](https://img.shields.io/codacy/grade/7171eec682c549a88dee0da9cc9b92b3?logo=codacy&logoColor=9cf&style=for-the-badge)
43
+ ![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/Inspyre-Softworks/inSPy-Logger?color=9cf&include_prereleases&label=Pre-Release&logo=pypi&logoColor=9cf&style=for-the-badge)
44
+ ![PyPI](https://img.shields.io/pypi/v/inspy-logger?color=9cf&label=Latest&logo=pypi&logoColor=9cf&style=for-the-badge)
45
+ ![GitHub issues](https://img.shields.io/github/issues/Inspyre-Softworks/inSPy-Logger?color=9cf&logo=github&logoColor=9cf&style=for-the-badge)
46
+ ![PyPI - Format](https://img.shields.io/pypi/format/inSPy-Logger?logo=PyPi&style=for-the-badge)
47
+
48
+ ## Installation
49
+
50
+ ### Prerequisites:
51
+
52
+ **Platform/Env**:
53
+ * **Python**: ^3.6.3
54
+ * **PIP**: (_If you want to install via PIP, that is_)
55
+
56
+ **inSPy-Logger Runtime Dependencies**:
57
+ I am providing this list of dependencies for transparency and for instances where one would not be able to install inSPy-Logger via PyPi's package manager. It is **highly** recommended you use `python3 -m pip install inspy_logger==<ver>` to install inSPy-Logger 2.0+
58
+
59
+ * [colorlog](https://pypi.org/project/colorlog) (^4.2.1)
60
+ * [setuptools-autover](https://pypi.org/project/setuptools-autover) = (^1.0.2)
61
+ * [luddite](https://pypi.org/project/luddite) = (^1.0.1)
62
+ * [packaging](https://pypi.org/project/packaging) = (^20.4)
63
+
64
+ ### Installation via Pip on Python 3.6.3+ (recommended method):
65
+
66
+ - `$> python3 -m pip install inspy_logger==<version>`
67
+
68
+ #### Test out InspyLogger:
69
+
70
+ ```python3
71
+
72
+ import inspy_logger
73
+
74
+ # Set up a log device object. The first parameter is the root loggers name, and the second is the debug level
75
+ log_device = inspy_logger.InspyLogger('LogName', 'debug')
76
+
77
+ # Start a running log from that device
78
+ log = log_device.start()
79
+
80
+ # Output our own logger lines:
81
+ log.debug('This is a debug log entry')
82
+ log.info('This is an info log entry')
83
+ log.warning('This is a warning log entry!')
84
+ log.error('This is an error log entry!')
85
+ log.exception('This is an exception log entry!')
86
+
87
+ ```
88
+
89
+ If you run the code above you'll get output similar to this:
90
+
91
+ ![output1](https://github.com/Inspyre-Softworks/inSPy-Logger/blob/v2.0-alpha.6/examples/v2.0/output_screenies/v2.0.0a.6_screenie1.png)
92
+
93
+ v2.0+ repository for inSPy-Logger
94
+
@@ -0,0 +1,54 @@
1
+ # inSPy-Logger
2
+ ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/Inspyre-Softworks/inSPy-Logger/CI?label=CI&logo=github&logoColor=9cf&style=for-the-badge)
3
+ ![Codacy grade](https://img.shields.io/codacy/grade/7171eec682c549a88dee0da9cc9b92b3?logo=codacy&logoColor=9cf&style=for-the-badge)
4
+ ![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/Inspyre-Softworks/inSPy-Logger?color=9cf&include_prereleases&label=Pre-Release&logo=pypi&logoColor=9cf&style=for-the-badge)
5
+ ![PyPI](https://img.shields.io/pypi/v/inspy-logger?color=9cf&label=Latest&logo=pypi&logoColor=9cf&style=for-the-badge)
6
+ ![GitHub issues](https://img.shields.io/github/issues/Inspyre-Softworks/inSPy-Logger?color=9cf&logo=github&logoColor=9cf&style=for-the-badge)
7
+ ![PyPI - Format](https://img.shields.io/pypi/format/inSPy-Logger?logo=PyPi&style=for-the-badge)
8
+
9
+ ## Installation
10
+
11
+ ### Prerequisites:
12
+
13
+ **Platform/Env**:
14
+ * **Python**: ^3.6.3
15
+ * **PIP**: (_If you want to install via PIP, that is_)
16
+
17
+ **inSPy-Logger Runtime Dependencies**:
18
+ I am providing this list of dependencies for transparency and for instances where one would not be able to install inSPy-Logger via PyPi's package manager. It is **highly** recommended you use `python3 -m pip install inspy_logger==<ver>` to install inSPy-Logger 2.0+
19
+
20
+ * [colorlog](https://pypi.org/project/colorlog) (^4.2.1)
21
+ * [setuptools-autover](https://pypi.org/project/setuptools-autover) = (^1.0.2)
22
+ * [luddite](https://pypi.org/project/luddite) = (^1.0.1)
23
+ * [packaging](https://pypi.org/project/packaging) = (^20.4)
24
+
25
+ ### Installation via Pip on Python 3.6.3+ (recommended method):
26
+
27
+ - `$> python3 -m pip install inspy_logger==<version>`
28
+
29
+ #### Test out InspyLogger:
30
+
31
+ ```python3
32
+
33
+ import inspy_logger
34
+
35
+ # Set up a log device object. The first parameter is the root loggers name, and the second is the debug level
36
+ log_device = inspy_logger.InspyLogger('LogName', 'debug')
37
+
38
+ # Start a running log from that device
39
+ log = log_device.start()
40
+
41
+ # Output our own logger lines:
42
+ log.debug('This is a debug log entry')
43
+ log.info('This is an info log entry')
44
+ log.warning('This is a warning log entry!')
45
+ log.error('This is an error log entry!')
46
+ log.exception('This is an exception log entry!')
47
+
48
+ ```
49
+
50
+ If you run the code above you'll get output similar to this:
51
+
52
+ ![output1](https://github.com/Inspyre-Softworks/inSPy-Logger/blob/v2.0-alpha.6/examples/v2.0/output_screenies/v2.0.0a.6_screenie1.png)
53
+
54
+ v2.0+ repository for inSPy-Logger
@@ -0,0 +1,91 @@
1
+ """
2
+
3
+ File:
4
+ inspy_logger/Scripts/main.py
5
+
6
+ Author:
7
+ Inspyre Softworks
8
+
9
+ """
10
+ INSPY_LOG_LEVEL = 'info'
11
+
12
+ from inspy_logger.version import parse_version, get_full_version_name, PyPiVersionInfo
13
+ import sys
14
+ from rich import print
15
+ from rich.table import Table
16
+ from argparse import ArgumentParser
17
+
18
+
19
+ parser = ArgumentParser('inspy-logger-version', description='Displays version information for inspy-logger.')
20
+
21
+ parser.add_argument('-v', '--version', action='version', version=parse_version())
22
+
23
+ subparsers = parser.add_subparsers(title='subcommands', dest='subcommand')
24
+
25
+ update_parser =subparsers.add_parser('update', help='Checks for updates to inspy-logger.')
26
+
27
+ update_parser.add_argument('-p', '--pre-release', action='store_true', help='When checking for updates, include pre-release versions.')
28
+
29
+ parsed_args = parser.parse_args()
30
+
31
+
32
+ if parsed_args.subcommand == 'update':
33
+ INCLUDE_PRE_RELEASE_FOR_UPDATE_CHECK = parsed_args.pre_release
34
+ else:
35
+ INCLUDE_PRE_RELEASE_FOR_UPDATE_CHECK = False
36
+
37
+
38
+ pypi_info = PyPiVersionInfo(include_pre_release_for_update_check=INCLUDE_PRE_RELEASE_FOR_UPDATE_CHECK)
39
+
40
+
41
+ def update():
42
+ """
43
+ Checks for updates to inspy-logger.
44
+
45
+ Returns:
46
+ None
47
+
48
+ Since:
49
+ v3.0
50
+ """
51
+ if pypi_info.update_available:
52
+ print(f'\n\n[bold green]Update Available![/bold green] New version: [bold cyan]{pypi_info.new_version_available}[/bold cyan]')
53
+ else:
54
+ print(f'\n\n[bold green]No update available.[/bold green] Current version: [bold cyan]{parse_version()}[/bold cyan] {"which is newer than the available version on PyPi.org" if pypi_info.installed_newer_than_latest else ""}')
55
+
56
+
57
+ def get_version_info():
58
+ # Create a table
59
+ table = Table(show_header=False, show_lines=True, expand=True, border_style='bright_blue',
60
+ row_styles=['none', 'dim'])
61
+
62
+ # Add columns
63
+ table.add_column('Property', style='cyan', width=20)
64
+ table.add_column('Value', justify='center')
65
+
66
+ # Add rows
67
+ table.add_row('Version', parse_version(), )
68
+ table.add_row('Full Version Name', get_full_version_name())
69
+
70
+ if pypi_info.update_available:
71
+ table.add_row('Update Available', f'[bold green]Yes[/bold green]')
72
+ table.add_row('Latest Version', f'{pypi_info.new_version_available_num}')
73
+
74
+ table.add_row('Python Executable Path', sys.executable)
75
+ table.add_row('Python Version', sys.version)
76
+
77
+ print(table)
78
+
79
+
80
+
81
+ def main():
82
+
83
+ if parsed_args.subcommand == 'update':
84
+ return update()
85
+
86
+ return get_version_info()
87
+
88
+
89
+
90
+ if __name__ == '__main__':
91
+ main()
@@ -1,33 +1,33 @@
1
- """
2
-
3
- File: inspy_logger/__about__.py
4
- Project: inSPy-Logger
5
- Description:
6
- Holds information about the inSPy-Logger program and its version.
7
-
8
- Created: 11/3/22 - 21:05:06
9
-
10
- Since:
11
- v2.1.1
12
-
13
- """
14
-
15
- __prog__ = 'inSPy-Logger'
16
- __version__ = '2.2'
17
- __authors__ = [
18
- ('Inspyre-Softworks', 'https://inspyre.tech'),
19
- ('Taylor-Jayde Blackstone', '<t.blackstone@inspyre.tech')
20
- ]
21
-
22
- '''
23
- File Change History:
24
- 11/5/22 - 4:21 AM (v2.1.2):
25
- - Added description to docstring. (target: 2.1.2)
26
-
27
- 11/5/22 - 4:41 AM:
28
- - Version bump (target: 2.1.3)
29
-
30
- 03/06/23 - 19:08:
31
- - Version bump (target: 2.2)
32
-
33
- '''
1
+ """
2
+
3
+ File: inspy_logger/__about__.py
4
+ Project: inSPy-Logger
5
+ Description:
6
+ Holds information about the inSPy-Logger program and its version.
7
+
8
+ Created: 11/3/22 - 21:05:06
9
+
10
+ Since:
11
+ v2.1.1
12
+
13
+ """
14
+
15
+ __prog__ = 'inSPy-Logger'
16
+ __version__ = '3.0-dev.1'
17
+ __authors__ = [
18
+ ('Inspyre-Softworks', 'https://inspyre.tech'),
19
+ ('Taylor-Jayde Blackstone', '<t.blackstone@inspyre.tech')
20
+ ]
21
+
22
+ '''
23
+ File Change History:
24
+ 11/5/22 - 4:21 AM (v2.1.2):
25
+ - Added description to docstring. (target: 2.1.2)
26
+
27
+ 11/5/22 - 4:41 AM:
28
+ - Version bump (target: 2.1.3)
29
+
30
+ 08/16/2023 - 13:49 (v3.0):
31
+ - Version bump (v2.1.3 -> v3.0-dev.1)
32
+
33
+ '''
@@ -0,0 +1,326 @@
1
+ #!/usr/bin/env python3
2
+ import inspect
3
+ import logging
4
+ from rich.logging import RichHandler
5
+ from inspy_logger.__about__ import __prog__ as PROG_NAME
6
+ from inspy_logger.helpers import find_variable_in_call_stack
7
+
8
+ # Let's set up some constants.
9
+ LEVEL_MAP = [
10
+ ('debug', logging.DEBUG),
11
+ ('info', logging.INFO),
12
+ ('warning', logging.WARNING),
13
+ ('error', logging.ERROR),
14
+ ('critical', logging.CRITICAL),
15
+ ('fatal', logging.FATAL),
16
+ ]
17
+ """
18
+ List[Tuple[str, int]]:
19
+ A list of tuples containing the name of a logging level and it's corresponding logging level constant.
20
+ """
21
+
22
+ LEVELS = [level[0] for level in LEVEL_MAP]
23
+ """The list of level names."""
24
+
25
+ DEFAULT_LOGGING_LEVEL = logging.DEBUG
26
+
27
+ logging_level = getattr
28
+
29
+ from inspy_logger.helpers import (
30
+ translate_to_logging_level,
31
+ clean_module_name,
32
+ CustomFormatter,
33
+ )
34
+
35
+
36
+ class Logger:
37
+ """
38
+ A Singleton class responsible for managing the logging mechanisms of the application.
39
+ """
40
+
41
+ instances = {}
42
+
43
+ def __new__(cls, name, *args, **kwargs):
44
+ """
45
+ Creates or returns an existing instance of the Logger class for the provided name.
46
+ """
47
+
48
+ if name not in cls.instances:
49
+ instance = super(Logger, cls).__new__(cls)
50
+ cls.instances[name] = instance
51
+ return instance
52
+ return cls.instances[name]
53
+
54
+ def __init__(
55
+ self,
56
+ name,
57
+ console_level=DEFAULT_LOGGING_LEVEL,
58
+ file_level=logging.DEBUG,
59
+ filename="app.log",
60
+ parent=None,
61
+ ):
62
+ """
63
+ Initializes a logger instance.
64
+ """
65
+
66
+ if not hasattr(self, "logger"):
67
+ self.__name = name
68
+ self.logger = logging.getLogger(name)
69
+ self.logger.setLevel(logging.DEBUG)
70
+ determined_level = console_level
71
+
72
+ if isinstance(console_level, str):
73
+ determined_level = translate_to_logging_level(console_level)
74
+
75
+ self.__console_level = determined_level
76
+ self.filename = filename
77
+ self.__file_level = file_level or DEFAULT_LOGGING_LEVEL
78
+ self.parent = parent
79
+
80
+ self.logger.debug("Initializing Logger")
81
+
82
+ # Remove existing handlers
83
+ for handler in self.logger.handlers[:]:
84
+ self.logger.removeHandler(handler)
85
+
86
+ self.logger.propagate = False
87
+
88
+ self.set_up_console()
89
+ self.set_up_file()
90
+ self.children = []
91
+
92
+ @property
93
+ def device(self):
94
+ return self.logger
95
+
96
+ @property
97
+ def name(self):
98
+ return self.logger.name
99
+
100
+ def set_up_console(self):
101
+ """
102
+ Configures and attaches a console handler to the logger.
103
+ """
104
+
105
+ self.logger.debug("Setting up console handler")
106
+ console_handler = RichHandler(
107
+ show_level=True, markup=True, rich_tracebacks=True, tracebacks_show_locals=True
108
+ )
109
+ formatter = CustomFormatter(
110
+ f"[{self.logger.name}] %(message)s"
111
+ )
112
+ console_handler.setFormatter(formatter)
113
+ console_handler.setLevel(self.__console_level)
114
+ self.logger.addHandler(console_handler)
115
+
116
+ def set_up_file(self):
117
+ """
118
+ Configures and attaches a file handler to the logger.
119
+ """
120
+
121
+ self.logger.debug("Setting up file handler")
122
+ file_handler = logging.FileHandler(self.filename)
123
+ file_handler.setLevel(self.__file_level)
124
+ formatter = CustomFormatter(
125
+ "%(asctime)s - [%(name)s] - %(levelname)s - %(message)s |-| %(filename)s:%(lineno)d"
126
+ )
127
+ file_handler.setFormatter(formatter)
128
+ self.logger.addHandler(file_handler)
129
+
130
+ def set_level(self, console_level=None, file_level=None):
131
+ """
132
+ Updates the logging levels for both console and file handlers.
133
+ """
134
+
135
+ self.logger.debug("Setting log levels")
136
+ if console_level is not None:
137
+ self.logger.handlers[0].setLevel(console_level)
138
+ for child in self.children:
139
+ child.set_level(console_level=console_level)
140
+
141
+ if file_level is not None:
142
+ self.logger.handlers[1].setLevel(file_level)
143
+ for child in self.children:
144
+ child.set_level(file_level=file_level)
145
+
146
+ def get_child(self, name=None, console_level=None, file_level=None):
147
+ self.logger.debug("Getting child logger")
148
+ console_level = console_level or DEFAULT_LOGGING_LEVEL
149
+ caller_frame = inspect.stack()[1]
150
+
151
+ if name is None:
152
+ name = caller_frame.function
153
+
154
+ caller_self = caller_frame.frame.f_locals.get("self", None)
155
+ separator = ":" if caller_self and hasattr(caller_self, name) else "."
156
+ child_logger_name = f"{self.logger.name}{separator}{name}"
157
+
158
+ for child in self.children:
159
+ if child.logger.name == child_logger_name:
160
+ return child
161
+
162
+ child_logger = Logger(child_logger_name, console_level, file_level, parent=self)
163
+ self.children.append(child_logger)
164
+ return child_logger
165
+
166
+ def get_child_names(self):
167
+ """
168
+ Fetches the names of all child loggers associated with this logger instance.
169
+ """
170
+
171
+ self.logger.debug("Getting child logger names")
172
+ return [child.logger.name for child in self.children]
173
+
174
+ def get_parent(self):
175
+ """
176
+ Fetches the parent logger associated with this logger instance.
177
+ """
178
+
179
+ self.logger.debug("Getting parent logger")
180
+ return self.parent
181
+
182
+ def find_child_by_name(self, name: str, case_sensitive=True, exact_match=False):
183
+ self.logger.debug(f'Searching for child with name: {name}')
184
+ results = []
185
+
186
+ if not case_sensitive:
187
+ name = name.lower()
188
+
189
+ for logger in self.children:
190
+ logger_name = logger.name if case_sensitive else logger.name.lower()
191
+ if exact_match and name == logger_name:
192
+ return logger
193
+ elif not exact_match and name in logger_name:
194
+ results.append(logger)
195
+
196
+ return results
197
+
198
+ def __repr__(self):
199
+ name = self.name
200
+ hex_id = hex(id(self))
201
+ if self.parent is not None:
202
+ parent_part = f' | Parent Logger: {self.parent.name} |'
203
+ if self.children:
204
+ parent_part += f' | Number of children: {len(self.children)} |'
205
+ else:
206
+ parent_part = f' | This is a root logger with {len(self.children)} children. '
207
+
208
+ if parent_part.endswith('|'):
209
+ parent_part = str(parent_part[:-2])
210
+
211
+ return f'<Logger: {name} at {hex_id}{parent_part}>'
212
+
213
+
214
+ found_level = find_variable_in_call_stack('INSPY_LOG_LEVEL', DEFAULT_LOGGING_LEVEL)
215
+
216
+
217
+ LOG_DEVICE = Logger(PROG_NAME, found_level)
218
+ MOD_LOG_DEVICE = LOG_DEVICE.get_child("log_engine", found_level)
219
+ MOD_LOGGER = MOD_LOG_DEVICE.logger
220
+ MOD_LOGGER.debug(f"Started logger for {__name__}.")
221
+
222
+ add_child = LOG_DEVICE.get_child
223
+
224
+ InspyLogger = Logger
225
+
226
+ def _get_parent_logging_device():
227
+ """
228
+ Determines the parent logging device by inspecting the caller's log_device or parent_log_device attribute.
229
+
230
+ Returns:
231
+ Logger: The parent logging device.
232
+ """
233
+ MOD_LOGGER.debug("Determining parent logging device")
234
+ caller_frame = inspect.currentframe().f_back
235
+ caller_locals = caller_frame.f_locals
236
+
237
+ if "logger" in caller_locals:
238
+ return caller_locals["logger"]
239
+ elif "parent_log_device" in caller_locals:
240
+ return caller_locals["parent_log_device"]
241
+ else:
242
+ raise ValueError("Unable to determine the parent logging device.")
243
+
244
+
245
+ class Loggable:
246
+ """
247
+ A metaclass to enhance classes with logging capabilities. Classes that inherit from
248
+ 'Loggable' can instantly access a logger without manually setting it up. This logger
249
+ is derived from a parent logger, ensuring consistent logging behavior and hierarchy.
250
+
251
+ Attributes:
252
+ - log_device: The logger device associated with the instance of the class.
253
+ """
254
+
255
+ def __init__(self, parent_log_device=None, **kwargs):
256
+ self.parent_log_device = parent_log_device
257
+ self.__log_name = self.__class__.__name__
258
+ if self.parent_log_device is not None:
259
+ self.__log_device = self.parent_log_device.get_child(
260
+ self.__class__.__name__
261
+ )
262
+ else:
263
+ self.__log_device = _get_parent_logging_device().get_child(
264
+ self.__class__.__name__
265
+ )
266
+
267
+ @property
268
+ def log_device(self):
269
+ return self.__log_device
270
+
271
+ @log_device.setter
272
+ def log_device(self, new):
273
+ if not isinstance(new, Logger):
274
+ raise TypeError('log_device must be of type "Logger"')
275
+
276
+ self.__log_device = new
277
+
278
+ def create_child_logger(self, name=None, override=False):
279
+ """
280
+ Creates and returns a child logger of this object's logger.
281
+
282
+ Parameters:
283
+ name (str, optional): The name of the child logger.
284
+ If not provided, the name of the calling function is used.
285
+ override (bool, optional): A flag to override the membership check. Defaults to False.
286
+
287
+ Returns:
288
+ Logger: An instance of the Logger class that represents the child logger.
289
+ """
290
+ if not override:
291
+ self.__is_member__()
292
+
293
+ if name is None:
294
+ name = inspect.stack()[1][
295
+ 3
296
+ ] # Get the name of the calling function if no name is provided
297
+
298
+ return self.log_device.get_child(name)
299
+
300
+ def __is_member__(self):
301
+ """
302
+ Checks whether the caller of this method is a member of the same class.
303
+
304
+ Raises:
305
+ PermissionError: If the caller of this method is not a member of the same class.
306
+ """
307
+ log_device = self.log_device.get_child("__is_member__")
308
+ log = log_device.logger
309
+
310
+ current_frame = inspect.currentframe()
311
+ log.debug(f"Current frame: {current_frame}")
312
+
313
+ caller_frame = current_frame.f_back
314
+ log.debug(f"Caller frame: {caller_frame}")
315
+
316
+ caller_self = caller_frame.f_locals.get("self", None)
317
+ log.debug(f"Caller self: {caller_self}")
318
+
319
+ log.debug("Checking if caller is a member of this class...")
320
+ if not isinstance(caller_self, self.__class__):
321
+ raise PermissionError(
322
+ "Access denied.\n"
323
+ f"Method can only be accessed by members of the same class. {caller_self.__class__.__name__} is not such a member"
324
+ )
325
+
326
+ log.debug(f"Access granted to {caller_self.__class__.__name__}")