iris-pex-embedded-python 4.0.0b4__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.0b4/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.0b4 → iris_pex_embedded_python-4.0.0b6}/pyproject.toml +1 -1
  3. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/__init__.py +12 -0
  4. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cli/main.py +3 -0
  5. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cli/parser.py +1 -1
  6. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Utils.cls +10 -0
  7. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/settings.py +11 -1
  8. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/migration/utils.py +15 -5
  9. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/__init__.py +10 -0
  10. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/common.py +5 -0
  11. iris_pex_embedded_python-4.0.0b6/src/iop/production/declarations.py +194 -0
  12. iris_pex_embedded_python-4.0.0b6/src/iop/production/declarative.py +154 -0
  13. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/model.py +165 -36
  14. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/reconstruction.py +3 -4
  15. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/rendering.py +296 -18
  16. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/types.py +10 -1
  17. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/validation.py +7 -8
  18. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6/src/iris_pex_embedded_python.egg-info}/PKG-INFO +1 -1
  19. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iris_pex_embedded_python.egg-info/SOURCES.txt +2 -0
  20. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/LICENSE +0 -0
  21. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/README.md +0 -0
  22. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/setup.cfg +0 -0
  23. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/__main__.py +0 -0
  24. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cli/__init__.py +0 -0
  25. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cli/formatting.py +0 -0
  26. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cli/types.py +0 -0
  27. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/BusinessOperation.cls +0 -0
  28. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/BusinessProcess.cls +0 -0
  29. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/BusinessService.cls +0 -0
  30. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Common.cls +0 -0
  31. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Director.cls +0 -0
  32. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Duplex/Operation.cls +0 -0
  33. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Duplex/Process.cls +0 -0
  34. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Duplex/Service.cls +0 -0
  35. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Generator/Message/Ack.cls +0 -0
  36. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Generator/Message/Poll.cls +0 -0
  37. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Generator/Message/Start.cls +0 -0
  38. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Generator/Message/StartPickle.cls +0 -0
  39. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Generator/Message/Stop.cls +0 -0
  40. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/InboundAdapter.cls +0 -0
  41. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Message/JSONSchema.cls +0 -0
  42. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Message.cls +0 -0
  43. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/OutboundAdapter.cls +0 -0
  44. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/PickleMessage.cls +0 -0
  45. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/PrivateSession/Duplex.cls +0 -0
  46. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/PrivateSession/Message/Ack.cls +0 -0
  47. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/PrivateSession/Message/Poll.cls +0 -0
  48. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/PrivateSession/Message/Start.cls +0 -0
  49. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/PrivateSession/Message/Stop.cls +0 -0
  50. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Projection.cls +0 -0
  51. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Service/Remote/Handler.cls +0 -0
  52. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Service/Remote/Rest/v1.cls +0 -0
  53. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Test.cls +0 -0
  54. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/cls/IOP/Wrapper.cls +0 -0
  55. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/__init__.py +0 -0
  56. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/async_request.py +0 -0
  57. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/business_host.py +0 -0
  58. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/business_operation.py +0 -0
  59. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/business_process.py +0 -0
  60. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/business_service.py +0 -0
  61. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/common.py +0 -0
  62. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/debugpy.py +0 -0
  63. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/generator_request.py +0 -0
  64. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/inbound_adapter.py +0 -0
  65. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/log_manager.py +0 -0
  66. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/outbound_adapter.py +0 -0
  67. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/polling_business_service.py +0 -0
  68. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/private_session_duplex.py +0 -0
  69. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/components/private_session_process.py +0 -0
  70. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/messages/__init__.py +0 -0
  71. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/messages/base.py +0 -0
  72. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/messages/decorators.py +0 -0
  73. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/messages/dispatch.py +0 -0
  74. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/messages/persistent.py +0 -0
  75. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/messages/serialization.py +0 -0
  76. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/messages/validation.py +0 -0
  77. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/migration/__init__.py +0 -0
  78. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/migration/io.py +0 -0
  79. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/migration/plans.py +0 -0
  80. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/actions.py +0 -0
  81. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/component.py +0 -0
  82. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/diff.py +0 -0
  83. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/import_.py +0 -0
  84. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/inspection.py +0 -0
  85. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/production/runtime.py +0 -0
  86. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/__init__.py +0 -0
  87. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/director.py +0 -0
  88. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/environment.py +0 -0
  89. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/iris.py +0 -0
  90. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/local.py +0 -0
  91. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/protocol.py +0 -0
  92. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/remote/__init__.py +0 -0
  93. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/remote/client.py +0 -0
  94. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/remote/director.py +0 -0
  95. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/remote/migration.py +0 -0
  96. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/remote/settings.py +0 -0
  97. {iris_pex_embedded_python-4.0.0b4 → iris_pex_embedded_python-4.0.0b6}/src/iop/runtime/remote/setup.py +0 -0
  98. {iris_pex_embedded_python-4.0.0b4 → 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.0b4 → 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.0b4 → 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.0b4 → 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.0b4
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.0b4"
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"
@@ -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",
@@ -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
@@ -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
  )
