inspy-logger 3.0.dev2__tar.gz → 3.0.dev4__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,25 +1,25 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: inspy-logger
3
- Version: 3.0.dev2
3
+ Version: 3.0.dev4
4
4
  Summary: Colorable, scalable logger for CLI
5
5
  Home-page: https://github.com/Inspyre-Softworks/inSPy-Logger
6
6
  License: MIT
7
7
  Keywords: cli,color,logging,log,terminal,console,colorama,colorlog,rich
8
8
  Author: Taylor-Jayde Blackstone
9
9
  Author-email: tayjaybabee@gmail.com
10
- Requires-Python: >=3.7,<4.0
10
+ Requires-Python: >=3.9,<4.0
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: License :: OSI Approved :: MIT License
14
14
  Classifier: Natural Language :: English
15
15
  Classifier: Operating System :: OS Independent
16
16
  Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.7
18
- Classifier: Programming Language :: Python :: 3.8
19
17
  Classifier: Programming Language :: Python :: 3.9
20
18
  Classifier: Programming Language :: Python :: 3.10
21
19
  Classifier: Programming Language :: Python :: 3.11
22
20
  Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3.7
22
+ Classifier: Programming Language :: Python :: 3.8
23
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
24
  Classifier: Topic :: System :: Logging
25
25
  Classifier: Topic :: Utilities
@@ -68,7 +68,7 @@ def get_version_info():
68
68
  table.add_row('Full Version Name', get_full_version_name())
69
69
 
70
70
  if pypi_info.update_available:
71
- table.add_row('Update Available', f'[bold green]Yes[/bold green]')
71
+ table.add_row('Update Available', '[bold green]Yes[/bold green]')
72
72
  table.add_row('Latest Version', f'{pypi_info.new_version_available_num}')
73
73
 
74
74
  table.add_row('Python Executable Path', sys.executable)
@@ -80,10 +80,7 @@ def get_version_info():
80
80
 
81
81
  def main():
82
82
 
83
- if parsed_args.subcommand == 'update':
84
- return update()
85
-
86
- return get_version_info()
83
+ return update() if parsed_args.subcommand == 'update' else get_version_info()
87
84
 
88
85
 
89
86
 
@@ -11,9 +11,10 @@ Since:
11
11
  v2.1.1
12
12
 
13
13
  """
14
+ from inspy_logger.version import parse_version
14
15
 
15
16
  __prog__ = 'inSPy-Logger'
16
- __version__ = '3.0-dev.1'
17
+ __version__ = parse_version()
17
18
  __authors__ = [
18
19
  ('Inspyre-Softworks', 'https://inspyre.tech'),
19
20
  ('Taylor-Jayde Blackstone', '<t.blackstone@inspyre.tech')
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  import inspect
3
+ import os
3
4
  import logging
4
5
  from rich.logging import RichHandler
5
6
  from inspy_logger.__about__ import __prog__ as PROG_NAME
@@ -38,11 +39,17 @@ class Logger:
38
39
  A Singleton class responsible for managing the logging mechanisms of the application.
39
40
  """
40
41
 
41
- instances = {}
42
+ instances = {} # A dictionary to hold instances of the Logger class.
42
43
 
43
44
  def __new__(cls, name, *args, **kwargs):
44
45
  """
45
46
  Creates or returns an existing instance of the Logger class for the provided name.
47
+
48
+ Args:
49
+ name (str): The name of the logger instance.
50
+
51
+ Returns:
52
+ Logger: An instance of the Logger class.
46
53
  """
47
54
 
48
55
  if name not in cls.instances:
@@ -52,15 +59,22 @@ class Logger:
52
59
  return cls.instances[name]
53
60
 
54
61
  def __init__(
55
- self,
56
- name,
57
- console_level=DEFAULT_LOGGING_LEVEL,
58
- file_level=logging.DEBUG,
59
- filename="app.log",
60
- parent=None,
62
+ self,
63
+ name,
64
+ console_level=DEFAULT_LOGGING_LEVEL,
65
+ file_level=logging.DEBUG,
66
+ filename="app.log",
67
+ parent=None,
61
68
  ):
62
69
  """
63
70
  Initializes a logger instance.
71
+
72
+ Args:
73
+ name (str): The name of the logger instance.
74
+ console_level (str, optional): The logging level for the console. Defaults to DEFAULT_LOGGING_LEVEL.
75
+ file_level (str, optional): The logging level for the file. Defaults to logging.DEBUG.
76
+ filename (str, optional): The name of the log file. Defaults to "app.log".
77
+ parent (Logger, optional): The parent logger instance. Defaults to None.
64
78
  """
65
79
 
66
80
  if not hasattr(self, "logger"):
