iris-pex-embedded-python 4.0.0b5__tar.gz → 4.0.0b6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. {iris_pex_embedded_python-4.0.0b5/src/iris_pex_embedded_python.egg-info → iris_pex_embedded_python-4.0.0b6}/PKG-INFO +1 -1
  2. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/pyproject.toml +1 -1
  3. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/declarations.py +34 -5
  4. iris_pex_embedded_python-4.0.0b6/src/iop/production/declarative.py +154 -0
  5. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/model.py +77 -146
  6. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/reconstruction.py +1 -2
  7. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/rendering.py +64 -60
  8. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6/src/iris_pex_embedded_python.egg-info}/PKG-INFO +1 -1
  9. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iris_pex_embedded_python.egg-info/SOURCES.txt +1 -0
  10. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/LICENSE +0 -0
  11. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/README.md +0 -0
  12. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/setup.cfg +0 -0
  13. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/__init__.py +0 -0
  14. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/__main__.py +0 -0
  15. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cli/__init__.py +0 -0
  16. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cli/formatting.py +0 -0
  17. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cli/main.py +0 -0
  18. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cli/parser.py +0 -0
  19. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cli/types.py +0 -0
  20. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/BusinessOperation.cls +0 -0
  21. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/BusinessProcess.cls +0 -0
  22. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/BusinessService.cls +0 -0
  23. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Common.cls +0 -0
  24. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Director.cls +0 -0
  25. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Duplex/Operation.cls +0 -0
  26. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Duplex/Process.cls +0 -0
  27. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Duplex/Service.cls +0 -0
  28. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Generator/Message/Ack.cls +0 -0
  29. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Generator/Message/Poll.cls +0 -0
  30. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Generator/Message/Start.cls +0 -0
  31. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Generator/Message/StartPickle.cls +0 -0
  32. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Generator/Message/Stop.cls +0 -0
  33. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/InboundAdapter.cls +0 -0
  34. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Message/JSONSchema.cls +0 -0
  35. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Message.cls +0 -0
  36. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/OutboundAdapter.cls +0 -0
  37. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/PickleMessage.cls +0 -0
  38. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/PrivateSession/Duplex.cls +0 -0
  39. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/PrivateSession/Message/Ack.cls +0 -0
  40. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/PrivateSession/Message/Poll.cls +0 -0
  41. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/PrivateSession/Message/Start.cls +0 -0
  42. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/PrivateSession/Message/Stop.cls +0 -0
  43. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Projection.cls +0 -0
  44. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Service/Remote/Handler.cls +0 -0
  45. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Service/Remote/Rest/v1.cls +0 -0
  46. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Test.cls +0 -0
  47. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Utils.cls +0 -0
  48. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Wrapper.cls +0 -0
  49. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/__init__.py +0 -0
  50. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/async_request.py +0 -0
  51. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/business_host.py +0 -0
  52. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/business_operation.py +0 -0
  53. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/business_process.py +0 -0
  54. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/business_service.py +0 -0
  55. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/common.py +0 -0
  56. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/debugpy.py +0 -0
  57. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/generator_request.py +0 -0
  58. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/inbound_adapter.py +0 -0
  59. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/log_manager.py +0 -0
  60. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/outbound_adapter.py +0 -0
  61. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/polling_business_service.py +0 -0
  62. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/private_session_duplex.py +0 -0
  63. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/private_session_process.py +0 -0
  64. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/settings.py +0 -0
  65. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/messages/__init__.py +0 -0
  66. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/messages/base.py +0 -0
  67. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/messages/decorators.py +0 -0
  68. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/messages/dispatch.py +0 -0
  69. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/messages/persistent.py +0 -0
  70. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/messages/serialization.py +0 -0
  71. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/messages/validation.py +0 -0
  72. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/migration/__init__.py +0 -0
  73. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/migration/io.py +0 -0
  74. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/migration/plans.py +0 -0
  75. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/migration/utils.py +0 -0
  76. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/__init__.py +0 -0
  77. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/actions.py +0 -0
  78. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/common.py +0 -0
  79. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/component.py +0 -0
  80. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/diff.py +0 -0
  81. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/import_.py +0 -0
  82. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/inspection.py +0 -0
  83. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/runtime.py +0 -0
  84. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/types.py +0 -0
  85. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/validation.py +0 -0
  86. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/__init__.py +0 -0
  87. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/director.py +0 -0
  88. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/environment.py +0 -0
  89. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/iris.py +0 -0
  90. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/local.py +0 -0
  91. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/protocol.py +0 -0
  92. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/remote/__init__.py +0 -0
  93. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/remote/client.py +0 -0
  94. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/remote/director.py +0 -0
  95. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/remote/migration.py +0 -0
  96. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/remote/settings.py +0 -0
  97. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/remote/setup.py +0 -0
  98. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iris_pex_embedded_python.egg-info/dependency_links.txt +0 -0
  99. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iris_pex_embedded_python.egg-info/entry_points.txt +0 -0
  100. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iris_pex_embedded_python.egg-info/requires.txt +0 -0
  101. {iris_pex_embedded_python-4.0.0b5 → iris_pex_embedded_python-4.0.0b6}/src/iris_pex_embedded_python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iris_pex_embedded_python