@@ -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:
@@ -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",
@@ -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,194 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable, Mapping
4
+ from dataclasses import dataclass, field
5
+ from typing import Any, ClassVar, Protocol
6
+
7
+ from .common import SETTING_NAME_ALIASES
8
+ from .types import TargetSetting
9
+
10
+
11
+ class _NamedRouteTarget(Protocol):
12
+ @property
13
+ def name(self) -> str: ...
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class Route:
18
+ """Declarative route from a production item port to one or more targets."""
19
+
20
+ port: str | TargetSetting
21
+ targets: str | _NamedRouteTarget | Iterable[str | _NamedRouteTarget]
22
+ logical_name: str = ""
23
+
24
+ @property
25
+ def port_name(self) -> str:
26
+ return normalize_route_port(self.port)
27
+
28
+ @property
29
+ def port_owner(self) -> type | None:
30
+ if isinstance(self.port, TargetSetting):
31
+ return self.port.owner
32
+ return None
33
+
34
+ @property
35
+ def route_logical_name(self) -> str:
36
+ if self.logical_name:
37
+ return self.logical_name
38
+ if isinstance(self.port, TargetSetting):
39
+ return self.port.logical_name
40
+ return ""
41
+
42
+ @property
43
+ def target_names(self) -> tuple[str, ...]:
44
+ if _is_route_target(self.targets):
45
+ targets = (self.targets,)
46
+ else:
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
54
+ if not targets:
55
+ raise ValueError(f"Route {self.port_name!r} requires at least one target")
56
+ return tuple(_route_target_name(target, self.port_name) for target in targets)
57
+
58
+
59
+ @dataclass(frozen=True)
60
+ class _ProductionItemDeclaration:
61
+ name: str
62
+ component: type | str | None = None
63
+ class_name: str | None = None
64
+ adapter_class: type | str | None = None
65
+ adapter_class_name: str | None = None
66
+ enabled: bool | str = True
67
+ pool_size: int | str = 1
68
+ category: str = ""
69
+ foreground: bool | str = False
70
+ comment: str = ""
71
+ log_trace_events: bool | str = False
72
+ schedule: str = ""
73
+ settings: Mapping[str, Any] | None = None
74
+ host_settings: Mapping[str, Any] | None = None
75
+ adapter_settings: Mapping[str, Any] | None = None
76
+ other_settings: Iterable[Mapping[str, Any]] | None = None
77
+ routes: Route | Iterable[Route] | None = field(default_factory=tuple)
78
+
79
+ kind: ClassVar[str] = "component"
80
+
81
+ @property
82
+ def host_setting_values(self) -> dict[str, Any]:
83
+ settings = _mapping(self.settings, "settings", self.name)
84
+ host_settings = _mapping(self.host_settings, "host_settings", self.name)
85
+ duplicates = sorted(settings.keys() & host_settings.keys())
86
+ if duplicates:
87
+ names = ", ".join(repr(name) for name in duplicates)
88
+ raise ValueError(
89
+ f"{self.kind.title()} item {self.name!r} declares duplicate "
90
+ f"Host setting keys: {names}"
91
+ )
92
+ merged = dict(settings)
93
+ merged.update(host_settings)
94
+ return merged
95
+
96
+ @property
97
+ def adapter_setting_values(self) -> dict[str, Any]:
98
+ return _mapping(self.adapter_settings, "adapter_settings", self.name)
99
+
100
+ @property
101
+ def other_setting_values(self) -> list[dict[str, Any]]:
102
+ return [dict(setting) for setting in self.other_settings or ()]
103
+
104
+ @property
105
+ def route_values(self) -> tuple[Route, ...]:
106
+ if self.routes is None:
107
+ return ()
108
+ if isinstance(self.routes, Route):
109
+ return (self.routes,)
110
+ routes = tuple(self.routes)
111
+ for route in routes:
112
+ if not isinstance(route, Route):
113
+ raise TypeError(
114
+ f"{self.kind.title()} item {self.name!r} routes must be Route "
115
+ f"instances"
116
+ )
117
+ return routes
118
+
119
+
120
+ @dataclass(frozen=True)
121
+ class ServiceItem(_ProductionItemDeclaration):
122
+ kind: ClassVar[str] = "service"
123
+
124
+
125
+ @dataclass(frozen=True)
126
+ class ComponentItem(_ProductionItemDeclaration):
127
+ kind: ClassVar[str] = "component"
128
+
129
+
130
+ @dataclass(frozen=True)
131
+ class ProcessItem(_ProductionItemDeclaration):
132
+ kind: ClassVar[str] = "process"
133
+
134
+
135
+ @dataclass(frozen=True)
136
+ class OperationItem(_ProductionItemDeclaration):
137
+ kind: ClassVar[str] = "operation"
138
+
139
+
140
+ def normalize_route_port(name: str | TargetSetting) -> str:
141
+ """Normalize known Pythonic route aliases without changing other settings."""
142
+
143
+ if isinstance(name, TargetSetting):
144
+ if not name.name:
145
+ raise ValueError(
146
+ "Route target setting must be declared on a component class"
147
+ )
148
+ return name.name
149
+ port_name = str(name)
150
+ return SETTING_NAME_ALIASES.get(port_name, port_name)
151
+
152
+
153
+ def normalize_route_port_for_match(name: str | TargetSetting) -> str:
154
+ return normalize_route_port(name)
155
+
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
+
175
+ def _mapping(
176
+ values: Mapping[str, Any] | None,
177
+ field_name: str,
178
+ item_name: str,
179
+ ) -> dict[str, Any]:
180
+ try:
181
+ return dict(values or {})
182
+ except (TypeError, ValueError) as exc:
183
+ raise TypeError(
184
+ f"Production item {item_name!r} {field_name} must be a mapping"
185
+ ) from exc
186
+
187
+
188
+ __all__ = [
189
+ "ComponentItem",
190
+ "OperationItem",
191
+ "ProcessItem",
192
+ "Route",
193
+ "ServiceItem",
194
+ ]
@@ -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
+ )