iris-pex-embedded-python 4.0.0b4__py3-none-any.whl → 4.0.0b5__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 +12 -0
- iop/cli/main.py +3 -0
- iop/cli/parser.py +1 -1
- iop/cls/IOP/Utils.cls +10 -0
- iop/components/settings.py +11 -1
- iop/migration/utils.py +15 -5
- iop/production/__init__.py +10 -0
- iop/production/common.py +5 -0
- iop/production/declarations.py +165 -0
- iop/production/model.py +233 -35
- iop/production/reconstruction.py +3 -3
- iop/production/rendering.py +274 -0
- iop/production/types.py +10 -1
- iop/production/validation.py +7 -8
- {iris_pex_embedded_python-4.0.0b4.dist-info → iris_pex_embedded_python-4.0.0b5.dist-info}/METADATA +1 -1
- {iris_pex_embedded_python-4.0.0b4.dist-info → iris_pex_embedded_python-4.0.0b5.dist-info}/RECORD +20 -19
- {iris_pex_embedded_python-4.0.0b4.dist-info → iris_pex_embedded_python-4.0.0b5.dist-info}/WHEEL +0 -0
- {iris_pex_embedded_python-4.0.0b4.dist-info → iris_pex_embedded_python-4.0.0b5.dist-info}/entry_points.txt +0 -0
- {iris_pex_embedded_python-4.0.0b4.dist-info → iris_pex_embedded_python-4.0.0b5.dist-info}/licenses/LICENSE +0 -0
- {iris_pex_embedded_python-4.0.0b4.dist-info → iris_pex_embedded_python-4.0.0b5.dist-info}/top_level.txt +0 -0
iop/__init__.py
CHANGED
|
@@ -25,8 +25,11 @@ from iop.migration.utils import list_bindings as list_bindings
|
|
|
25
25
|
from iop.migration.utils import register_component as register_component
|
|
26
26
|
from iop.migration.utils import unbind_component as unbind_component
|
|
27
27
|
from iop.migration.utils import unregister_component as unregister_component
|
|
28
|
+
from iop.production import ComponentItem as ComponentItem
|
|
28
29
|
from iop.production import ComponentRef as ComponentRef
|
|
30
|
+
from iop.production import OperationItem as OperationItem
|
|
29
31
|
from iop.production import Port as Port
|
|
32
|
+
from iop.production import ProcessItem as ProcessItem
|
|
30
33
|
from iop.production import Production as Production
|
|
31
34
|
from iop.production import ProductionDiff as ProductionDiff
|
|
32
35
|
from iop.production import ProductionDiffEntry as ProductionDiffEntry
|
|
@@ -35,6 +38,9 @@ from iop.production import ProductionValidationError as ProductionValidationErro
|
|
|
35
38
|
from iop.production import ProductionValidationIssue as ProductionValidationIssue
|
|
36
39
|
from iop.production import ProductionValidationReport as ProductionValidationReport
|
|
37
40
|
from iop.production import ProductionValidationWarning as ProductionValidationWarning
|
|
41
|
+
from iop.production import Route as Route
|
|
42
|
+
from iop.production import ServiceItem as ServiceItem
|
|
43
|
+
from iop.production import TargetSetting as TargetSetting
|
|
38
44
|
from iop.production import target as target
|
|
39
45
|
from iop.runtime.director import _Director
|
|
40
46
|
from iop.runtime.protocol import DirectorProtocol as DirectorProtocol
|
|
@@ -44,6 +50,7 @@ __all__ = [
|
|
|
44
50
|
"BusinessProcess",
|
|
45
51
|
"BusinessService",
|
|
46
52
|
"Category",
|
|
53
|
+
"ComponentItem",
|
|
47
54
|
"ComponentRef",
|
|
48
55
|
"Director",
|
|
49
56
|
"DirectorProtocol",
|
|
@@ -54,11 +61,13 @@ __all__ = [
|
|
|
54
61
|
"InboundAdapter",
|
|
55
62
|
"Message",
|
|
56
63
|
"Model",
|
|
64
|
+
"OperationItem",
|
|
57
65
|
"OutboundAdapter",
|
|
58
66
|
"PersistentMessage",
|
|
59
67
|
"PickleMessage",
|
|
60
68
|
"PollingBusinessService",
|
|
61
69
|
"Port",
|
|
70
|
+
"ProcessItem",
|
|
62
71
|
"Production",
|
|
63
72
|
"ProductionDiff",
|
|
64
73
|
"ProductionDiffEntry",
|
|
@@ -69,7 +78,10 @@ __all__ = [
|
|
|
69
78
|
"ProductionValidationWarning",
|
|
70
79
|
"PydanticMessage",
|
|
71
80
|
"PydanticPickleMessage",
|
|
81
|
+
"Route",
|
|
82
|
+
"ServiceItem",
|
|
72
83
|
"Setting",
|
|
84
|
+
"TargetSetting",
|
|
73
85
|
"Utils",
|
|
74
86
|
"bind_component",
|
|
75
87
|
"controls",
|
iop/cli/main.py
CHANGED
|
@@ -230,6 +230,9 @@ class Command:
|
|
|
230
230
|
if export_format == "python":
|
|
231
231
|
print(production.to_python(), end="")
|
|
232
232
|
return
|
|
233
|
+
if export_format == "class":
|
|
234
|
+
print(production.to_class(), end="")
|
|
235
|
+
return
|
|
233
236
|
if export_format == "graph":
|
|
234
237
|
print(production.graph())
|
|
235
238
|
return
|
iop/cli/parser.py
CHANGED
|
@@ -105,7 +105,7 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
105
105
|
export.add_argument(
|
|
106
106
|
"--format",
|
|
107
107
|
dest="export_format",
|
|
108
|
-
choices=("json", "python", "graph"),
|
|
108
|
+
choices=("json", "python", "class", "graph"),
|
|
109
109
|
default="json",
|
|
110
110
|
help="export format for -e/--export",
|
|
111
111
|
)
|
iop/cls/IOP/Utils.cls
CHANGED
|
@@ -595,6 +595,7 @@ ClassMethod CreateProductionFromJSON(
|
|
|
595
595
|
#dim tPayload As %DynamicObject
|
|
596
596
|
#dim tProductionData As %DynamicObject
|
|
597
597
|
#dim tProduction As Ens.Config.Production
|
|
598
|
+
#dim tProductionClass As %Dictionary.ClassDefinition
|
|
598
599
|
#dim tItems,tDeclaredName
|
|
599
600
|
Try {
|
|
600
601
|
Return:(""=pProductionName) $$$ERROR($$$EnsErrGeneral,"Production name is required.")
|
|
@@ -614,6 +615,15 @@ ClassMethod CreateProductionFromJSON(
|
|
|
614
615
|
}
|
|
615
616
|
}
|
|
616
617
|
|
|
618
|
+
If ('##class(%Dictionary.ClassDefinition).%ExistsId(pProductionName)) {
|
|
619
|
+
Set tProductionClass = ##class(%Dictionary.ClassDefinition).%New()
|
|
620
|
+
Set tProductionClass.Name = pProductionName
|
|
621
|
+
Set tProductionClass.Super = "Ens.Production"
|
|
622
|
+
Set tProductionClass.ClassVersion = 25
|
|
623
|
+
Set tSC = tProductionClass.%Save()
|
|
624
|
+
Quit:$$$ISERR(tSC)
|
|
625
|
+
}
|
|
626
|
+
|
|
617
627
|
Set tProduction = ##class(Ens.Config.Production).%New()
|
|
618
628
|
Set tProduction.Name = pProductionName
|
|
619
629
|
Set tProduction.Description = ..DynamicGet(tProductionData,"Description","")
|
iop/components/settings.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from enum import Enum
|
|
2
|
-
from typing import Any
|
|
4
|
+
from typing import Any, overload
|
|
3
5
|
|
|
4
6
|
_MISSING = object()
|
|
5
7
|
|
|
@@ -45,9 +47,17 @@ class Setting:
|
|
|
45
47
|
self.control = control or ""
|
|
46
48
|
self.exclude = exclude
|
|
47
49
|
self.name = ""
|
|
50
|
+
self.owner = None
|
|
48
51
|
|
|
49
52
|
def __set_name__(self, owner, name):
|
|
50
53
|
self.name = name
|
|
54
|
+
self.owner = owner
|
|
55
|
+
|
|
56
|
+
@overload
|
|
57
|
+
def __get__(self, instance: None, owner: type | None = None) -> Setting: ...
|
|
58
|
+
|
|
59
|
+
@overload
|
|
60
|
+
def __get__(self, instance: object, owner: type | None = None) -> Any: ...
|
|
51
61
|
|
|
52
62
|
def __get__(self, instance, owner=None):
|
|
53
63
|
if instance is None:
|
iop/migration/utils.py
CHANGED
|
@@ -1036,11 +1036,21 @@ def register_production_definition(production_name: str, production: dict):
|
|
|
1036
1036
|
:param production_name: full IRIS production class name
|
|
1037
1037
|
:param production: normalized {"Production": {...}} dictionary
|
|
1038
1038
|
"""
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1039
|
+
try:
|
|
1040
|
+
raise_on_error(
|
|
1041
|
+
_iris.get_iris()
|
|
1042
|
+
.cls("IOP.Utils")
|
|
1043
|
+
.CreateProductionFromJSON(production_name, json.dumps(production))
|
|
1044
|
+
)
|
|
1045
|
+
except RuntimeError as exc:
|
|
1046
|
+
if not _is_missing_production_class_error(exc, production_name):
|
|
1047
|
+
raise
|
|
1048
|
+
register_production(production_name, dict_to_xml(production))
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
def _is_missing_production_class_error(exc: RuntimeError, production_name: str) -> bool:
|
|
1052
|
+
message = str(exc)
|
|
1053
|
+
return "CLASS DOES NOT EXIST" in message and production_name in message
|
|
1044
1054
|
|
|
1045
1055
|
|
|
1046
1056
|
def export_production(production_name):
|
iop/production/__init__.py
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from .component import ComponentRef as ComponentRef
|
|
4
|
+
from .declarations import ComponentItem as ComponentItem
|
|
5
|
+
from .declarations import OperationItem as OperationItem
|
|
6
|
+
from .declarations import ProcessItem as ProcessItem
|
|
7
|
+
from .declarations import Route as Route
|
|
8
|
+
from .declarations import ServiceItem as ServiceItem
|
|
4
9
|
from .model import Production as Production
|
|
5
10
|
from .runtime import resolve_target as resolve_target
|
|
6
11
|
from .types import GraphEdge as GraphEdge
|
|
@@ -19,10 +24,13 @@ from .validation import ProductionValidationWarning as ProductionValidationWarni
|
|
|
19
24
|
|
|
20
25
|
__all__ = [
|
|
21
26
|
"ComponentRef",
|
|
27
|
+
"ComponentItem",
|
|
22
28
|
"GraphEdge",
|
|
23
29
|
"GraphNode",
|
|
30
|
+
"OperationItem",
|
|
24
31
|
"PersistentMessageRegistration",
|
|
25
32
|
"Port",
|
|
33
|
+
"ProcessItem",
|
|
26
34
|
"Production",
|
|
27
35
|
"ProductionDiff",
|
|
28
36
|
"ProductionDiffEntry",
|
|
@@ -31,6 +39,8 @@ __all__ = [
|
|
|
31
39
|
"ProductionValidationIssue",
|
|
32
40
|
"ProductionValidationReport",
|
|
33
41
|
"ProductionValidationWarning",
|
|
42
|
+
"Route",
|
|
43
|
+
"ServiceItem",
|
|
34
44
|
"TargetSetting",
|
|
35
45
|
"resolve_target",
|
|
36
46
|
"target",
|
iop/production/common.py
CHANGED
|
@@ -22,6 +22,11 @@ PRODUCTION_SETTING_FIELDS_BY_IRIS: dict[str, str] = {
|
|
|
22
22
|
for field_name, (iris_name, _default) in PRODUCTION_SETTING_FIELDS.items()
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
SETTING_NAME_ALIASES = {
|
|
26
|
+
"target_config_name": "TargetConfigName",
|
|
27
|
+
"target_config_names": "TargetConfigNames",
|
|
28
|
+
}
|
|
29
|
+
|
|
25
30
|
|
|
26
31
|
def _bool_text(value: bool | str) -> str:
|
|
27
32
|
if isinstance(value, bool):
|
|
@@ -0,0 +1,165 @@
|
|
|
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
|
|
6
|
+
|
|
7
|
+
from .common import SETTING_NAME_ALIASES
|
|
8
|
+
from .types import TargetSetting
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class Route:
|
|
13
|
+
"""Declarative route from a production item port to one or more targets."""
|
|
14
|
+
|
|
15
|
+
port: str | TargetSetting
|
|
16
|
+
targets: str | Iterable[str]
|
|
17
|
+
logical_name: str = ""
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def port_name(self) -> str:
|
|
21
|
+
return normalize_route_port(self.port)
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def port_owner(self) -> type | None:
|
|
25
|
+
if isinstance(self.port, TargetSetting):
|
|
26
|
+
return self.port.owner
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def route_logical_name(self) -> str:
|
|
31
|
+
if self.logical_name:
|
|
32
|
+
return self.logical_name
|
|
33
|
+
if isinstance(self.port, TargetSetting):
|
|
34
|
+
return self.port.logical_name
|
|
35
|
+
return ""
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def target_names(self) -> tuple[str, ...]:
|
|
39
|
+
if isinstance(self.targets, str):
|
|
40
|
+
targets = (self.targets,)
|
|
41
|
+
else:
|
|
42
|
+
targets = tuple(str(target) for target in self.targets)
|
|
43
|
+
if not targets:
|
|
44
|
+
raise ValueError(f"Route {self.port_name!r} requires at least one target")
|
|
45
|
+
return targets
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class _ProductionItemDeclaration:
|
|
50
|
+
name: str
|
|
51
|
+
component: type | str | None = None
|
|
52
|
+
class_name: str | None = None
|
|
53
|
+
adapter_class: type | str | None = None
|
|
54
|
+
adapter_class_name: str | None = None
|
|
55
|
+
enabled: bool | str = True
|
|
56
|
+
pool_size: int | str = 1
|
|
57
|
+
category: str = ""
|
|
58
|
+
foreground: bool | str = False
|
|
59
|
+
comment: str = ""
|
|
60
|
+
log_trace_events: bool | str = False
|
|
61
|
+
schedule: str = ""
|
|
62
|
+
settings: Mapping[str, Any] | None = None
|
|
63
|
+
host_settings: Mapping[str, Any] | None = None
|
|
64
|
+
adapter_settings: Mapping[str, Any] | None = None
|
|
65
|
+
other_settings: Iterable[Mapping[str, Any]] | None = None
|
|
66
|
+
routes: Route | Iterable[Route] | None = field(default_factory=tuple)
|
|
67
|
+
|
|
68
|
+
kind: ClassVar[str] = "component"
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def host_setting_values(self) -> dict[str, Any]:
|
|
72
|
+
settings = _mapping(self.settings, "settings", self.name)
|
|
73
|
+
host_settings = _mapping(self.host_settings, "host_settings", self.name)
|
|
74
|
+
duplicates = sorted(settings.keys() & host_settings.keys())
|
|
75
|
+
if duplicates:
|
|
76
|
+
names = ", ".join(repr(name) for name in duplicates)
|
|
77
|
+
raise ValueError(
|
|
78
|
+
f"{self.kind.title()} item {self.name!r} declares duplicate "
|
|
79
|
+
f"Host setting keys: {names}"
|
|
80
|
+
)
|
|
81
|
+
merged = dict(settings)
|
|
82
|
+
merged.update(host_settings)
|
|
83
|
+
return merged
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def adapter_setting_values(self) -> dict[str, Any]:
|
|
87
|
+
return _mapping(self.adapter_settings, "adapter_settings", self.name)
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def other_setting_values(self) -> list[dict[str, Any]]:
|
|
91
|
+
return [dict(setting) for setting in self.other_settings or ()]
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def route_values(self) -> tuple[Route, ...]:
|
|
95
|
+
if self.routes is None:
|
|
96
|
+
return ()
|
|
97
|
+
if isinstance(self.routes, Route):
|
|
98
|
+
return (self.routes,)
|
|
99
|
+
routes = tuple(self.routes)
|
|
100
|
+
for route in routes:
|
|
101
|
+
if not isinstance(route, Route):
|
|
102
|
+
raise TypeError(
|
|
103
|
+
f"{self.kind.title()} item {self.name!r} routes must be Route "
|
|
104
|
+
f"instances"
|
|
105
|
+
)
|
|
106
|
+
return routes
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass(frozen=True)
|
|
110
|
+
class ServiceItem(_ProductionItemDeclaration):
|
|
111
|
+
kind: ClassVar[str] = "service"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass(frozen=True)
|
|
115
|
+
class ComponentItem(_ProductionItemDeclaration):
|
|
116
|
+
kind: ClassVar[str] = "component"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass(frozen=True)
|
|
120
|
+
class ProcessItem(_ProductionItemDeclaration):
|
|
121
|
+
kind: ClassVar[str] = "process"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass(frozen=True)
|
|
125
|
+
class OperationItem(_ProductionItemDeclaration):
|
|
126
|
+
kind: ClassVar[str] = "operation"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def normalize_route_port(name: str | TargetSetting) -> str:
|
|
130
|
+
"""Normalize known Pythonic route aliases without changing other settings."""
|
|
131
|
+
|
|
132
|
+
if isinstance(name, TargetSetting):
|
|
133
|
+
if not name.name:
|
|
134
|
+
raise ValueError(
|
|
135
|
+
"Route target setting must be declared on a component class"
|
|
136
|
+
)
|
|
137
|
+
return name.name
|
|
138
|
+
port_name = str(name)
|
|
139
|
+
return SETTING_NAME_ALIASES.get(port_name, port_name)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def normalize_route_port_for_match(name: str | TargetSetting) -> str:
|
|
143
|
+
return normalize_route_port(name)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _mapping(
|
|
147
|
+
values: Mapping[str, Any] | None,
|
|
148
|
+
field_name: str,
|
|
149
|
+
item_name: str,
|
|
150
|
+
) -> dict[str, Any]:
|
|
151
|
+
try:
|
|
152
|
+
return dict(values or {})
|
|
153
|
+
except (TypeError, ValueError) as exc:
|
|
154
|
+
raise TypeError(
|
|
155
|
+
f"Production item {item_name!r} {field_name} must be a mapping"
|
|
156
|
+
) from exc
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
__all__ = [
|
|
160
|
+
"ComponentItem",
|
|
161
|
+
"OperationItem",
|
|
162
|
+
"ProcessItem",
|
|
163
|
+
"Route",
|
|
164
|
+
"ServiceItem",
|
|
165
|
+
]
|
iop/production/model.py
CHANGED
|
@@ -11,11 +11,16 @@ from .common import (
|
|
|
11
11
|
_auto_proxy_class_name,
|
|
12
12
|
)
|
|
13
13
|
from .component import ComponentRef
|
|
14
|
+
from .declarations import (
|
|
15
|
+
_ProductionItemDeclaration,
|
|
16
|
+
normalize_route_port_for_match,
|
|
17
|
+
)
|
|
14
18
|
from .diff import _diff_productions
|
|
15
19
|
from .inspection import component_runtime_info, inspect_component
|
|
16
20
|
from .reconstruction import production_from_dict
|
|
17
21
|
from .rendering import (
|
|
18
22
|
production_graph,
|
|
23
|
+
production_to_class,
|
|
19
24
|
production_to_dict,
|
|
20
25
|
production_to_python,
|
|
21
26
|
production_to_xml,
|
|
@@ -30,6 +35,8 @@ from .types import (
|
|
|
30
35
|
_edge_identity,
|
|
31
36
|
)
|
|
32
37
|
|
|
38
|
+
_MISSING = object()
|
|
39
|
+
|
|
33
40
|
|
|
34
41
|
class Production:
|
|
35
42
|
"""Python authoring DSL for IRIS interoperability production topology.
|
|
@@ -41,42 +48,65 @@ class Production:
|
|
|
41
48
|
|
|
42
49
|
def __init__(
|
|
43
50
|
self,
|
|
44
|
-
name: str,
|
|
51
|
+
name: str | object = _MISSING,
|
|
45
52
|
*,
|
|
46
|
-
testing_enabled: bool | str =
|
|
47
|
-
log_general_trace_events: bool | str =
|
|
48
|
-
actor_pool_size: int | str =
|
|
49
|
-
description: str =
|
|
50
|
-
shutdown_timeout: int | str =
|
|
51
|
-
update_timeout: int | str =
|
|
52
|
-
alert_notification_manager: str =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"alert_notification_recipients"
|
|
60
|
-
][1],
|
|
61
|
-
alert_action_window: int | str = PRODUCTION_SETTING_FIELDS[
|
|
62
|
-
"alert_action_window"
|
|
63
|
-
][1],
|
|
64
|
-
namespace: str | None = None,
|
|
65
|
-
director: _DirectorProtocol | None = None,
|
|
53
|
+
testing_enabled: bool | str | object = _MISSING,
|
|
54
|
+
log_general_trace_events: bool | str | object = _MISSING,
|
|
55
|
+
actor_pool_size: int | str | object = _MISSING,
|
|
56
|
+
description: str | object = _MISSING,
|
|
57
|
+
shutdown_timeout: int | str | object = _MISSING,
|
|
58
|
+
update_timeout: int | str | object = _MISSING,
|
|
59
|
+
alert_notification_manager: str | object = _MISSING,
|
|
60
|
+
alert_notification_operation: str | object = _MISSING,
|
|
61
|
+
alert_notification_recipients: str | object = _MISSING,
|
|
62
|
+
alert_action_window: int | str | object = _MISSING,
|
|
63
|
+
namespace: str | None | object = _MISSING,
|
|
64
|
+
director: _DirectorProtocol | None | object = _MISSING,
|
|
65
|
+
_hydrate_declarations: bool = True,
|
|
66
66
|
):
|
|
67
|
-
self.name = name
|
|
68
|
-
self.testing_enabled =
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
self.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
self.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
self.
|
|
78
|
-
self.
|
|
79
|
-
|
|
67
|
+
self.name = self._resolve_production_name(name)
|
|
68
|
+
self.testing_enabled = self._production_default(
|
|
69
|
+
"testing_enabled", testing_enabled, False
|
|
70
|
+
)
|
|
71
|
+
self.log_general_trace_events = self._production_default(
|
|
72
|
+
"log_general_trace_events", log_general_trace_events, False
|
|
73
|
+
)
|
|
74
|
+
self.actor_pool_size = self._production_default(
|
|
75
|
+
"actor_pool_size", actor_pool_size, 2
|
|
76
|
+
)
|
|
77
|
+
self.description = self._production_default("description", description, "")
|
|
78
|
+
self.shutdown_timeout = self._production_default(
|
|
79
|
+
"shutdown_timeout",
|
|
80
|
+
shutdown_timeout,
|
|
81
|
+
PRODUCTION_SETTING_FIELDS["shutdown_timeout"][1],
|
|
82
|
+
)
|
|
83
|
+
self.update_timeout = self._production_default(
|
|
84
|
+
"update_timeout",
|
|
85
|
+
update_timeout,
|
|
86
|
+
PRODUCTION_SETTING_FIELDS["update_timeout"][1],
|
|
87
|
+
)
|
|
88
|
+
self.alert_notification_manager = self._production_default(
|
|
89
|
+
"alert_notification_manager",
|
|
90
|
+
alert_notification_manager,
|
|
91
|
+
PRODUCTION_SETTING_FIELDS["alert_notification_manager"][1],
|
|
92
|
+
)
|
|
93
|
+
self.alert_notification_operation = self._production_default(
|
|
94
|
+
"alert_notification_operation",
|
|
95
|
+
alert_notification_operation,
|
|
96
|
+
PRODUCTION_SETTING_FIELDS["alert_notification_operation"][1],
|
|
97
|
+
)
|
|
98
|
+
self.alert_notification_recipients = self._production_default(
|
|
99
|
+
"alert_notification_recipients",
|
|
100
|
+
alert_notification_recipients,
|
|
101
|
+
PRODUCTION_SETTING_FIELDS["alert_notification_recipients"][1],
|
|
102
|
+
)
|
|
103
|
+
self.alert_action_window = self._production_default(
|
|
104
|
+
"alert_action_window",
|
|
105
|
+
alert_action_window,
|
|
106
|
+
PRODUCTION_SETTING_FIELDS["alert_action_window"][1],
|
|
107
|
+
)
|
|
108
|
+
self.namespace = self._production_default("namespace", namespace, None)
|
|
109
|
+
self._director = self._production_default("director", director, None)
|
|
80
110
|
self._items: list[ComponentRef] = []
|
|
81
111
|
self._items_by_name: dict[str, ComponentRef] = {}
|
|
82
112
|
self._connections: dict[tuple[str, str], list[str]] = {}
|
|
@@ -84,6 +114,166 @@ class Production:
|
|
|
84
114
|
self._graph_warnings: list[str] = []
|
|
85
115
|
self._queue_info: dict[str, dict[str, Any]] = {}
|
|
86
116
|
self._messages: list[PersistentMessageRegistration] = []
|
|
117
|
+
if _hydrate_declarations:
|
|
118
|
+
self._hydrate_declared_items()
|
|
119
|
+
|
|
120
|
+
def _resolve_production_name(self, name: str | object) -> str:
|
|
121
|
+
if name is not _MISSING:
|
|
122
|
+
return name # type: ignore[return-value]
|
|
123
|
+
class_name = getattr(type(self), "name", _MISSING)
|
|
124
|
+
if class_name is not _MISSING:
|
|
125
|
+
return class_name
|
|
126
|
+
return f"{type(self).__module__}.{type(self).__name__}"
|
|
127
|
+
|
|
128
|
+
def _production_default(
|
|
129
|
+
self,
|
|
130
|
+
field_name: str,
|
|
131
|
+
explicit: Any,
|
|
132
|
+
default: Any,
|
|
133
|
+
) -> Any:
|
|
134
|
+
if explicit is not _MISSING:
|
|
135
|
+
return explicit
|
|
136
|
+
class_value = getattr(type(self), field_name, _MISSING)
|
|
137
|
+
if class_value is not _MISSING:
|
|
138
|
+
return class_value
|
|
139
|
+
return default
|
|
140
|
+
|
|
141
|
+
def _hydrate_declared_items(self) -> None:
|
|
142
|
+
declarations: list[_ProductionItemDeclaration] = []
|
|
143
|
+
for attr_name, expected_kind in (
|
|
144
|
+
("components", "component"),
|
|
145
|
+
("services", "service"),
|
|
146
|
+
("processes", "process"),
|
|
147
|
+
("operations", "operation"),
|
|
148
|
+
):
|
|
149
|
+
for declaration in self._declared_items(attr_name):
|
|
150
|
+
if declaration.kind != expected_kind:
|
|
151
|
+
raise TypeError(
|
|
152
|
+
f"{attr_name} must contain {expected_kind.title()}Item "
|
|
153
|
+
f"declarations"
|
|
154
|
+
)
|
|
155
|
+
self._raise_declared_route_conflicts(declaration)
|
|
156
|
+
self._add_declared_item(declaration)
|
|
157
|
+
declarations.append(declaration)
|
|
158
|
+
|
|
159
|
+
for declaration in declarations:
|
|
160
|
+
self._connect_declared_routes(declaration)
|
|
161
|
+
|
|
162
|
+
def _declared_items(self, attr_name: str) -> tuple[_ProductionItemDeclaration, ...]:
|
|
163
|
+
values = getattr(type(self), attr_name, ())
|
|
164
|
+
if values is None:
|
|
165
|
+
return ()
|
|
166
|
+
if isinstance(values, _ProductionItemDeclaration):
|
|
167
|
+
return (values,)
|
|
168
|
+
try:
|
|
169
|
+
declarations = tuple(values)
|
|
170
|
+
except TypeError as exc:
|
|
171
|
+
raise TypeError(f"{attr_name} must be an iterable of item declarations") from exc
|
|
172
|
+
for declaration in declarations:
|
|
173
|
+
if not isinstance(declaration, _ProductionItemDeclaration):
|
|
174
|
+
raise TypeError(f"{attr_name} must contain production item declarations")
|
|
175
|
+
return declarations
|
|
176
|
+
|
|
177
|
+
def _raise_declared_route_conflicts(
|
|
178
|
+
self,
|
|
179
|
+
declaration: _ProductionItemDeclaration,
|
|
180
|
+
) -> None:
|
|
181
|
+
host_ports = {
|
|
182
|
+
normalize_route_port_for_match(name)
|
|
183
|
+
for name in declaration.host_setting_values
|
|
184
|
+
}
|
|
185
|
+
route_ports = {route.port_name for route in declaration.route_values}
|
|
186
|
+
conflicts = sorted(host_ports & route_ports)
|
|
187
|
+
if not conflicts:
|
|
188
|
+
return
|
|
189
|
+
names = ", ".join(repr(name) for name in conflicts)
|
|
190
|
+
raise ValueError(
|
|
191
|
+
f"{declaration.kind.title()} item {declaration.name!r} declares route "
|
|
192
|
+
f"port(s) in Host settings: {names}. Declare route ports with Route only."
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def _add_declared_item(self, declaration: _ProductionItemDeclaration) -> None:
|
|
196
|
+
kwargs: dict[str, Any] = {
|
|
197
|
+
"enabled": declaration.enabled,
|
|
198
|
+
"pool_size": declaration.pool_size,
|
|
199
|
+
"category": declaration.category,
|
|
200
|
+
"foreground": declaration.foreground,
|
|
201
|
+
"comment": declaration.comment,
|
|
202
|
+
"log_trace_events": declaration.log_trace_events,
|
|
203
|
+
"schedule": declaration.schedule,
|
|
204
|
+
"settings": declaration.host_setting_values,
|
|
205
|
+
"adapter_settings": declaration.adapter_setting_values,
|
|
206
|
+
}
|
|
207
|
+
if declaration.adapter_class is not None:
|
|
208
|
+
kwargs["adapter_class"] = declaration.adapter_class
|
|
209
|
+
if declaration.adapter_class_name is not None:
|
|
210
|
+
kwargs["adapter_class_name"] = declaration.adapter_class_name
|
|
211
|
+
|
|
212
|
+
method = {
|
|
213
|
+
"component": self.component,
|
|
214
|
+
"service": self.service,
|
|
215
|
+
"process": self.process,
|
|
216
|
+
"operation": self.operation,
|
|
217
|
+
}[declaration.kind]
|
|
218
|
+
|
|
219
|
+
component = declaration.component
|
|
220
|
+
if isinstance(component, type):
|
|
221
|
+
if declaration.class_name is not None:
|
|
222
|
+
kwargs["class_name"] = declaration.class_name
|
|
223
|
+
ref = method(declaration.name, component, **kwargs)
|
|
224
|
+
ref.other_settings = declaration.other_setting_values
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
class_name = declaration.class_name
|
|
228
|
+
if component is not None:
|
|
229
|
+
component_class_name = str(component)
|
|
230
|
+
if class_name is not None and class_name != component_class_name:
|
|
231
|
+
raise ValueError(
|
|
232
|
+
f"{declaration.kind.title()} item {declaration.name!r} "
|
|
233
|
+
"declares conflicting component and class_name values"
|
|
234
|
+
)
|
|
235
|
+
class_name = component_class_name
|
|
236
|
+
|
|
237
|
+
if class_name is None:
|
|
238
|
+
raise ValueError(
|
|
239
|
+
f"{declaration.kind.title()} item {declaration.name!r} requires "
|
|
240
|
+
"a component class or class_name"
|
|
241
|
+
)
|
|
242
|
+
kwargs["class_name"] = class_name
|
|
243
|
+
ref = method(declaration.name, **kwargs)
|
|
244
|
+
ref.other_settings = declaration.other_setting_values
|
|
245
|
+
|
|
246
|
+
def _connect_declared_routes(self, declaration: _ProductionItemDeclaration) -> None:
|
|
247
|
+
source = self.item(declaration.name)
|
|
248
|
+
for route in declaration.route_values:
|
|
249
|
+
self._raise_if_route_port_owner_mismatch(source, route)
|
|
250
|
+
port = source.port(
|
|
251
|
+
route.port_name,
|
|
252
|
+
logical_name=route.route_logical_name,
|
|
253
|
+
)
|
|
254
|
+
targets = route.target_names
|
|
255
|
+
self.connect(port, targets[0])
|
|
256
|
+
for target in targets[1:]:
|
|
257
|
+
self.connect_add(port, target)
|
|
258
|
+
|
|
259
|
+
def _raise_if_route_port_owner_mismatch(
|
|
260
|
+
self,
|
|
261
|
+
source: ComponentRef,
|
|
262
|
+
route: Any,
|
|
263
|
+
) -> None:
|
|
264
|
+
owner = route.port_owner
|
|
265
|
+
if (
|
|
266
|
+
owner is None
|
|
267
|
+
or source.component_class is None
|
|
268
|
+
or issubclass(source.component_class, owner)
|
|
269
|
+
):
|
|
270
|
+
return
|
|
271
|
+
raise ValueError(
|
|
272
|
+
f"Route port {route.port_name!r} belongs to "
|
|
273
|
+
f"{owner.__module__}.{owner.__qualname__}, not "
|
|
274
|
+
f"{source.component_class.__module__}."
|
|
275
|
+
f"{source.component_class.__qualname__}"
|
|
276
|
+
)
|
|
87
277
|
|
|
88
278
|
def testing(self, enabled: bool | str = True) -> Production:
|
|
89
279
|
self.testing_enabled = enabled
|
|
@@ -147,7 +337,12 @@ class Production:
|
|
|
147
337
|
namespace: str | None = None,
|
|
148
338
|
director: _DirectorProtocol | None = None,
|
|
149
339
|
) -> Production:
|
|
150
|
-
seed = cls(
|
|
340
|
+
seed = cls(
|
|
341
|
+
name,
|
|
342
|
+
namespace=namespace,
|
|
343
|
+
director=director,
|
|
344
|
+
_hydrate_declarations=False,
|
|
345
|
+
)
|
|
151
346
|
runtime_director = _ProductionRuntime(seed).director
|
|
152
347
|
exported = runtime_director.export_production(name)
|
|
153
348
|
connections = None
|
|
@@ -553,6 +748,9 @@ class Production:
|
|
|
553
748
|
def to_python(self) -> str:
|
|
554
749
|
return production_to_python(self)
|
|
555
750
|
|
|
751
|
+
def to_class(self) -> str:
|
|
752
|
+
return production_to_class(self)
|
|
753
|
+
|
|
556
754
|
def graph(self) -> ProductionGraph:
|
|
557
755
|
return production_graph(self)
|
|
558
756
|
|
iop/production/reconstruction.py
CHANGED
|
@@ -48,6 +48,7 @@ def production_from_dict(
|
|
|
48
48
|
alert_action_window=production_settings.get("alert_action_window", 60),
|
|
49
49
|
namespace=namespace,
|
|
50
50
|
director=director,
|
|
51
|
+
_hydrate_declarations=False,
|
|
51
52
|
)
|
|
52
53
|
|
|
53
54
|
_add_imported_items(production, production_data)
|
|
@@ -117,9 +118,7 @@ def _apply_runtime_item_metadata(production, connections: Any) -> None:
|
|
|
117
118
|
def _apply_runtime_connections(production, connections: Any) -> tuple[set[str], set[str]]:
|
|
118
119
|
connection_map, runtime_sources, warnings = _normalize_connections(connections)
|
|
119
120
|
production._graph_warnings.extend(warnings)
|
|
120
|
-
runtime_sources_with_targets =
|
|
121
|
-
source_item for source_item, targets in connection_map.items() if targets
|
|
122
|
-
}
|
|
121
|
+
runtime_sources_with_targets: set[str] = set()
|
|
123
122
|
|
|
124
123
|
for source_item, targets in connection_map.items():
|
|
125
124
|
if source_item not in production._items_by_name:
|
|
@@ -144,6 +143,7 @@ def _apply_runtime_connections(production, connections: Any) -> tuple[set[str],
|
|
|
144
143
|
f"Runtime connection target does not exist: "
|
|
145
144
|
f"{source_item} -> {target_name}"
|
|
146
145
|
)
|
|
146
|
+
runtime_sources_with_targets.add(source_item)
|
|
147
147
|
production._register_connection(
|
|
148
148
|
source_item,
|
|
149
149
|
source_port,
|
iop/production/rendering.py
CHANGED
|
@@ -86,6 +86,280 @@ def production_to_python(production) -> str:
|
|
|
86
86
|
return "\n".join(lines) + "\n"
|
|
87
87
|
|
|
88
88
|
|
|
89
|
+
def production_to_class(production) -> str:
|
|
90
|
+
item_names = {item.name for item in production.items}
|
|
91
|
+
route_targets = _valid_route_targets(_route_targets(production, item_names), item_names)
|
|
92
|
+
item_groups = _class_item_groups(production.items)
|
|
93
|
+
used_items = {
|
|
94
|
+
_class_item_type(kind)
|
|
95
|
+
for kind, items in item_groups.items()
|
|
96
|
+
if items
|
|
97
|
+
}
|
|
98
|
+
has_routes = bool(route_targets)
|
|
99
|
+
imports = ["Production", *sorted(used_items)]
|
|
100
|
+
if has_routes:
|
|
101
|
+
imports.append("Route")
|
|
102
|
+
|
|
103
|
+
class_name = _production_class_name(production.name)
|
|
104
|
+
lines = [
|
|
105
|
+
"# Generated from IRIS production export.",
|
|
106
|
+
"# Review before using as source of truth; some runtime/dynamic routing intent",
|
|
107
|
+
"# cannot be fully reconstructed from deployed IRIS metadata.",
|
|
108
|
+
f"from iop import {', '.join(imports)}",
|
|
109
|
+
"",
|
|
110
|
+
"",
|
|
111
|
+
f"class {class_name}(Production):",
|
|
112
|
+
f" name = {_literal(production.name)}",
|
|
113
|
+
]
|
|
114
|
+
lines.extend(_class_production_setting_lines(production))
|
|
115
|
+
lines.append("")
|
|
116
|
+
|
|
117
|
+
for kind in ("component", "service", "process", "operation"):
|
|
118
|
+
items = item_groups.get(kind, [])
|
|
119
|
+
if not items:
|
|
120
|
+
continue
|
|
121
|
+
attr_name = _class_item_attr(kind)
|
|
122
|
+
lines.append(f" {attr_name} = [")
|
|
123
|
+
for item in items:
|
|
124
|
+
lines.extend(_class_item_lines(item, kind, route_targets))
|
|
125
|
+
lines.append(" ]")
|
|
126
|
+
lines.append("")
|
|
127
|
+
|
|
128
|
+
unresolved = _unresolved_class_routes(production, item_names)
|
|
129
|
+
if unresolved:
|
|
130
|
+
lines.append(" # TODO: review unresolved or runtime-only routes:")
|
|
131
|
+
lines.extend(f" # - {value}" for value in unresolved)
|
|
132
|
+
lines.append("")
|
|
133
|
+
if production.graph().warnings:
|
|
134
|
+
lines.append(" # Import warnings:")
|
|
135
|
+
lines.extend(f" # - {warning}" for warning in production.graph().warnings)
|
|
136
|
+
lines.append("")
|
|
137
|
+
|
|
138
|
+
lines.append("")
|
|
139
|
+
lines.append(f"PRODUCTIONS = [{class_name}()]")
|
|
140
|
+
return "\n".join(lines) + "\n"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _class_production_setting_lines(production) -> list[str]:
|
|
144
|
+
settings = [
|
|
145
|
+
("testing_enabled", _bool_literal(production.testing_enabled), False),
|
|
146
|
+
(
|
|
147
|
+
"log_general_trace_events",
|
|
148
|
+
_bool_literal(production.log_general_trace_events),
|
|
149
|
+
False,
|
|
150
|
+
),
|
|
151
|
+
("actor_pool_size", _int_literal(production.actor_pool_size), 2),
|
|
152
|
+
("description", production.description, ""),
|
|
153
|
+
("shutdown_timeout", _int_literal(production.shutdown_timeout), 120),
|
|
154
|
+
("update_timeout", _int_literal(production.update_timeout), 10),
|
|
155
|
+
("alert_notification_manager", production.alert_notification_manager, ""),
|
|
156
|
+
("alert_notification_operation", production.alert_notification_operation, ""),
|
|
157
|
+
("alert_notification_recipients", production.alert_notification_recipients, ""),
|
|
158
|
+
("alert_action_window", _int_literal(production.alert_action_window), 60),
|
|
159
|
+
]
|
|
160
|
+
return [
|
|
161
|
+
f" {name} = {_literal(value)}"
|
|
162
|
+
for name, value, default in settings
|
|
163
|
+
if value != default
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _class_item_groups(items) -> dict[str, list[Any]]:
|
|
168
|
+
groups: dict[str, list[Any]] = {
|
|
169
|
+
"component": [],
|
|
170
|
+
"service": [],
|
|
171
|
+
"process": [],
|
|
172
|
+
"operation": [],
|
|
173
|
+
}
|
|
174
|
+
for item in items:
|
|
175
|
+
groups[_class_item_kind(item)].append(item)
|
|
176
|
+
return groups
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _class_item_kind(item) -> str:
|
|
180
|
+
if item.kind in {"service", "process", "operation"}:
|
|
181
|
+
return item.kind
|
|
182
|
+
class_name = str(item.class_name or "").lower()
|
|
183
|
+
if "service" in class_name:
|
|
184
|
+
return "service"
|
|
185
|
+
if "process" in class_name:
|
|
186
|
+
return "process"
|
|
187
|
+
if "operation" in class_name:
|
|
188
|
+
return "operation"
|
|
189
|
+
return "component"
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _class_item_type(kind: str) -> str:
|
|
193
|
+
return {
|
|
194
|
+
"component": "ComponentItem",
|
|
195
|
+
"service": "ServiceItem",
|
|
196
|
+
"process": "ProcessItem",
|
|
197
|
+
"operation": "OperationItem",
|
|
198
|
+
}[kind]
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _class_item_attr(kind: str) -> str:
|
|
202
|
+
return {
|
|
203
|
+
"component": "components",
|
|
204
|
+
"service": "services",
|
|
205
|
+
"process": "processes",
|
|
206
|
+
"operation": "operations",
|
|
207
|
+
}[kind]
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _class_item_lines(item, kind: str, route_targets) -> list[str]:
|
|
211
|
+
kwargs: list[tuple[str, Any]] = []
|
|
212
|
+
optional_fields = [
|
|
213
|
+
("category", item.category, ""),
|
|
214
|
+
("pool_size", _int_literal(item.pool_size), 1),
|
|
215
|
+
("enabled", _bool_literal(item.enabled), True),
|
|
216
|
+
("foreground", _bool_literal(item.foreground), False),
|
|
217
|
+
("comment", item.comment, ""),
|
|
218
|
+
("log_trace_events", _bool_literal(item.log_trace_events), False),
|
|
219
|
+
("schedule", item.schedule, ""),
|
|
220
|
+
]
|
|
221
|
+
kwargs.extend(
|
|
222
|
+
(name, value)
|
|
223
|
+
for name, value, default in optional_fields
|
|
224
|
+
if value != default
|
|
225
|
+
)
|
|
226
|
+
settings = {
|
|
227
|
+
name: value
|
|
228
|
+
for name, value in item.host_settings.items()
|
|
229
|
+
if (item.name, name) not in route_targets
|
|
230
|
+
}
|
|
231
|
+
if settings:
|
|
232
|
+
kwargs.append(("settings", settings))
|
|
233
|
+
if item.adapter_settings:
|
|
234
|
+
kwargs.append(("adapter_settings", dict(item.adapter_settings)))
|
|
235
|
+
if item.other_settings:
|
|
236
|
+
kwargs.append(("other_settings", [dict(value) for value in item.other_settings]))
|
|
237
|
+
|
|
238
|
+
routes = _class_item_route_values(item, route_targets)
|
|
239
|
+
if routes:
|
|
240
|
+
kwargs.append(("routes", routes))
|
|
241
|
+
|
|
242
|
+
lines = [
|
|
243
|
+
f" {_class_item_type(kind)}(",
|
|
244
|
+
f" {_literal(item.name)},",
|
|
245
|
+
f" {_literal(item.class_name or '')},",
|
|
246
|
+
]
|
|
247
|
+
for name, value in kwargs:
|
|
248
|
+
lines.extend(_class_keyword_lines(name, value))
|
|
249
|
+
lines.append(" ),")
|
|
250
|
+
return lines
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _class_item_route_values(item, route_targets) -> list[tuple[str, list[str]]]:
|
|
254
|
+
values: list[tuple[str, list[str]]] = []
|
|
255
|
+
for source_item, source_port in sorted(route_targets):
|
|
256
|
+
if source_item != item.name:
|
|
257
|
+
continue
|
|
258
|
+
targets = route_targets[(source_item, source_port)]
|
|
259
|
+
values.append((source_port, list(targets)))
|
|
260
|
+
return values
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _valid_route_targets(
|
|
264
|
+
route_targets: dict[tuple[str, str], list[str]],
|
|
265
|
+
item_names: set[str],
|
|
266
|
+
) -> dict[tuple[str, str], list[str]]:
|
|
267
|
+
valid_routes: dict[tuple[str, str], list[str]] = {}
|
|
268
|
+
for source, targets in route_targets.items():
|
|
269
|
+
valid_targets = [target for target in targets if target in item_names]
|
|
270
|
+
if valid_targets:
|
|
271
|
+
valid_routes[source] = valid_targets
|
|
272
|
+
return valid_routes
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _class_keyword_lines(name: str, value: Any) -> list[str]:
|
|
276
|
+
if isinstance(value, dict):
|
|
277
|
+
return _indented_dict_keyword_lines(name, value, indent=12)
|
|
278
|
+
if isinstance(value, list) and name == "routes":
|
|
279
|
+
return _class_route_keyword_lines(value)
|
|
280
|
+
if isinstance(value, list):
|
|
281
|
+
return _indented_list_keyword_lines(name, value, indent=12)
|
|
282
|
+
return [f" {name}={_literal(value)},"]
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _class_route_keyword_lines(routes: list[tuple[str, list[str]]]) -> list[str]:
|
|
286
|
+
if len(routes) == 1:
|
|
287
|
+
port, targets = routes[0]
|
|
288
|
+
target_literal = _literal(targets[0]) if len(targets) == 1 else _literal(targets)
|
|
289
|
+
return [f" routes=[Route({_literal(port)}, {target_literal})],"]
|
|
290
|
+
|
|
291
|
+
lines = [" routes=["]
|
|
292
|
+
for port, targets in routes:
|
|
293
|
+
target_literal = _literal(targets[0]) if len(targets) == 1 else _literal(targets)
|
|
294
|
+
lines.append(f" Route({_literal(port)}, {target_literal}),")
|
|
295
|
+
lines.append(" ],")
|
|
296
|
+
return lines
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _indented_dict_keyword_lines(
|
|
300
|
+
name: str,
|
|
301
|
+
value: dict[str, Any],
|
|
302
|
+
*,
|
|
303
|
+
indent: int,
|
|
304
|
+
) -> list[str]:
|
|
305
|
+
prefix = " " * indent
|
|
306
|
+
item_prefix = " " * (indent + 4)
|
|
307
|
+
lines = [f"{prefix}{name}={{"]
|
|
308
|
+
for key, item in value.items():
|
|
309
|
+
lines.append(f"{item_prefix}{_literal(key)}: {_literal(item)},")
|
|
310
|
+
lines.append(f"{prefix}}},")
|
|
311
|
+
return lines
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _indented_list_keyword_lines(
|
|
315
|
+
name: str,
|
|
316
|
+
value: list[Any],
|
|
317
|
+
*,
|
|
318
|
+
indent: int,
|
|
319
|
+
) -> list[str]:
|
|
320
|
+
prefix = " " * indent
|
|
321
|
+
item_prefix = " " * (indent + 4)
|
|
322
|
+
lines = [f"{prefix}{name}=["]
|
|
323
|
+
for item in value:
|
|
324
|
+
lines.append(f"{item_prefix}{_literal(item)},")
|
|
325
|
+
lines.append(f"{prefix}],")
|
|
326
|
+
return lines
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _unresolved_class_routes(production, item_names: set[str]) -> list[str]:
|
|
330
|
+
unresolved: list[str] = []
|
|
331
|
+
for edge in production.edges:
|
|
332
|
+
if not edge.source_port:
|
|
333
|
+
unresolved.append(f"{edge.source_item} -> {edge.target}")
|
|
334
|
+
elif edge.target not in item_names:
|
|
335
|
+
unresolved.append(f"{edge.source_item}.{edge.source_port} -> {edge.target}")
|
|
336
|
+
return sorted(set(unresolved))
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _production_class_name(production_name: str) -> str:
|
|
340
|
+
parts = [
|
|
341
|
+
part
|
|
342
|
+
for part in re.split(r"\W+", production_name)
|
|
343
|
+
if part
|
|
344
|
+
]
|
|
345
|
+
if not parts:
|
|
346
|
+
return "GeneratedProduction"
|
|
347
|
+
candidate = parts[-1]
|
|
348
|
+
if candidate == "Production" and len(parts) > 1:
|
|
349
|
+
candidate = "".join(parts[-2:])
|
|
350
|
+
else:
|
|
351
|
+
candidate = "".join(
|
|
352
|
+
part[:1].upper() + part[1:]
|
|
353
|
+
for part in re.split(r"[_\s]+", candidate)
|
|
354
|
+
if part
|
|
355
|
+
)
|
|
356
|
+
if not candidate or not candidate[0].isalpha():
|
|
357
|
+
candidate = f"Generated{candidate}"
|
|
358
|
+
if keyword.iskeyword(candidate) or candidate == "Production":
|
|
359
|
+
candidate = f"{candidate}Definition"
|
|
360
|
+
return candidate
|
|
361
|
+
|
|
362
|
+
|
|
89
363
|
def _production_constructor_lines(production) -> list[str]:
|
|
90
364
|
kwargs = [
|
|
91
365
|
("testing_enabled", _bool_literal(production.testing_enabled), False),
|
iop/production/types.py
CHANGED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any, overload
|
|
6
6
|
|
|
7
7
|
from ..components.settings import Category, Setting, controls
|
|
8
8
|
|
|
@@ -17,6 +17,15 @@ class TargetSetting(Setting):
|
|
|
17
17
|
super().__init__("", **kwargs)
|
|
18
18
|
self.logical_name = logical_name
|
|
19
19
|
|
|
20
|
+
@overload
|
|
21
|
+
def __get__(self, instance: None, owner: type | None = None) -> TargetSetting: ...
|
|
22
|
+
|
|
23
|
+
@overload
|
|
24
|
+
def __get__(self, instance: object, owner: type | None = None) -> str: ...
|
|
25
|
+
|
|
26
|
+
def __get__(self, instance, owner=None):
|
|
27
|
+
return super().__get__(instance, owner)
|
|
28
|
+
|
|
20
29
|
|
|
21
30
|
def target(logical_name: str = "", **kwargs: Any) -> TargetSetting:
|
|
22
31
|
"""Declare an outbound target port on a component class."""
|
iop/production/validation.py
CHANGED
|
@@ -4,7 +4,11 @@ import warnings
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
|
-
from .common import
|
|
7
|
+
from .common import (
|
|
8
|
+
PRODUCTION_SETTING_FIELDS_BY_IRIS,
|
|
9
|
+
PRODUCTION_SETTING_NAMES,
|
|
10
|
+
SETTING_NAME_ALIASES,
|
|
11
|
+
)
|
|
8
12
|
from .import_ import _as_list, _production_payload, _split_settings
|
|
9
13
|
|
|
10
14
|
_PRODUCTION_PUBLIC_FIELDS = {
|
|
@@ -32,11 +36,6 @@ _PRODUCTION_DICT_KEYS = {
|
|
|
32
36
|
"Item",
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
_SETTING_NAME_ALIASES = {
|
|
36
|
-
"target_config_name": "TargetConfigName",
|
|
37
|
-
"target_config_names": "TargetConfigNames",
|
|
38
|
-
}
|
|
39
|
-
|
|
40
39
|
_PRODUCTION_SETTING_NAME_ALIASES = {
|
|
41
40
|
**PRODUCTION_SETTING_NAMES,
|
|
42
41
|
**{iris_name: iris_name for iris_name in PRODUCTION_SETTING_FIELDS_BY_IRIS},
|
|
@@ -350,7 +349,7 @@ def _validate_names_with_known_set(
|
|
|
350
349
|
for setting_name in sorted(settings):
|
|
351
350
|
if setting_name.startswith("%") or setting_name in known_names:
|
|
352
351
|
continue
|
|
353
|
-
alias =
|
|
352
|
+
alias = SETTING_NAME_ALIASES.get(setting_name)
|
|
354
353
|
if alias in known_names:
|
|
355
354
|
issues.append(_setting_alias_issue(path_prefix, setting_name, alias))
|
|
356
355
|
continue
|
|
@@ -372,7 +371,7 @@ def _validate_names_with_iris_dictionary(
|
|
|
372
371
|
for setting_name in sorted(settings):
|
|
373
372
|
if _iris_property_exists(iris, iris_class_name, setting_name):
|
|
374
373
|
continue
|
|
375
|
-
alias =
|
|
374
|
+
alias = SETTING_NAME_ALIASES.get(setting_name)
|
|
376
375
|
if alias and _iris_property_exists(iris, iris_class_name, alias):
|
|
377
376
|
issues.append(_setting_alias_issue(path_prefix, setting_name, alias))
|
|
378
377
|
continue
|
{iris_pex_embedded_python-4.0.0b4.dist-info → iris_pex_embedded_python-4.0.0b5.dist-info}/RECORD
RENAMED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
iop/__init__.py,sha256=
|
|
1
|
+
iop/__init__.py,sha256=XgOE11EBCWbk_lBPUv7oTnrTfg2XcW_9Eygr6lMltTU,4439
|
|
2
2
|
iop/__main__.py,sha256=2rUNM7vaYK0W57Jj72rm7GORyIFLMXKwPq3QNLwYBmY,100
|
|
3
3
|
iop/cli/__init__.py,sha256=Y78pBdA-ULzq1c_9xQZsFBlzm3x5xgxSzkvrH5DoM7I,31
|
|
4
4
|
iop/cli/formatting.py,sha256=OLgHA1d8_paAuINQyKkYgihWMLoBhPn7kSXW5whRlIs,1606
|
|
5
|
-
iop/cli/main.py,sha256=
|
|
6
|
-
iop/cli/parser.py,sha256=
|
|
5
|
+
iop/cli/main.py,sha256=sKiZWZVqxQKdSTK7lHRMDN89A8wrTFSLgN2woXnQCHM,13302
|
|
6
|
+
iop/cli/parser.py,sha256=4Es5xJPR3HVcEgFKn6sONEJQ7bSwZ8yluV1WuofoLf8,4887
|
|
7
7
|
iop/cli/types.py,sha256=7GxUy2KHw_LNOWAQ2-EalKOikIDQFiKjMmEEmlWeOB8,1343
|
|
8
8
|
iop/cls/IOP/BusinessOperation.cls,sha256=NlvbNtm1ZFZmHaMX_9FMKoptY-hQMq5jYN1nLQwvYJw,936
|
|
9
9
|
iop/cls/IOP/BusinessProcess.cls,sha256=XJxzbiV0xokzRm-iI2Be5UIJLE3MlXr7W3WS_LkOCYs,3363
|
|
@@ -16,7 +16,7 @@ iop/cls/IOP/OutboundAdapter.cls,sha256=OQoGFHUy2qV_kcsShTlWGOngDrdH5dhwux4eopZyI
|
|
|
16
16
|
iop/cls/IOP/PickleMessage.cls,sha256=S3y7AClQ8mAILjxPuHdCjGosBZYzGbUQ5WTv4mYPNMQ,1673
|
|
17
17
|
iop/cls/IOP/Projection.cls,sha256=AZgbfpbEk02llhyIwrSw0M3QMcQNcjhjY3_vU_yx8FU,1315
|
|
18
18
|
iop/cls/IOP/Test.cls,sha256=zvlCZJfOCmSFdmi-ZQHWDGYmqZAgRspaJj1j0r_IxJQ,2017
|
|
19
|
-
iop/cls/IOP/Utils.cls,sha256=
|
|
19
|
+
iop/cls/IOP/Utils.cls,sha256=fhKWWouv4ND1-3JHojBQbOH9ufjaiS8M-4n6oM4jCxk,31496
|
|
20
20
|
iop/cls/IOP/Wrapper.cls,sha256=ydlNpqAekoH_XdGk0ZpspX1pxgv__E00Xf57F8oxsqs,1624
|
|
21
21
|
iop/cls/IOP/Duplex/Operation.cls,sha256=K_fmgeLjPZQbHgNrc0kd6DUQoW0fDn1VHQjJxHo95Zk,525
|
|
22
22
|
iop/cls/IOP/Duplex/Process.cls,sha256=xbefZ4z84a_IUhavWN6P_gZBzqkdJ5XRTXxro6iDvAg,6986
|
|
@@ -49,7 +49,7 @@ iop/components/outbound_adapter.py,sha256=Bm6KeaLR0fO3z5o9cmJxcMdIiOvdExik-Oc272
|
|
|
49
49
|
iop/components/polling_business_service.py,sha256=5rVr6DVgfK3xwEfjFHa4dsWb6wI3JXJOQQYtHLAIjSY,901
|
|
50
50
|
iop/components/private_session_duplex.py,sha256=IZ50ThI074qwFF1wFyhFCsSJK0huyuoIeVxwlGyweeQ,5157
|
|
51
51
|
iop/components/private_session_process.py,sha256=edugk8iLTjaHMsz_fBuj9VE8aL66IgOz-mMlqPyk3Zc,1731
|
|
52
|
-
iop/components/settings.py,sha256=
|
|
52
|
+
iop/components/settings.py,sha256=Y-MkpGaMOS-AcvuxVp46hLosQQIn4vMhyugoSKD4ikI,5798
|
|
53
53
|
iop/messages/__init__.py,sha256=k1AKplpjdqSxM5Gl7bpWAfw93xUgigovlI4SKhwKHRI,355
|
|
54
54
|
iop/messages/base.py,sha256=2DBU8CzKLXVYUT3Vp6QeL6QHJbbfOGfoE4Kmkrq8Hpg,1183
|
|
55
55
|
iop/messages/decorators.py,sha256=jOjMllMHaDzS3Apo1h68LIn87boGfDnsQ1OhyyuReA0,2363
|
|
@@ -60,20 +60,21 @@ iop/messages/validation.py,sha256=cbq2VaaWTcqesmV-DDwReHh8peynvEMw9h0JNf4q9kQ,15
|
|
|
60
60
|
iop/migration/__init__.py,sha256=wp-RvjLKYW_Pi2AJLfiWe1bACxENzEgjXtP9UMNiSVY,36
|
|
61
61
|
iop/migration/io.py,sha256=hAehtApFZFgGzjzgwBmDMxdA1xoRhE-okOcEzvgFdQ4,1822
|
|
62
62
|
iop/migration/plans.py,sha256=oj0sNLMBtWZBCoVEoP82RagIFumfTPqmGylDUxGLJNE,6738
|
|
63
|
-
iop/migration/utils.py,sha256=
|
|
64
|
-
iop/production/__init__.py,sha256=
|
|
63
|
+
iop/migration/utils.py,sha256=tSnfQLsmPkuW4HC-fN5CYkYVjrurSAgSeGjiIQohqZQ,41229
|
|
64
|
+
iop/production/__init__.py,sha256=9-Eot8R2ioCSKbZlQDPCXJ8Xdp73KtLygGBtTljNLaM,1714
|
|
65
65
|
iop/production/actions.py,sha256=h95K9O6N8S2thV0dYEKOWxo5uPhfvgALZfB6G2jrUt8,7992
|
|
66
|
-
iop/production/common.py,sha256=
|
|
66
|
+
iop/production/common.py,sha256=IoUg9jkFzSQwDsHcXvkVAC4qxpWKs3kTfXFm_EP07EU,3091
|
|
67
67
|
iop/production/component.py,sha256=uRCQZwlPNFbAEaWgDtbDHUdf4VD8A0R1sJhLTj8kHcA,7988
|
|
68
|
+
iop/production/declarations.py,sha256=N5-TEYMQbuMnYZRYYyxCuc5e2mx5b6tp4MVDsZgoxdY,4896
|
|
68
69
|
iop/production/diff.py,sha256=iVJeixEZzy-TaK7b7wdR5wzO5dyIGr2KPVsN0pf2dp4,9968
|
|
69
70
|
iop/production/import_.py,sha256=hK5d6CKtd-_FOmuU39wiZ2cmn-SCUoZ-r3hRz8HgwgA,9865
|
|
70
71
|
iop/production/inspection.py,sha256=LsAI4vhF27v8OMXtRAOHQn8XeaT4CIhjBu-CA-aorO0,3343
|
|
71
|
-
iop/production/model.py,sha256=
|
|
72
|
-
iop/production/reconstruction.py,sha256=
|
|
73
|
-
iop/production/rendering.py,sha256=
|
|
72
|
+
iop/production/model.py,sha256=1P_LS3gKAc1X6RqAlJGK-8lSU5nmP7sBFFZpEsbtl6c,38964
|
|
73
|
+
iop/production/reconstruction.py,sha256=INDFySH9B0_t8N67r6fo4-RT7HpUYJwCug5NAv8kns0,7156
|
|
74
|
+
iop/production/rendering.py,sha256=lPo7vVht9TVYYK1nnAV1FyNXkJki0RfMbmWKxd070uM,19973
|
|
74
75
|
iop/production/runtime.py,sha256=JngIVVkigt1PNLUXBvQu3t8cBos-zLKlgguf0_WWh-M,2302
|
|
75
|
-
iop/production/types.py,sha256=
|
|
76
|
-
iop/production/validation.py,sha256=
|
|
76
|
+
iop/production/types.py,sha256=z2e4VG50KK8oDP3zNeT0_6FzPEhrAykjdd6uK_HvBZM,9431
|
|
77
|
+
iop/production/validation.py,sha256=14BfjrLXW1AOuP8AdBrs-CY6zynaDc8_AUD9frsYAIU,13753
|
|
77
78
|
iop/runtime/__init__.py,sha256=wcyWMRuPCYE_CLd1NmgmI1M-IbFs2Gz2yIj2-EW_2AA,70
|
|
78
79
|
iop/runtime/director.py,sha256=KW8BvkATK8XuIUPNBD9rMww9r3Zg4K3H54868eOgvl0,12867
|
|
79
80
|
iop/runtime/environment.py,sha256=0KZ4EoW6NUEbF7FW471emxQ-9FbaiX6ngUHVGihgZXU,1564
|
|
@@ -86,9 +87,9 @@ iop/runtime/remote/director.py,sha256=jNnBIocWS76vog6noJtVky5Tv7ZDnPwiWl-hCu958D
|
|
|
86
87
|
iop/runtime/remote/migration.py,sha256=Iboz-Ye7i9xif9bn433GazCF-gtD-HJW3wiUwWS5P6w,1831
|
|
87
88
|
iop/runtime/remote/settings.py,sha256=P1Hsgld5o57HpSZgPVZzobyjlUmxJj6bstiWvR0JXnc,2144
|
|
88
89
|
iop/runtime/remote/setup.py,sha256=Tr80F3JJTy7oSUso2jyx0iA_KZayd4MYt9vL0BvPpMA,2600
|
|
89
|
-
iris_pex_embedded_python-4.0.
|
|
90
|
-
iris_pex_embedded_python-4.0.
|
|
91
|
-
iris_pex_embedded_python-4.0.
|
|
92
|
-
iris_pex_embedded_python-4.0.
|
|
93
|
-
iris_pex_embedded_python-4.0.
|
|
94
|
-
iris_pex_embedded_python-4.0.
|
|
90
|
+
iris_pex_embedded_python-4.0.0b5.dist-info/licenses/LICENSE,sha256=rZSiBFId_sfbJ6RL0GjjPX-InNLkNS9ou7eQsikciI8,1089
|
|
91
|
+
iris_pex_embedded_python-4.0.0b5.dist-info/METADATA,sha256=nhwhlcAAkAJxPcxWZ6W97sXZonTS_zd4C984yDNXoCU,4412
|
|
92
|
+
iris_pex_embedded_python-4.0.0b5.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
93
|
+
iris_pex_embedded_python-4.0.0b5.dist-info/entry_points.txt,sha256=fzS8ZFsPe8cnpEx208X60DNugCPQmuSloPeg4pyyJDY,42
|
|
94
|
+
iris_pex_embedded_python-4.0.0b5.dist-info/top_level.txt,sha256=BM54-OXQ6l2BDtmesWNXckh033s9gcjoO7bfjbwZbxU,4
|
|
95
|
+
iris_pex_embedded_python-4.0.0b5.dist-info/RECORD,,
|
{iris_pex_embedded_python-4.0.0b4.dist-info → iris_pex_embedded_python-4.0.0b5.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|