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.
Files changed (100) hide show
  1. vmx/__about__.py +10 -0
  2. vmx/__init__.py +289 -0
  3. vmx/aggregates/__init__.py +60 -0
  4. vmx/aggregates/aggregate_vm.py +653 -0
  5. vmx/aggregates/builders.py +451 -0
  6. vmx/builders/__init__.py +14 -0
  7. vmx/builders/_validation.py +31 -0
  8. vmx/builders/exceptions.py +20 -0
  9. vmx/capabilities/__init__.py +56 -0
  10. vmx/capabilities/crud.py +40 -0
  11. vmx/capabilities/current_crud.py +21 -0
  12. vmx/capabilities/dialog.py +29 -0
  13. vmx/capabilities/expandable_state.py +68 -0
  14. vmx/capabilities/expansion.py +33 -0
  15. vmx/capabilities/filter.py +28 -0
  16. vmx/capabilities/lifecycle_capabilities.py +32 -0
  17. vmx/capabilities/management.py +16 -0
  18. vmx/capabilities/pageable.py +103 -0
  19. vmx/capabilities/search.py +21 -0
  20. vmx/capabilities/searchable_state.py +103 -0
  21. vmx/capabilities/selection.py +29 -0
  22. vmx/collections/__init__.py +34 -0
  23. vmx/collections/batch.py +45 -0
  24. vmx/collections/collection_changed.py +21 -0
  25. vmx/collections/observable_dictionary.py +288 -0
  26. vmx/collections/observable_list.py +225 -0
  27. vmx/collections/paged_composition.py +240 -0
  28. vmx/collections/serviced_observable_collection.py +136 -0
  29. vmx/commands/__init__.py +48 -0
  30. vmx/commands/composite_command.py +55 -0
  31. vmx/commands/confirmation_decorator_command.py +62 -0
  32. vmx/commands/decorator_command.py +67 -0
  33. vmx/commands/fluent.py +109 -0
  34. vmx/commands/modeled_crud_commands.py +88 -0
  35. vmx/commands/protocols.py +57 -0
  36. vmx/commands/relay_command.py +278 -0
  37. vmx/components/__init__.py +44 -0
  38. vmx/components/base.py +457 -0
  39. vmx/components/builders.py +346 -0
  40. vmx/components/component_vm.py +156 -0
  41. vmx/components/protocols.py +136 -0
  42. vmx/components/readonly_component_vm.py +77 -0
  43. vmx/composites/__init__.py +29 -0
  44. vmx/composites/builders.py +265 -0
  45. vmx/composites/composite_vm.py +517 -0
  46. vmx/composites/protocols.py +58 -0
  47. vmx/dialogs/__init__.py +18 -0
  48. vmx/dialogs/dialog_service.py +96 -0
  49. vmx/dialogs/null_dialog_service.py +56 -0
  50. vmx/forms/__init__.py +15 -0
  51. vmx/forms/builders.py +107 -0
  52. vmx/forms/form_vm.py +229 -0
  53. vmx/forwarding/__init__.py +14 -0
  54. vmx/forwarding/component.py +153 -0
  55. vmx/forwarding/composite.py +214 -0
  56. vmx/groups/__init__.py +20 -0
  57. vmx/groups/builders.py +118 -0
  58. vmx/groups/group_vm.py +305 -0
  59. vmx/hierarchical/__init__.py +11 -0
  60. vmx/hierarchical/builders.py +157 -0
  61. vmx/hierarchical/hierarchical_vm.py +298 -0
  62. vmx/lifecycle/__init__.py +24 -0
  63. vmx/lifecycle/_data/__init__.py +1 -0
  64. vmx/lifecycle/_data/lifecycle-transitions.json +37 -0
  65. vmx/lifecycle/exceptions.py +27 -0
  66. vmx/lifecycle/status.py +31 -0
  67. vmx/lifecycle/transition_validator.py +116 -0
  68. vmx/localization/__init__.py +12 -0
  69. vmx/localization/localizer.py +15 -0
  70. vmx/localization/null_localizer.py +16 -0
  71. vmx/messages/__init__.py +44 -0
  72. vmx/messages/collection_changed.py +111 -0
  73. vmx/messages/construction_status_changed.py +44 -0
  74. vmx/messages/form_reverted.py +34 -0
  75. vmx/messages/property_changed.py +45 -0
  76. vmx/messages/property_value_changed.py +60 -0
  77. vmx/messages/protocols.py +55 -0
  78. vmx/messages/tree_structure_changed.py +68 -0
  79. vmx/notifications/__init__.py +33 -0
  80. vmx/notifications/confirm_helper.py +25 -0
  81. vmx/notifications/confirmation_vm.py +92 -0
  82. vmx/notifications/notification.py +23 -0
  83. vmx/notifications/notification_hub.py +137 -0
  84. vmx/notifications/notification_reaction.py +13 -0
  85. vmx/notifications/notification_type.py +13 -0
  86. vmx/notifications/notification_vm.py +186 -0
  87. vmx/notifications/null_notification_hub.py +40 -0
  88. vmx/properties/__init__.py +25 -0
  89. vmx/properties/derived.py +222 -0
  90. vmx/py.typed +0 -0
  91. vmx/services/__init__.py +29 -0
  92. vmx/services/dispatcher.py +83 -0
  93. vmx/services/message_hub.py +82 -0
  94. vmx/services/null_dispatcher.py +32 -0
  95. vmx/services/null_message_hub.py +77 -0
  96. vmx/tree/__init__.py +10 -0
  97. vmx/tree/walk.py +65 -0
  98. vmx-2.6.0.dist-info/METADATA +271 -0
  99. vmx-2.6.0.dist-info/RECORD +100 -0
  100. 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()