iris-pex-embedded-python 3.7.2b2__py3-none-any.whl → 4.0.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.
- iop/__init__.py +276 -18
- iop/__main__.py +3 -3
- iop/cli/__init__.py +1 -0
- iop/cli/formatting.py +45 -0
- iop/{_cli.py → cli/main.py} +238 -216
- iop/cli/parser.py +165 -0
- iop/cli/types.py +73 -0
- iop/cls/IOP/Common.cls +11 -2
- iop/cls/IOP/PrivateSession/Duplex.cls +1 -1
- iop/cls/IOP/Service/Remote/Rest/v1.cls +152 -5
- iop/cls/IOP/Utils.cls +814 -7
- iop/cls/IOP/Wrapper.cls +1 -1
- iop/components/__init__.py +16 -0
- iop/{_async_request.py → components/async_request.py} +7 -7
- iop/components/business_host.py +363 -0
- iop/components/business_operation.py +84 -0
- iop/{_business_process.py → components/business_process.py} +145 -125
- iop/components/business_service.py +106 -0
- iop/{_common.py → components/common.py} +160 -164
- iop/{_debugpy.py → components/debugpy.py} +11 -11
- iop/{_generator_request.py → components/generator_request.py} +4 -4
- iop/{_inbound_adapter.py → components/inbound_adapter.py} +10 -18
- iop/{_log_manager.py → components/log_manager.py} +1 -1
- iop/{_outbound_adapter.py → components/outbound_adapter.py} +1 -1
- iop/components/polling_business_service.py +48 -0
- iop/{_private_session_duplex.py → components/private_session_duplex.py} +7 -9
- iop/{_private_session_process.py → components/private_session_process.py} +2 -2
- iop/{_settings.py → components/settings.py} +23 -3
- iop/messages/__init__.py +7 -0
- iop/messages/base.py +61 -0
- iop/{_decorators.py → messages/decorators.py} +8 -2
- iop/messages/dispatch.py +351 -0
- iop/{_persistent_message.py → messages/persistent.py} +24 -19
- iop/{_serialization.py → messages/serialization.py} +34 -21
- iop/{_message_validator.py → messages/validation.py} +15 -13
- iop/migration/__init__.py +1 -0
- iop/migration/io.py +61 -0
- iop/migration/manifest.py +534 -0
- iop/migration/plans.py +80 -0
- iop/migration/utils.py +1126 -0
- iop/production/__init__.py +55 -0
- iop/production/actions.py +525 -0
- iop/production/common.py +103 -0
- iop/production/component.py +234 -0
- iop/production/declarations.py +190 -0
- iop/production/declarative.py +214 -0
- iop/production/diff.py +327 -0
- iop/production/import_.py +296 -0
- iop/production/inspection.py +92 -0
- iop/production/model.py +1143 -0
- iop/production/planning.py +678 -0
- iop/production/reconstruction.py +341 -0
- iop/production/rendering.py +610 -0
- iop/production/runtime.py +91 -0
- iop/production/source_inference.py +729 -0
- iop/production/types.py +546 -0
- iop/production/validation.py +452 -0
- iop/runtime/__init__.py +1 -0
- iop/runtime/director.py +415 -0
- iop/runtime/environment.py +59 -0
- iop/{_iris.py → runtime/iris.py} +2 -2
- iop/runtime/local.py +145 -0
- iop/{_director_protocol.py → runtime/protocol.py} +25 -8
- iop/runtime/remote/__init__.py +12 -0
- iop/runtime/remote/client.py +90 -0
- iop/runtime/remote/director.py +283 -0
- iop/runtime/remote/migration.py +57 -0
- iop/runtime/remote/settings.py +69 -0
- iop/runtime/remote/setup.py +72 -0
- {iris_pex_embedded_python-3.7.2b2.dist-info → iris_pex_embedded_python-4.0.0.dist-info}/METADATA +11 -2
- iris_pex_embedded_python-4.0.0.dist-info/RECORD +99 -0
- iris_pex_embedded_python-4.0.0.dist-info/entry_points.txt +2 -0
- iris_pex_embedded_python-4.0.0.dist-info/top_level.txt +1 -0
- grongier/__init__.py +0 -0
- grongier/cls/Grongier/PEX/BusinessOperation.cls +0 -8
- grongier/cls/Grongier/PEX/BusinessProcess.cls +0 -13
- grongier/cls/Grongier/PEX/BusinessService.cls +0 -8
- grongier/cls/Grongier/PEX/Common.cls +0 -10
- grongier/cls/Grongier/PEX/Director.cls +0 -10
- grongier/cls/Grongier/PEX/Duplex/Operation.cls +0 -4
- grongier/cls/Grongier/PEX/Duplex/Process.cls +0 -13
- grongier/cls/Grongier/PEX/Duplex/Service.cls +0 -4
- grongier/cls/Grongier/PEX/InboundAdapter.cls +0 -8
- grongier/cls/Grongier/PEX/Message.cls +0 -13
- grongier/cls/Grongier/PEX/OutboundAdapter.cls +0 -8
- grongier/cls/Grongier/PEX/PickleMessage.cls +0 -13
- grongier/cls/Grongier/PEX/PrivateSession/Duplex.cls +0 -8
- grongier/cls/Grongier/PEX/PrivateSession/Message/Ack.cls +0 -14
- grongier/cls/Grongier/PEX/PrivateSession/Message/Poll.cls +0 -14
- grongier/cls/Grongier/PEX/PrivateSession/Message/Start.cls +0 -14
- grongier/cls/Grongier/PEX/PrivateSession/Message/Stop.cls +0 -14
- grongier/cls/Grongier/PEX/Test.cls +0 -10
- grongier/cls/Grongier/PEX/Utils.cls +0 -10
- grongier/cls/Grongier/Service/WSGI.cls +0 -4
- grongier/pex/__init__.py +0 -75
- grongier/pex/__main__.py +0 -4
- grongier/pex/_business_host.py +0 -1
- grongier/pex/_cli.py +0 -4
- grongier/pex/_common.py +0 -1
- grongier/pex/_director.py +0 -1
- grongier/pex/_utils.py +0 -1
- grongier/pex/wsgi/handlers.py +0 -104
- iop/_business_host.py +0 -322
- iop/_business_operation.py +0 -77
- iop/_business_service.py +0 -67
- iop/_director.py +0 -333
- iop/_dispatch.py +0 -194
- iop/_local.py +0 -106
- iop/_message.py +0 -45
- iop/_polling_business_service.py +0 -6
- iop/_remote.py +0 -473
- iop/_utils.py +0 -912
- iop/cls/IOP/Service/WSGI.cls +0 -310
- iop/wsgi/handlers.py +0 -112
- iris_pex_embedded_python-3.7.2b2.dist-info/RECORD +0 -96
- iris_pex_embedded_python-3.7.2b2.dist-info/entry_points.txt +0 -2
- iris_pex_embedded_python-3.7.2b2.dist-info/top_level.txt +0 -2
- {iris_pex_embedded_python-3.7.2b2.dist-info → iris_pex_embedded_python-4.0.0.dist-info}/WHEEL +0 -0
- {iris_pex_embedded_python-3.7.2b2.dist-info → iris_pex_embedded_python-4.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .common import (
|
|
7
|
+
_adapter_type_from_class_name,
|
|
8
|
+
_adapter_type_from_component_class,
|
|
9
|
+
_apply_settings_update,
|
|
10
|
+
_auto_proxy_class_name,
|
|
11
|
+
_bool_text,
|
|
12
|
+
_settings_to_iris,
|
|
13
|
+
_text_value,
|
|
14
|
+
)
|
|
15
|
+
from .types import TargetSetting, TargetSettingRef
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class ComponentRef:
|
|
20
|
+
"""Reference to a component item declared inside a Production."""
|
|
21
|
+
|
|
22
|
+
production: Any
|
|
23
|
+
name: str
|
|
24
|
+
component_class: type | None = None
|
|
25
|
+
class_name: str | None = None
|
|
26
|
+
adapter_class: type | None = None
|
|
27
|
+
adapter_class_name: str = ""
|
|
28
|
+
kind: str = "component"
|
|
29
|
+
category: str = ""
|
|
30
|
+
pool_size: int | str = 1
|
|
31
|
+
enabled: bool | str = True
|
|
32
|
+
foreground: bool | str = False
|
|
33
|
+
comment: str = ""
|
|
34
|
+
log_trace_events: bool | str = False
|
|
35
|
+
schedule: str = ""
|
|
36
|
+
host_settings: dict[str, Any] = field(default_factory=dict)
|
|
37
|
+
adapter_settings: dict[str, Any] = field(default_factory=dict)
|
|
38
|
+
other_settings: list[dict[str, Any]] = field(default_factory=list)
|
|
39
|
+
target_setting_names: set[str] = field(default_factory=set)
|
|
40
|
+
runtime_metadata: dict[str, Any] = field(default_factory=dict)
|
|
41
|
+
|
|
42
|
+
def __post_init__(self) -> None:
|
|
43
|
+
if self.class_name is None:
|
|
44
|
+
if self.component_class is None:
|
|
45
|
+
raise ValueError("component_class or class_name is required")
|
|
46
|
+
self.class_name = _auto_proxy_class_name(self.component_class)
|
|
47
|
+
if self.adapter_class is not None and not self.adapter_class_name:
|
|
48
|
+
self.adapter_class_name = _auto_proxy_class_name(self.adapter_class)
|
|
49
|
+
if not self.adapter_class_name:
|
|
50
|
+
self.adapter_class_name = _adapter_type_from_component_class(
|
|
51
|
+
self.component_class
|
|
52
|
+
)
|
|
53
|
+
if not self.adapter_class_name:
|
|
54
|
+
self.adapter_class_name = _adapter_type_from_class_name(self.class_name)
|
|
55
|
+
|
|
56
|
+
def __getattr__(self, name: str) -> TargetSettingRef:
|
|
57
|
+
if self.component_class is not None:
|
|
58
|
+
descriptor = getattr(self.component_class, name, None)
|
|
59
|
+
if isinstance(descriptor, TargetSetting):
|
|
60
|
+
return TargetSettingRef(
|
|
61
|
+
production=self.production,
|
|
62
|
+
component=self,
|
|
63
|
+
name=name,
|
|
64
|
+
)
|
|
65
|
+
if name in self.target_setting_names:
|
|
66
|
+
return self.target_setting(name)
|
|
67
|
+
raise AttributeError(name)
|
|
68
|
+
|
|
69
|
+
def target_setting(self, name: str) -> TargetSettingRef:
|
|
70
|
+
self.target_setting_names.add(name)
|
|
71
|
+
return TargetSettingRef(
|
|
72
|
+
production=self.production,
|
|
73
|
+
component=self,
|
|
74
|
+
name=name,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def set_host_setting(self, name: str, value: Any) -> None:
|
|
78
|
+
self.host_settings[name] = value
|
|
79
|
+
|
|
80
|
+
def pool(self, size: int | str) -> ComponentRef:
|
|
81
|
+
self.pool_size = size
|
|
82
|
+
return self
|
|
83
|
+
|
|
84
|
+
def enable(self, enabled: bool | str = True) -> ComponentRef:
|
|
85
|
+
self.enabled = enabled
|
|
86
|
+
return self
|
|
87
|
+
|
|
88
|
+
def disable(self) -> ComponentRef:
|
|
89
|
+
return self.enable(False)
|
|
90
|
+
|
|
91
|
+
def run_foreground(self, enabled: bool | str = True) -> ComponentRef:
|
|
92
|
+
self.foreground = enabled
|
|
93
|
+
return self
|
|
94
|
+
|
|
95
|
+
def trace(self, enabled: bool | str = True) -> ComponentRef:
|
|
96
|
+
self.log_trace_events = enabled
|
|
97
|
+
return self
|
|
98
|
+
|
|
99
|
+
def schedule_on(self, schedule: str) -> ComponentRef:
|
|
100
|
+
self.schedule = schedule
|
|
101
|
+
return self
|
|
102
|
+
|
|
103
|
+
def comment_as(self, text: str) -> ComponentRef:
|
|
104
|
+
self.comment = text
|
|
105
|
+
return self
|
|
106
|
+
|
|
107
|
+
def category_as(self, category: str) -> ComponentRef:
|
|
108
|
+
self.category = category
|
|
109
|
+
return self
|
|
110
|
+
|
|
111
|
+
def host_setting(self, name: str, value: Any) -> ComponentRef:
|
|
112
|
+
_apply_settings_update(self.host_settings, {name: value})
|
|
113
|
+
return self
|
|
114
|
+
|
|
115
|
+
def host_settings_update(self, values: dict[str, Any]) -> ComponentRef:
|
|
116
|
+
_apply_settings_update(self.host_settings, values)
|
|
117
|
+
return self
|
|
118
|
+
|
|
119
|
+
def setting(self, name: str, value: Any) -> ComponentRef:
|
|
120
|
+
return self.host_setting(name, value)
|
|
121
|
+
|
|
122
|
+
def settings_update(self, values: dict[str, Any]) -> ComponentRef:
|
|
123
|
+
return self.host_settings_update(values)
|
|
124
|
+
|
|
125
|
+
def adapter_setting(self, name: str, value: Any) -> ComponentRef:
|
|
126
|
+
_apply_settings_update(self.adapter_settings, {name: value})
|
|
127
|
+
return self
|
|
128
|
+
|
|
129
|
+
def adapter_settings_update(self, values: dict[str, Any]) -> ComponentRef:
|
|
130
|
+
_apply_settings_update(self.adapter_settings, values)
|
|
131
|
+
return self
|
|
132
|
+
|
|
133
|
+
def other_setting(
|
|
134
|
+
self,
|
|
135
|
+
target: str,
|
|
136
|
+
name: str,
|
|
137
|
+
value: Any,
|
|
138
|
+
) -> ComponentRef:
|
|
139
|
+
if target == "Host":
|
|
140
|
+
return self.host_setting(name, value)
|
|
141
|
+
if target == "Adapter":
|
|
142
|
+
return self.adapter_setting(name, value)
|
|
143
|
+
|
|
144
|
+
self.other_settings = [
|
|
145
|
+
setting
|
|
146
|
+
for setting in self.other_settings
|
|
147
|
+
if not (
|
|
148
|
+
setting.get("@Target") == target
|
|
149
|
+
and setting.get("@Name") == name
|
|
150
|
+
)
|
|
151
|
+
]
|
|
152
|
+
if value is not None:
|
|
153
|
+
self.other_settings.append(
|
|
154
|
+
{
|
|
155
|
+
"@Target": target,
|
|
156
|
+
"@Name": name,
|
|
157
|
+
"#text": _text_value(value),
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
return self
|
|
161
|
+
|
|
162
|
+
def connect(
|
|
163
|
+
self,
|
|
164
|
+
target_setting: str | TargetSettingRef,
|
|
165
|
+
target_component: ComponentRef | str | None = None,
|
|
166
|
+
**kwargs: Any,
|
|
167
|
+
) -> ComponentRef:
|
|
168
|
+
target_setting_ref = self._coerce_target_setting_ref(target_setting)
|
|
169
|
+
self.production.connect(target_setting_ref, target_component, **kwargs)
|
|
170
|
+
return self
|
|
171
|
+
|
|
172
|
+
def _coerce_target_setting_ref(
|
|
173
|
+
self,
|
|
174
|
+
target_setting: str | TargetSettingRef,
|
|
175
|
+
) -> TargetSettingRef:
|
|
176
|
+
if isinstance(target_setting, TargetSettingRef):
|
|
177
|
+
if target_setting.production is not self.production:
|
|
178
|
+
raise ValueError(
|
|
179
|
+
"source target setting belongs to a different Production"
|
|
180
|
+
)
|
|
181
|
+
if target_setting.component is not self:
|
|
182
|
+
raise ValueError(
|
|
183
|
+
"source target setting belongs to a different component"
|
|
184
|
+
)
|
|
185
|
+
return target_setting
|
|
186
|
+
return self.target_setting(str(target_setting))
|
|
187
|
+
|
|
188
|
+
def inspect(self, *, refresh: bool = True) -> dict[str, Any]:
|
|
189
|
+
return self.production.inspect_component(self, refresh=refresh)
|
|
190
|
+
|
|
191
|
+
def start(self) -> None:
|
|
192
|
+
self.production.start_component(self)
|
|
193
|
+
|
|
194
|
+
def stop(self) -> None:
|
|
195
|
+
self.production.stop_component(self)
|
|
196
|
+
|
|
197
|
+
def restart(self) -> None:
|
|
198
|
+
self.production.restart_component(self)
|
|
199
|
+
|
|
200
|
+
def test(
|
|
201
|
+
self,
|
|
202
|
+
message: Any = None,
|
|
203
|
+
*,
|
|
204
|
+
classname: str | None = None,
|
|
205
|
+
body: str | dict | None = None,
|
|
206
|
+
) -> Any:
|
|
207
|
+
return self.production.test_component(
|
|
208
|
+
self,
|
|
209
|
+
message=message,
|
|
210
|
+
classname=classname,
|
|
211
|
+
body=body,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def to_dict(self) -> dict[str, Any]:
|
|
215
|
+
item: dict[str, Any] = {
|
|
216
|
+
"@Name": self.name,
|
|
217
|
+
"@Category": self.category,
|
|
218
|
+
"@ClassName": self.class_name,
|
|
219
|
+
"@PoolSize": _text_value(self.pool_size),
|
|
220
|
+
"@Enabled": _bool_text(self.enabled),
|
|
221
|
+
"@Foreground": _bool_text(self.foreground),
|
|
222
|
+
"@Comment": self.comment,
|
|
223
|
+
"@LogTraceEvents": _bool_text(self.log_trace_events),
|
|
224
|
+
"@Schedule": self.schedule,
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
settings = []
|
|
228
|
+
settings.extend(_settings_to_iris("Host", self.host_settings))
|
|
229
|
+
settings.extend(_settings_to_iris("Adapter", self.adapter_settings))
|
|
230
|
+
settings.extend(dict(setting) for setting in self.other_settings)
|
|
231
|
+
if settings:
|
|
232
|
+
item["Setting"] = settings
|
|
233
|
+
|
|
234
|
+
return item
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable, Mapping
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Any, ClassVar, Protocol
|
|
6
|
+
|
|
7
|
+
from .common import SETTING_NAME_ALIASES
|
|
8
|
+
from .types import TargetSetting
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class _NamedRouteTarget(Protocol):
|
|
12
|
+
@property
|
|
13
|
+
def name(self) -> str: ...
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class Route:
|
|
18
|
+
"""Declarative route from a target setting to one or more targets."""
|
|
19
|
+
|
|
20
|
+
target_setting: str | TargetSetting
|
|
21
|
+
targets: str | _NamedRouteTarget | Iterable[str | _NamedRouteTarget]
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def target_setting_name(self) -> str:
|
|
25
|
+
return normalize_route_target_setting(self.target_setting)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def target_setting_owner(self) -> type | None:
|
|
29
|
+
if isinstance(self.target_setting, TargetSetting):
|
|
30
|
+
return self.target_setting.owner
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def target_names(self) -> tuple[str, ...]:
|
|
35
|
+
if _is_route_target(self.targets):
|
|
36
|
+
targets = (self.targets,)
|
|
37
|
+
else:
|
|
38
|
+
try:
|
|
39
|
+
targets = tuple(self.targets)
|
|
40
|
+
except TypeError as exc:
|
|
41
|
+
raise TypeError(
|
|
42
|
+
f"Route {self.target_setting_name!r} targets must be an item name, "
|
|
43
|
+
"a production item declaration, or an iterable of either"
|
|
44
|
+
) from exc
|
|
45
|
+
if not targets:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
f"Route {self.target_setting_name!r} requires at least one target"
|
|
48
|
+
)
|
|
49
|
+
return tuple(
|
|
50
|
+
_route_target_name(target, self.target_setting_name)
|
|
51
|
+
for target in targets
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass(frozen=True)
|
|
56
|
+
class _ProductionItemDeclaration:
|
|
57
|
+
name: str
|
|
58
|
+
component: type | str | None = None
|
|
59
|
+
class_name: str | None = None
|
|
60
|
+
adapter_class: type | str | None = None
|
|
61
|
+
adapter_class_name: str | None = None
|
|
62
|
+
enabled: bool | str = True
|
|
63
|
+
pool_size: int | str = 1
|
|
64
|
+
category: str = ""
|
|
65
|
+
foreground: bool | str = False
|
|
66
|
+
comment: str = ""
|
|
67
|
+
log_trace_events: bool | str = False
|
|
68
|
+
schedule: str = ""
|
|
69
|
+
settings: Mapping[str, Any] | None = None
|
|
70
|
+
host_settings: Mapping[str, Any] | None = None
|
|
71
|
+
adapter_settings: Mapping[str, Any] | None = None
|
|
72
|
+
other_settings: Iterable[Mapping[str, Any]] | None = None
|
|
73
|
+
routes: Route | Iterable[Route] | None = field(default_factory=tuple)
|
|
74
|
+
|
|
75
|
+
kind: ClassVar[str] = "component"
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def host_setting_values(self) -> dict[str, Any]:
|
|
79
|
+
settings = _mapping(self.settings, "settings", self.name)
|
|
80
|
+
host_settings = _mapping(self.host_settings, "host_settings", self.name)
|
|
81
|
+
duplicates = sorted(settings.keys() & host_settings.keys())
|
|
82
|
+
if duplicates:
|
|
83
|
+
names = ", ".join(repr(name) for name in duplicates)
|
|
84
|
+
raise ValueError(
|
|
85
|
+
f"{self.kind.title()} item {self.name!r} declares duplicate "
|
|
86
|
+
f"Host setting keys: {names}"
|
|
87
|
+
)
|
|
88
|
+
merged = dict(settings)
|
|
89
|
+
merged.update(host_settings)
|
|
90
|
+
return merged
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def adapter_setting_values(self) -> dict[str, Any]:
|
|
94
|
+
return _mapping(self.adapter_settings, "adapter_settings", self.name)
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def other_setting_values(self) -> list[dict[str, Any]]:
|
|
98
|
+
return [dict(setting) for setting in self.other_settings or ()]
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def route_values(self) -> tuple[Route, ...]:
|
|
102
|
+
if self.routes is None:
|
|
103
|
+
return ()
|
|
104
|
+
if isinstance(self.routes, Route):
|
|
105
|
+
return (self.routes,)
|
|
106
|
+
routes = tuple(self.routes)
|
|
107
|
+
for route in routes:
|
|
108
|
+
if not isinstance(route, Route):
|
|
109
|
+
raise TypeError(
|
|
110
|
+
f"{self.kind.title()} item {self.name!r} routes must be Route "
|
|
111
|
+
f"instances"
|
|
112
|
+
)
|
|
113
|
+
return routes
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass(frozen=True)
|
|
117
|
+
class ServiceItem(_ProductionItemDeclaration):
|
|
118
|
+
kind: ClassVar[str] = "service"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass(frozen=True)
|
|
122
|
+
class ComponentItem(_ProductionItemDeclaration):
|
|
123
|
+
kind: ClassVar[str] = "component"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass(frozen=True)
|
|
127
|
+
class ProcessItem(_ProductionItemDeclaration):
|
|
128
|
+
kind: ClassVar[str] = "process"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass(frozen=True)
|
|
132
|
+
class OperationItem(_ProductionItemDeclaration):
|
|
133
|
+
kind: ClassVar[str] = "operation"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def normalize_route_target_setting(name: str | TargetSetting) -> str:
|
|
137
|
+
"""Normalize known Pythonic route aliases without changing other settings."""
|
|
138
|
+
|
|
139
|
+
if isinstance(name, TargetSetting):
|
|
140
|
+
if not name.name:
|
|
141
|
+
raise ValueError(
|
|
142
|
+
"Route target setting must be declared on a component class"
|
|
143
|
+
)
|
|
144
|
+
return name.name
|
|
145
|
+
target_setting_name = str(name)
|
|
146
|
+
return SETTING_NAME_ALIASES.get(target_setting_name, target_setting_name)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def normalize_route_target_setting_for_match(name: str | TargetSetting) -> str:
|
|
150
|
+
return normalize_route_target_setting(name)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _is_route_target(value: Any) -> bool:
|
|
154
|
+
return isinstance(value, str) or isinstance(value, _ProductionItemDeclaration)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _route_target_name(value: Any, target_setting_name: str) -> str:
|
|
158
|
+
if isinstance(value, str):
|
|
159
|
+
if value:
|
|
160
|
+
return value
|
|
161
|
+
elif isinstance(value, _ProductionItemDeclaration):
|
|
162
|
+
if value.name:
|
|
163
|
+
return value.name
|
|
164
|
+
|
|
165
|
+
raise TypeError(
|
|
166
|
+
f"Route {target_setting_name!r} targets must be item names or production item "
|
|
167
|
+
"declarations"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _mapping(
|
|
172
|
+
values: Mapping[str, Any] | None,
|
|
173
|
+
field_name: str,
|
|
174
|
+
item_name: str,
|
|
175
|
+
) -> dict[str, Any]:
|
|
176
|
+
try:
|
|
177
|
+
return dict(values or {})
|
|
178
|
+
except (TypeError, ValueError) as exc:
|
|
179
|
+
raise TypeError(
|
|
180
|
+
f"Production item {item_name!r} {field_name} must be a mapping"
|
|
181
|
+
) from exc
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
__all__ = [
|
|
185
|
+
"ComponentItem",
|
|
186
|
+
"OperationItem",
|
|
187
|
+
"ProcessItem",
|
|
188
|
+
"Route",
|
|
189
|
+
"ServiceItem",
|
|
190
|
+
]
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from typing import Any, ClassVar, Protocol, cast
|
|
5
|
+
|
|
6
|
+
from .component import ComponentRef
|
|
7
|
+
from .declarations import (
|
|
8
|
+
Route,
|
|
9
|
+
_ProductionItemDeclaration,
|
|
10
|
+
normalize_route_target_setting_for_match,
|
|
11
|
+
)
|
|
12
|
+
from .types import TargetSettingRef
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class _ComponentFactory(Protocol):
|
|
16
|
+
def __call__(
|
|
17
|
+
self,
|
|
18
|
+
name_or_cls: str | type,
|
|
19
|
+
cls: type | None = None,
|
|
20
|
+
**kwargs: Any,
|
|
21
|
+
) -> ComponentRef: ...
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class _DeclarativeProductionHost(Protocol):
|
|
25
|
+
def component(
|
|
26
|
+
self,
|
|
27
|
+
name_or_cls: str | type,
|
|
28
|
+
cls: type | None = None,
|
|
29
|
+
**kwargs: Any,
|
|
30
|
+
) -> ComponentRef: ...
|
|
31
|
+
|
|
32
|
+
def service(
|
|
33
|
+
self,
|
|
34
|
+
name_or_cls: str | type,
|
|
35
|
+
cls: type | None = None,
|
|
36
|
+
**kwargs: Any,
|
|
37
|
+
) -> ComponentRef: ...
|
|
38
|
+
|
|
39
|
+
def process(
|
|
40
|
+
self,
|
|
41
|
+
name_or_cls: str | type,
|
|
42
|
+
cls: type | None = None,
|
|
43
|
+
**kwargs: Any,
|
|
44
|
+
) -> ComponentRef: ...
|
|
45
|
+
|
|
46
|
+
def operation(
|
|
47
|
+
self,
|
|
48
|
+
name_or_cls: str | type,
|
|
49
|
+
cls: type | None = None,
|
|
50
|
+
**kwargs: Any,
|
|
51
|
+
) -> ComponentRef: ...
|
|
52
|
+
|
|
53
|
+
def item(self, name: str) -> ComponentRef: ...
|
|
54
|
+
|
|
55
|
+
def connect(
|
|
56
|
+
self,
|
|
57
|
+
source: TargetSettingRef,
|
|
58
|
+
target_component: ComponentRef | str | None = None,
|
|
59
|
+
*,
|
|
60
|
+
mode: str = "replace",
|
|
61
|
+
) -> None: ...
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class _DeclarativeProductionMixin:
|
|
65
|
+
components: ClassVar[Iterable[_ProductionItemDeclaration] | None] = ()
|
|
66
|
+
services: ClassVar[Iterable[_ProductionItemDeclaration] | None] = ()
|
|
67
|
+
processes: ClassVar[Iterable[_ProductionItemDeclaration] | None] = ()
|
|
68
|
+
operations: ClassVar[Iterable[_ProductionItemDeclaration] | None] = ()
|
|
69
|
+
|
|
70
|
+
def _hydrate_declared_items(self) -> None:
|
|
71
|
+
declarations: list[_ProductionItemDeclaration] = []
|
|
72
|
+
for attr_name, expected_kind in (
|
|
73
|
+
("components", "component"),
|
|
74
|
+
("services", "service"),
|
|
75
|
+
("processes", "process"),
|
|
76
|
+
("operations", "operation"),
|
|
77
|
+
):
|
|
78
|
+
for declaration in self._declared_items(attr_name):
|
|
79
|
+
if declaration.kind != expected_kind:
|
|
80
|
+
raise TypeError(
|
|
81
|
+
f"{attr_name} must contain {expected_kind.title()}Item "
|
|
82
|
+
f"declarations"
|
|
83
|
+
)
|
|
84
|
+
self._raise_declared_route_conflicts(declaration)
|
|
85
|
+
self._add_declared_item(declaration)
|
|
86
|
+
declarations.append(declaration)
|
|
87
|
+
|
|
88
|
+
for declaration in declarations:
|
|
89
|
+
self._connect_declared_routes(declaration)
|
|
90
|
+
|
|
91
|
+
def _declared_items(self, attr_name: str) -> tuple[_ProductionItemDeclaration, ...]:
|
|
92
|
+
values: object = getattr(type(self), attr_name, ())
|
|
93
|
+
if values is None:
|
|
94
|
+
return ()
|
|
95
|
+
if isinstance(values, _ProductionItemDeclaration):
|
|
96
|
+
return (values,)
|
|
97
|
+
try:
|
|
98
|
+
declarations = tuple(cast(Iterable[object], values))
|
|
99
|
+
except TypeError as exc:
|
|
100
|
+
raise TypeError(f"{attr_name} must be an iterable of item declarations") from exc
|
|
101
|
+
items: list[_ProductionItemDeclaration] = []
|
|
102
|
+
for declaration in declarations:
|
|
103
|
+
if not isinstance(declaration, _ProductionItemDeclaration):
|
|
104
|
+
raise TypeError(f"{attr_name} must contain production item declarations")
|
|
105
|
+
items.append(declaration)
|
|
106
|
+
return tuple(items)
|
|
107
|
+
|
|
108
|
+
def _raise_declared_route_conflicts(
|
|
109
|
+
self,
|
|
110
|
+
declaration: _ProductionItemDeclaration,
|
|
111
|
+
) -> None:
|
|
112
|
+
host_target_settings = {
|
|
113
|
+
normalize_route_target_setting_for_match(name)
|
|
114
|
+
for name in declaration.host_setting_values
|
|
115
|
+
}
|
|
116
|
+
route_target_settings = {
|
|
117
|
+
route.target_setting_name for route in declaration.route_values
|
|
118
|
+
}
|
|
119
|
+
conflicts = sorted(host_target_settings & route_target_settings)
|
|
120
|
+
if not conflicts:
|
|
121
|
+
return
|
|
122
|
+
names = ", ".join(repr(name) for name in conflicts)
|
|
123
|
+
raise ValueError(
|
|
124
|
+
f"{declaration.kind.title()} item {declaration.name!r} declares route "
|
|
125
|
+
"target setting(s) in Host settings: "
|
|
126
|
+
f"{names}. Declare route target settings with Route only."
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def _add_declared_item(self, declaration: _ProductionItemDeclaration) -> None:
|
|
130
|
+
kwargs: dict[str, Any] = {
|
|
131
|
+
"enabled": declaration.enabled,
|
|
132
|
+
"pool_size": declaration.pool_size,
|
|
133
|
+
"category": declaration.category,
|
|
134
|
+
"foreground": declaration.foreground,
|
|
135
|
+
"comment": declaration.comment,
|
|
136
|
+
"log_trace_events": declaration.log_trace_events,
|
|
137
|
+
"schedule": declaration.schedule,
|
|
138
|
+
"settings": declaration.host_setting_values,
|
|
139
|
+
"adapter_settings": declaration.adapter_setting_values,
|
|
140
|
+
}
|
|
141
|
+
if declaration.adapter_class is not None:
|
|
142
|
+
kwargs["adapter_class"] = declaration.adapter_class
|
|
143
|
+
if declaration.adapter_class_name is not None:
|
|
144
|
+
kwargs["adapter_class_name"] = declaration.adapter_class_name
|
|
145
|
+
|
|
146
|
+
host = cast(_DeclarativeProductionHost, self)
|
|
147
|
+
methods: dict[str, _ComponentFactory] = {
|
|
148
|
+
"component": host.component,
|
|
149
|
+
"service": host.service,
|
|
150
|
+
"process": host.process,
|
|
151
|
+
"operation": host.operation,
|
|
152
|
+
}
|
|
153
|
+
method = methods[declaration.kind]
|
|
154
|
+
|
|
155
|
+
component = declaration.component
|
|
156
|
+
if isinstance(component, type):
|
|
157
|
+
if declaration.class_name is not None:
|
|
158
|
+
kwargs["class_name"] = declaration.class_name
|
|
159
|
+
ref = method(declaration.name, component, **kwargs)
|
|
160
|
+
ref.other_settings = declaration.other_setting_values
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
class_name = declaration.class_name
|
|
164
|
+
if component is not None:
|
|
165
|
+
component_class_name = str(component)
|
|
166
|
+
if class_name is not None and class_name != component_class_name:
|
|
167
|
+
raise ValueError(
|
|
168
|
+
f"{declaration.kind.title()} item {declaration.name!r} "
|
|
169
|
+
"declares conflicting component and class_name values"
|
|
170
|
+
)
|
|
171
|
+
class_name = component_class_name
|
|
172
|
+
|
|
173
|
+
if class_name is None:
|
|
174
|
+
raise ValueError(
|
|
175
|
+
f"{declaration.kind.title()} item {declaration.name!r} requires "
|
|
176
|
+
"a component class or class_name"
|
|
177
|
+
)
|
|
178
|
+
kwargs["class_name"] = class_name
|
|
179
|
+
ref = method(declaration.name, **kwargs)
|
|
180
|
+
ref.other_settings = declaration.other_setting_values
|
|
181
|
+
|
|
182
|
+
def _connect_declared_routes(self, declaration: _ProductionItemDeclaration) -> None:
|
|
183
|
+
host = cast(_DeclarativeProductionHost, self)
|
|
184
|
+
source = host.item(declaration.name)
|
|
185
|
+
for route in declaration.route_values:
|
|
186
|
+
self._raise_if_route_target_setting_owner_mismatch(source, route)
|
|
187
|
+
target_setting_ref = source.target_setting(route.target_setting_name)
|
|
188
|
+
targets = route.target_names
|
|
189
|
+
host.connect(target_setting_ref, targets[0])
|
|
190
|
+
for target in targets[1:]:
|
|
191
|
+
host.connect(
|
|
192
|
+
target_setting_ref,
|
|
193
|
+
target,
|
|
194
|
+
mode="add",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def _raise_if_route_target_setting_owner_mismatch(
|
|
198
|
+
self,
|
|
199
|
+
source: ComponentRef,
|
|
200
|
+
route: Route,
|
|
201
|
+
) -> None:
|
|
202
|
+
owner = route.target_setting_owner
|
|
203
|
+
if (
|
|
204
|
+
owner is None
|
|
205
|
+
or source.component_class is None
|
|
206
|
+
or issubclass(source.component_class, owner)
|
|
207
|
+
):
|
|
208
|
+
return
|
|
209
|
+
raise ValueError(
|
|
210
|
+
f"Route target setting {route.target_setting_name!r} belongs to "
|
|
211
|
+
f"{owner.__module__}.{owner.__qualname__}, not "
|
|
212
|
+
f"{source.component_class.__module__}."
|
|
213
|
+
f"{source.component_class.__qualname__}"
|
|
214
|
+
)
|