@@ -91,10 +105,22 @@ class Logger:
91
105
 
92
106
  @property
93
107
  def device(self):
108
+ """
109
+ Returns the logger instance.
110
+
111
+ Returns:
112
+ Logger: The logger instance.
113
+ """
94
114
  return self.logger
95
115
 
96
116
  @property
97
117
  def name(self):
118
+ """
119
+ Returns the name of the logger instance.
120
+
121
+ Returns:
122
+ str: The name of the logger instance.
123
+ """
98
124
  return self.logger.name
99
125
 
100
126
  def set_up_console(self):
@@ -144,8 +170,7 @@ class Logger:
144
170
  child.set_level(file_level=file_level)
145
171
 
146
172
  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
173
+ console_level = console_level or self.__console_level
149
174
  caller_frame = inspect.stack()[1]
150
175
 
151
176
  if name is None:
@@ -159,7 +184,7 @@ class Logger:
159
184
  if child.logger.name == child_logger_name:
160
185
  return child
161
186
 
162
- child_logger = Logger(child_logger_name, console_level, file_level, parent=self)
187
+ child_logger = Logger(name=child_logger_name, console_level=console_level, file_level=file_level, parent=self)
163
188
  self.children.append(child_logger)
164
189
  return child_logger
165
190
 
@@ -180,6 +205,18 @@ class Logger:
180
205
  return self.parent
181
206
 
182
207
  def find_child_by_name(self, name: str, case_sensitive=True, exact_match=False):
208
+ """
209
+ Searches for a child logger by its name.
210
+
211
+ Args:
212
+ name (str): The name of the child logger to search for.
213
+ case_sensitive (bool, optional): Whether the search should be case sensitive. Defaults to True.
214
+ exact_match (bool, optional): Whether the search should only return exact matches. Defaults to False.
215
+
216
+ Returns:
217
+ list or Logger: If exact_match is True, returns the Logger instance if found, else returns an empty list.
218
+ If exact_match is False, returns a list of Logger instances whose names contain the search term.
219
+ """
183
220
  self.logger.debug(f'Searching for child with name: {name}')
184
221
  results = []
185
222
 
@@ -195,6 +232,43 @@ class Logger:
195
232
 
196
233
  return results
197
234
 
235
+ def debug(self, message):
236
+ """
237
+ Logs a debug message.
238
+
239
+ Args:
240
+ message (str): The message to log.
241
+ """
242
+ self._log(logging.DEBUG, message, args=(), stacklevel=2)
243
+
244
+
245
+ def info(self, message):
246
+ """
247
+ Logs an info message.
248
+
249
+ Args:
250
+ message (str): The message to log.
251
+ """
252
+ self._log(logging.INFO, message, args=(), stacklevel=2)
253
+
254
+ def warning(self, message):
255
+ """
256
+ Logs a warning message.
257
+
258
+ Args:
259
+ message (str): The message to log.
260
+ """
261
+ self._log(logging.WARNING, message, args=(), stacklevel=2)
262
+
263
+ def error(self, message):
264
+ """
265
+ Logs an error message.
266
+
267
+ Args:
268
+ message (str): The message to log.
269
+ """
270
+ self._log(logging.ERROR, message, args=(), stacklevel=2)
271
+
198
272
  def __repr__(self):
199
273
  name = self.name
200
274
  hex_id = hex(id(self))
@@ -208,119 +282,58 @@ class Logger:
208
282
  if parent_part.endswith('|'):
209
283
  parent_part = str(parent_part[:-2])
210
284
 
211
- return f'<Logger: {name} at {hex_id}{parent_part}>'
285
+ return f'<Logger: {name} w/ level {self.logger.level} at {hex_id}{parent_part}>'
212
286
 
287
+ @classmethod
288
+ def create_logger_for_caller(cls):
289
+ """
290
+ Creates a logger for the module that calls this method.
213
291
 
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
- )
292
+ Returns:
293
+ Logger: An instance of the Logger class for the calling module.
294
+ """
295
+ frame = inspect.currentframe().f_back
296
+ if module_path := cls._determine_module_path(frame):
297
+ return cls(module_path)
262
298
  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
299
+ raise ValueError("Unable to determine module path for logger creation.")
277
300
 
278
- def create_child_logger(self, name=None, override=False):
301
+ @staticmethod
302
+ def _determine_module_path(frame):
279
303
  """
280
- Creates and returns a child logger of this object's logger.
304
+ Determines the in-project path of the module from the call frame.
281
305
 
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.
306
+ Args:
307
+ frame:
308
+ The frame from which to determine the module path.
286
309
 
287
310
  Returns:
288
- Logger: An instance of the Logger class that represents the child logger.
311
+ str:
312
+ The in-project path of the module.
289
313
  """
