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 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","")
@@ -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
- raise_on_error(
1040
- _iris.get_iris()
1041
- .cls("IOP.Utils")
1042
- .CreateProductionFromJSON(production_name, json.dumps(production))
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):
@@ -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 = False,
47
- log_general_trace_events: bool | str = False,
48
- actor_pool_size: int | str = 2,
49
- description: str = "",
50
- shutdown_timeout: int | str = PRODUCTION_SETTING_FIELDS["shutdown_timeout"][1],
51
- update_timeout: int | str = PRODUCTION_SETTING_FIELDS["update_timeout"][1],
52
- alert_notification_manager: str = PRODUCTION_SETTING_FIELDS[
53
- "alert_notification_manager"
54
- ][1],
55
- alert_notification_operation: str = PRODUCTION_SETTING_FIELDS[
56
- "alert_notification_operation"
57
- ][1],
58
- alert_notification_recipients: str = PRODUCTION_SETTING_FIELDS[
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 = testing_enabled
69
- self.log_general_trace_events = log_general_trace_events
70
- self.actor_pool_size = actor_pool_size
71
- self.description = description
72
- self.shutdown_timeout = shutdown_timeout
73
- self.update_timeout = update_timeout
74
- self.alert_notification_manager = alert_notification_manager
75
- self.alert_notification_operation = alert_notification_operation
76
- self.alert_notification_recipients = alert_notification_recipients
77
- self.alert_action_window = alert_action_window
78
- self.namespace = namespace
79
- self._director = director
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(name, namespace=namespace, director=director)
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
 
@@ -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,
@@ -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."""
@@ -4,7 +4,11 @@ import warnings
4
4
  from dataclasses import dataclass
5
5
  from typing import Any
6
6
 
7
- from .common import PRODUCTION_SETTING_FIELDS_BY_IRIS, PRODUCTION_SETTING_NAMES
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 = _SETTING_NAME_ALIASES.get(setting_name)
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 = _SETTING_NAME_ALIASES.get(setting_name)
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iris_pex_embedded_python
3
- Version: 4.0.0b4
3
+ Version: 4.0.0b5
4
4
  Summary: Iris Interoperability based on Embedded Python
5
5
  Author-email: grongier <guillaume.rongier@intersystems.com>
6
6
  License: MIT License
@@ -1,9 +1,9 @@
1
- iop/__init__.py,sha256=JCkmGv50W1i4oi7CKrvLXyQ88-lv-2y-8SJ_05BOsPA,4001
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=FzE86PbdIrJ8zVIIcsTRD7-v5_zdFUisnQ5Rs5hbQH8,13197
6
- iop/cli/parser.py,sha256=HWn3mxndR8Fw9HrnjtSP-ZMTps9X1cbQWCcye7NPIug,4878
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=SZOKKVaJEUp3Y4xkt1OIWw2ExsjNZoWCBsySP7CjqoY,31095
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=VfUQ4H0GrJ7N-IG-KYUxtTQbRJmsTI5crU7V90mPczY,5509
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=s2CoqWj4HeW8KrKTaQZGBKH4ZMku3Zg8eolNZvGKMq0,40817
64
- iop/production/__init__.py,sha256=bVFLRI4lvhbUlKApRvvSqM6pVkOsBuofRrzuEIBT6nY,1360
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=RopwhFJT7jP15a4iBvMWipwYwIAV6A0d0UsoxWLo-is,2969
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=7YDAgQ-fo87VnE5A-CxmNtNsRHMVFB2NKkpBykH-ohQ,31106
72
- iop/production/reconstruction.py,sha256=YJMOBtpTzT-IxgQ4lx1kivA8zbWz3UZfwt-5q7d_kMk,7135
73
- iop/production/rendering.py,sha256=d9Et3hE8aPTCfnk0RKc6vu5HYOrI-JXFCfU6vZLKsKg,10649
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=SsTYPspc8VGrSH8hy49tDBVLWu55D5yODNT7lfFbjmc,9131
76
- iop/production/validation.py,sha256=yeWEQmWANQ8md2H8QN4DhHyR5nIDLMJWZN7e0d2y2Rk,13839
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.0b4.dist-info/licenses/LICENSE,sha256=rZSiBFId_sfbJ6RL0GjjPX-InNLkNS9ou7eQsikciI8,1089
90
- iris_pex_embedded_python-4.0.0b4.dist-info/METADATA,sha256=wksvI6gY4uyZ7OftWfoAvCB4AspPjHl5xNqMsiA0sIY,4412
91
- iris_pex_embedded_python-4.0.0b4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
92
- iris_pex_embedded_python-4.0.0b4.dist-info/entry_points.txt,sha256=fzS8ZFsPe8cnpEx208X60DNugCPQmuSloPeg4pyyJDY,42
93
- iris_pex_embedded_python-4.0.0b4.dist-info/top_level.txt,sha256=BM54-OXQ6l2BDtmesWNXckh033s9gcjoO7bfjbwZbxU,4
94
- iris_pex_embedded_python-4.0.0b4.dist-info/RECORD,,
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,,