File: __init__.py

package info (click to toggle)
python-pytooling 8.11.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 3,760 kB
  • sloc: python: 25,806; makefile: 13
file content (269 lines) | stat: -rw-r--r-- 10,872 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# ==================================================================================================================== #
#             _____           _ _           __        __               _                                               #
#  _ __  _   |_   _|__   ___ | (_)_ __   __ \ \      / /_ _ _ __ _ __ (_)_ __   __ _                                   #
# | '_ \| | | || |/ _ \ / _ \| | | '_ \ / _` \ \ /\ / / _` | '__| '_ \| | '_ \ / _` |                                  #
# | |_) | |_| || | (_) | (_) | | | | | | (_| |\ V  V / (_| | |  | | | | | | | | (_| |                                  #
# | .__/ \__, ||_|\___/ \___/|_|_|_| |_|\__, (_)_/\_/ \__,_|_|  |_| |_|_|_| |_|\__, |                                  #
# |_|    |___/                          |___/                                  |___/                                   #
# ==================================================================================================================== #
# Authors:                                                                                                             #
#   Patrick Lehmann                                                                                                    #
#                                                                                                                      #
# License:                                                                                                             #
# ==================================================================================================================== #
# Copyright 2025-2026 Patrick Lehmann - Bötzingen, Germany                                                             #
#                                                                                                                      #
# Licensed under the Apache License, Version 2.0 (the "License");                                                      #
# you may not use this file except in compliance with the License.                                                     #
# You may obtain a copy of the License at                                                                              #
#                                                                                                                      #
#   http://www.apache.org/licenses/LICENSE-2.0                                                                         #
#                                                                                                                      #
# Unless required by applicable law or agreed to in writing, software                                                  #
# distributed under the License is distributed on an "AS IS" BASIS,                                                    #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.                                             #
# See the License for the specific language governing permissions and                                                  #
# limitations under the License.                                                                                       #
#                                                                                                                      #
# SPDX-License-Identifier: Apache-2.0                                                                                  #
# ==================================================================================================================== #
#
"""
A solution to send warnings like exceptions to a handler in the upper part of the call-stack.

.. hint::

   See :ref:`high-level help <WARNING>` for explanations and usage examples.
"""
from threading import local
from types     import TracebackType
from typing    import List, Callable, Optional as Nullable, Type, Iterator, Self

try:
	from pyTooling.Decorators import export, readonly
	from pyTooling.Common     import getFullyQualifiedName
	from pyTooling.Exceptions import ExceptionBase
except ModuleNotFoundError:  # pragma: no cover
	print("[pyTooling.Warning] Could not import from 'pyTooling.*'!")

	try:
		from Decorators         import export, readonly
		from Common             import getFullyQualifiedName
		from Exceptions         import ExceptionBase
	except ModuleNotFoundError as ex:  # pragma: no cover
		print("[pyTooling.Warning] Could not import directly!")
		raise ex


__all__ = ["_threadLocalData"]

_threadLocalData = local()
"""A reference to the thread local data needed by the pyTooling.Warning classes."""


@export
class Warning(BaseException):
	"""
	Base-exception of all warnings handled by :class:`WarningCollector`.

	.. tip::

	   Warnings can be unhandled within a call hierarchy.
	"""


@export
class CriticalWarning(BaseException):
	"""
	Base-exception of all critical warnings handled by :class:`WarningCollector`.

	.. tip::

	   Critical warnings must be unhandled within a call hierarchy, otherwise a :exc:`UnhandledCriticalWarningException`
	   will be raised.
	"""


@export
class UnhandledWarningException(ExceptionBase):   # FIXME: to be removed in v9.0.0
	"""
	Deprecated.

	.. deprecated:: v9.0.0

	   Please use :exc:`UnhandledCriticalWarningException`.
	"""


@export
class UnhandledCriticalWarningException(UnhandledWarningException):
	"""
	This exception is raised when a critical warning isn't handled by a :class:`WarningCollector` within the
	call-hierarchy.
	"""


@export
class UnhandledExceptionException(UnhandledWarningException):
	"""
	This exception is raised when an exception isn't handled by a :class:`WarningCollector` within the call-hierarchy.
	"""