290
- if not override:
291
- self.__is_member__()
314
+ if module := inspect.getmodule(frame):
315
+ base_path = os.path.dirname(os.path.abspath(module.__file__))
316
+ relative_path = os.path.relpath(frame.f_code.co_filename, base_path)
317
+ return relative_path.replace(os.path.sep, '.').rstrip('.py')
318
+ return None
292
319
 
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):
320
+ def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False, stacklevel=1):
301
321
  """
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.
322
+ Low-level logging implementation, passing stacklevel to findCaller.
306
323
  """
307
- log_device = self.log_device.get_child("__is_member__")
308
- log = log_device.logger
324
+ if self.logger.isEnabledFor(level):
325
+ self.logger._log(level, msg, args, exc_info, extra, stack_info, stacklevel + 1)
309
326
 
310
- current_frame = inspect.currentframe()
311
- log.debug(f"Current frame: {current_frame}")
312
327
 
313
- caller_frame = current_frame.f_back
314
- log.debug(f"Caller frame: {caller_frame}")
328
+ found_level = find_variable_in_call_stack('INSPY_LOG_LEVEL', DEFAULT_LOGGING_LEVEL)
329
+
330
+ LOG_DEVICE = Logger(PROG_NAME, found_level)
331
+ MOD_LOG_DEVICE = LOG_DEVICE.get_child("log_engine", found_level)
332
+ MOD_LOGGER = MOD_LOG_DEVICE.logger
333
+ MOD_LOGGER.debug(f"Started logger for {__name__}.")
315
334
 
316
- caller_self = caller_frame.f_locals.get("self", None)
317
- log.debug(f"Caller self: {caller_self}")
335
+ add_child = LOG_DEVICE.get_child
318
336
 
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
- )
337
+ InspyLogger = Logger
325
338
 
326
- log.debug(f"Access granted to {caller_self.__class__.__name__}")
339
+ from inspy_logger.helpers.base_classes import Loggable
@@ -0,0 +1,136 @@
1
+ import inspect
2
+ from inspy_logger import MOD_LOGGER, MOD_LOG_DEVICE, Logger
3
+
4
+ import inspect
5
+
6
+
7
+ class LoggableDescriptor:
8
+ """
9
+ Descriptor for accessing a logger specific to a class method.
10
+ """
11
+
12
+ def __get__(self, instance, owner):
13
+ if instance is None:
14
+ # Accessing through the class, not an instance
15
+ return owner.class_logger
16
+
17
+ # Determine the calling method's name
18
+ stack = inspect.stack()
19
+ # Start from 1 to skip the current __get__ frame
20
+ for frame_record in stack[1:]:
21
+ if 'self' in frame_record.frame.f_locals:
22
+ if frame_record.frame.f_locals['self'] is instance:
23
+ method_name = frame_record.function
24
+ break
25
+ else:
26
+ raise Exception("Could not determine the calling method's name.")
27
+
28
+ # Get a child logger named after the class and method
29
+ return instance.log_device.get_child()
30
+
31
+
32
+ def _get_parent_logging_device():
33
+ """
34
+ Determines the parent logging device by inspecting the caller's log_device or parent_log_device attribute.
35
+
36
+ Returns:
37
+ Logger: The parent logging device.
38
+ """
39
+ MOD_LOGGER.debug("Determining parent logging device")
40
+ caller_frame = inspect.currentframe().f_back
41
+ caller_locals = caller_frame.f_locals
42
+
43
+ if "logger" in caller_locals:
44
+ return caller_locals["logger"]
45
+ elif "parent_log_device" in caller_locals:
46
+ return caller_locals["parent_log_device"]
47
+ else:
48
+ raise ValueError("Unable to determine the parent logging device.")
49
+
50
+
51
+ class Loggable:
52
+ """
53
+ A metaclass to enhance classes with logging capabilities. Classes that inherit from
54
+ 'Loggable' can instantly access a logger without manually setting it up. This logger
55
+ is derived from a parent logger, ensuring consistent logging behavior and hierarchy.
56
+
57
+ Attributes:
58
+ - log_device: The logger device associated with the instance of the class.
59
+ """
60
+ method_logger = LoggableDescriptor()
61
+ class_logger = None
62
+
63
+ def __init__(self, parent_log_device=None, **kwargs):
64
+ self.parent_log_device = parent_log_device
65
+ self.__log_name = self.__class__.__name__
66
+ if self.parent_log_device is not None:
67
+ self.__log_device = self.parent_log_device.get_child(self.__log_name)
68
+ else:
69
+ self.__log_device = _get_parent_logging_device().get_child()
70
+
71
+ # Set up class-level logger if it's not already set
72
+ if self.__class__.class_logger is None:
73
+ self.__class__.class_logger = self.__log_device
74
+
75
+ self.method_logger = self.__class__.method_logger.logger
76
+
77
+ @property
78
+ def log_device(self):
79
+ return self.__log_device
80
+
81
+ @log_device.setter
82
+ def log_device(self, new):
83
+ if not isinstance(new, Logger):
84
+ raise TypeError('log_device must be of type "Logger"')
85
+
86
+ self.__log_device = new
87
+
88
+ def create_child_logger(self, name=None, override=False):
89
+ """
90
+ Creates and returns a child logger of this object's logger.
91
+
92
+ Parameters:
93
+ name (str, optional): The name of the child logger.
94
+ If not provided, the name of the calling function is used.
95
+ override (bool, optional): A flag to override the membership check. Defaults to False.
96
+
97
+ Returns:
98
+ Logger: An instance of the Logger class that represents the child logger.
99
+ """
100
+ if not override:
101
+ self.__is_member__()
102
+
103
+ if name is None:
104
+ name = inspect.stack()[1][
105
+ 3
106
+ ] # Get the name of the calling function if no name is provided
107
+ #print(inspect.stack()[1])
108
+ return self.log_device.get_child(name)
109
+
110
+ def __is_member__(self):
111
+ """
112
+ Checks whether the caller of this method is a member of the same class.
113
+
114
+ Raises:
115
+ PermissionError: If the caller of this method is not a member of the same class.
116
+ """
117
+ log_device = self.log_device.get_child("__is_member__")
118
+ log = log_device.logger
119
+
120
+ current_frame = inspect.currentframe()
121
+ log.debug(f"Current frame: {current_frame}")
122
+
123
+ caller_frame = current_frame.f_back
124
+ log.debug(f"Caller frame: {caller_frame}")
125
+
126
+ caller_self = caller_frame.f_locals.get("self", None)
127
+ log.debug(f"Caller self: {caller_self}")
128
+
129
+ log.debug("Checking if caller is a member of this class...")
130
+ if not isinstance(caller_self, self.__class__):
131
+ raise PermissionError(
132
+ "Access denied.\n"
133
+ f"Method can only be accessed by members of the same class. {caller_self.__class__.__name__} is not such a member"
134
+ )
135
+
136
+ log.debug(f"Access granted to {caller_self.__class__.__name__}")
@@ -16,7 +16,7 @@ __VERSION__ = {
16
16
  'minor': 0,
17
17
  'patch': 0,
18
18
  'release': 'dev',
19
- 'release_num': 2
19
+ 'release_num': 4
20
20
  }
