owlplanner 2025.12.20__py3-none-any.whl → 2026.2.2__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.
owlplanner/mylogging.py CHANGED
@@ -1,22 +1,64 @@
1
1
  """
2
+ Logging utility module with support for multiple backends.
2
3
 
3
- Owl/logging
4
+ This module provides a flexible logging system that supports both standard
5
+ Python logging and loguru backends, with verbose mode control and stream management.
4
6
 
5
- This file contains routines for handling error messages.
7
+ Copyright (C) 2025-2026 The Owlplanner Authors
6
8
 
7
- Copyright © 2024 - Martin-D. Lacasse
9
+ This program is free software: you can redistribute it and/or modify
10
+ it under the terms of the GNU General Public License as published by
11
+ the Free Software Foundation, either version 3 of the License, or
12
+ (at your option) any later version.
8
13
 
9
- Disclaimers: This code is for educational purposes only and does not constitute financial advice.
14
+ This program is distributed in the hope that it will be useful,
15
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ GNU General Public License for more details.
10
18
 
19
+ You should have received a copy of the GNU General Public License
20
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
11
21
  """
12
22
 
13
23
  import sys
24
+ import copy
25
+ import inspect
26
+ import os
27
+
28
+ # Conditional import of loguru - only available if package is installed
29
+ try:
30
+ from loguru import logger as loguru_logger
31
+ HAS_LOGURU = True
32
+ except ImportError:
33
+ loguru_logger = None
34
+ HAS_LOGURU = False
14
35
 
15
36
 
16
37
  class Logger(object):
17
38
  def __init__(self, verbose=True, logstreams=None):
18
39
  self._verbose = verbose
19
40
  self._prevState = self._verbose
41
+ self._verboseStack = [] # Stack to track verbose states for proper restoration
42
+ self._use_loguru = False
43
+
44
+ # --- Detect loguru backend ---------------------------------
45
+ if logstreams == "loguru" or logstreams == ["loguru"]:
46
+ if not HAS_LOGURU:
47
+ raise ImportError(
48
+ "loguru is required when using loguru logging backend. "
49
+ "Install it with: pip install loguru"
50
+ )
51
+ self._use_loguru = True
52
+ self._logstreams = None
53
+
54
+ loguru_logger.debug("Using loguru as logging backend.")
55
+ return
56
+
57
+ # --- Existing stream-based behavior ------------------------
58
+ # First check if logstreams is a valid type (list or None)
59
+ if logstreams is not None and not isinstance(logstreams, list):
60
+ raise ValueError(f"Log streams {logstreams} must be a list.")
61
+
20
62
  if logstreams is None or logstreams == [] or len(logstreams) > 2:
21
63
  self._logstreams = [sys.stdout, sys.stderr]
22
64
  self.vprint("Using stdout and stderr as stream loggers.")
@@ -26,59 +68,149 @@ class Logger(object):
26
68
  elif len(logstreams) == 1:
27
69
  self._logstreams = 2 * logstreams
28
70
  self.vprint("Using logstream as stream logger.")
29
- else:
30
- raise ValueError(f"Log streams {logstreams} must be a list.")
31
71
 
32
72
  def setVerbose(self, verbose=True):
33
- """
34
- Set verbose to True if you want the module to be chatty,
35
- or False to make it silent.
36
- """
73
+ # Push current state onto stack before changing it
74
+ self._verboseStack.append(self._verbose)
37
75
  self._prevState = self._verbose
38
76
  self._verbose = verbose
39
77
  self.vprint("Setting verbose to", verbose)
40
-
41
78
  return self._prevState
42
79
 
43
80
  def resetVerbose(self):
81
+ # Pop the previous state from the stack if available
82
+ if self._verboseStack:
83
+ self._verbose = self._verboseStack.pop()
84
+ self._prevState = self._verbose
85
+ else:
86
+ # Fallback to _prevState if stack is empty (shouldn't happen in normal usage)
87
+ self._verbose = self._prevState
88
+
89
+ def __deepcopy__(self, memo):
44
90
  """
45
- Reset verbose to previous state.
91
+ Custom deepcopy implementation to handle file descriptors properly.
92
+ Creates a new Logger instance with the same settings instead of
93
+ attempting to copy file descriptors (sys.stdout, sys.stderr, etc.).
46
94
  """
47
- self._verbose = self._prevState
95
+ # Determine logstreams parameter for new instance
96
+ if self._use_loguru:
97
+ logstreams = "loguru"
98
+ elif self._logstreams is None:
99
+ logstreams = None
100
+ elif self._logstreams == [sys.stdout, sys.stderr]:
101
+ # Default case - will be recreated as [sys.stdout, sys.stderr]
102
+ logstreams = None
103
+ else:
104
+ # Custom streams - preserve them (they might be StringIO or similar)
105
+ logstreams = self._logstreams
106
+
107
+ # Create a new Logger instance with the same settings
108
+ new_logger = Logger(
109
+ verbose=self._verbose,
110
+ logstreams=logstreams
111
+ )
48
112
 