3
- Version: 4.0.0b5
3
+ Version: 4.0.0b6
4
4
  Summary: Iris Interoperability based on Embedded Python
5
5
  Author-email: grongier <guillaume.rongier@intersystems.com>
6
6
  License: MIT License
@@ -3,7 +3,7 @@ requires = ["setuptools", "wheel"]
3
3
 
4
4
  [project]
5
5
  name = "iris_pex_embedded_python"
6
- version = "4.0.0b5"
6
+ version = "4.0.0b6"
7
7
  description = "Iris Interoperability based on Embedded Python"
8
8
  readme = "README.md"
9
9
  requires-python = ">=3.10"
@@ -2,18 +2,23 @@ from __future__ import annotations
2
2
 
3
3
  from collections.abc import Iterable, Mapping
4
4
  from dataclasses import dataclass, field
5
- from typing import Any, ClassVar
5
+ from typing import Any, ClassVar, Protocol
6
6
 
7
7
  from .common import SETTING_NAME_ALIASES
8
8
  from .types import TargetSetting
9
9
 
10
10
 
11
+ class _NamedRouteTarget(Protocol):
12
+ @property
13
+ def name(self) -> str: ...
14
+
15
+
11
16
  @dataclass(frozen=True)
12
17
  class Route:
13
18
  """Declarative route from a production item port to one or more targets."""
14
19
 
15
20
  port: str | TargetSetting
16
- targets: str | Iterable[str]
21
+ targets: str | _NamedRouteTarget | Iterable[str | _NamedRouteTarget]
17
22
  logical_name: str = ""
18
23
 
19
24
  @property
@@ -36,13 +41,19 @@ class Route:
36
41
 
37
42
  @property
38
43
  def target_names(self) -> tuple[str, ...]:
39
- if isinstance(self.targets, str):
44
+ if _is_route_target(self.targets):
40
45
  targets = (self.targets,)
41
46
  else:
42
- targets = tuple(str(target) for target in self.targets)
47
+ try:
48
+ targets = tuple(self.targets)
49
+ except TypeError as exc:
50
+ raise TypeError(
51
+ f"Route {self.port_name!r} targets must be an item name, "
52
+ "a production item declaration, or an iterable of either"
53
+ ) from exc
43
54
  if not targets:
44
55
  raise ValueError(f"Route {self.port_name!r} requires at least one target")
45
- return targets
56
+ return tuple(_route_target_name(target, self.port_name) for target in targets)
46
57
 
47
58
 
48
59
  @dataclass(frozen=True)
@@ -143,6 +154,24 @@ def normalize_route_port_for_match(name: str | TargetSetting) -> str:
143
154
  return normalize_route_port(name)
144
155
 
145
156
 