21
21
 
22
22
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "inspy-logger"
3
- version = "3.0-dev.2"
3
+ version = "3.0-dev.4"
4
4
  description = "Colorable, scalable logger for CLI"
5
5
  authors = ["Taylor-Jayde Blackstone <tayjaybabee@gmail.com>"]
6
6
  homepage = 'https://github.com/Inspyre-Softworks/inSPy-Logger'
@@ -28,7 +28,7 @@ classifiers = [
28
28
  ]
29
29
 
30
30
  [tool.poetry.dependencies]
31
- python = "^3.7"
31
+ python = ">=3.9,<4.0"
32
32
  colorlog = "^6.7"
33
33
  setuptools-autover = "^1.0.2"
34
34
  luddite = "^1.0.1"
@@ -42,15 +42,22 @@ rich = "^13.5.2"
42
42
 
43
43
  [tool.poetry.dev-dependencies]
44
44
  ptipython = "^1.0.1"
45
- Sphinx = "^5.3.0"
46
- sphinxcontrib-napoleon = "^0.7"
47
- sphinx-rtd-theme = "^1.0.0"
48
45
  asciinema = "^2.2.0"
49
46
  bpython = "^0.23"
50
47
 
51
48
  [tool.poetry.scripts]
52
49
  inspy-logger-tool = "inspy_logger.Scripts.main:main"
53
50
 
51
+ [tool.poetry.group.docs.dependencies]
52
+ sphinx = "^7.2.6"
53
+ sphinx-autodoc-typehints = "^1.25.2"
54
+ sphinxcontrib-napoleon = "^0.7"
55
+ sphinx-rtd-theme = "^2.0.0"
56
+ sphinxcontrib-argdoc = "^0.1.3"
57
+ autoclasstoc = "^1.6.0"
58
+ sphinx-autodoc-annotation = "^1.0.post1"
59
+ sphinxcontrib-autoprogram = "^0.1.8"
60
+
54
61
  [build-system]
55
62
  requires = ["poetry-core>=1.0.0"]
56
63
  build-backend = "poetry.core.masonry.api"