algomancy-utils 0.3.12__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.
- algomancy_utils/__init__.py +13 -0
- algomancy_utils/baseparameterset.py +479 -0
- algomancy_utils/logger.py +102 -0
- algomancy_utils/py.typed +0 -0
- algomancy_utils/unit.py +1004 -0
- algomancy_utils-0.3.12.dist-info/METADATA +9 -0
- algomancy_utils-0.3.12.dist-info/RECORD +8 -0
- algomancy_utils-0.3.12.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .logger import Message, MessageStatus, Logger
|
|
2
|
+
from .unit import Unit, Quantity, BaseMeasurement, Measurement, QUANTITIES
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"Message",
|
|
6
|
+
"MessageStatus",
|
|
7
|
+
"Logger",
|
|
8
|
+
"Unit",
|
|
9
|
+
"Quantity",
|
|
10
|
+
"BaseMeasurement",
|
|
11
|
+
"Measurement",
|
|
12
|
+
"QUANTITIES",
|
|
13
|
+
]
|
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod, ABCMeta
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
from typing import Any, Dict, TypeVar
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ParameterError(Exception):
|
|
8
|
+
def __init__(self, message: str) -> None:
|
|
9
|
+
self.message = message
|
|
10
|
+
super().__init__(self.message)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ParameterType(StrEnum):
|
|
14
|
+
STRING = "string"
|
|
15
|
+
INTEGER = "integer"
|
|
16
|
+
FLOAT = "float"
|
|
17
|
+
BOOLEAN = "boolean"
|
|
18
|
+
ENUM = "enum"
|
|
19
|
+
MULTI_ENUM = "multi_enum"
|
|
20
|
+
TIME = "time"
|
|
21
|
+
INTERVAL = "interval"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TypedParameter(ABC):
|
|
25
|
+
def __init__(
|
|
26
|
+
self, name: str, parameter_type: ParameterType, required: bool
|
|
27
|
+
) -> None:
|
|
28
|
+
self.name = name
|
|
29
|
+
self.parameter_type = parameter_type
|
|
30
|
+
self.required = required
|
|
31
|
+
self._value = None
|
|
32
|
+
self.is_list = False
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def value(self) -> Any:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def _validate(self, value) -> None:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
def _check_required(self, value) -> None:
|
|
44
|
+
if self.required and value is None:
|
|
45
|
+
raise ParameterError(f"Parameter '{self.name}' is required.")
|
|
46
|
+
|
|
47
|
+
def _set_value(self, value: Any) -> None:
|
|
48
|
+
self._value = value
|
|
49
|
+
|
|
50
|
+
def set_validated_value(self, value: Any) -> None:
|
|
51
|
+
self._check_required(value)
|
|
52
|
+
self._validate(value)
|
|
53
|
+
self._set_value(value)
|
|
54
|
+
|
|
55
|
+
def __str__(self):
|
|
56
|
+
return self._serialize()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class StringParameter(TypedParameter):
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
name: str,
|
|
63
|
+
value: str = None,
|
|
64
|
+
required: bool = True,
|
|
65
|
+
default: str = "default",
|
|
66
|
+
) -> None:
|
|
67
|
+
super().__init__(name, ParameterType.STRING, required)
|
|
68
|
+
self.default = default
|
|
69
|
+
if value is not None:
|
|
70
|
+
self.set_validated_value(value)
|
|
71
|
+
|
|
72
|
+
def _validate(self, value):
|
|
73
|
+
if not isinstance(value, str):
|
|
74
|
+
raise ParameterError(f"Parameter '{self.name}' must be a string.")
|
|
75
|
+
|
|
76
|
+
def __str__(self) -> str:
|
|
77
|
+
return f"{self.name}: {self.value}"
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def value(self) -> str:
|
|
81
|
+
if self._value is None:
|
|
82
|
+
return self.default
|
|
83
|
+
return self._value
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class EnumParameter(TypedParameter):
|
|
87
|
+
def __init__(
|
|
88
|
+
self, name: str, choices: list[str], value: str = None, required: bool = True
|
|
89
|
+
) -> None:
|
|
90
|
+
super().__init__(name, ParameterType.ENUM, required)
|
|
91
|
+
assert len(choices) > 0, "Parameter must have at least one choice."
|
|
92
|
+
self.choices = choices
|
|
93
|
+
if value is not None:
|
|
94
|
+
self.set_validated_value(value)
|
|
95
|
+
|
|
96
|
+
def __str__(self) -> str:
|
|
97
|
+
return f"{self.name}: {self.value}"
|
|
98
|
+
|
|
99
|
+
def _validate(self, value: str):
|
|
100
|
+
if not isinstance(value, str):
|
|
101
|
+
raise ParameterError(f"Parameter '{self.name}' must be a string.")
|
|
102
|
+
if value not in self.choices:
|
|
103
|
+
raise ParameterError(
|
|
104
|
+
f"Parameter '{self.name}' must be one of {self.choices}."
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def value(self) -> str:
|
|
109
|
+
if self._value is None:
|
|
110
|
+
return self.choices[0]
|
|
111
|
+
return self._value
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class MultiEnumParameter(TypedParameter):
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
name: str,
|
|
118
|
+
choices: list[str],
|
|
119
|
+
value: list[str] = None,
|
|
120
|
+
required: bool = True,
|
|
121
|
+
) -> None:
|
|
122
|
+
super().__init__(name, ParameterType.MULTI_ENUM, required)
|
|
123
|
+
assert len(choices) > 0, "Parameter must have at least one choice."
|
|
124
|
+
self.choices = choices
|
|
125
|
+
if value is not None:
|
|
126
|
+
self.set_validated_value(value)
|
|
127
|
+
|
|
128
|
+
def __str__(self) -> str:
|
|
129
|
+
return f"{self.name}: {self.value}"
|
|
130
|
+
|
|
131
|
+
def _validate(self, value_lst: list[str]):
|
|
132
|
+
if not isinstance(value_lst, list):
|
|
133
|
+
raise ParameterError(f"Parameter '{self.name}' must be a list.")
|
|
134
|
+
for value in value_lst:
|
|
135
|
+
if not isinstance(value, str):
|
|
136
|
+
raise ParameterError(f"Parameter '{self.name}' must be a string.")
|
|
137
|
+
if value not in self.choices:
|
|
138
|
+
raise ParameterError(
|
|
139
|
+
f"Parameter '{self.name}' must be one of {self.choices}."
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def value(self) -> list[str]:
|
|
144
|
+
if self._value is None:
|
|
145
|
+
return [self.choices[0]]
|
|
146
|
+
return self._value
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class NumericParameter(TypedParameter, ABC):
|
|
150
|
+
def __init__(
|
|
151
|
+
self,
|
|
152
|
+
name: str,
|
|
153
|
+
parameter_type: ParameterType,
|
|
154
|
+
required: bool,
|
|
155
|
+
default,
|
|
156
|
+
minvalue: float = None,
|
|
157
|
+
maxvalue: float = None,
|
|
158
|
+
value: float = None,
|
|
159
|
+
) -> None:
|
|
160
|
+
super().__init__(name, parameter_type, required)
|
|
161
|
+
self.default = default
|
|
162
|
+
assert parameter_type in [
|
|
163
|
+
ParameterType.INTEGER,
|
|
164
|
+
ParameterType.FLOAT,
|
|
165
|
+
], "Numeric parameter must be of type integer or float."
|
|
166
|
+
self.min = minvalue
|
|
167
|
+
self.max = maxvalue
|
|
168
|
+
|
|
169
|
+
if minvalue is not None and maxvalue is not None:
|
|
170
|
+
assert minvalue <= maxvalue, (
|
|
171
|
+
"Minimum value must be less than or equal to maximum value."
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if value is not None:
|
|
175
|
+
self.set_validated_value(value)
|
|
176
|
+
|
|
177
|
+
def _validate(self, value) -> None:
|
|
178
|
+
if self.parameter_type == ParameterType.FLOAT and not (
|
|
179
|
+
isinstance(value, float) or isinstance(value, int)
|
|
180
|
+
):
|
|
181
|
+
raise ParameterError(f"Parameter '{self.name}' must be a float.")
|
|
182
|
+
elif self.parameter_type == ParameterType.INTEGER and not isinstance(
|
|
183
|
+
value, int
|
|
184
|
+
):
|
|
185
|
+
raise ParameterError(f"Parameter '{self.name}' must be an integer.")
|
|
186
|
+
if self.min is not None and value < self.min:
|
|
187
|
+
raise ParameterError(
|
|
188
|
+
f"Parameter '{self.name}' must be greater than or equal to {self.min}."
|
|
189
|
+
)
|
|
190
|
+
if self.max is not None and value > self.max:
|
|
191
|
+
raise ParameterError(
|
|
192
|
+
f"Parameter '{self.name}' must be less than or equal to {self.max}."
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class FloatParameter(NumericParameter):
|
|
197
|
+
EPSILON = 1e-6
|
|
198
|
+
|
|
199
|
+
def __init__(
|
|
200
|
+
self,
|
|
201
|
+
name: str,
|
|
202
|
+
minvalue: float = None,
|
|
203
|
+
maxvalue: float = None,
|
|
204
|
+
value: float = None,
|
|
205
|
+
required: bool = True,
|
|
206
|
+
default: float = 1.0,
|
|
207
|
+
) -> None:
|
|
208
|
+
super().__init__(
|
|
209
|
+
name, ParameterType.FLOAT, required, default, minvalue, maxvalue, value
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def __str__(self) -> str:
|
|
213
|
+
return f"{self.name}: {self.value:.2f}"
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def value(self) -> float:
|
|
217
|
+
if self._value is None:
|
|
218
|
+
return self.default
|
|
219
|
+
return self._value
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class IntegerParameter(NumericParameter):
|
|
223
|
+
def __init__(
|
|
224
|
+
self,
|
|
225
|
+
name: str,
|
|
226
|
+
minvalue: int = None,
|
|
227
|
+
maxvalue: int = None,
|
|
228
|
+
value: int = None,
|
|
229
|
+
required: bool = True,
|
|
230
|
+
default: int = 1,
|
|
231
|
+
) -> None:
|
|
232
|
+
super().__init__(
|
|
233
|
+
name, ParameterType.INTEGER, required, default, minvalue, maxvalue, value
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def __str__(self) -> str:
|
|
237
|
+
return f"{self.name}: {self.value}"
|
|
238
|
+
|
|
239
|
+
@property
|
|
240
|
+
def value(self) -> int:
|
|
241
|
+
if self._value is None:
|
|
242
|
+
return self.default
|
|
243
|
+
return self._value
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class BooleanParameter(TypedParameter):
|
|
247
|
+
def __init__(
|
|
248
|
+
self,
|
|
249
|
+
name: str,
|
|
250
|
+
value: bool = None,
|
|
251
|
+
required: bool = True,
|
|
252
|
+
default: bool = False,
|
|
253
|
+
) -> None:
|
|
254
|
+
super().__init__(name, ParameterType.BOOLEAN, required)
|
|
255
|
+
self.default = default
|
|
256
|
+
if value is not None:
|
|
257
|
+
self.set_validated_value(value)
|
|
258
|
+
|
|
259
|
+
def __str__(self) -> str:
|
|
260
|
+
return f"{self.name}: {self.value}"
|
|
261
|
+
|
|
262
|
+
def serialize(self) -> str:
|
|
263
|
+
return str(self.value)
|
|
264
|
+
|
|
265
|
+
def _validate(self, value):
|
|
266
|
+
if not isinstance(value, bool):
|
|
267
|
+
raise ParameterError(f"Parameter '{self.name}' must be a boolean.")
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def value(self) -> bool:
|
|
271
|
+
if self._value is None:
|
|
272
|
+
return self.default
|
|
273
|
+
return self._value
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class TimeParameter(TypedParameter):
|
|
277
|
+
def __init__(
|
|
278
|
+
self,
|
|
279
|
+
name: str,
|
|
280
|
+
value: datetime | None = None,
|
|
281
|
+
required: bool = True,
|
|
282
|
+
default: datetime | None = None,
|
|
283
|
+
) -> None:
|
|
284
|
+
super().__init__(name, ParameterType.TIME, required)
|
|
285
|
+
self._default = default
|
|
286
|
+
if value is not None:
|
|
287
|
+
self.set_validated_value(value)
|
|
288
|
+
|
|
289
|
+
def __str__(self) -> str:
|
|
290
|
+
return f"{self.name}: {self.value.isoformat()}"
|
|
291
|
+
|
|
292
|
+
def _validate(self, value) -> None:
|
|
293
|
+
if not isinstance(value, datetime):
|
|
294
|
+
raise ParameterError(f"Parameter '{self.name}' must be a datetime.")
|
|
295
|
+
|
|
296
|
+
@property
|
|
297
|
+
def default(self) -> datetime:
|
|
298
|
+
if self._default is None:
|
|
299
|
+
return datetime.today()
|
|
300
|
+
else:
|
|
301
|
+
return self._default
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def value(self) -> datetime:
|
|
305
|
+
if self._value is None:
|
|
306
|
+
return self._default
|
|
307
|
+
return self._value
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class IntervalParameter(TypedParameter):
|
|
311
|
+
def __init__(
|
|
312
|
+
self,
|
|
313
|
+
name: str,
|
|
314
|
+
value: list[datetime] | tuple[datetime, datetime] | None = None,
|
|
315
|
+
required: bool = True,
|
|
316
|
+
default: tuple[datetime, datetime] | None = None,
|
|
317
|
+
) -> None:
|
|
318
|
+
super().__init__(name, ParameterType.INTERVAL, required)
|
|
319
|
+
self.default = default
|
|
320
|
+
if value is not None:
|
|
321
|
+
self.set_validated_value(value)
|
|
322
|
+
|
|
323
|
+
def __str__(self) -> str:
|
|
324
|
+
s, e = self.value
|
|
325
|
+
return f"{self.name}: [{s.isoformat()}, {e.isoformat()}]"
|
|
326
|
+
|
|
327
|
+
def _validate(self, value) -> None:
|
|
328
|
+
if not (isinstance(value, (list, tuple)) and len(value) == 2):
|
|
329
|
+
raise ParameterError(
|
|
330
|
+
f"Parameter '{self.name}' must be a list/tuple of two datetimes."
|
|
331
|
+
)
|
|
332
|
+
start, end = value[0], value[1]
|
|
333
|
+
if not isinstance(start, datetime) or not isinstance(end, datetime):
|
|
334
|
+
raise ParameterError(
|
|
335
|
+
f"Parameter '{self.name}' must contain datetime values."
|
|
336
|
+
)
|
|
337
|
+
if end < start:
|
|
338
|
+
raise ParameterError(
|
|
339
|
+
f"Parameter '{self.name}' interval end must be greater than or equal to start."
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
@property
|
|
343
|
+
def default_start(self) -> datetime:
|
|
344
|
+
if self.default:
|
|
345
|
+
return self.default[0]
|
|
346
|
+
else:
|
|
347
|
+
now = datetime.today()
|
|
348
|
+
return datetime(now.year, 1, 1)
|
|
349
|
+
|
|
350
|
+
@property
|
|
351
|
+
def default_end(self) -> datetime:
|
|
352
|
+
if self.default:
|
|
353
|
+
return self.default[1]
|
|
354
|
+
else:
|
|
355
|
+
now = datetime.today()
|
|
356
|
+
return datetime(now.year, 12, 31)
|
|
357
|
+
|
|
358
|
+
@property
|
|
359
|
+
def value(self) -> tuple[datetime, datetime]:
|
|
360
|
+
if self._value is None:
|
|
361
|
+
return self.default_start, self.default_end
|
|
362
|
+
# Normalize internal storage to tuple
|
|
363
|
+
if isinstance(self._value, list):
|
|
364
|
+
return (self._value[0], self._value[1])
|
|
365
|
+
return self._value
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class PostInitMeta(ABCMeta):
|
|
369
|
+
def __call__(cls, *args, **kwargs):
|
|
370
|
+
instance = super().__call__(*args, **kwargs)
|
|
371
|
+
post_init = getattr(instance, "_post_init", None)
|
|
372
|
+
if callable(post_init):
|
|
373
|
+
post_init()
|
|
374
|
+
return instance
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
class BaseParameterSet(ABC, metaclass=PostInitMeta):
|
|
378
|
+
def __init__(self, name: str) -> None:
|
|
379
|
+
self.name: str = name
|
|
380
|
+
self._parameters: Dict[str, TypedParameter] = {}
|
|
381
|
+
self._is_locked = False
|
|
382
|
+
|
|
383
|
+
def __str__(self):
|
|
384
|
+
return str(self.serialize())
|
|
385
|
+
|
|
386
|
+
def __dict__(self):
|
|
387
|
+
return {p.name: p.value for p in self._parameters.values()}
|
|
388
|
+
|
|
389
|
+
def __getitem__(self, key):
|
|
390
|
+
return self._parameters[key].value
|
|
391
|
+
|
|
392
|
+
def _post_init(self):
|
|
393
|
+
"""is called directly after the __init__ method in PostInitMeta classes"""
|
|
394
|
+
self._is_locked = True
|
|
395
|
+
|
|
396
|
+
def copy(self):
|
|
397
|
+
return self.deserialize(self.serialize())
|
|
398
|
+
|
|
399
|
+
@abstractmethod
|
|
400
|
+
def validate(self):
|
|
401
|
+
"""Validates parameters, must be implemented in subclass."""
|
|
402
|
+
pass
|
|
403
|
+
|
|
404
|
+
def get_parameters(self) -> Dict[str, TypedParameter]:
|
|
405
|
+
return self._parameters
|
|
406
|
+
|
|
407
|
+
def contains(self, param_name: str) -> bool:
|
|
408
|
+
return param_name in self._parameters
|
|
409
|
+
|
|
410
|
+
def serialize(self):
|
|
411
|
+
import json
|
|
412
|
+
|
|
413
|
+
dct = {"name": self.name, "parameters": self.get_values()}
|
|
414
|
+
return json.dumps(dct)
|
|
415
|
+
|
|
416
|
+
@classmethod
|
|
417
|
+
def deserialize(cls, json_str: str):
|
|
418
|
+
import json
|
|
419
|
+
|
|
420
|
+
data = json.loads(json_str)
|
|
421
|
+
rv = cls()
|
|
422
|
+
|
|
423
|
+
# apply the stored values to the newly created instance.
|
|
424
|
+
if "parameters" in data:
|
|
425
|
+
rv.set_values(data["parameters"])
|
|
426
|
+
|
|
427
|
+
return rv
|
|
428
|
+
|
|
429
|
+
def add_parameters(self, parameters: list[TypedParameter]):
|
|
430
|
+
if self._is_locked:
|
|
431
|
+
raise ParameterError("Cannot add parameter after initialization.")
|
|
432
|
+
for parameter in parameters:
|
|
433
|
+
self._parameters[parameter.name] = parameter
|
|
434
|
+
|
|
435
|
+
def set_values(self, values: dict[str, Any]):
|
|
436
|
+
self.repair_param_dict(values)
|
|
437
|
+
for name, value in values.items():
|
|
438
|
+
if name in self._parameters:
|
|
439
|
+
self._parameters[name].set_validated_value(value)
|
|
440
|
+
else:
|
|
441
|
+
raise ParameterError(f"Parameter '{name}' not found.")
|
|
442
|
+
|
|
443
|
+
def set_validated_values(self, values: dict[str, Any]) -> None:
|
|
444
|
+
self.set_values(values)
|
|
445
|
+
self.validate()
|
|
446
|
+
|
|
447
|
+
def get_values(self) -> dict[str, Any]:
|
|
448
|
+
return {key: p.value for key, p in self._parameters.items()}
|
|
449
|
+
|
|
450
|
+
def has_inputs(self) -> bool:
|
|
451
|
+
return len(self._parameters) > 0
|
|
452
|
+
|
|
453
|
+
def get_boolean_parameter_names(self) -> list[str]:
|
|
454
|
+
return [
|
|
455
|
+
p.name for p in self._parameters.values() if type(p) is BooleanParameter
|
|
456
|
+
]
|
|
457
|
+
|
|
458
|
+
def repair_param_dict(self, dct):
|
|
459
|
+
# retrieve the boolean variables
|
|
460
|
+
boolean_keys = self.get_boolean_parameter_names()
|
|
461
|
+
|
|
462
|
+
# set value appropriately
|
|
463
|
+
for key in boolean_keys:
|
|
464
|
+
if key in dct:
|
|
465
|
+
if dct[key]:
|
|
466
|
+
dct[key] = True
|
|
467
|
+
else:
|
|
468
|
+
dct[key] = False
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
BASE_PARAMS_BOUND = TypeVar("BASE_PARAMS_BOUND", bound=BaseParameterSet)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class EmptyParameters(BaseParameterSet):
|
|
475
|
+
def __init__(self) -> None:
|
|
476
|
+
super().__init__(name="empty")
|
|
477
|
+
|
|
478
|
+
def validate(self):
|
|
479
|
+
pass
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import traceback
|
|
3
|
+
from enum import StrEnum, auto
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MessageStatus(StrEnum):
|
|
8
|
+
INFO = auto()
|
|
9
|
+
SUCCESS = auto()
|
|
10
|
+
WARNING = auto()
|
|
11
|
+
ERROR = auto()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Message:
|
|
15
|
+
def __init__(
|
|
16
|
+
self, message: str, status: MessageStatus = MessageStatus.INFO
|
|
17
|
+
) -> None:
|
|
18
|
+
self.message = message
|
|
19
|
+
self.status = status
|
|
20
|
+
self.timestamp = datetime.datetime.now()
|
|
21
|
+
|
|
22
|
+
def __str__(self):
|
|
23
|
+
return f"[{self.timestamp.isoformat()}] {self.status.name.rjust(7)}: {self.message}"
|
|
24
|
+
|
|
25
|
+
def print(self):
|
|
26
|
+
RESET = "\033[0m"
|
|
27
|
+
GREEN = "\033[92m"
|
|
28
|
+
ORANGE = "\033[93m"
|
|
29
|
+
RED = "\033[91m"
|
|
30
|
+
|
|
31
|
+
match self.status:
|
|
32
|
+
case MessageStatus.INFO:
|
|
33
|
+
print(f"{RESET}{self.__str__()}")
|
|
34
|
+
case MessageStatus.SUCCESS:
|
|
35
|
+
print(f"{GREEN}{self.__str__()}")
|
|
36
|
+
case MessageStatus.WARNING:
|
|
37
|
+
print(f"{ORANGE}{self.__str__()}")
|
|
38
|
+
case MessageStatus.ERROR:
|
|
39
|
+
print(f"{RED}{self.__str__()}")
|
|
40
|
+
case _:
|
|
41
|
+
print(f"{self.__str__()}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Logger:
|
|
45
|
+
"""
|
|
46
|
+
Eenvoudige logger die berichten opslaat met status en timestamp.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self) -> None:
|
|
50
|
+
self._logs: List[Message] = []
|
|
51
|
+
self.latest_log: Optional[Message] = None
|
|
52
|
+
self._print_to_console = True
|
|
53
|
+
|
|
54
|
+
def toggle_print_to_console(self, value: bool = None) -> None:
|
|
55
|
+
if not value:
|
|
56
|
+
value = not self._print_to_console
|
|
57
|
+
|
|
58
|
+
self._print_to_console = value
|
|
59
|
+
|
|
60
|
+
def log(self, message: str, status: MessageStatus = MessageStatus.INFO) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Voeg een logbericht toe.
|
|
63
|
+
|
|
64
|
+
:param message: Het bericht dat gelogd wordt
|
|
65
|
+
:param status: Status/type van het bericht (bijv. 'info', 'success', 'warning', 'error')
|
|
66
|
+
"""
|
|
67
|
+
self._logs.append(Message(message=message, status=status))
|
|
68
|
+
self.latest_log = self._logs[-1]
|
|
69
|
+
|
|
70
|
+
if self._print_to_console:
|
|
71
|
+
self.latest_log.print()
|
|
72
|
+
|
|
73
|
+
def success(self, message: str):
|
|
74
|
+
self.log(message, status=MessageStatus.SUCCESS)
|
|
75
|
+
|
|
76
|
+
def warning(self, message: str):
|
|
77
|
+
self.log(message, status=MessageStatus.WARNING)
|
|
78
|
+
|
|
79
|
+
def error(self, message: str):
|
|
80
|
+
self.log(message, status=MessageStatus.ERROR)
|
|
81
|
+
|
|
82
|
+
def get_logs(self, status_filter: Optional[MessageStatus] = None) -> List[Message]:
|
|
83
|
+
"""
|
|
84
|
+
Haal alle logs op, eventueel gefilterd op status.
|
|
85
|
+
|
|
86
|
+
:param status_filter: Optioneel, filter op status (bijv. 'info')
|
|
87
|
+
:return: Lijst van logs (dicts)
|
|
88
|
+
"""
|
|
89
|
+
if status_filter:
|
|
90
|
+
return [log for log in self._logs if log.status == status_filter]
|
|
91
|
+
return list(self._logs)
|
|
92
|
+
|
|
93
|
+
def clear(self) -> None:
|
|
94
|
+
"""
|
|
95
|
+
Verwijdert alle opgeslagen logs.
|
|
96
|
+
"""
|
|
97
|
+
self._logs.clear()
|
|
98
|
+
|
|
99
|
+
def log_traceback(self, e: Exception):
|
|
100
|
+
self.error(f"An error occurred: {e.__class__.__name__}: {e}")
|
|
101
|
+
for msg in traceback.format_tb(e.__traceback__):
|
|
102
|
+
self.error(msg)
|
algomancy_utils/py.typed
ADDED
|
File without changes
|