@export
class WarningCollector:
	"""
	A context manager to collect warnings within the call hierarchy.
	"""
	_parent:   Nullable["WarningCollector"]               #: Parent WarningCollector
	_warnings: List[BaseException]                        #: List of collected warnings (and exceptions).
	_handler:  Nullable[Callable[[BaseException], bool]]  #: Optional handler function, which is called per collected warning.

	def __init__(
		self,
		warnings: Nullable[List[BaseException]] = None,
		handler: Nullable[Callable[[BaseException], bool]] = None
	) -> None:
		"""
		Initializes a warning collector.

		:param warnings:   An optional reference to a list of warnings, which can be modified (appended) by this warning
		                   collector. If ``None``, an internal list is created and can be referenced by the collector's
		                   instance.
		:param handler:    An optional handler function, which processes the current warning and decides if a warning should
		                   be reraised as an exception.
		:raises TypeError: If optional parameter 'warnings' is not of type list.
		:raises TypeError: If optional parameter 'handler' is not a callable.
		"""
		if warnings is None:
			warnings = []
		elif not isinstance(warnings, list):
			ex = TypeError(f"Parameter 'warnings' is not list.")
			ex.add_note(f"Got type '{getFullyQualifiedName(warnings)}'.")
			raise ex

		if handler is not None and not isinstance(handler, Callable):
			ex = TypeError(f"Parameter 'handler' is not callable.")
			ex.add_note(f"Got type '{getFullyQualifiedName(handler)}'.")
			raise ex

		self._parent =   None
		self._warnings = warnings
		self._handler =  handler

	def __len__(self) -> int:
		"""
		Returns the number of collected warnings.

		:returns: Number of collected warnings.
		"""
		return len(self._warnings)

	def __iter__(self) -> Iterator[BaseException]:
		return iter(self._warnings)

	def __getitem__(self, index: int) -> BaseException:
		return self._warnings[index]

	def __enter__(self) -> Self:
		"""
		Enter the warning collector context.

		:returns: The warning collector instance.
		"""
		global _threadLocalData

		try:
			self.Parent = _threadLocalData.warningCollector
		except AttributeError:
			pass

		_threadLocalData.warningCollector = self

		return self

	def __exit__(
		self,
		exc_type: Nullable[Type[BaseException]] = None,
		exc_val:  Nullable[BaseException] = None,
		exc_tb:   Nullable[TracebackType] = None
	) -> Nullable[bool]:
		"""
		Exit the warning collector context.

		:param exc_type: Exception type
		:param exc_val:  Exception instance
		:param exc_tb:   Exception's traceback.
		:returns:        ``None``
		"""
		global _threadLocalData

		_threadLocalData.warningCollector = self._parent

	@property
	def Parent(self) -> Nullable["WarningCollector"]:
		"""
		Property to access the parent warning collected.

		:returns: The parent warning collector or ``None``.
		"""
		return self._parent

	@Parent.setter
	def Parent(self, value: "WarningCollector") -> None:
		self._parent = value

	@readonly
	def Warnings(self) -> List[BaseException]:
		"""
		Read-only property to access the list of collected warnings.

		:returns: A list of collected warnings.
		"""
		return self._warnings

	def AddWarning(self, warning: BaseException) -> bool:
		"""
		Add a warning to the list of warnings managed by this warning collector.

		:param warning:     The warning to add to the collectors internal warning list.
		:returns:           Return ``True`` if the warning collector has a local handler callback and this handler returned
		                    ``True``; otherwise ``False``.
		:raises ValueError: If parameter ``warning`` is None.
		:raises TypeError:  If parameter ``warning`` is not of type :class:`Warning`.
		"""
		if warning is None:
			raise ValueError("Parameter 'warning' is None.")
		elif not isinstance(warning, (Warning, CriticalWarning, Exception)):
			ex = TypeError(f"Parameter 'warning' is not of type 'Warning', 'CriticalWarning' or 'Exception'.")
			ex.add_note(f"Got type '{getFullyQualifiedName(warning)}'.")
			raise ex

		self._warnings.append(warning)

		return False if self._handler is None else self._handler(warning)

	@classmethod
	def Raise(cls, warning: BaseException) -> None:
		"""
		Walk the callstack frame by frame upwards and search for the first warning collector.

		:param warning:    Warning to send upwards in the call stack.
		:raises Exception: If warning should be converted to an exception.
		:raises Exception: If the call-stack walk couldn't find a warning collector.
		"""
		global _threadLocalData
		try:
			warningCollector = _threadLocalData.warningCollector
			if warningCollector.AddWarning(warning):
				raise Exception(f"Warning: {warning}") from warning
		except AttributeError:
			ex = None
			if isinstance(warning, Exception):
				ex = UnhandledExceptionException(f"Unhandled Exception: {warning}")
			elif isinstance(warning, CriticalWarning):
				ex = UnhandledCriticalWarningException(f"Unhandled Critical Warning: {warning}")

			if ex is not None:
				ex.add_note(f"Add a 'with'-statement using '{cls.__name__}' somewhere up the call-hierarchy to receive and collect warnings.")
				raise ex from warning