haiway 0.18.1__py3-none-any.whl → 0.19.0__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.
- haiway/__init__.py +0 -2
- haiway/context/access.py +53 -64
- haiway/context/observability.py +57 -35
- haiway/helpers/__init__.py +1 -2
- haiway/helpers/observability.py +54 -35
- haiway/helpers/tracing.py +36 -45
- haiway/opentelemetry/observability.py +60 -38
- haiway/state/structure.py +3 -151
- haiway/utils/__init__.py +2 -0
- haiway/utils/formatting.py +148 -0
- {haiway-0.18.1.dist-info → haiway-0.19.0.dist-info}/METADATA +1 -1
- {haiway-0.18.1.dist-info → haiway-0.19.0.dist-info}/RECORD +14 -13
- {haiway-0.18.1.dist-info → haiway-0.19.0.dist-info}/WHEEL +0 -0
- {haiway-0.18.1.dist-info → haiway-0.19.0.dist-info}/licenses/LICENSE +0 -0
haiway/helpers/tracing.py
CHANGED
@@ -1,40 +1,13 @@
|
|
1
1
|
from asyncio import iscoroutinefunction
|
2
2
|
from collections.abc import Callable, Coroutine
|
3
|
-
from typing import Any,
|
3
|
+
from typing import Any, cast, overload
|
4
4
|
|
5
5
|
from haiway.context import ctx
|
6
|
-
from haiway.
|
7
|
-
from haiway.types import MISSING, Missing
|
6
|
+
from haiway.types import MISSING
|
8
7
|
from haiway.utils import mimic_function
|
8
|
+
from haiway.utils.formatting import format_str
|
9
9
|
|
10
|
-
__all__ = (
|
11
|
-
"ResultTrace",
|
12
|
-
"traced",
|
13
|
-
)
|
14
|
-
|
15
|
-
|
16
|
-
class ResultTrace(State):
|
17
|
-
if __debug__:
|
18
|
-
|
19
|
-
@classmethod
|
20
|
-
def of(
|
21
|
-
cls,
|
22
|
-
value: Any,
|
23
|
-
/,
|
24
|
-
) -> Self:
|
25
|
-
return cls(result=f"{value}")
|
26
|
-
|
27
|
-
else: # remove tracing for non debug runs to prevent accidental secret leaks
|
28
|
-
|
29
|
-
@classmethod
|
30
|
-
def of(
|
31
|
-
cls,
|
32
|
-
value: Any,
|
33
|
-
/,
|
34
|
-
) -> Self:
|
35
|
-
return cls(result=MISSING)
|
36
|
-
|
37
|
-
result: str | Missing
|
10
|
+
__all__ = ("traced",)
|
38
11
|
|
39
12
|
|
40
13
|
@overload
|
@@ -96,19 +69,28 @@ def _traced_sync[**Args, Result](
|
|
96
69
|
**kwargs: Args.kwargs,
|
97
70
|
) -> Result:
|
98
71
|
with ctx.scope(label):
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
72
|
+
ctx.record(
|
73
|
+
attributes={
|
74
|
+
f"[{idx}]": f"{arg}" for idx, arg in enumerate(args) if arg is not MISSING
|
75
|
+
}
|
76
|
+
)
|
77
|
+
ctx.record(
|
78
|
+
attributes={key: f"{arg}" for key, arg in kwargs.items() if arg is not MISSING}
|
79
|
+
)
|
104
80
|
|
105
81
|
try:
|
106
82
|
result: Result = function(*args, **kwargs)
|
107
|
-
ctx.
|
83
|
+
ctx.record(
|
84
|
+
event="result",
|
85
|
+
attributes={"value": format_str(result)},
|
86
|
+
)
|
108
87
|
return result
|
109
88
|
|
110
89
|
except BaseException as exc:
|
111
|
-
ctx.
|
90
|
+
ctx.record(
|
91
|
+
event="result",
|
92
|
+
attributes={"error": f"{type(exc)}: {exc}"},
|
93
|
+
)
|
112
94
|
raise exc
|
113
95
|
|
114
96
|
return mimic_function(
|
@@ -127,19 +109,28 @@ def _traced_async[**Args, Result](
|
|
127
109
|
**kwargs: Args.kwargs,
|
128
110
|
) -> Result:
|
129
111
|
with ctx.scope(label):
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
112
|
+
ctx.record(
|
113
|
+
attributes={
|
114
|
+
f"[{idx}]": f"{arg}" for idx, arg in enumerate(args) if arg is not MISSING
|
115
|
+
}
|
116
|
+
)
|
117
|
+
ctx.record(
|
118
|
+
attributes={key: f"{arg}" for key, arg in kwargs.items() if arg is not MISSING}
|
119
|
+
)
|
135
120
|
|
136
121
|
try:
|
137
122
|
result: Result = await function(*args, **kwargs)
|
138
|
-
ctx.
|
123
|
+
ctx.record(
|
124
|
+
event="result",
|
125
|
+
attributes={"value": format_str(result)},
|
126
|
+
)
|
139
127
|
return result
|
140
128
|
|
141
129
|
except BaseException as exc:
|
142
|
-
ctx.
|
130
|
+
ctx.record(
|
131
|
+
event="result",
|
132
|
+
attributes={"error": f"{type(exc)}: {exc}"},
|
133
|
+
)
|
143
134
|
raise exc
|
144
135
|
|
145
136
|
return mimic_function(
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import os
|
2
2
|
from collections.abc import Mapping
|
3
|
-
from typing import Any, Self, final
|
3
|
+
from typing import Any, Self, cast, final
|
4
4
|
|
5
5
|
from opentelemetry import metrics, trace
|
6
6
|
from opentelemetry._logs import get_logger, set_logger_provider
|
@@ -29,12 +29,12 @@ from opentelemetry.sdk.resources import Resource
|
|
29
29
|
from opentelemetry.sdk.trace import TracerProvider
|
30
30
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter, SpanExporter
|
31
31
|
from opentelemetry.trace import Span, StatusCode, Tracer
|
32
|
+
from opentelemetry.trace.span import SpanContext
|
32
33
|
|
33
34
|
from haiway.context import Observability, ObservabilityLevel, ScopeIdentifier
|
34
|
-
|
35
|
-
###
|
36
35
|
from haiway.context.observability import ObservabilityAttribute
|
37
36
|
from haiway.state import State
|
37
|
+
from haiway.types import MISSING
|
38
38
|
|
39
39
|
__all__ = ("OpenTelemetry",)
|
40
40
|
|
@@ -45,9 +45,9 @@ class ScopeStore:
|
|
45
45
|
"_counters",
|
46
46
|
"_exited",
|
47
47
|
"_histograms",
|
48
|
+
"_span_context",
|
48
49
|
"identifier",
|
49
50
|
"logger",
|
50
|
-
"logger_2",
|
51
51
|
"meter",
|
52
52
|
"nested",
|
53
53
|
"span",
|
@@ -68,6 +68,7 @@ class ScopeStore:
|
|
68
68
|
self._exited: bool = False
|
69
69
|
self._completed: bool = False
|
70
70
|
self.span: Span = span
|
71
|
+
self._span_context: SpanContext = span.get_span_context()
|
71
72
|
self.meter: Meter = meter
|
72
73
|
self.logger: Logger = logger
|
73
74
|
|
@@ -105,16 +106,16 @@ class ScopeStore:
|
|
105
106
|
) -> None:
|
106
107
|
self.logger.emit(
|
107
108
|
LogRecord(
|
108
|
-
span_id=self.
|
109
|
-
trace_id=self.
|
110
|
-
trace_flags=self.
|
109
|
+
span_id=self._span_context.span_id,
|
110
|
+
trace_id=self._span_context.trace_id,
|
111
|
+
trace_flags=self._span_context.trace_flags,
|
111
112
|
body=message,
|
112
113
|
severity_text=level.name,
|
113
114
|
severity_number=SEVERITY_MAPPING[level],
|
114
115
|
attributes={
|
115
|
-
"trace_id": self.identifier.trace_id,
|
116
|
-
"scope_id": self.identifier.scope_id,
|
117
|
-
"parent_id": self.identifier.parent_id,
|
116
|
+
"context.trace_id": self.identifier.trace_id,
|
117
|
+
"context.scope_id": self.identifier.scope_id,
|
118
|
+
"context.parent_id": self.identifier.parent_id,
|
118
119
|
},
|
119
120
|
)
|
120
121
|
)
|
@@ -128,12 +129,18 @@ class ScopeStore:
|
|
128
129
|
|
129
130
|
def record_event(
|
130
131
|
self,
|
131
|
-
event:
|
132
|
+
event: str,
|
132
133
|
/,
|
134
|
+
*,
|
135
|
+
attributes: Mapping[str, ObservabilityAttribute],
|
133
136
|
) -> None:
|
134
137
|
self.span.add_event(
|
135
|
-
|
136
|
-
attributes=
|
138
|
+
event,
|
139
|
+
attributes={
|
140
|
+
key: cast(Any, value)
|
141
|
+
for key, value in attributes.items()
|
142
|
+
if value is not None and value is not MISSING
|
143
|
+
},
|
137
144
|
)
|
138
145
|
|
139
146
|
def record_metric(
|
@@ -143,13 +150,8 @@ class ScopeStore:
|
|
143
150
|
*,
|
144
151
|
value: float | int,
|
145
152
|
unit: str | None,
|
153
|
+
attributes: Mapping[str, ObservabilityAttribute],
|
146
154
|
) -> None:
|
147
|
-
attributes: Mapping[str, Any] = {
|
148
|
-
"trace_id": self.identifier.trace_id,
|
149
|
-
"scope_id": self.identifier.scope_id,
|
150
|
-
"parent_id": self.identifier.parent_id,
|
151
|
-
}
|
152
|
-
|
153
155
|
if name not in self._counters:
|
154
156
|
self._counters[name] = self.meter.create_counter(
|
155
157
|
name=name,
|
@@ -158,7 +160,18 @@ class ScopeStore:
|
|
158
160
|
|
159
161
|
self._counters[name].add(
|
160
162
|
value,
|
161
|
-
attributes=
|
163
|
+
attributes={
|
164
|
+
**{
|
165
|
+
"context.trace_id": self.identifier.trace_id,
|
166
|
+
"context.scope_id": self.identifier.scope_id,
|
167
|
+
"context.parent_id": self.identifier.parent_id,
|
168
|
+
},
|
169
|
+
**{
|
170
|
+
key: cast(Any, value)
|
171
|
+
for key, value in attributes.items()
|
172
|
+
if value is not None and value is not MISSING
|
173
|
+
},
|
174
|
+
},
|
162
175
|
)
|
163
176
|
|
164
177
|
def record_attribute(
|
@@ -168,10 +181,11 @@ class ScopeStore:
|
|
168
181
|
*,
|
169
182
|
value: ObservabilityAttribute,
|
170
183
|
) -> None:
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
184
|
+
if value is not None and value is not MISSING:
|
185
|
+
self.span.set_attribute(
|
186
|
+
name,
|
187
|
+
value=cast(Any, value),
|
188
|
+
)
|
175
189
|
|
176
190
|
|
177
191
|
@final
|
@@ -193,8 +207,8 @@ class OpenTelemetry:
|
|
193
207
|
{
|
194
208
|
"service.name": service,
|
195
209
|
"service.version": version,
|
196
|
-
"deployment.environment": environment,
|
197
210
|
"service.pid": os.getpid(),
|
211
|
+
"deployment.environment": environment,
|
198
212
|
**(attributes if attributes is not None else {}),
|
199
213
|
},
|
200
214
|
)
|
@@ -273,7 +287,6 @@ class OpenTelemetry:
|
|
273
287
|
message: str,
|
274
288
|
*args: Any,
|
275
289
|
exception: BaseException | None,
|
276
|
-
**extra: Any,
|
277
290
|
) -> None:
|
278
291
|
assert root_scope is not None # nosec: B101
|
279
292
|
assert scope.scope_id in scopes # nosec: B101
|
@@ -291,10 +304,10 @@ class OpenTelemetry:
|
|
291
304
|
def event_recording(
|
292
305
|
scope: ScopeIdentifier,
|
293
306
|
/,
|
294
|
-
*,
|
295
307
|
level: ObservabilityLevel,
|
296
|
-
|
297
|
-
|
308
|
+
*,
|
309
|
+
event: str,
|
310
|
+
attributes: Mapping[str, ObservabilityAttribute],
|
298
311
|
) -> None:
|
299
312
|
assert root_scope is not None # nosec: B101
|
300
313
|
assert scope.scope_id in scopes # nosec: B101
|
@@ -302,16 +315,20 @@ class OpenTelemetry:
|
|
302
315
|
if level < observed_level:
|
303
316
|
return
|
304
317
|
|
305
|
-
scopes[scope.scope_id].record_event(
|
318
|
+
scopes[scope.scope_id].record_event(
|
319
|
+
event,
|
320
|
+
attributes=attributes,
|
321
|
+
)
|
306
322
|
|
307
323
|
def metric_recording(
|
308
324
|
scope: ScopeIdentifier,
|
309
325
|
/,
|
326
|
+
level: ObservabilityLevel,
|
310
327
|
*,
|
311
328
|
metric: str,
|
312
329
|
value: float | int,
|
313
330
|
unit: str | None,
|
314
|
-
|
331
|
+
attributes: Mapping[str, ObservabilityAttribute],
|
315
332
|
) -> None:
|
316
333
|
assert root_scope is not None # nosec: B101
|
317
334
|
assert scope.scope_id in scopes # nosec: B101
|
@@ -323,13 +340,18 @@ class OpenTelemetry:
|
|
323
340
|
metric,
|
324
341
|
value=value,
|
325
342
|
unit=unit,
|
343
|
+
attributes=attributes,
|
326
344
|
)
|
327
345
|
|
328
346
|
def attributes_recording(
|
329
347
|
scope: ScopeIdentifier,
|
330
348
|
/,
|
331
|
-
|
349
|
+
level: ObservabilityLevel,
|
350
|
+
attributes: Mapping[str, ObservabilityAttribute],
|
332
351
|
) -> None:
|
352
|
+
if level < observed_level:
|
353
|
+
return
|
354
|
+
|
333
355
|
if not attributes:
|
334
356
|
return
|
335
357
|
|
@@ -363,9 +385,9 @@ class OpenTelemetry:
|
|
363
385
|
parent_id=scope.parent_id,
|
364
386
|
),
|
365
387
|
attributes={
|
366
|
-
"trace_id": scope.trace_id,
|
367
|
-
"scope_id": scope.scope_id,
|
368
|
-
"parent_id": scope.parent_id,
|
388
|
+
"context.trace_id": scope.trace_id,
|
389
|
+
"context.scope_id": scope.scope_id,
|
390
|
+
"context.parent_id": scope.parent_id,
|
369
391
|
},
|
370
392
|
),
|
371
393
|
meter=meter,
|
@@ -390,9 +412,9 @@ class OpenTelemetry:
|
|
390
412
|
),
|
391
413
|
),
|
392
414
|
attributes={
|
393
|
-
"trace_id": scope.trace_id,
|
394
|
-
"scope_id": scope.scope_id,
|
395
|
-
"parent_id": scope.parent_id,
|
415
|
+
"context.trace_id": scope.trace_id,
|
416
|
+
"context.scope_id": scope.scope_id,
|
417
|
+
"context.parent_id": scope.parent_id,
|
396
418
|
},
|
397
419
|
),
|
398
420
|
meter=meter,
|
haiway/state/structure.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import typing
|
2
|
-
from collections.abc import
|
2
|
+
from collections.abc import Mapping
|
3
3
|
from types import EllipsisType, GenericAlias
|
4
4
|
from typing import (
|
5
5
|
Any,
|
@@ -366,15 +366,8 @@ class State(metaclass=StateMeta):
|
|
366
366
|
) -> Self:
|
367
367
|
return self.__replace__(**kwargs)
|
368
368
|
|
369
|
-
def to_str(
|
370
|
-
self
|
371
|
-
pretty: bool = False,
|
372
|
-
) -> str:
|
373
|
-
if pretty:
|
374
|
-
return _state_str(self)
|
375
|
-
|
376
|
-
else:
|
377
|
-
return self.__str__()
|
369
|
+
def to_str(self) -> str:
|
370
|
+
return self.__str__()
|
378
371
|
|
379
372
|
def to_mapping(
|
380
373
|
self,
|
@@ -448,144 +441,3 @@ class State(metaclass=StateMeta):
|
|
448
441
|
**kwargs,
|
449
442
|
}
|
450
443
|
)
|
451
|
-
|
452
|
-
|
453
|
-
def _attribute_str(
|
454
|
-
*,
|
455
|
-
key: str,
|
456
|
-
value: str,
|
457
|
-
) -> str:
|
458
|
-
return f"┝ {key}: {value}"
|
459
|
-
|
460
|
-
|
461
|
-
def _element_str(
|
462
|
-
*,
|
463
|
-
key: Any,
|
464
|
-
value: Any,
|
465
|
-
) -> str:
|
466
|
-
return f"[{key}]: {value}"
|
467
|
-
|
468
|
-
|
469
|
-
def _state_str(
|
470
|
-
state: State,
|
471
|
-
/,
|
472
|
-
) -> str:
|
473
|
-
variables: ItemsView[str, Any] = vars(state).items()
|
474
|
-
|
475
|
-
parts: list[str] = [f"┍━ {type(state).__name__}:"]
|
476
|
-
for key, value in variables:
|
477
|
-
value_string: str | None = _value_str(value)
|
478
|
-
|
479
|
-
if value_string:
|
480
|
-
parts.append(
|
481
|
-
_attribute_str(
|
482
|
-
key=key,
|
483
|
-
value=value_string,
|
484
|
-
)
|
485
|
-
)
|
486
|
-
|
487
|
-
else:
|
488
|
-
continue # skip empty elements
|
489
|
-
|
490
|
-
if parts:
|
491
|
-
return "\n".join(parts) + "\n┕━"
|
492
|
-
|
493
|
-
else:
|
494
|
-
return "╍"
|
495
|
-
|
496
|
-
|
497
|
-
def _mapping_str(
|
498
|
-
dictionary: Mapping[Any, Any],
|
499
|
-
/,
|
500
|
-
) -> str | None:
|
501
|
-
elements: ItemsView[Any, Any] = dictionary.items()
|
502
|
-
|
503
|
-
parts: list[str] = []
|
504
|
-
for key, value in elements:
|
505
|
-
value_string: str | None = _value_str(value)
|
506
|
-
|
507
|
-
if value_string:
|
508
|
-
parts.append(
|
509
|
-
_element_str(
|
510
|
-
key=key,
|
511
|
-
value=value_string,
|
512
|
-
)
|
513
|
-
)
|
514
|
-
|
515
|
-
else:
|
516
|
-
continue # skip empty elements
|
517
|
-
|
518
|
-
if parts:
|
519
|
-
return "\n| " + "\n".join(parts).replace("\n", "\n| ")
|
520
|
-
|
521
|
-
else:
|
522
|
-
return None
|
523
|
-
|
524
|
-
|
525
|
-
def _sequence_str(
|
526
|
-
sequence: Sequence[Any],
|
527
|
-
/,
|
528
|
-
) -> str | None:
|
529
|
-
parts: list[str] = []
|
530
|
-
for idx, element in enumerate(sequence):
|
531
|
-
element_string: str | None = _value_str(element)
|
532
|
-
|
533
|
-
if element_string:
|
534
|
-
parts.append(
|
535
|
-
_element_str(
|
536
|
-
key=idx,
|
537
|
-
value=element_string,
|
538
|
-
)
|
539
|
-
)
|
540
|
-
|
541
|
-
else:
|
542
|
-
continue # skip empty elements
|
543
|
-
|
544
|
-
if parts:
|
545
|
-
return "\n| " + "\n".join(parts).replace("\n", "\n| ")
|
546
|
-
|
547
|
-
else:
|
548
|
-
return None
|
549
|
-
|
550
|
-
|
551
|
-
def _raw_value_str(
|
552
|
-
value: Any,
|
553
|
-
/,
|
554
|
-
) -> str | None:
|
555
|
-
if value is MISSING:
|
556
|
-
return None # skip missing
|
557
|
-
|
558
|
-
else:
|
559
|
-
return str(value).strip().replace("\n", "\n| ")
|
560
|
-
|
561
|
-
|
562
|
-
def _value_str( # noqa: PLR0911
|
563
|
-
value: Any,
|
564
|
-
/,
|
565
|
-
) -> str | None:
|
566
|
-
# check for string
|
567
|
-
if isinstance(value, str):
|
568
|
-
if "\n" in value:
|
569
|
-
return f'"""\n{value}\n"""'.replace("\n", "\n| ")
|
570
|
-
|
571
|
-
else:
|
572
|
-
return f'"{value}"'
|
573
|
-
|
574
|
-
# check for bytes
|
575
|
-
elif isinstance(value, bytes):
|
576
|
-
return f'b"{value}"'
|
577
|
-
|
578
|
-
# try unpack state
|
579
|
-
elif isinstance(value, State):
|
580
|
-
return _state_str(value)
|
581
|
-
|
582
|
-
# try unpack mapping
|
583
|
-
elif isinstance(value, Mapping):
|
584
|
-
return _mapping_str(value)
|
585
|
-
|
586
|
-
# try unpack sequence
|
587
|
-
elif isinstance(value, Sequence):
|
588
|
-
return _sequence_str(value)
|
589
|
-
|
590
|
-
else: # fallback to other
|
591
|
-
return _raw_value_str(value)
|
haiway/utils/__init__.py
CHANGED
@@ -8,6 +8,7 @@ from haiway.utils.env import (
|
|
8
8
|
getenv_str,
|
9
9
|
load_env,
|
10
10
|
)
|
11
|
+
from haiway.utils.formatting import format_str
|
11
12
|
from haiway.utils.freezing import freeze
|
12
13
|
from haiway.utils.logs import setup_logging
|
13
14
|
from haiway.utils.mimic import mimic_function
|
@@ -25,6 +26,7 @@ __all__ = (
|
|
25
26
|
"as_tuple",
|
26
27
|
"async_always",
|
27
28
|
"async_noop",
|
29
|
+
"format_str",
|
28
30
|
"freeze",
|
29
31
|
"getenv_base64",
|
30
32
|
"getenv_bool",
|
@@ -0,0 +1,148 @@
|
|
1
|
+
from collections.abc import ItemsView, Mapping, Sequence
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
from haiway.types.missing import MISSING
|
5
|
+
|
6
|
+
__all__ = ("format_str",)
|
7
|
+
|
8
|
+
|
9
|
+
def format_str( # noqa: PLR0911
|
10
|
+
value: Any,
|
11
|
+
/,
|
12
|
+
) -> str:
|
13
|
+
# check for string
|
14
|
+
if isinstance(value, str):
|
15
|
+
if "\n" in value:
|
16
|
+
return f'"""\n{value.replace("\n", "\n ")}\n"""'
|
17
|
+
|
18
|
+
else:
|
19
|
+
return f'"{value}"'
|
20
|
+
|
21
|
+
# check for bytes
|
22
|
+
elif isinstance(value, bytes):
|
23
|
+
return f"b'{value}'"
|
24
|
+
|
25
|
+
# try unpack mapping
|
26
|
+
elif isinstance(value, Mapping):
|
27
|
+
return _mapping_str(value)
|
28
|
+
|
29
|
+
# try unpack sequence
|
30
|
+
elif isinstance(value, Sequence):
|
31
|
+
return _sequence_str(value)
|
32
|
+
|
33
|
+
elif value is MISSING:
|
34
|
+
return ""
|
35
|
+
|
36
|
+
else: # fallback to object
|
37
|
+
return _object_str(value)
|
38
|
+
|
39
|
+
|
40
|
+
def _attribute_str(
|
41
|
+
*,
|
42
|
+
key: str,
|
43
|
+
value: str,
|
44
|
+
) -> str:
|
45
|
+
if "\n" in value:
|
46
|
+
formatted_value: str = value.replace("\n", "\n| ")
|
47
|
+
return f"┝ {key}:\n{formatted_value}"
|
48
|
+
|
49
|
+
else:
|
50
|
+
return f"┝ {key}: {value}"
|
51
|
+
|
52
|
+
|
53
|
+
def _element_str(
|
54
|
+
*,
|
55
|
+
key: Any,
|
56
|
+
value: Any,
|
57
|
+
) -> str:
|
58
|
+
if "\n" in value:
|
59
|
+
formatted_value: str = value.replace("\n", "\n ")
|
60
|
+
return f"[{key}]:\n{formatted_value}"
|
61
|
+
|
62
|
+
else:
|
63
|
+
return f"[{key}]: {value}"
|
64
|
+
|
65
|
+
|
66
|
+
def _object_str(
|
67
|
+
other: object,
|
68
|
+
/,
|
69
|
+
) -> str:
|
70
|
+
variables: ItemsView[str, Any] = vars(other).items()
|
71
|
+
|
72
|
+
parts: list[str] = [f"┍━ {type(other).__name__}:"]
|
73
|
+
for key, value in variables:
|
74
|
+
if key.startswith("_"):
|
75
|
+
continue # skip private and dunder
|
76
|
+
|
77
|
+
value_string: str = format_str(value)
|
78
|
+
|
79
|
+
if value_string:
|
80
|
+
parts.append(
|
81
|
+
_attribute_str(
|
82
|
+
key=key,
|
83
|
+
value=value_string,
|
84
|
+
)
|
85
|
+
)
|
86
|
+
|
87
|
+
else:
|
88
|
+
continue # skip empty elements
|
89
|
+
|
90
|
+
if parts:
|
91
|
+
return "\n".join(parts) + "\n┕━"
|
92
|
+
|
93
|
+
else:
|
94
|
+
return ""
|
95
|
+
|
96
|
+
|
97
|
+
def _mapping_str(
|
98
|
+
mapping: Mapping[Any, Any],
|
99
|
+
/,
|
100
|
+
) -> str:
|
101
|
+
items: ItemsView[Any, Any] = mapping.items()
|
102
|
+
|
103
|
+
parts: list[str] = []
|
104
|
+
for key, value in items:
|
105
|
+
value_string: str = format_str(value)
|
106
|
+
|
107
|
+
if value_string:
|
108
|
+
parts.append(
|
109
|
+
_element_str(
|
110
|
+
key=key,
|
111
|
+
value=value_string,
|
112
|
+
)
|
113
|
+
)
|
114
|
+
|
115
|
+
else:
|
116
|
+
continue # skip empty items
|
117
|
+
|
118
|
+
if parts:
|
119
|
+
return "{\n " + "\n".join(parts) + "\n}"
|
120
|
+
|
121
|
+
else:
|
122
|
+
return "{}"
|
123
|
+
|
124
|
+
|
125
|
+
def _sequence_str(
|
126
|
+
sequence: Sequence[Any],
|
127
|
+
/,
|
128
|
+
) -> str:
|
129
|
+
parts: list[str] = []
|
130
|
+
for idx, element in enumerate(sequence):
|
131
|
+
element_string: str = format_str(element)
|
132
|
+
|
133
|
+
if element_string:
|
134
|
+
parts.append(
|
135
|
+
_element_str(
|
136
|
+
key=idx,
|
137
|
+
value=element_string,
|
138
|
+
)
|
139
|
+
)
|
140
|
+
|
141
|
+
else:
|
142
|
+
continue # skip empty elements
|
143
|
+
|
144
|
+
if parts:
|
145
|
+
return "[\n " + "\n".join(parts) + "\n]"
|
146
|
+
|
147
|
+
else:
|
148
|
+
return "[]"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: haiway
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.19.0
|
4
4
|
Summary: Framework for dependency injection and state management within structured concurrency model.
|
5
5
|
Project-URL: Homepage, https://miquido.com
|
6
6
|
Project-URL: Repository, https://github.com/miquido/haiway.git
|