49
- def print(self, *args, **kwargs):
113
+ # Copy the verbose stack state
114
+ new_logger._verboseStack = copy.deepcopy(self._verboseStack, memo)
115
+ new_logger._prevState = self._prevState
116
+
117
+ return new_logger
118
+
119
+ # ------------------------------------------------------------
120
+ # Printing methods
121
+ # ------------------------------------------------------------
122
+
123
+ def print(self, *args, tag="INFO", **kwargs):
50
124
  """
51
- Unconditional printing regardless of the value of the verbose variable
52
- previously set.
125
+ Unconditional printing regardless of verbosity.
53
126
  """
127
+
128
+ if self._use_loguru:
129
+ loguru_logger.opt(depth=1).debug(" ".join(map(str, args)))
130
+ return
131
+
132
+ # Get caller information (loguru style: name:function:line)
133
+ frame = inspect.currentframe()
134
+ caller_frame = frame.f_back
135
+ filename = os.path.basename(caller_frame.f_code.co_filename)
136
+ # Remove .py extension if present
137
+ if filename.endswith('.py'):
138
+ filename = filename[:-3]
139
+ function_name = caller_frame.f_code.co_name
140
+ line_number = caller_frame.f_lineno
141
+ location = f"{filename}:{function_name}:{line_number}"
142
+
143
+ # Format message with timestamp, location, and tag
144
+ from datetime import datetime
145
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
146
+ message = " ".join(map(str, args))
147
+ formatted_message = f"{timestamp} | {tag} | {location} | {message}"
148
+
54
149
  if "file" not in kwargs:
55
150
  file = self._logstreams[0]
56
151
  kwargs["file"] = file
152
+ else:
153
+ file = kwargs["file"]
57
154
 
58
- print(*args, **kwargs)
155
+ print(formatted_message, **kwargs)
59
156
  file.flush()
60
157
 
61
- def vprint(self, *args, **kwargs):
158
+ def vprint(self, *args, tag="DEBUG", **kwargs):
62
159
  """
63
- Conditional printing depending on the value of the verbose variable
64
- previously set.
160
+ Conditional printing depending on verbose flag.
65
161
  """
66
162
  if self._verbose:
67
- self.print(*args, **kwargs)
163
+ if self._use_loguru:
164
+ loguru_logger.opt(depth=1).debug(" ".join(map(str, args)))
165
+ return
166
+
167
+ # Get caller information (loguru style: name:function:line)
168
+ frame = inspect.currentframe()
169
+ caller_frame = frame.f_back
170
+ filename = os.path.basename(caller_frame.f_code.co_filename)
171
+ # Remove .py extension if present
172
+ if filename.endswith('.py'):
173
+ filename = filename[:-3]
174
+ function_name = caller_frame.f_code.co_name
175
+ line_number = caller_frame.f_lineno
176
+ location = f"{filename}:{function_name}:{line_number}"
177
+
178
+ # Format message with timestamp, location, and tag
179
+ from datetime import datetime
180
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
181
+ message = " ".join(map(str, args))
182
+ formatted_message = f"{timestamp} | {tag} | {location} | {message}"
183
+
184
+ if "file" not in kwargs:
185
+ file = self._logstreams[0]
186
+ kwargs["file"] = file
187
+ else:
188
+ file = kwargs["file"]
189
+
190
+ print(formatted_message, **kwargs)
191
+ file.flush()
68
192
 
69
193
  def xprint(self, *args, **kwargs):
70
194
  """
71
- Print message and exit. Use to print error messages on stderr.
72
- The exit() used throws an exception in an interactive environment.
195
+ Print message and exit. Used for fatal errors.
73
196
  """
197
+ if self._use_loguru:
198
+ loguru_logger.opt(depth=1).debug("ERROR: " + " ".join(map(str, args)))
199
+ loguru_logger.opt(depth=1).debug("Exiting...")
200
+ raise Exception("Fatal error.")
201
+
74
202
  if "file" not in kwargs:
75
203
  file = self._logstreams[1]
76
204
  kwargs["file"] = file
205
+ else:
206
+ file = kwargs["file"]
77
207
 
78
208
  if self._verbose:
79
209
  print("ERROR:", *args, **kwargs)
80
- print("Exiting...")
210
+ print("Exiting...", file=file)
81
211
  file.flush()
82
212
 
83
213
  raise Exception("Fatal error.")
84
- # sys.exit(-1)
214
+
215
+
216
+ # Log filtering utility functions removed - no longer needed since StringIO guarantees ordered messages