haiway 0.11.1__py3-none-any.whl → 0.12.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 CHANGED
@@ -48,6 +48,7 @@ from haiway.utils import (
48
48
  async_always,
49
49
  async_noop,
50
50
  freeze,
51
+ getenv_base64,
51
52
  getenv_bool,
52
53
  getenv_float,
53
54
  getenv_int,
@@ -96,6 +97,7 @@ __all__ = [
96
97
  "ctx",
97
98
  "freeze",
98
99
  "frozenlist",
100
+ "getenv_base64",
99
101
  "getenv_bool",
100
102
  "getenv_float",
101
103
  "getenv_int",
haiway/context/access.py CHANGED
@@ -23,7 +23,7 @@ from haiway.context.metrics import MetricsContext, MetricsHandler
23
23
  from haiway.context.state import StateContext
24
24
  from haiway.context.tasks import TaskGroupContext
25
25
  from haiway.state import State
26
- from haiway.utils import freeze, mimic_function
26
+ from haiway.utils import mimic_function
27
27
 
28
28
  __all__ = [
29
29
  "ctx",
@@ -32,6 +32,16 @@ __all__ = [
32
32
 
33
33
  @final
34
34
  class ScopeContext:
35
+ __slots__ = (
36
+ "_disposables",
37
+ "_identifier",
38
+ "_logger_context",
39
+ "_metrics_context",
40
+ "_state",
41
+ "_state_context",
42
+ "_task_group_context",
43
+ )
44
+
35
45
  def __init__(
36
46
  self,
37
47
  label: str,
@@ -40,29 +50,77 @@ class ScopeContext:
40
50
  disposables: Disposables | None,
41
51
  metrics: MetricsHandler | None,
42
52
  ) -> None:
43
- self._identifier: ScopeIdentifier = ScopeIdentifier.scope(label)
44
- self._logger_context: LoggerContext = LoggerContext(
45
- self._identifier,
46
- logger=logger,
53
+ self._identifier: ScopeIdentifier
54
+ object.__setattr__(
55
+ self,
56
+ "_identifier",
57
+ ScopeIdentifier.scope(label),
58
+ )
59
+ self._logger_context: LoggerContext
60
+ object.__setattr__(
61
+ self,
62
+ "_logger_context",
63
+ LoggerContext(
64
+ self._identifier,
65
+ logger=logger,
66
+ ),
47
67
  )
48
- self._task_group_context: TaskGroupContext = TaskGroupContext()
68
+ # postponing task group creation to include only when needed
69
+ self._task_group_context: TaskGroupContext
49
70
  # postponing state creation to include disposables state when prepared
50
71
  self._state_context: StateContext
51
- self._state: tuple[State, ...] = state
52
- self._disposables: Disposables | None = disposables
53
- # pre-building metrics context to ensure nested context registering
54
- self._metrics_context: MetricsContext = MetricsContext.scope(
55
- self._identifier,
56
- metrics=metrics,
72
+ self._state: tuple[State, ...]
73
+ object.__setattr__(
74
+ self,
75
+ "_state",
76
+ state,
77
+ )
78
+ self._disposables: Disposables | None
79
+ object.__setattr__(
80
+ self,
81
+ "_disposables",
82
+ disposables,
83
+ )
84
+ self._metrics_context: MetricsContext
85
+ object.__setattr__(
86
+ self,
87
+ "_metrics_context",
88
+ # pre-building metrics context to ensure nested context registering
89
+ MetricsContext.scope(
90
+ self._identifier,
91
+ metrics=metrics,
92
+ ),
57
93
  )
58
94
 
59
- freeze(self)
95
+ def __setattr__(
96
+ self,
97
+ name: str,
98
+ value: Any,
99
+ ) -> Any:
100
+ raise AttributeError(
101
+ f"Can't modify immutable {self.__class__.__qualname__},"
102
+ f" attribute - '{name}' cannot be modified"
103
+ )
104
+
105
+ def __delattr__(
106
+ self,
107
+ name: str,
108
+ ) -> None:
109
+ raise AttributeError(
110
+ f"Can't modify immutable {self.__class__.__qualname__},"
111
+ f" attribute - '{name}' cannot be deleted"
112
+ )
60
113
 
61
114
  def __enter__(self) -> str:
62
115
  assert self._disposables is None, "Can't enter synchronous context with disposables" # nosec: B101
63
116
  self._identifier.__enter__()
64
117
  self._logger_context.__enter__()
65
- self._state_context = StateContext.updated(self._state)
118
+ # lazily initialize state
119
+ object.__setattr__(
120
+ self,
121
+ "_state_context",
122
+ StateContext.updated(self._state),
123
+ )
66
124
  self._state_context.__enter__()
67
125
  self._metrics_context.__enter__()
68
126
 
@@ -101,15 +159,33 @@ class ScopeContext:
101
159
  async def __aenter__(self) -> str:
102
160
  self._identifier.__enter__()
103
161
  self._logger_context.__enter__()
162
+ # lazily initialize group when needed
163
+ object.__setattr__(
164
+ self,
165
+ "_task_group_context",
166
+ TaskGroupContext(),
167
+ )
104
168
  await self._task_group_context.__aenter__()
105
169
 
170
+ # lazily initialize state to include disposables results
106
171
  if self._disposables is not None:
107
- self._state_context = StateContext.updated(
108
- (*self._state, *await self._disposables.__aenter__())
172
+ object.__setattr__(
173
+ self,
174
+ "_state_context",
175
+ StateContext.updated(
176
+ (
177
+ *self._state,
178
+ *await self._disposables.__aenter__(),
179
+ )
180
+ ),
109
181
  )
110
182
 
111
183
  else:
112
- self._state_context = StateContext.updated(self._state)
184
+ object.__setattr__(
185
+ self,
186
+ "_state_context",
187
+ StateContext.updated(self._state),
188
+ )
113
189
 
114
190
  self._state_context.__enter__()
115
191
  self._metrics_context.__enter__()
@@ -200,6 +276,8 @@ class ScopeContext:
200
276
 
201
277
  @final
202
278
  class ctx:
279
+ __slots__ = ()
280
+
203
281
  @staticmethod
204
282
  def trace_id() -> str:
205
283
  """
@@ -3,10 +3,9 @@ from collections.abc import Iterable
3
3
  from contextlib import AbstractAsyncContextManager
4
4
  from itertools import chain
5
5
  from types import TracebackType
6
- from typing import final
6
+ from typing import Any, final
7
7
 
8
8
  from haiway.state import State
9
- from haiway.utils import freeze
10
9
 
11
10
  __all__ = [
12
11
  "Disposable",
@@ -18,13 +17,37 @@ type Disposable = AbstractAsyncContextManager[Iterable[State] | State | None]
18
17
 
19
18
  @final
20
19
  class Disposables:
20
+ __slots__ = ("_disposables",)
21
+
21
22
  def __init__(
22
23
  self,
23
24
  *disposables: Disposable,
24
25
  ) -> None:
25
- self._disposables: tuple[Disposable, ...] = disposables
26
+ self._disposables: tuple[Disposable, ...]
27
+ object.__setattr__(
28
+ self,
29
+ "_disposables",
30
+ disposables,
31
+ )
32
+
33
+ def __setattr__(
34
+ self,
35
+ name: str,
36
+ value: Any,
37
+ ) -> Any:
38
+ raise AttributeError(
39
+ f"Can't modify immutable {self.__class__.__qualname__},"
40
+ f" attribute - '{name}' cannot be modified"
41
+ )
26
42
 
27
- freeze(self)
43
+ def __delattr__(
44
+ self,
45
+ name: str,
46
+ ) -> None:
47
+ raise AttributeError(
48
+ f"Can't modify immutable {self.__class__.__qualname__},"
49
+ f" attribute - '{name}' cannot be deleted"
50
+ )
28
51
 
29
52
  def __bool__(self) -> bool:
30
53
  return len(self._disposables) > 0
@@ -48,6 +48,15 @@ class ScopeIdentifier:
48
48
  trace_id=current.trace_id,
49
49
  )
50
50
 
51
+ __slots__ = (
52
+ "_token",
53
+ "label",
54
+ "parent_id",
55
+ "scope_id",
56
+ "trace_id",
57
+ "unique_name",
58
+ )
59
+
51
60
  def __init__(
52
61
  self,
53
62
  trace_id: str,
@@ -55,11 +64,61 @@ class ScopeIdentifier:
55
64
  scope_id: str,
56
65
  label: str,
57
66
  ) -> None:
58
- self.trace_id: str = trace_id
59
- self.parent_id: str = parent_id
60
- self.scope_id: str = scope_id
61
- self.label: str = label
62
- self.unique_name: str = f"[{trace_id}] [{label}] [{scope_id}]"
67
+ self.trace_id: str
68
+ object.__setattr__(
69
+ self,
70
+ "trace_id",
71
+ trace_id,
72
+ )
73
+ self.parent_id: str
74
+ object.__setattr__(
75
+ self,
76
+ "parent_id",
77
+ parent_id,
78
+ )
79
+ self.scope_id: str
80
+ object.__setattr__(
81
+ self,
82
+ "scope_id",
83
+ scope_id,
84
+ )
85
+ self.label: str
86
+ object.__setattr__(
87
+ self,
88
+ "label",
89
+ label,
90
+ )
91
+ self.unique_name: str
92
+ object.__setattr__(
93
+ self,
94
+ "unique_name",
95
+ f"[{trace_id}] [{label}] [{scope_id}]",
96
+ )
97
+ self._token: Token[ScopeIdentifier] | None
98
+ object.__setattr__(
99
+ self,
100
+ "_token",
101
+ None,
102
+ )
103
+
104
+ def __setattr__(
105
+ self,
106
+ name: str,
107
+ value: Any,
108
+ ) -> Any:
109
+ raise AttributeError(
110
+ f"Can't modify immutable {self.__class__.__qualname__},"
111
+ f" attribute - '{name}' cannot be modified"
112
+ )
113
+
114
+ def __delattr__(
115
+ self,
116
+ name: str,
117
+ ) -> None:
118
+ raise AttributeError(
119
+ f"Can't modify immutable {self.__class__.__qualname__},"
120
+ f" attribute - '{name}' cannot be deleted"
121
+ )
63
122
 
64
123
  @property
65
124
  def is_root(self) -> bool:
@@ -78,8 +137,12 @@ class ScopeIdentifier:
78
137
  return hash(self.scope_id)
79
138
 
80
139
  def __enter__(self) -> None:
81
- assert not hasattr(self, "_token"), "Context reentrance is not allowed" # nosec: B101
82
- self._token: Token[ScopeIdentifier] = ScopeIdentifier._context.set(self)
140
+ assert self._token is None, "Context reentrance is not allowed" # nosec: B101
141
+ object.__setattr__(
142
+ self,
143
+ "_token",
144
+ ScopeIdentifier._context.set(self),
145
+ )
83
146
 
84
147
  def __exit__(
85
148
  self,
@@ -87,6 +150,10 @@ class ScopeIdentifier:
87
150
  exc_val: BaseException | None,
88
151
  exc_tb: TracebackType | None,
89
152
  ) -> None:
90
- assert hasattr(self, "_token"), "Unbalanced context enter/exit" # nosec: B101
153
+ assert self._token is not None, "Unbalanced context enter/exit" # nosec: B101
91
154
  ScopeIdentifier._context.reset(self._token)
92
- del self._token
155
+ object.__setattr__(
156
+ self,
157
+ "_token",
158
+ None,
159
+ )
haiway/context/logging.py CHANGED
@@ -133,13 +133,61 @@ class LoggerContext:
133
133
  exc_info=exception,
134
134
  )
135
135
 
136
+ __slots__ = (
137
+ "_entered",
138
+ "_logger",
139
+ "_prefix",
140
+ "_token",
141
+ )
142
+
136
143
  def __init__(
137
144
  self,
138
145
  scope: ScopeIdentifier,
139
146
  logger: Logger | None,
140
147
  ) -> None:
141
- self._prefix: str = scope.unique_name
142
- self._logger: Logger = logger or getLogger(name=scope.label)
148
+ self._prefix: str
149
+ object.__setattr__(
150
+ self,
151
+ "_prefix",
152
+ scope.unique_name,
153
+ )
154
+ self._logger: Logger
155
+ object.__setattr__(
156
+ self,
157
+ "_logger",
158
+ logger or getLogger(name=scope.label),
159
+ )
160
+ self._token: Token[LoggerContext] | None
161
+ object.__setattr__(
162
+ self,
163
+ "_token",
164
+ None,
165
+ )
166
+ self._entered: float | None
167
+ object.__setattr__(
168
+ self,
169
+ "_entered",
170
+ None,
171
+ )
172
+
173
+ def __setattr__(
174
+ self,
175
+ name: str,
176
+ value: Any,
177
+ ) -> Any:
178
+ raise AttributeError(
179
+ f"Can't modify immutable {self.__class__.__qualname__},"
180
+ f" attribute - '{name}' cannot be modified"
181
+ )
182
+
183
+ def __delattr__(
184
+ self,
185
+ name: str,
186
+ ) -> None:
187
+ raise AttributeError(
188
+ f"Can't modify immutable {self.__class__.__qualname__},"
189
+ f" attribute - '{name}' cannot be deleted"
190
+ )
143
191
 
144
192
  def log(
145
193
  self,
@@ -157,10 +205,18 @@ class LoggerContext:
157
205
  )
158
206
 
159
207
  def __enter__(self) -> None:
160
- assert not hasattr(self, "_token"), "Context reentrance is not allowed" # nosec: B101
161
- assert not hasattr(self, "_entered"), "Context reentrance is not allowed" # nosec: B101
162
- self._entered: float = monotonic()
163
- self._token: Token[LoggerContext] = LoggerContext._context.set(self)
208
+ assert self._token is None, "Context reentrance is not allowed" # nosec: B101
209
+ assert self._entered is None, "Context reentrance is not allowed" # nosec: B101
210
+ object.__setattr__(
211
+ self,
212
+ "_token",
213
+ LoggerContext._context.set(self),
214
+ )
215
+ object.__setattr__(
216
+ self,
217
+ "_entered",
218
+ monotonic(),
219
+ )
164
220
  self.log(DEBUG, "Entering context...")
165
221
 
166
222
  def __exit__(
@@ -169,8 +225,18 @@ class LoggerContext:
169
225
  exc_val: BaseException | None,
170
226
  exc_tb: TracebackType | None,
171
227
  ) -> None:
172
- assert hasattr(self, "_token"), "Unbalanced context enter/exit" # nosec: B101
228
+ assert ( # nosec: B101
229
+ self._token is not None and self._entered is not None
230
+ ), "Unbalanced context enter/exit"
173
231
  LoggerContext._context.reset(self._token)
174
- del self._token
232
+ object.__setattr__(
233
+ self,
234
+ "_token",
235
+ None,
236
+ )
175
237
  self.log(DEBUG, f"...exiting context after {monotonic() - self._entered:.2f}s")
176
- del self._entered
238
+ object.__setattr__(
239
+ self,
240
+ "_entered",
241
+ None,
242
+ )
haiway/context/metrics.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from contextvars import ContextVar, Token
2
2
  from types import TracebackType
3
- from typing import Protocol, Self, final, runtime_checkable
3
+ from typing import Any, Protocol, Self, final, runtime_checkable
4
4
 
5
5
  from haiway.context.identifier import ScopeIdentifier
6
6
  from haiway.context.logging import LoggerContext
@@ -138,17 +138,62 @@ class MetricsContext:
138
138
  exception=exc,
139
139
  )
140
140
 
141
+ __slots__ = (
142
+ "_metrics",
143
+ "_scope",
144
+ "_token",
145
+ )
146
+
141
147
  def __init__(
142
148
  self,
143
149
  scope: ScopeIdentifier,
144
150
  metrics: MetricsHandler | None,
145
151
  ) -> None:
146
- self._scope: ScopeIdentifier = scope
147
- self._metrics: MetricsHandler | None = metrics
152
+ self._scope: ScopeIdentifier
153
+ object.__setattr__(
154
+ self,
155
+ "_scope",
156
+ scope,
157
+ )
158
+ self._metrics: MetricsHandler | None
159
+ object.__setattr__(
160
+ self,
161
+ "_metrics",
162
+ metrics,
163
+ )
164
+ self._token: Token[MetricsContext] | None
165
+ object.__setattr__(
166
+ self,
167
+ "_token",
168
+ None,
169
+ )
170
+
171
+ def __setattr__(
172
+ self,
173
+ name: str,
174
+ value: Any,
175
+ ) -> Any:
176
+ raise AttributeError(
177
+ f"Can't modify immutable {self.__class__.__qualname__},"
178
+ f" attribute - '{name}' cannot be modified"
179
+ )
180
+
181
+ def __delattr__(
182
+ self,
183
+ name: str,
184
+ ) -> None:
185
+ raise AttributeError(
186
+ f"Can't modify immutable {self.__class__.__qualname__},"
187
+ f" attribute - '{name}' cannot be deleted"
188
+ )
148
189
 
149
190
  def __enter__(self) -> None:
150
- assert not hasattr(self, "_token"), "Context reentrance is not allowed" # nosec: B101
151
- self._token: Token[MetricsContext] = MetricsContext._context.set(self)
191
+ assert self._token is None, "Context reentrance is not allowed" # nosec: B101
192
+ object.__setattr__(
193
+ self,
194
+ "_token",
195
+ MetricsContext._context.set(self),
196
+ )
152
197
  if self._metrics is not None:
153
198
  self._metrics.enter_scope(self._scope)
154
199
 
@@ -158,8 +203,12 @@ class MetricsContext:
158
203
  exc_val: BaseException | None,
159
204
  exc_tb: TracebackType | None,
160
205
  ) -> None:
161
- assert hasattr(self, "_token"), "Unbalanced context enter/exit" # nosec: B101
206
+ assert self._token is not None, "Unbalanced context enter/exit" # nosec: B101
162
207
  MetricsContext._context.reset(self._token)
163
- del self._token
208
+ object.__setattr__(
209
+ self,
210
+ "_token",
211
+ None,
212
+ )
164
213
  if self._metrics is not None:
165
214
  self._metrics.exit_scope(self._scope)
haiway/context/state.py CHANGED
@@ -1,11 +1,10 @@
1
- from collections.abc import Iterable
1
+ from collections.abc import Iterable, MutableMapping
2
2
  from contextvars import ContextVar, Token
3
3
  from types import TracebackType
4
- from typing import Self, cast, final
4
+ from typing import Any, Self, cast, final
5
5
 
6
6
  from haiway.context.types import MissingContext, MissingState
7
7
  from haiway.state import State
8
- from haiway.utils import freeze
9
8
 
10
9
  __all__ = [
11
10
  "ScopeState",
@@ -15,12 +14,37 @@ __all__ = [
15
14
 
16
15
  @final
17
16
  class ScopeState:
17
+ __slots__ = ("_state",)
18
+
18
19
  def __init__(
19
20
  self,
20
21
  state: Iterable[State],
21
22
  ) -> None:
22
- self._state: dict[type[State], State] = {type(element): element for element in state}
23
- freeze(self)
23
+ self._state: MutableMapping[type[State], State]
24
+ object.__setattr__(
25
+ self,
26
+ "_state",
27
+ {type(element): element for element in state},
28
+ )
29
+
30
+ def __setattr__(
31
+ self,
32
+ name: str,
33
+ value: Any,
34
+ ) -> Any:
35
+ raise AttributeError(
36
+ f"Can't modify immutable {self.__class__.__qualname__},"
37
+ f" attribute - '{name}' cannot be modified"
38
+ )
39
+
40
+ def __delattr__(
41
+ self,
42
+ name: str,
43
+ ) -> None:
44
+ raise AttributeError(
45
+ f"Can't modify immutable {self.__class__.__qualname__},"
46
+ f" attribute - '{name}' cannot be deleted"
47
+ )
24
48
 
25
49
  def state[StateType: State](
26
50
  self,
@@ -37,6 +61,7 @@ class ScopeState:
37
61
  else:
38
62
  try:
39
63
  initialized: StateType = state()
64
+ # we would need a locking here in multithreaded environment
40
65
  self._state[state] = initialized
41
66
  return initialized
42
67
 
@@ -66,6 +91,11 @@ class ScopeState:
66
91
  class StateContext:
67
92
  _context = ContextVar[ScopeState]("StateContext")
68
93
 
94
+ __slots__ = (
95
+ "_state",
96
+ "_token",
97
+ )
98
+
69
99
  @classmethod
70
100
  def current[StateType: State](
71
101
  cls,
@@ -96,11 +126,45 @@ class StateContext:
96
126
  self,
97
127
  state: ScopeState,
98
128
  ) -> None:
99
- self._state: ScopeState = state
129
+ self._state: ScopeState
130
+ object.__setattr__(
131
+ self,
132
+ "_state",
133
+ state,
134
+ )
135
+ self._token: Token[ScopeState] | None
136
+ object.__setattr__(
137
+ self,
138
+ "_token",
139
+ None,
140
+ )
141
+
142
+ def __setattr__(
143
+ self,
144
+ name: str,
145
+ value: Any,
146
+ ) -> Any:
147
+ raise AttributeError(
148
+ f"Can't modify immutable {self.__class__.__qualname__},"
149
+ f" attribute - '{name}' cannot be modified"
150
+ )
151
+
152
+ def __delattr__(
153
+ self,
154
+ name: str,
155
+ ) -> None:
156
+ raise AttributeError(
157
+ f"Can't modify immutable {self.__class__.__qualname__},"
158
+ f" attribute - '{name}' cannot be deleted"
159
+ )
100
160
 
101
161
  def __enter__(self) -> None:
102
- assert not hasattr(self, "_token"), "Context reentrance is not allowed" # nosec: B101
103
- self._token: Token[ScopeState] = StateContext._context.set(self._state)
162
+ assert self._token is None, "Context reentrance is not allowed" # nosec: B101
163
+ object.__setattr__(
164
+ self,
165
+ "_token",
166
+ StateContext._context.set(self._state),
167
+ )
104
168
 
105
169
  def __exit__(
106
170
  self,
@@ -108,6 +172,10 @@ class StateContext:
108
172
  exc_val: BaseException | None,
109
173
  exc_tb: TracebackType | None,
110
174
  ) -> None:
111
- assert hasattr(self, "_token"), "Unbalanced context enter/exit" # nosec: B101
175
+ assert self._token is not None, "Unbalanced context enter/exit" # nosec: B101
112
176
  StateContext._context.reset(self._token)
113
- del self._token
177
+ object.__setattr__(
178
+ self,
179
+ "_token",
180
+ None,
181
+ )