vmx 2.6.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.
- vmx/__about__.py +10 -0
- vmx/__init__.py +289 -0
- vmx/aggregates/__init__.py +60 -0
- vmx/aggregates/aggregate_vm.py +653 -0
- vmx/aggregates/builders.py +451 -0
- vmx/builders/__init__.py +14 -0
- vmx/builders/_validation.py +31 -0
- vmx/builders/exceptions.py +20 -0
- vmx/capabilities/__init__.py +56 -0
- vmx/capabilities/crud.py +40 -0
- vmx/capabilities/current_crud.py +21 -0
- vmx/capabilities/dialog.py +29 -0
- vmx/capabilities/expandable_state.py +68 -0
- vmx/capabilities/expansion.py +33 -0
- vmx/capabilities/filter.py +28 -0
- vmx/capabilities/lifecycle_capabilities.py +32 -0
- vmx/capabilities/management.py +16 -0
- vmx/capabilities/pageable.py +103 -0
- vmx/capabilities/search.py +21 -0
- vmx/capabilities/searchable_state.py +103 -0
- vmx/capabilities/selection.py +29 -0
- vmx/collections/__init__.py +34 -0
- vmx/collections/batch.py +45 -0
- vmx/collections/collection_changed.py +21 -0
- vmx/collections/observable_dictionary.py +288 -0
- vmx/collections/observable_list.py +225 -0
- vmx/collections/paged_composition.py +240 -0
- vmx/collections/serviced_observable_collection.py +136 -0
- vmx/commands/__init__.py +48 -0
- vmx/commands/composite_command.py +55 -0
- vmx/commands/confirmation_decorator_command.py +62 -0
- vmx/commands/decorator_command.py +67 -0
- vmx/commands/fluent.py +109 -0
- vmx/commands/modeled_crud_commands.py +88 -0
- vmx/commands/protocols.py +57 -0
- vmx/commands/relay_command.py +278 -0
- vmx/components/__init__.py +44 -0
- vmx/components/base.py +457 -0
- vmx/components/builders.py +346 -0
- vmx/components/component_vm.py +156 -0
- vmx/components/protocols.py +136 -0
- vmx/components/readonly_component_vm.py +77 -0
- vmx/composites/__init__.py +29 -0
- vmx/composites/builders.py +265 -0
- vmx/composites/composite_vm.py +517 -0
- vmx/composites/protocols.py +58 -0
- vmx/dialogs/__init__.py +18 -0
- vmx/dialogs/dialog_service.py +96 -0
- vmx/dialogs/null_dialog_service.py +56 -0
- vmx/forms/__init__.py +15 -0
- vmx/forms/builders.py +107 -0
- vmx/forms/form_vm.py +229 -0
- vmx/forwarding/__init__.py +14 -0
- vmx/forwarding/component.py +153 -0
- vmx/forwarding/composite.py +214 -0
- vmx/groups/__init__.py +20 -0
- vmx/groups/builders.py +118 -0
- vmx/groups/group_vm.py +305 -0
- vmx/hierarchical/__init__.py +11 -0
- vmx/hierarchical/builders.py +157 -0
- vmx/hierarchical/hierarchical_vm.py +298 -0
- vmx/lifecycle/__init__.py +24 -0
- vmx/lifecycle/_data/__init__.py +1 -0
- vmx/lifecycle/_data/lifecycle-transitions.json +37 -0
- vmx/lifecycle/exceptions.py +27 -0
- vmx/lifecycle/status.py +31 -0
- vmx/lifecycle/transition_validator.py +116 -0
- vmx/localization/__init__.py +12 -0
- vmx/localization/localizer.py +15 -0
- vmx/localization/null_localizer.py +16 -0
- vmx/messages/__init__.py +44 -0
- vmx/messages/collection_changed.py +111 -0
- vmx/messages/construction_status_changed.py +44 -0
- vmx/messages/form_reverted.py +34 -0
- vmx/messages/property_changed.py +45 -0
- vmx/messages/property_value_changed.py +60 -0
- vmx/messages/protocols.py +55 -0
- vmx/messages/tree_structure_changed.py +68 -0
- vmx/notifications/__init__.py +33 -0
- vmx/notifications/confirm_helper.py +25 -0
- vmx/notifications/confirmation_vm.py +92 -0
- vmx/notifications/notification.py +23 -0
- vmx/notifications/notification_hub.py +137 -0
- vmx/notifications/notification_reaction.py +13 -0
- vmx/notifications/notification_type.py +13 -0
- vmx/notifications/notification_vm.py +186 -0
- vmx/notifications/null_notification_hub.py +40 -0
- vmx/properties/__init__.py +25 -0
- vmx/properties/derived.py +222 -0
- vmx/py.typed +0 -0
- vmx/services/__init__.py +29 -0
- vmx/services/dispatcher.py +83 -0
- vmx/services/message_hub.py +82 -0
- vmx/services/null_dispatcher.py +32 -0
- vmx/services/null_message_hub.py +77 -0
- vmx/tree/__init__.py +10 -0
- vmx/tree/walk.py +65 -0
- vmx-2.6.0.dist-info/METADATA +271 -0
- vmx-2.6.0.dist-info/RECORD +100 -0
- vmx-2.6.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
"""AggregateVM1 through AggregateVM6 — fixed-arity tuples of heterogeneous component VMs.
|
|
2
|
+
|
|
3
|
+
Each AggregateVMN holds N component slots populated lazily by factories at construct time.
|
|
4
|
+
See spec/08-aggregate-vm.md and ADR-0007 (arity 6 added per ADR-0034).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from typing import Generic, TypeVar
|
|
11
|
+
|
|
12
|
+
from vmx.components.base import _ComponentVMBase
|
|
13
|
+
from vmx.components.protocols import ComponentVMProto, ViewModelType
|
|
14
|
+
from vmx.messages.property_changed import PropertyChangedMessage
|
|
15
|
+
from vmx.messages.protocols import Message
|
|
16
|
+
from vmx.services.dispatcher import Dispatcher
|
|
17
|
+
from vmx.services.message_hub import MessageHub
|
|
18
|
+
|
|
19
|
+
V1 = TypeVar("V1", bound=ComponentVMProto)
|
|
20
|
+
V2 = TypeVar("V2", bound=ComponentVMProto)
|
|
21
|
+
V3 = TypeVar("V3", bound=ComponentVMProto)
|
|
22
|
+
V4 = TypeVar("V4", bound=ComponentVMProto)
|
|
23
|
+
V5 = TypeVar("V5", bound=ComponentVMProto)
|
|
24
|
+
V6 = TypeVar("V6", bound=ComponentVMProto)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# AggregateVM1
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AggregateVM1(Generic[V1], _ComponentVMBase):
|
|
33
|
+
"""Arity-1 aggregate viewmodel. A fixed tuple of one heterogeneous component VM.
|
|
34
|
+
|
|
35
|
+
The component slot is populated lazily on construct() by invoking the factory
|
|
36
|
+
provided via the builder.
|
|
37
|
+
|
|
38
|
+
See spec/08-aggregate-vm.md and ADR-0007.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
*,
|
|
44
|
+
name: str,
|
|
45
|
+
hint: str,
|
|
46
|
+
hub: MessageHub[Message],
|
|
47
|
+
dispatcher: Dispatcher,
|
|
48
|
+
factory1: Callable[[], V1],
|
|
49
|
+
) -> None:
|
|
50
|
+
super().__init__(
|
|
51
|
+
name=name,
|
|
52
|
+
hint=hint,
|
|
53
|
+
hub=hub,
|
|
54
|
+
dispatcher=dispatcher,
|
|
55
|
+
)
|
|
56
|
+
self._factory1: Callable[[], V1] = factory1
|
|
57
|
+
self._component1: V1 | None = None
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def type(self) -> ViewModelType:
|
|
61
|
+
return ViewModelType.AGGREGATE
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def component_1(self) -> V1 | None:
|
|
65
|
+
"""The first (and only) component slot; None until construct() is called."""
|
|
66
|
+
return self._component1
|
|
67
|
+
|
|
68
|
+
def _on_construct(self) -> None:
|
|
69
|
+
# On Reconstruct, the previous slot instance is in Destructed state but
|
|
70
|
+
# still holds hub subscriptions and command Subjects. Dispose it before
|
|
71
|
+
# overwriting so subscribers don't leak across the Reconstruct boundary.
|
|
72
|
+
if self._component1 is not None:
|
|
73
|
+
self._component1.dispose()
|
|
74
|
+
self._component1 = self._factory1()
|
|
75
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_1"))
|
|
76
|
+
self._raise_property_changed("component_1")
|
|
77
|
+
self._component1.construct()
|
|
78
|
+
|
|
79
|
+
def _on_destruct(self) -> None:
|
|
80
|
+
if self._component1 is not None:
|
|
81
|
+
self._component1.destruct()
|
|
82
|
+
|
|
83
|
+
def dispose(self) -> None:
|
|
84
|
+
"""Depth-first dispose (LIFE-013): the component slot first, then self.
|
|
85
|
+
Mirrors C# / TS / Swift AggregateVM1.Dispose so subscribers observe child
|
|
86
|
+
Disposed transitions before the aggregate's own Disposed transition — a
|
|
87
|
+
single dispose-ordering rule across all aggregate arities."""
|
|
88
|
+
if self._component1 is not None:
|
|
89
|
+
self._component1.dispose()
|
|
90
|
+
super().dispose()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
# AggregateVM2
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class AggregateVM2(Generic[V1, V2], _ComponentVMBase):
|
|
99
|
+
"""Arity-2 aggregate viewmodel. A fixed tuple of two heterogeneous component VMs."""
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
*,
|
|
104
|
+
name: str,
|
|
105
|
+
hint: str,
|
|
106
|
+
hub: MessageHub[Message],
|
|
107
|
+
dispatcher: Dispatcher,
|
|
108
|
+
factory1: Callable[[], V1],
|
|
109
|
+
factory2: Callable[[], V2],
|
|
110
|
+
) -> None:
|
|
111
|
+
super().__init__(
|
|
112
|
+
name=name,
|
|
113
|
+
hint=hint,
|
|
114
|
+
hub=hub,
|
|
115
|
+
dispatcher=dispatcher,
|
|
116
|
+
)
|
|
117
|
+
self._factory1: Callable[[], V1] = factory1
|
|
118
|
+
self._factory2: Callable[[], V2] = factory2
|
|
119
|
+
self._component1: V1 | None = None
|
|
120
|
+
self._component2: V2 | None = None
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def type(self) -> ViewModelType:
|
|
124
|
+
return ViewModelType.AGGREGATE
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def component_1(self) -> V1 | None:
|
|
128
|
+
return self._component1
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def component_2(self) -> V2 | None:
|
|
132
|
+
return self._component2
|
|
133
|
+
|
|
134
|
+
def _on_construct(self) -> None:
|
|
135
|
+
# On Reconstruct, dispose previous slot instances before overwriting
|
|
136
|
+
# so their hub subscriptions and command Subjects don't leak.
|
|
137
|
+
if self._component1 is not None:
|
|
138
|
+
self._component1.dispose()
|
|
139
|
+
if self._component2 is not None:
|
|
140
|
+
self._component2.dispose()
|
|
141
|
+
|
|
142
|
+
self._component1 = self._factory1()
|
|
143
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_1"))
|
|
144
|
+
self._raise_property_changed("component_1")
|
|
145
|
+
|
|
146
|
+
self._component2 = self._factory2()
|
|
147
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_2"))
|
|
148
|
+
self._raise_property_changed("component_2")
|
|
149
|
+
|
|
150
|
+
self._component1.construct()
|
|
151
|
+
self._component2.construct()
|
|
152
|
+
|
|
153
|
+
def _on_destruct(self) -> None:
|
|
154
|
+
if self._component1 is not None:
|
|
155
|
+
self._component1.destruct()
|
|
156
|
+
if self._component2 is not None:
|
|
157
|
+
self._component2.destruct()
|
|
158
|
+
|
|
159
|
+
def dispose(self) -> None:
|
|
160
|
+
"""Depth-first dispose (LIFE-013): each component slot first, then self.
|
|
161
|
+
See AggregateVM1.dispose for the cross-flavor ordering rationale."""
|
|
162
|
+
if self._component1 is not None:
|
|
163
|
+
self._component1.dispose()
|
|
164
|
+
if self._component2 is not None:
|
|
165
|
+
self._component2.dispose()
|
|
166
|
+
super().dispose()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ---------------------------------------------------------------------------
|
|
170
|
+
# AggregateVM3
|
|
171
|
+
# ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class AggregateVM3(Generic[V1, V2, V3], _ComponentVMBase):
|
|
175
|
+
"""Arity-3 aggregate viewmodel. A fixed tuple of three heterogeneous component VMs."""
|
|
176
|
+
|
|
177
|
+
def __init__(
|
|
178
|
+
self,
|
|
179
|
+
*,
|
|
180
|
+
name: str,
|
|
181
|
+
hint: str,
|
|
182
|
+
hub: MessageHub[Message],
|
|
183
|
+
dispatcher: Dispatcher,
|
|
184
|
+
factory1: Callable[[], V1],
|
|
185
|
+
factory2: Callable[[], V2],
|
|
186
|
+
factory3: Callable[[], V3],
|
|
187
|
+
) -> None:
|
|
188
|
+
super().__init__(
|
|
189
|
+
name=name,
|
|
190
|
+
hint=hint,
|
|
191
|
+
hub=hub,
|
|
192
|
+
dispatcher=dispatcher,
|
|
193
|
+
)
|
|
194
|
+
self._factory1: Callable[[], V1] = factory1
|
|
195
|
+
self._factory2: Callable[[], V2] = factory2
|
|
196
|
+
self._factory3: Callable[[], V3] = factory3
|
|
197
|
+
self._component1: V1 | None = None
|
|
198
|
+
self._component2: V2 | None = None
|
|
199
|
+
self._component3: V3 | None = None
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def type(self) -> ViewModelType:
|
|
203
|
+
return ViewModelType.AGGREGATE
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def component_1(self) -> V1 | None:
|
|
207
|
+
return self._component1
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def component_2(self) -> V2 | None:
|
|
211
|
+
return self._component2
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def component_3(self) -> V3 | None:
|
|
215
|
+
return self._component3
|
|
216
|
+
|
|
217
|
+
def _on_construct(self) -> None:
|
|
218
|
+
# On Reconstruct, dispose previous slot instances before overwriting
|
|
219
|
+
# so their hub subscriptions and command Subjects don't leak.
|
|
220
|
+
if self._component1 is not None:
|
|
221
|
+
self._component1.dispose()
|
|
222
|
+
if self._component2 is not None:
|
|
223
|
+
self._component2.dispose()
|
|
224
|
+
if self._component3 is not None:
|
|
225
|
+
self._component3.dispose()
|
|
226
|
+
|
|
227
|
+
self._component1 = self._factory1()
|
|
228
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_1"))
|
|
229
|
+
self._raise_property_changed("component_1")
|
|
230
|
+
|
|
231
|
+
self._component2 = self._factory2()
|
|
232
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_2"))
|
|
233
|
+
self._raise_property_changed("component_2")
|
|
234
|
+
|
|
235
|
+
self._component3 = self._factory3()
|
|
236
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_3"))
|
|
237
|
+
self._raise_property_changed("component_3")
|
|
238
|
+
|
|
239
|
+
self._component1.construct()
|
|
240
|
+
self._component2.construct()
|
|
241
|
+
self._component3.construct()
|
|
242
|
+
|
|
243
|
+
def _on_destruct(self) -> None:
|
|
244
|
+
if self._component1 is not None:
|
|
245
|
+
self._component1.destruct()
|
|
246
|
+
if self._component2 is not None:
|
|
247
|
+
self._component2.destruct()
|
|
248
|
+
if self._component3 is not None:
|
|
249
|
+
self._component3.destruct()
|
|
250
|
+
|
|
251
|
+
def dispose(self) -> None:
|
|
252
|
+
"""Depth-first dispose (LIFE-013): each component slot first, then self.
|
|
253
|
+
See AggregateVM1.dispose for the cross-flavor ordering rationale."""
|
|
254
|
+
if self._component1 is not None:
|
|
255
|
+
self._component1.dispose()
|
|
256
|
+
if self._component2 is not None:
|
|
257
|
+
self._component2.dispose()
|
|
258
|
+
if self._component3 is not None:
|
|
259
|
+
self._component3.dispose()
|
|
260
|
+
super().dispose()
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
# ---------------------------------------------------------------------------
|
|
264
|
+
# AggregateVM4
|
|
265
|
+
# ---------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class AggregateVM4(Generic[V1, V2, V3, V4], _ComponentVMBase):
|
|
269
|
+
"""Arity-4 aggregate viewmodel. A fixed tuple of four heterogeneous component VMs."""
|
|
270
|
+
|
|
271
|
+
def __init__(
|
|
272
|
+
self,
|
|
273
|
+
*,
|
|
274
|
+
name: str,
|
|
275
|
+
hint: str,
|
|
276
|
+
hub: MessageHub[Message],
|
|
277
|
+
dispatcher: Dispatcher,
|
|
278
|
+
factory1: Callable[[], V1],
|
|
279
|
+
factory2: Callable[[], V2],
|
|
280
|
+
factory3: Callable[[], V3],
|
|
281
|
+
factory4: Callable[[], V4],
|
|
282
|
+
) -> None:
|
|
283
|
+
super().__init__(
|
|
284
|
+
name=name,
|
|
285
|
+
hint=hint,
|
|
286
|
+
hub=hub,
|
|
287
|
+
dispatcher=dispatcher,
|
|
288
|
+
)
|
|
289
|
+
self._factory1: Callable[[], V1] = factory1
|
|
290
|
+
self._factory2: Callable[[], V2] = factory2
|
|
291
|
+
self._factory3: Callable[[], V3] = factory3
|
|
292
|
+
self._factory4: Callable[[], V4] = factory4
|
|
293
|
+
self._component1: V1 | None = None
|
|
294
|
+
self._component2: V2 | None = None
|
|
295
|
+
self._component3: V3 | None = None
|
|
296
|
+
self._component4: V4 | None = None
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def type(self) -> ViewModelType:
|
|
300
|
+
return ViewModelType.AGGREGATE
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def component_1(self) -> V1 | None:
|
|
304
|
+
return self._component1
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def component_2(self) -> V2 | None:
|
|
308
|
+
return self._component2
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def component_3(self) -> V3 | None:
|
|
312
|
+
return self._component3
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
def component_4(self) -> V4 | None:
|
|
316
|
+
return self._component4
|
|
317
|
+
|
|
318
|
+
def _on_construct(self) -> None:
|
|
319
|
+
# On Reconstruct, dispose previous slot instances before overwriting
|
|
320
|
+
# so their hub subscriptions and command Subjects don't leak.
|
|
321
|
+
if self._component1 is not None:
|
|
322
|
+
self._component1.dispose()
|
|
323
|
+
if self._component2 is not None:
|
|
324
|
+
self._component2.dispose()
|
|
325
|
+
if self._component3 is not None:
|
|
326
|
+
self._component3.dispose()
|
|
327
|
+
if self._component4 is not None:
|
|
328
|
+
self._component4.dispose()
|
|
329
|
+
|
|
330
|
+
self._component1 = self._factory1()
|
|
331
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_1"))
|
|
332
|
+
self._raise_property_changed("component_1")
|
|
333
|
+
|
|
334
|
+
self._component2 = self._factory2()
|
|
335
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_2"))
|
|
336
|
+
self._raise_property_changed("component_2")
|
|
337
|
+
|
|
338
|
+
self._component3 = self._factory3()
|
|
339
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_3"))
|
|
340
|
+
self._raise_property_changed("component_3")
|
|
341
|
+
|
|
342
|
+
self._component4 = self._factory4()
|
|
343
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_4"))
|
|
344
|
+
self._raise_property_changed("component_4")
|
|
345
|
+
|
|
346
|
+
self._component1.construct()
|
|
347
|
+
self._component2.construct()
|
|
348
|
+
self._component3.construct()
|
|
349
|
+
self._component4.construct()
|
|
350
|
+
|
|
351
|
+
def _on_destruct(self) -> None:
|
|
352
|
+
if self._component1 is not None:
|
|
353
|
+
self._component1.destruct()
|
|
354
|
+
if self._component2 is not None:
|
|
355
|
+
self._component2.destruct()
|
|
356
|
+
if self._component3 is not None:
|
|
357
|
+
self._component3.destruct()
|
|
358
|
+
if self._component4 is not None:
|
|
359
|
+
self._component4.destruct()
|
|
360
|
+
|
|
361
|
+
def dispose(self) -> None:
|
|
362
|
+
"""Depth-first dispose (LIFE-013): each component slot first, then self.
|
|
363
|
+
See AggregateVM1.dispose for the cross-flavor ordering rationale."""
|
|
364
|
+
if self._component1 is not None:
|
|
365
|
+
self._component1.dispose()
|
|
366
|
+
if self._component2 is not None:
|
|
367
|
+
self._component2.dispose()
|
|
368
|
+
if self._component3 is not None:
|
|
369
|
+
self._component3.dispose()
|
|
370
|
+
if self._component4 is not None:
|
|
371
|
+
self._component4.dispose()
|
|
372
|
+
super().dispose()
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
# ---------------------------------------------------------------------------
|
|
376
|
+
# AggregateVM5
|
|
377
|
+
# ---------------------------------------------------------------------------
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class AggregateVM5(Generic[V1, V2, V3, V4, V5], _ComponentVMBase):
|
|
381
|
+
"""Arity-5 aggregate viewmodel. A fixed tuple of five heterogeneous component VMs."""
|
|
382
|
+
|
|
383
|
+
def __init__(
|
|
384
|
+
self,
|
|
385
|
+
*,
|
|
386
|
+
name: str,
|
|
387
|
+
hint: str,
|
|
388
|
+
hub: MessageHub[Message],
|
|
389
|
+
dispatcher: Dispatcher,
|
|
390
|
+
factory1: Callable[[], V1],
|
|
391
|
+
factory2: Callable[[], V2],
|
|
392
|
+
factory3: Callable[[], V3],
|
|
393
|
+
factory4: Callable[[], V4],
|
|
394
|
+
factory5: Callable[[], V5],
|
|
395
|
+
) -> None:
|
|
396
|
+
super().__init__(
|
|
397
|
+
name=name,
|
|
398
|
+
hint=hint,
|
|
399
|
+
hub=hub,
|
|
400
|
+
dispatcher=dispatcher,
|
|
401
|
+
)
|
|
402
|
+
self._factory1: Callable[[], V1] = factory1
|
|
403
|
+
self._factory2: Callable[[], V2] = factory2
|
|
404
|
+
self._factory3: Callable[[], V3] = factory3
|
|
405
|
+
self._factory4: Callable[[], V4] = factory4
|
|
406
|
+
self._factory5: Callable[[], V5] = factory5
|
|
407
|
+
self._component1: V1 | None = None
|
|
408
|
+
self._component2: V2 | None = None
|
|
409
|
+
self._component3: V3 | None = None
|
|
410
|
+
self._component4: V4 | None = None
|
|
411
|
+
self._component5: V5 | None = None
|
|
412
|
+
|
|
413
|
+
@property
|
|
414
|
+
def type(self) -> ViewModelType:
|
|
415
|
+
return ViewModelType.AGGREGATE
|
|
416
|
+
|
|
417
|
+
@property
|
|
418
|
+
def component_1(self) -> V1 | None:
|
|
419
|
+
return self._component1
|
|
420
|
+
|
|
421
|
+
@property
|
|
422
|
+
def component_2(self) -> V2 | None:
|
|
423
|
+
return self._component2
|
|
424
|
+
|
|
425
|
+
@property
|
|
426
|
+
def component_3(self) -> V3 | None:
|
|
427
|
+
return self._component3
|
|
428
|
+
|
|
429
|
+
@property
|
|
430
|
+
def component_4(self) -> V4 | None:
|
|
431
|
+
return self._component4
|
|
432
|
+
|
|
433
|
+
@property
|
|
434
|
+
def component_5(self) -> V5 | None:
|
|
435
|
+
return self._component5
|
|
436
|
+
|
|
437
|
+
def _on_construct(self) -> None:
|
|
438
|
+
# On Reconstruct, dispose previous slot instances before overwriting
|
|
439
|
+
# so their hub subscriptions and command Subjects don't leak.
|
|
440
|
+
if self._component1 is not None:
|
|
441
|
+
self._component1.dispose()
|
|
442
|
+
if self._component2 is not None:
|
|
443
|
+
self._component2.dispose()
|
|
444
|
+
if self._component3 is not None:
|
|
445
|
+
self._component3.dispose()
|
|
446
|
+
if self._component4 is not None:
|
|
447
|
+
self._component4.dispose()
|
|
448
|
+
if self._component5 is not None:
|
|
449
|
+
self._component5.dispose()
|
|
450
|
+
|
|
451
|
+
self._component1 = self._factory1()
|
|
452
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_1"))
|
|
453
|
+
self._raise_property_changed("component_1")
|
|
454
|
+
|
|
455
|
+
self._component2 = self._factory2()
|
|
456
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_2"))
|
|
457
|
+
self._raise_property_changed("component_2")
|
|
458
|
+
|
|
459
|
+
self._component3 = self._factory3()
|
|
460
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_3"))
|
|
461
|
+
self._raise_property_changed("component_3")
|
|
462
|
+
|
|
463
|
+
self._component4 = self._factory4()
|
|
464
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_4"))
|
|
465
|
+
self._raise_property_changed("component_4")
|
|
466
|
+
|
|
467
|
+
self._component5 = self._factory5()
|
|
468
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_5"))
|
|
469
|
+
self._raise_property_changed("component_5")
|
|
470
|
+
|
|
471
|
+
self._component1.construct()
|
|
472
|
+
self._component2.construct()
|
|
473
|
+
self._component3.construct()
|
|
474
|
+
self._component4.construct()
|
|
475
|
+
self._component5.construct()
|
|
476
|
+
|
|
477
|
+
def _on_destruct(self) -> None:
|
|
478
|
+
if self._component1 is not None:
|
|
479
|
+
self._component1.destruct()
|
|
480
|
+
if self._component2 is not None:
|
|
481
|
+
self._component2.destruct()
|
|
482
|
+
if self._component3 is not None:
|
|
483
|
+
self._component3.destruct()
|
|
484
|
+
if self._component4 is not None:
|
|
485
|
+
self._component4.destruct()
|
|
486
|
+
if self._component5 is not None:
|
|
487
|
+
self._component5.destruct()
|
|
488
|
+
|
|
489
|
+
def dispose(self) -> None:
|
|
490
|
+
"""Depth-first dispose (LIFE-013): each component slot first, then self.
|
|
491
|
+
See AggregateVM1.dispose for the cross-flavor ordering rationale."""
|
|
492
|
+
if self._component1 is not None:
|
|
493
|
+
self._component1.dispose()
|
|
494
|
+
if self._component2 is not None:
|
|
495
|
+
self._component2.dispose()
|
|
496
|
+
if self._component3 is not None:
|
|
497
|
+
self._component3.dispose()
|
|
498
|
+
if self._component4 is not None:
|
|
499
|
+
self._component4.dispose()
|
|
500
|
+
if self._component5 is not None:
|
|
501
|
+
self._component5.dispose()
|
|
502
|
+
super().dispose()
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
# ---------------------------------------------------------------------------
|
|
506
|
+
# AggregateVM6
|
|
507
|
+
# ---------------------------------------------------------------------------
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
class AggregateVM6(Generic[V1, V2, V3, V4, V5, V6], _ComponentVMBase):
|
|
511
|
+
"""Arity-6 aggregate viewmodel. A fixed tuple of six heterogeneous component VMs.
|
|
512
|
+
|
|
513
|
+
Added in spec 2.2.0 per ADR-0034.
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
def __init__(
|
|
517
|
+
self,
|
|
518
|
+
*,
|
|
519
|
+
name: str,
|
|
520
|
+
hint: str,
|
|
521
|
+
hub: MessageHub[Message],
|
|
522
|
+
dispatcher: Dispatcher,
|
|
523
|
+
factory1: Callable[[], V1],
|
|
524
|
+
factory2: Callable[[], V2],
|
|
525
|
+
factory3: Callable[[], V3],
|
|
526
|
+
factory4: Callable[[], V4],
|
|
527
|
+
factory5: Callable[[], V5],
|
|
528
|
+
factory6: Callable[[], V6],
|
|
529
|
+
) -> None:
|
|
530
|
+
super().__init__(
|
|
531
|
+
name=name,
|
|
532
|
+
hint=hint,
|
|
533
|
+
hub=hub,
|
|
534
|
+
dispatcher=dispatcher,
|
|
535
|
+
)
|
|
536
|
+
self._factory1: Callable[[], V1] = factory1
|
|
537
|
+
self._factory2: Callable[[], V2] = factory2
|
|
538
|
+
self._factory3: Callable[[], V3] = factory3
|
|
539
|
+
self._factory4: Callable[[], V4] = factory4
|
|
540
|
+
self._factory5: Callable[[], V5] = factory5
|
|
541
|
+
self._factory6: Callable[[], V6] = factory6
|
|
542
|
+
self._component1: V1 | None = None
|
|
543
|
+
self._component2: V2 | None = None
|
|
544
|
+
self._component3: V3 | None = None
|
|
545
|
+
self._component4: V4 | None = None
|
|
546
|
+
self._component5: V5 | None = None
|
|
547
|
+
self._component6: V6 | None = None
|
|
548
|
+
|
|
549
|
+
@property
|
|
550
|
+
def type(self) -> ViewModelType:
|
|
551
|
+
return ViewModelType.AGGREGATE
|
|
552
|
+
|
|
553
|
+
@property
|
|
554
|
+
def component_1(self) -> V1 | None:
|
|
555
|
+
return self._component1
|
|
556
|
+
|
|
557
|
+
@property
|
|
558
|
+
def component_2(self) -> V2 | None:
|
|
559
|
+
return self._component2
|
|
560
|
+
|
|
561
|
+
@property
|
|
562
|
+
def component_3(self) -> V3 | None:
|
|
563
|
+
return self._component3
|
|
564
|
+
|
|
565
|
+
@property
|
|
566
|
+
def component_4(self) -> V4 | None:
|
|
567
|
+
return self._component4
|
|
568
|
+
|
|
569
|
+
@property
|
|
570
|
+
def component_5(self) -> V5 | None:
|
|
571
|
+
return self._component5
|
|
572
|
+
|
|
573
|
+
@property
|
|
574
|
+
def component_6(self) -> V6 | None:
|
|
575
|
+
return self._component6
|
|
576
|
+
|
|
577
|
+
def _on_construct(self) -> None:
|
|
578
|
+
# On Reconstruct, dispose previous slot instances before overwriting
|
|
579
|
+
# so their hub subscriptions and command Subjects don't leak.
|
|
580
|
+
if self._component1 is not None:
|
|
581
|
+
self._component1.dispose()
|
|
582
|
+
if self._component2 is not None:
|
|
583
|
+
self._component2.dispose()
|
|
584
|
+
if self._component3 is not None:
|
|
585
|
+
self._component3.dispose()
|
|
586
|
+
if self._component4 is not None:
|
|
587
|
+
self._component4.dispose()
|
|
588
|
+
if self._component5 is not None:
|
|
589
|
+
self._component5.dispose()
|
|
590
|
+
if self._component6 is not None:
|
|
591
|
+
self._component6.dispose()
|
|
592
|
+
|
|
593
|
+
self._component1 = self._factory1()
|
|
594
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_1"))
|
|
595
|
+
self._raise_property_changed("component_1")
|
|
596
|
+
|
|
597
|
+
self._component2 = self._factory2()
|
|
598
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_2"))
|
|
599
|
+
self._raise_property_changed("component_2")
|
|
600
|
+
|
|
601
|
+
self._component3 = self._factory3()
|
|
602
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_3"))
|
|
603
|
+
self._raise_property_changed("component_3")
|
|
604
|
+
|
|
605
|
+
self._component4 = self._factory4()
|
|
606
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_4"))
|
|
607
|
+
self._raise_property_changed("component_4")
|
|
608
|
+
|
|
609
|
+
self._component5 = self._factory5()
|
|
610
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_5"))
|
|
611
|
+
self._raise_property_changed("component_5")
|
|
612
|
+
|
|
613
|
+
self._component6 = self._factory6()
|
|
614
|
+
self._hub.send(PropertyChangedMessage.create(self, self._name, "component_6"))
|
|
615
|
+
self._raise_property_changed("component_6")
|
|
616
|
+
|
|
617
|
+
self._component1.construct()
|
|
618
|
+
self._component2.construct()
|
|
619
|
+
self._component3.construct()
|
|
620
|
+
self._component4.construct()
|
|
621
|
+
self._component5.construct()
|
|
622
|
+
self._component6.construct()
|
|
623
|
+
|
|
624
|
+
def _on_destruct(self) -> None:
|
|
625
|
+
if self._component1 is not None:
|
|
626
|
+
self._component1.destruct()
|
|
627
|
+
if self._component2 is not None:
|
|
628
|
+
self._component2.destruct()
|
|
629
|
+
if self._component3 is not None:
|
|
630
|
+
self._component3.destruct()
|
|
631
|
+
if self._component4 is not None:
|
|
632
|
+
self._component4.destruct()
|
|
633
|
+
if self._component5 is not None:
|
|
634
|
+
self._component5.destruct()
|
|
635
|
+
if self._component6 is not None:
|
|
636
|
+
self._component6.destruct()
|
|
637
|
+
|
|
638
|
+
def dispose(self) -> None:
|
|
639
|
+
"""Depth-first dispose (LIFE-013): each component slot first, then self.
|
|
640
|
+
See AggregateVM1.dispose for the cross-flavor ordering rationale."""
|
|
641
|
+
if self._component1 is not None:
|
|
642
|
+
self._component1.dispose()
|
|
643
|
+
if self._component2 is not None:
|
|
644
|
+
self._component2.dispose()
|
|
645
|
+
if self._component3 is not None:
|
|
646
|
+
self._component3.dispose()
|
|
647
|
+
if self._component4 is not None:
|
|
648
|
+
self._component4.dispose()
|
|
649
|
+
if self._component5 is not None:
|
|
650
|
+
self._component5.dispose()
|
|
651
|
+
if self._component6 is not None:
|
|
652
|
+
self._component6.dispose()
|
|
653
|
+
super().dispose()
|