157
+ def _is_route_target(value: Any) -> bool:
158
+ return isinstance(value, str) or isinstance(value, _ProductionItemDeclaration)
159
+
160
+
161
+ def _route_target_name(value: Any, port_name: str) -> str:
162
+ if isinstance(value, str):
163
+ if value:
164
+ return value
165
+ elif isinstance(value, _ProductionItemDeclaration):
166
+ if value.name:
167
+ return value.name
168
+
169
+ raise TypeError(
170
+ f"Route {port_name!r} targets must be item names or production item "
171
+ "declarations"
172
+ )
173
+
174
+
146
175
  def _mapping(
147
176
  values: Mapping[str, Any] | None,
148
177
  field_name: str,
@@ -0,0 +1,154 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+ from typing import Any, ClassVar
5
+
6
+ from .component import ComponentRef
7
+ from .declarations import (
8
+ _ProductionItemDeclaration,
9
+ normalize_route_port_for_match,
10
+ )
11
+
12
+
13
+ class _DeclarativeProductionMixin:
14
+ components: ClassVar[Iterable[_ProductionItemDeclaration] | None] = ()
15
+ services: ClassVar[Iterable[_ProductionItemDeclaration] | None] = ()
16
+ processes: ClassVar[Iterable[_ProductionItemDeclaration] | None] = ()
17
+ operations: ClassVar[Iterable[_ProductionItemDeclaration] | None] = ()
18
+
19
+ def _hydrate_declared_items(self) -> None:
20
+ declarations: list[_ProductionItemDeclaration] = []
21
+ for attr_name, expected_kind in (
22
+ ("components", "component"),
23
+ ("services", "service"),
24
+ ("processes", "process"),
25
+ ("operations", "operation"),
26
+ ):
27
+ for declaration in self._declared_items(attr_name):
28
+ if declaration.kind != expected_kind:
29
+ raise TypeError(
30
+ f"{attr_name} must contain {expected_kind.title()}Item "
31
+ f"declarations"
32
+ )
33
+ self._raise_declared_route_conflicts(declaration)
34
+ self._add_declared_item(declaration)
35
+ declarations.append(declaration)
36
+
37
+ for declaration in declarations:
38
+ self._connect_declared_routes(declaration)
39
+
40
+ def _declared_items(self, attr_name: str) -> tuple[_ProductionItemDeclaration, ...]:
41
+ values = getattr(type(self), attr_name, ())
42
+ if values is None:
43
+ return ()
44
+ if isinstance(values, _ProductionItemDeclaration):
45
+ return (values,)
46
+ try:
47
+ declarations = tuple(values)
48
+ except TypeError as exc:
49
+ raise TypeError(f"{attr_name} must be an iterable of item declarations") from exc
50
+ for declaration in declarations:
51
+ if not isinstance(declaration, _ProductionItemDeclaration):
52
+ raise TypeError(f"{attr_name} must contain production item declarations")
53
+ return declarations
54
+
55
+ def _raise_declared_route_conflicts(
56
+ self,
57
+ declaration: _ProductionItemDeclaration,
58
+ ) -> None:
59
+ host_ports = {
60
+ normalize_route_port_for_match(name)
61
+ for name in declaration.host_setting_values
62
+ }
63
+ route_ports = {route.port_name for route in declaration.route_values}
64
+ conflicts = sorted(host_ports & route_ports)
65
+ if not conflicts:
66
+ return
67
+ names = ", ".join(repr(name) for name in conflicts)
68
+ raise ValueError(
69
+ f"{declaration.kind.title()} item {declaration.name!r} declares route "
70
+ f"port(s) in Host settings: {names}. Declare route ports with Route only."
71
+ )
72
+
73
+ def _add_declared_item(self, declaration: _ProductionItemDeclaration) -> None:
74
+ kwargs: dict[str, Any] = {
75
+ "enabled": declaration.enabled,
76
+ "pool_size": declaration.pool_size,
77
+ "category": declaration.category,
78
+ "foreground": declaration.foreground,
79
+ "comment": declaration.comment,
80
+ "log_trace_events": declaration.log_trace_events,
81
+ "schedule": declaration.schedule,
82
+ "settings": declaration.host_setting_values,
83
+ "adapter_settings": declaration.adapter_setting_values,
84
+ }
85
+ if declaration.adapter_class is not None:
86
+ kwargs["adapter_class"] = declaration.adapter_class
87
+ if declaration.adapter_class_name is not None:
88
+ kwargs["adapter_class_name"] = declaration.adapter_class_name
89
+
90
+ method = {
91
+ "component": self.component,
92
+ "service": self.service,
93
+ "process": self.process,
94
+ "operation": self.operation,
95
+ }[declaration.kind]
96
+
97
+ component = declaration.component
98
+ if isinstance(component, type):
99
+ if declaration.class_name is not None:
100
+ kwargs["class_name"] = declaration.class_name
101
+ ref = method(declaration.name, component, **kwargs)
102
+ ref.other_settings = declaration.other_setting_values
103
+ return
104
+
105
+ class_name = declaration.class_name
106
+ if component is not None:
107
+ component_class_name = str(component)
108
+ if class_name is not None and class_name != component_class_name:
109
+ raise ValueError(
110
+ f"{declaration.kind.title()} item {declaration.name!r} "
111
+ "declares conflicting component and class_name values"
112
+ )
113
+ class_name = component_class_name
114
+
115
+ if class_name is None:
116
+ raise ValueError(
117
+ f"{declaration.kind.title()} item {declaration.name!r} requires "
118
+ "a component class or class_name"
119
+ )
120
+ kwargs["class_name"] = class_name
121
+ ref = method(declaration.name, **kwargs)
122
+ ref.other_settings = declaration.other_setting_values
123
+
124
+ def _connect_declared_routes(self, declaration: _ProductionItemDeclaration) -> None:
125
+ source = self.item(declaration.name)
126
+ for route in declaration.route_values:
127
+ self._raise_if_route_port_owner_mismatch(source, route)
128
+ port = source.port(
129
+ route.port_name,
130
+ logical_name=route.route_logical_name,
131
+ )
132
+ targets = route.target_names
133
+ self.connect(port, targets[0])
134
+ for target in targets[1:]:
135
+ self.connect_add(port, target)
136
+
137
+ def _raise_if_route_port_owner_mismatch(
138
+ self,
139
+ source: ComponentRef,
140
+ route: Any,
141
+ ) -> None:
142
+ owner = route.port_owner
143
+ if (
144
+ owner is None
145
+ or source.component_class is None
146
+ or issubclass(source.component_class, owner)
147
+ ):
148
+ return
149
+ raise ValueError(
150
+ f"Route port {route.port_name!r} belongs to "
151
+ f"{owner.__module__}.{owner.__qualname__}, not "
152
+ f"{source.component_class.__module__}."
153
+ f"{source.component_class.__qualname__}"
154
+ )
@@ -11,10 +11,7 @@ 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
+ from .declarative import _DeclarativeProductionMixin
18
15
  from .diff import _diff_productions
19
16
  from .inspection import component_runtime_info, inspect_component
20
17
  from .reconstruction import production_from_dict
@@ -38,7 +35,7 @@ from .types import (
38
35
  _MISSING = object()
39
36
 
40
37
 
41
- class Production:
38
+ class Production(_DeclarativeProductionMixin):
42
39
  """Python authoring DSL for IRIS interoperability production topology.
43
40
 
44
41
  Python Production is the source of truth for Python-authored topology.
@@ -62,8 +59,80 @@ class Production:
62
59
  alert_action_window: int | str | object = _MISSING,
63
60
  namespace: str | None | object = _MISSING,
64
61
  director: _DirectorProtocol | None | object = _MISSING,
65
- _hydrate_declarations: bool = True,
66
62
  ):
63
+ self._initialize(
64
+ name,
65
+ testing_enabled=testing_enabled,
66
+ log_general_trace_events=log_general_trace_events,
67
+ actor_pool_size=actor_pool_size,
68
+ description=description,
69
+ shutdown_timeout=shutdown_timeout,
70
+ update_timeout=update_timeout,
71
+ alert_notification_manager=alert_notification_manager,
72
+ alert_notification_operation=alert_notification_operation,
73
+ alert_notification_recipients=alert_notification_recipients,
74
+ alert_action_window=alert_action_window,
75
+ namespace=namespace,
76
+ director=director,
77
+ hydrate_declarations=True,
78
+ )
79
+
80
+ @classmethod
81
+ def _new_unhydrated(
82
+ cls,
83
+ name: str | object = _MISSING,
84
+ *,
85
+ testing_enabled: bool | str | object = _MISSING,
86
+ log_general_trace_events: bool | str | object = _MISSING,
87
+ actor_pool_size: int | str | object = _MISSING,
88
+ description: str | object = _MISSING,
89
+ shutdown_timeout: int | str | object = _MISSING,
90
+ update_timeout: int | str | object = _MISSING,
91
+ alert_notification_manager: str | object = _MISSING,
92
+ alert_notification_operation: str | object = _MISSING,
93
+ alert_notification_recipients: str | object = _MISSING,
94
+ alert_action_window: int | str | object = _MISSING,
95
+ namespace: str | None | object = _MISSING,
96
+ director: _DirectorProtocol | None | object = _MISSING,
97
+ ) -> Production:
98
+ production = object.__new__(cls)
99
+ Production._initialize(
100
+ production,
101
+ name,
102
+ testing_enabled=testing_enabled,
103
+ log_general_trace_events=log_general_trace_events,
104
+ actor_pool_size=actor_pool_size,
105
+ description=description,
106
+ shutdown_timeout=shutdown_timeout,
107
+ update_timeout=update_timeout,
108
+ alert_notification_manager=alert_notification_manager,
109
+ alert_notification_operation=alert_notification_operation,
110
+ alert_notification_recipients=alert_notification_recipients,
111
+ alert_action_window=alert_action_window,
112
+ namespace=namespace,
113
+ director=director,
114
+ hydrate_declarations=False,
115
+ )
116
+ return production
117
+
118
+ def _initialize(
119
+ self,
120
+ name: str | object = _MISSING,
121
+ *,
122
+ testing_enabled: bool | str | object = _MISSING,
123
+ log_general_trace_events: bool | str | object = _MISSING,
124
+ actor_pool_size: int | str | object = _MISSING,
125
+ description: str | object = _MISSING,
126
+ shutdown_timeout: int | str | object = _MISSING,
127
+ update_timeout: int | str | object = _MISSING,
128
+ alert_notification_manager: str | object = _MISSING,
129
+ alert_notification_operation: str | object = _MISSING,
130
+ alert_notification_recipients: str | object = _MISSING,
131
+ alert_action_window: int | str | object = _MISSING,
132
+ namespace: str | None | object = _MISSING,
133
+ director: _DirectorProtocol | None | object = _MISSING,
134
+ hydrate_declarations: bool,
135
+ ) -> None:
67
136
  self.name = self._resolve_production_name(name)
68
137
  self.testing_enabled = self._production_default(
69
138
  "testing_enabled", testing_enabled, False
@@ -114,7 +183,7 @@ class Production:
114
183
  self._graph_warnings: list[str] = []
115
184
  self._queue_info: dict[str, dict[str, Any]] = {}
116
185
  self._messages: list[PersistentMessageRegistration] = []
117
- if _hydrate_declarations:
186
+ if hydrate_declarations:
118
187
  self._hydrate_declared_items()
119
188
 
120
189
  def _resolve_production_name(self, name: str | object) -> str:
@@ -138,143 +207,6 @@ class Production:
138
207
  return class_value
139
208
  return default
140
209
 
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
- )
277
-
278
210
  def testing(self, enabled: bool | str = True) -> Production:
279
211
  self.testing_enabled = enabled
280
212
  return self
@@ -337,11 +269,10 @@ class Production:
337
269
  namespace: str | None = None,
338
270
  director: _DirectorProtocol | None = None,
339
271
  ) -> Production:
340
- seed = cls(
272
+ seed = cls._new_unhydrated(
341
273
  name,
342
274
  namespace=namespace,
343
275
  director=director,
344
- _hydrate_declarations=False,
345
276
  )
346
277
  runtime_director = _ProductionRuntime(seed).director
347
278
  exported = runtime_director.export_production(name)
@@ -28,7 +28,7 @@ def production_from_dict(
28
28
  ):
29
29
  production_name, production_data = _production_payload(data)
30
30
  production_settings = _split_production_settings(production_data.get("Setting"))
31
- production = production_cls(
31
+ production = production_cls._new_unhydrated(
32
32
  production_name,
33
33
  testing_enabled=production_data.get("@TestingEnabled", False),
34
34
  log_general_trace_events=production_data.get("@LogGeneralTraceEvents", False),
@@ -48,7 +48,6 @@ 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,
52
51
  )
53
52
 
54
53
  _add_imported_items(production, production_data)
@@ -88,7 +88,10 @@ def production_to_python(production) -> str:
88
88
 
89
89
  def production_to_class(production) -> str:
90
90
  item_names = {item.name for item in production.items}
91
- route_targets = _valid_route_targets(_route_targets(production, item_names), item_names)
91
+ route_targets = _valid_route_targets(
92
+ _route_targets(production, item_names),
93
+ item_names,
94
+ )
92
95
  item_groups = _class_item_groups(production.items)
93
96
  used_items = {
94
97
  _class_item_type(kind)
@@ -105,12 +108,23 @@ def production_to_class(production) -> str:
105
108
  "# Generated from IRIS production export.",
106
109
  "# Review before using as source of truth; some runtime/dynamic routing intent",
107
110
  "# 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
111
  ]
112
+ if _has_string_python_proxy_items(production.items):
113
+ lines.extend(
114
+ [
115
+ "# TODO: replace Python.* string class names with imported Python",
116
+ "# classes before re-migration, or ensure the proxy classes already exist.",
117
+ ]
118
+ )
119
+ lines.extend(
120
+ [
121
+ f"from iop import {', '.join(imports)}",
122
+ "",
123
+ "",
124
+ f"class {class_name}(Production):",
125
+ f" name = {_literal(production.name)}",
126
+ ]
127
+ )
114
128
  lines.extend(_class_production_setting_lines(production))
115
129
  lines.append("")
116
130
 
@@ -119,10 +133,10 @@ def production_to_class(production) -> str:
119
133
  if not items:
120
134
  continue
121
135
  attr_name = _class_item_attr(kind)
122
- lines.append(f" {attr_name} = [")
136
+ lines.append(f" {attr_name} = (")
123
137
  for item in items:
124
138
  lines.extend(_class_item_lines(item, kind, route_targets))
125
- lines.append(" ]")
139
+ lines.append(" )")
126
140
  lines.append("")
127
141
 
128
142
  unresolved = _unresolved_class_routes(production, item_names)
@@ -141,7 +155,15 @@ def production_to_class(production) -> str:
141
155
 
142
156
 
143
157
  def _class_production_setting_lines(production) -> list[str]:
144
- settings = [
158
+ return [
159
+ f" {name} = {_literal(value)}"
160
+ for name, value, default in _production_setting_literals(production)
161
+ if value != default
162
+ ]
163
+
164
+
165
+ def _production_setting_literals(production) -> list[tuple[str, Any, Any]]:
166
+ return [
145
167
  ("testing_enabled", _bool_literal(production.testing_enabled), False),
146
168
  (
147
169
  "log_general_trace_events",
@@ -157,11 +179,6 @@ def _class_production_setting_lines(production) -> list[str]:
157
179
  ("alert_notification_recipients", production.alert_notification_recipients, ""),
158
180
  ("alert_action_window", _int_literal(production.alert_action_window), 60),
159
181
  ]
160
- return [
161
- f" {name} = {_literal(value)}"
162
- for name, value, default in settings
163
- if value != default
164
- ]
165
182
 
166
183
 
167
184
  def _class_item_groups(items) -> dict[str, list[Any]]:
@@ -179,13 +196,6 @@ def _class_item_groups(items) -> dict[str, list[Any]]:
179
196
  def _class_item_kind(item) -> str:
180
197
  if item.kind in {"service", "process", "operation"}:
181
198
  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
199
  return "component"
190
200
 
191
201
 
@@ -239,11 +249,21 @@ def _class_item_lines(item, kind: str, route_targets) -> list[str]:
239
249
  if routes:
240
250
  kwargs.append(("routes", routes))
241
251
 
242
- lines = [
243
- f" {_class_item_type(kind)}(",
244
- f" {_literal(item.name)},",
245
- f" {_literal(item.class_name or '')},",
246
- ]
252
+ lines = []
253
+ if _is_string_python_proxy_item(item):
254
+ lines.extend(
255
+ [
256
+ " # TODO: replace this proxy class name with the Python",
257
+ " # class object if this item should be auto-registered.",
258
+ ]
259
+ )
260
+ lines.extend(
261
+ [
262
+ f" {_class_item_type(kind)}(",
263
+ f" {_literal(item.name)},",
264
+ f" {_literal(item.class_name or '')},",
265
+ ]
266
+ )
247
267
  for name, value in kwargs:
248
268
  lines.extend(_class_keyword_lines(name, value))
249
269
  lines.append(" ),")
@@ -285,17 +305,29 @@ def _class_keyword_lines(name: str, value: Any) -> list[str]:
285
305
  def _class_route_keyword_lines(routes: list[tuple[str, list[str]]]) -> list[str]:
286
306
  if len(routes) == 1:
287
307
  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})],"]
308
+ target_literal = _class_route_targets_literal(targets)
309
+ return [f" routes=(Route({_literal(port)}, {target_literal}),),"]
290
310
 
291
- lines = [" routes=["]
311
+ lines = [" routes=("]
292
312
  for port, targets in routes:
293
- target_literal = _literal(targets[0]) if len(targets) == 1 else _literal(targets)
313
+ target_literal = _class_route_targets_literal(targets)
294
314
  lines.append(f" Route({_literal(port)}, {target_literal}),")
295
- lines.append(" ],")
315
+ lines.append(" ),")
296
316
  return lines
297
317
 
298
318
 
319
+ def _class_route_targets_literal(targets: list[str]) -> str:
320
+ return _literal(targets[0]) if len(targets) == 1 else _literal(tuple(targets))
321
+
322
+
323
+ def _has_string_python_proxy_items(items) -> bool:
324
+ return any(_is_string_python_proxy_item(item) for item in items)
325
+
326
+
327
+ def _is_string_python_proxy_item(item) -> bool:
328
+ return str(item.class_name or "").startswith("Python.")
329
+
330
+
299
331
  def _indented_dict_keyword_lines(
300
332
  name: str,
301
333
  value: dict[str, Any],
@@ -361,37 +393,9 @@ def _production_class_name(production_name: str) -> str:
361
393
 
362
394
 
363
395
  def _production_constructor_lines(production) -> list[str]:
364
- kwargs = [
365
- ("testing_enabled", _bool_literal(production.testing_enabled), False),
366
- (
367
- "log_general_trace_events",
368
- _bool_literal(production.log_general_trace_events),
369
- False,
370
- ),
371
- ("actor_pool_size", _int_literal(production.actor_pool_size), 2),
372
- ("description", production.description, ""),
373
- ("shutdown_timeout", _int_literal(production.shutdown_timeout), 120),
374
- ("update_timeout", _int_literal(production.update_timeout), 10),
375
- (
376
- "alert_notification_manager",
377
- production.alert_notification_manager,
378
- "",
379
- ),
380
- (
381
- "alert_notification_operation",
382
- production.alert_notification_operation,
383
- "",
384
- ),
385
- (
386
- "alert_notification_recipients",
387
- production.alert_notification_recipients,
388
- "",
389
- ),
390
- ("alert_action_window", _int_literal(production.alert_action_window), 60),
391
- ]
392
396
  rendered = [
393
397
  (name, value)
394
- for name, value, default in kwargs
398
+ for name, value, default in _production_setting_literals(production)
395
399
  if value != default
396
400
  ]
397
401
  if not rendered:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iris_pex_embedded_python
3
- Version: 4.0.0b5
3
+ Version: 4.0.0b6
4
4
  Summary: Iris Interoperability based on Embedded Python
5
5
  Author-email: grongier <guillaume.rongier@intersystems.com>
6
6
  License: MIT License
@@ -69,6 +69,7 @@ src/iop/production/actions.py
69
69
  src/iop/production/common.py
70
70
  src/iop/production/component.py
71
71
  src/iop/production/declarations.py
72
+ src/iop/production/declarative.py
72
73
  src/iop/production/diff.py
73
74
  src/iop/production/import_.py
74
75
  src/iop/production/inspection.py