iris-pex-embedded-python 4.0.0b3__tar.gz → 4.0.0b5__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.0b3/src/iris_pex_embedded_python.egg-info → iris_pex_embedded_python-4.0.0b5}/PKG-INFO +1 -1
  2. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/pyproject.toml +1 -1
  3. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/__init__.py +20 -0
  4. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cli/main.py +22 -3
  5. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cli/parser.py +6 -1
  6. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cli/types.py +1 -0
  7. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/Service/Remote/Rest/v1.cls +2 -1
  8. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/Utils.cls +13 -1
  9. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/components/settings.py +11 -1
  10. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/migration/plans.py +25 -2
  11. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/migration/utils.py +66 -12
  12. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/production/__init__.py +18 -0
  13. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/production/common.py +24 -0
  14. iris_pex_embedded_python-4.0.0b5/src/iop/production/declarations.py +165 -0
  15. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/production/diff.py +9 -2
  16. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/production/import_.py +16 -1
  17. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/production/model.py +269 -15
  18. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/production/reconstruction.py +17 -3
  19. iris_pex_embedded_python-4.0.0b5/src/iop/production/rendering.py +599 -0
  20. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/production/types.py +10 -1
  21. iris_pex_embedded_python-4.0.0b5/src/iop/production/validation.py +452 -0
  22. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/runtime/local.py +11 -2
  23. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/runtime/protocol.py +5 -1
  24. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/runtime/remote/director.py +10 -2
  25. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/runtime/remote/migration.py +7 -1
  26. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5/src/iris_pex_embedded_python.egg-info}/PKG-INFO +1 -1
  27. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iris_pex_embedded_python.egg-info/SOURCES.txt +2 -0
  28. iris_pex_embedded_python-4.0.0b3/src/iop/production/rendering.py +0 -289
  29. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/LICENSE +0 -0
  30. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/README.md +0 -0
  31. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/setup.cfg +0 -0
  32. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/__main__.py +0 -0
  33. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cli/__init__.py +0 -0
  34. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cli/formatting.py +0 -0
  35. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/BusinessOperation.cls +0 -0
  36. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/BusinessProcess.cls +0 -0
  37. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/BusinessService.cls +0 -0
  38. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/Common.cls +0 -0
  39. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/Director.cls +0 -0
  40. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/Duplex/Operation.cls +0 -0
  41. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/Duplex/Process.cls +0 -0
  42. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/Duplex/Service.cls +0 -0
  43. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/Generator/Message/Ack.cls +0 -0
  44. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/Generator/Message/Poll.cls +0 -0
  45. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/Generator/Message/Start.cls +0 -0
  46. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/Generator/Message/StartPickle.cls +0 -0
  47. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/Generator/Message/Stop.cls +0 -0
  48. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/InboundAdapter.cls +0 -0
  49. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/Message/JSONSchema.cls +0 -0
  50. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/Message.cls +0 -0
  51. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/OutboundAdapter.cls +0 -0
  52. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/PickleMessage.cls +0 -0
  53. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/PrivateSession/Duplex.cls +0 -0
  54. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/PrivateSession/Message/Ack.cls +0 -0
  55. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/PrivateSession/Message/Poll.cls +0 -0
  56. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/PrivateSession/Message/Start.cls +0 -0
  57. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/PrivateSession/Message/Stop.cls +0 -0
  58. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/Projection.cls +0 -0
  59. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/Service/Remote/Handler.cls +0 -0
  60. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/Test.cls +0 -0
  61. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/cls/IOP/Wrapper.cls +0 -0
  62. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/components/__init__.py +0 -0
  63. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/components/async_request.py +0 -0
  64. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/components/business_host.py +0 -0
  65. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/components/business_operation.py +0 -0
  66. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/components/business_process.py +0 -0
  67. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/components/business_service.py +0 -0
  68. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/components/common.py +0 -0
  69. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/components/debugpy.py +0 -0
  70. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/components/generator_request.py +0 -0
  71. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/components/inbound_adapter.py +0 -0
  72. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/components/log_manager.py +0 -0
  73. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/components/outbound_adapter.py +0 -0
  74. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/components/polling_business_service.py +0 -0
  75. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/components/private_session_duplex.py +0 -0
  76. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/components/private_session_process.py +0 -0
  77. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/messages/__init__.py +0 -0
  78. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/messages/base.py +0 -0
  79. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/messages/decorators.py +0 -0
  80. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/messages/dispatch.py +0 -0
  81. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/messages/persistent.py +0 -0
  82. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/messages/serialization.py +0 -0
  83. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/messages/validation.py +0 -0
  84. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/migration/__init__.py +0 -0
  85. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/migration/io.py +0 -0
  86. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/production/actions.py +0 -0
  87. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/production/component.py +0 -0
  88. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/production/inspection.py +0 -0
  89. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/production/runtime.py +0 -0
  90. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/runtime/__init__.py +0 -0
  91. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/runtime/director.py +0 -0
  92. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/runtime/environment.py +0 -0
  93. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/runtime/iris.py +0 -0
  94. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/runtime/remote/__init__.py +0 -0
  95. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/runtime/remote/client.py +0 -0
  96. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/runtime/remote/settings.py +0 -0
  97. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iop/runtime/remote/setup.py +0 -0
  98. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iris_pex_embedded_python.egg-info/dependency_links.txt +0 -0
  99. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iris_pex_embedded_python.egg-info/entry_points.txt +0 -0
  100. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/src/iris_pex_embedded_python.egg-info/requires.txt +0 -0
  101. {iris_pex_embedded_python-4.0.0b3 → iris_pex_embedded_python-4.0.0b5}/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.0b3
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
@@ -3,7 +3,7 @@ requires = ["setuptools", "wheel"]
3
3
 
4
4
  [project]
5
5
  name = "iris_pex_embedded_python"
6
- version = "4.0.0b3"
6
+ version = "4.0.0b5"
7
7
  description = "Iris Interoperability based on Embedded Python"
8
8
  readme = "README.md"
9
9
  requires-python = ">=3.10"
@@ -25,12 +25,22 @@ 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
33
36
  from iop.production import ProductionGraph as ProductionGraph
37
+ from iop.production import ProductionValidationError as ProductionValidationError
38
+ from iop.production import ProductionValidationIssue as ProductionValidationIssue
39
+ from iop.production import ProductionValidationReport as ProductionValidationReport
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
34
44
  from iop.production import target as target
35
45
  from iop.runtime.director import _Director
36
46
  from iop.runtime.protocol import DirectorProtocol as DirectorProtocol
@@ -40,6 +50,7 @@ __all__ = [
40
50
  "BusinessProcess",
41
51
  "BusinessService",
42
52
  "Category",
53
+ "ComponentItem",
43
54
  "ComponentRef",
44
55
  "Director",
45
56
  "DirectorProtocol",
@@ -50,18 +61,27 @@ __all__ = [
50
61
  "InboundAdapter",
51
62
  "Message",
52
63
  "Model",
64
+ "OperationItem",
53
65
  "OutboundAdapter",
54
66
  "PersistentMessage",
55
67
  "PickleMessage",
56
68
  "PollingBusinessService",
57
69
  "Port",
70
+ "ProcessItem",
58
71
  "Production",
59
72
  "ProductionDiff",
60
73
  "ProductionDiffEntry",
61
74
  "ProductionGraph",
75
+ "ProductionValidationError",
76
+ "ProductionValidationIssue",
77
+ "ProductionValidationReport",
78
+ "ProductionValidationWarning",
62
79
  "PydanticMessage",
63
80
  "PydanticPickleMessage",
81
+ "Route",
82
+ "ServiceItem",
64
83
  "Setting",
84
+ "TargetSetting",
65
85
  "Utils",
66
86
  "bind_component",
67
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
@@ -244,17 +247,33 @@ class Command:
244
247
  if self.args.migration_plan:
245
248
  print(
246
249
  migration_utils.explain_migration(
247
- migrate_path, mode=mode, namespace=self.director.namespace
250
+ migrate_path,
251
+ mode=mode,
252
+ namespace=self.director.namespace,
253
+ strict_production_validation=(
254
+ self.args.strict_production_validation
255
+ ),
248
256
  )
249
257
  )
250
258
  return
251
259
  if self._is_remote:
252
260
  print(
253
261
  migration_utils.explain_migration(
254
- migrate_path, mode=mode, namespace=self.director.namespace
262
+ migrate_path,
263
+ mode=mode,
264
+ namespace=self.director.namespace,
265
+ strict_production_validation=(
266
+ self.args.strict_production_validation
267
+ ),
255
268
  )
256
269
  )
257
- self.director.migrate(migrate_path)
270
+ if self.args.strict_production_validation:
271
+ self.director.migrate(
272
+ migrate_path,
273
+ strict_production_validation=True,
274
+ )
275
+ else:
276
+ self.director.migrate(migrate_path)
258
277
  if self._is_remote:
259
278
  print(
260
279
  migration_utils.format_migration_success(
@@ -95,12 +95,17 @@ def create_parser() -> argparse.ArgumentParser:
95
95
  help="show the migration plan and validation messages without writing to IRIS",
96
96
  action="store_true",
97
97
  )
98
+ migrate.add_argument(
99
+ "--strict-production-validation",
100
+ help="fail migration when production validation reports issues",
101
+ action="store_true",
102
+ )
98
103
 
99
104
  export = main_parser.add_argument_group("export arguments")
100
105
  export.add_argument(
101
106
  "--format",
102
107
  dest="export_format",
103
- choices=("json", "python", "graph"),
108
+ choices=("json", "python", "class", "graph"),
104
109
  default="json",
105
110
  help="export format for -e/--export",
106
111
  )
@@ -55,3 +55,4 @@ class CommandArgs:
55
55
  remote_settings: str | None = None
56
56
  update: bool = False
57
57
  migration_plan: bool = False
58
+ strict_production_validation: bool = False
@@ -499,6 +499,7 @@ ClassMethod PutMigrate() As %DynamicObject
499
499
  set targetDirectory = dyna.%Get("remote_folder")
500
500
  set packageName = dyna.%Get("package")
501
501
  set settingsFile = dyna.%Get("settings_file")
502
+ set strictProductionValidation = dyna.%Get("strict_production_validation")
502
503
  If settingsFile = "" { Set settingsFile = "settings.py" }
503
504
  // check for namespace existence and user permissions against namespace
504
505
  If '..NamespaceCheck(namespace) {
@@ -551,7 +552,7 @@ ClassMethod PutMigrate() As %DynamicObject
551
552
  set iopUtils = ##class(IOP.Wrapper).Import("iop.migration.utils")
552
553
  set builtins = ##class(%SYS.Python).Import("builtins")
553
554
  If builtins.hasattr(iopUtils, "migrate") {
554
- do builtins.getattr(iopUtils, "migrate")."__call__"(##class(%Library.File).NormalizeFilename(settingsFile, packagePath))
555
+ do builtins.getattr(iopUtils, "migrate")."__call__"(##class(%Library.File).NormalizeFilename(settingsFile, packagePath),"REMOTE",namespace,strictProductionValidation)
555
556
  } Else {
556
557
  do iopUtils."_Utils".migrate(##class(%Library.File).NormalizeFilename(settingsFile, packagePath))
557
558
  }
@@ -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","")
@@ -719,8 +729,10 @@ ClassMethod InsertSetting(
719
729
  pSettingData) As %Status [ Internal, Private ]
720
730
  {
721
731
  #dim tSetting As Ens.Config.Setting
732
+ #dim tDefaultTarget As %String
733
+ Set tDefaultTarget = $Select(pOwner.%IsA("Ens.Config.Production"):"",1:"Host")
722
734
  Set tSetting = ##class(Ens.Config.Setting).%New()
723
- Set tSetting.Target = ..DynamicGet(pSettingData,"@Target","Host")
735
+ Set tSetting.Target = ..DynamicGet(pSettingData,"@Target",tDefaultTarget)
724
736
  Set tSetting.Name = ..DynamicGet(pSettingData,"@Name","")
725
737
  Set tSetting.Value = ..DynamicGet(pSettingData,"#text","")
726
738
  Do pOwner.Settings.Insert(tSetting)
@@ -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:
@@ -4,6 +4,8 @@ import inspect
4
4
  import os
5
5
  from typing import Any
6
6
 
7
+ from ..production.validation import validate_production_entry
8
+
7
9
 
8
10
  def format_migration_success(filename: str, namespace: str | None = None) -> str:
9
11
  suffix = f" in namespace {namespace}" if namespace else ""
@@ -21,6 +23,7 @@ def format_migration_plan(plan: dict[str, Any]) -> str:
21
23
  lines.extend(format_plan_section("CLASSES", plan["classes"]))
22
24
  lines.extend(format_plan_section("SCHEMAS", plan["schemas"]))
23
25
  lines.extend(format_plan_section("PRODUCTIONS", plan["productions"]))
26
+ lines.extend(format_plan_section("VALIDATION", plan.get("validation", [])))
24
27
  return "\n".join(lines)
25
28
 
26
29
 
@@ -47,6 +50,7 @@ class MigrationPlanner:
47
50
  filename=None,
48
51
  mode: str | None = None,
49
52
  namespace: str | None = None,
53
+ strict_production_validation: bool = False,
50
54
  ) -> dict[str, Any]:
51
55
  """Build and validate a migration plan from a settings module."""
52
56
  if not path:
@@ -59,11 +63,16 @@ class MigrationPlanner:
59
63
  "classes": [],
60
64
  "schemas": [],
61
65
  "productions": [],
66
+ "validation": [],
62
67
  }
63
68
 
64
69
  self._add_class_entries(plan, getattr(settings, "CLASSES", {}), path)
65
70
  self._add_schema_entries(plan, getattr(settings, "SCHEMAS", None))
66
- self._add_production_entries(plan, getattr(settings, "PRODUCTIONS", None))
71
+ self._add_production_entries(
72
+ plan,
73
+ getattr(settings, "PRODUCTIONS", None),
74
+ strict_production_validation=strict_production_validation,
75
+ )
67
76
  return plan
68
77
 
69
78
  @staticmethod
@@ -102,7 +111,11 @@ class MigrationPlanner:
102
111
  plan["schemas"].append(self._utils._python_classname(cls))
103
112
 
104
113
  def _add_production_entries(
105
- self, plan: dict[str, Any], productions: list[Any] | None
114
+ self,
115
+ plan: dict[str, Any],
116
+ productions: list[Any] | None,
117
+ *,
118
+ strict_production_validation: bool = False,
106
119
  ) -> None:
107
120
  if productions is None:
108
121
  return
@@ -110,6 +123,16 @@ class MigrationPlanner:
110
123
  raise ValueError("PRODUCTIONS must be a list.")
111
124
  auto_class_entries = set()
112
125
  for production in productions:
126
+ report = validate_production_entry(
127
+ production,
128
+ strict=strict_production_validation,
129
+ warn=False,
130
+ )
131
+ if report.has_issues:
132
+ plan["validation"].extend(
133
+ f"{report.production_name}: {issue.to_text()}"
134
+ for issue in report.issues
135
+ )
113
136
  if self._utils._is_production_object(production):
114
137
  plan["productions"].append(production.name)
115
138
  self._add_production_component_entries(
@@ -18,6 +18,7 @@ from ..messages.persistent import (
18
18
  is_persistent_message_class,
19
19
  register_persistent_message_class,
20
20
  )
21
+ from ..production.validation import validate_production_entry
21
22
  from ..runtime import iris as _iris
22
23
  from ..runtime.environment import remove_sys_path, temporary_sys_path
23
24
  from .io import (
@@ -425,7 +426,12 @@ def filename_to_module(filename) -> str:
425
426
  return module
426
427
 
427
428
 
428
- def migrate(filename=None, mode: str | None = None, namespace: str | None = None):
429
+ def migrate(
430
+ filename=None,
431
+ mode: str | None = None,
432
+ namespace: str | None = None,
433
+ strict_production_validation: bool = False,
434
+ ):
429
435
  """
430
436
  Read the settings.py file and register all the components
431
437
  settings.py file has two dictionaries:
@@ -443,10 +449,19 @@ def migrate(filename=None, mode: str | None = None, namespace: str | None = None
443
449
 
444
450
  try:
445
451
  plan = _build_migration_plan(
446
- settings, path, filename, mode=mode, namespace=namespace
452
+ settings,
453
+ path,
454
+ filename,
455
+ mode=mode,
456
+ namespace=namespace,
457
+ strict_production_validation=strict_production_validation,
447
458
  )
448
459
  print(format_migration_plan(plan))
449
- _register_settings_components(settings, path)
460
+ _register_settings_components(
461
+ settings,
462
+ path,
463
+ strict_production_validation=strict_production_validation,
464
+ )
450
465
  print(
451
466
  format_migration_success(
452
467
  filename or inspect.getfile(settings), namespace=namespace
@@ -457,13 +472,21 @@ def migrate(filename=None, mode: str | None = None, namespace: str | None = None
457
472
 
458
473
 
459
474
  def explain_migration(
460
- filename=None, mode: str | None = None, namespace: str | None = None
475
+ filename=None,
476
+ mode: str | None = None,
477
+ namespace: str | None = None,
478
+ strict_production_validation: bool = False,
461
479
  ):
462
480
  """Return a human-readable migration plan without writing to IRIS."""
463
481
  settings, path = _load_settings(filename)
464
482
  try:
465
483
  plan = _build_migration_plan(
466
- settings, path, filename, mode=mode, namespace=namespace
484
+ settings,
485
+ path,
486
+ filename,
487
+ mode=mode,
488
+ namespace=namespace,
489
+ strict_production_validation=strict_production_validation,
467
490
  )
468
491
  return _format_migration_plan(plan)
469
492
  finally:
@@ -488,6 +511,7 @@ def _build_migration_plan(
488
511
  filename=None,
489
512
  mode: str | None = None,
490
513
  namespace: str | None = None,
514
+ strict_production_validation: bool = False,
491
515
  ):
492
516
  return MigrationPlanner(sys.modules[__name__]).build(
493
517
  settings,
@@ -495,6 +519,7 @@ def _build_migration_plan(
495
519
  filename=filename,
496
520
  mode=mode,
497
521
  namespace=namespace,
522
+ strict_production_validation=strict_production_validation,
498
523
  )
499
524
 
500
525
 
@@ -589,7 +614,12 @@ def _validate_dtl_schema_class(cls, setting_name):
589
614
  )
590
615
 
591
616
 
592
- def _register_settings_components(settings, path):
617
+ def _register_settings_components(
618
+ settings,
619
+ path,
620
+ *,
621
+ strict_production_validation: bool = False,
622
+ ):
593
623
  """Register all components from settings (classes, productions, schemas).
594
624
 
595
625
  Args:
@@ -616,6 +646,7 @@ def _register_settings_components(settings, path):
616
646
  settings.PRODUCTIONS,
617
647
  path,
618
648
  persistent_registry=persistent_registry,
649
+ strict_production_validation=strict_production_validation,
619
650
  )
620
651
  except AttributeError:
621
652
  pass
@@ -835,18 +866,25 @@ def set_productions_settings(
835
866
  production_list,
836
867
  root_path=None,
837
868
  persistent_registry=None,
869
+ strict_production_validation: bool = False,
838
870
  ):
839
871
  """
840
872
  It takes a list of dictionaries and registers the productions
841
873
  """
842
874
  # for each production in the list
843
875
  for production in production_list:
844
- if _is_production_object(production):
876
+ production_is_object = _is_production_object(production)
877
+ if production_is_object:
845
878
  _register_production_object_messages(
846
879
  production,
847
880
  persistent_registry=persistent_registry,
848
881
  )
849
882
  _register_production_object_components(production, root_path)
883
+ validate_production_entry(
884
+ production,
885
+ strict=strict_production_validation,
886
+ warn=True,
887
+ )
850
888
  production = production.to_dict()
851
889
  else:
852
890
  production = copy.deepcopy(production)
@@ -860,6 +898,12 @@ def set_productions_settings(
860
898
  production["Production"] = production.pop(production_name)
861
899
  # handle Items
862
900
  production = handle_items(production, root_path)
901
+ if not production_is_object:
902
+ validate_production_entry(
903
+ production,
904
+ strict=strict_production_validation,
905
+ warn=True,
906
+ )
863
907
  # register the production
864
908
  register_production_definition(production_name, production)
865
909
 
@@ -992,11 +1036,21 @@ def register_production_definition(production_name: str, production: dict):
992
1036
  :param production_name: full IRIS production class name
993
1037
  :param production: normalized {"Production": {...}} dictionary
994
1038
  """
995
- raise_on_error(
996
- _iris.get_iris()
997
- .cls("IOP.Utils")
998
- .CreateProductionFromJSON(production_name, json.dumps(production))
999
- )
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
1000
1054
 
1001
1055
 
1002
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
@@ -12,17 +17,30 @@ from .types import ProductionDiffEntry as ProductionDiffEntry
12
17
  from .types import ProductionGraph as ProductionGraph
13
18
  from .types import TargetSetting as TargetSetting
14
19
  from .types import target as target
20
+ from .validation import ProductionValidationError as ProductionValidationError
21
+ from .validation import ProductionValidationIssue as ProductionValidationIssue
22
+ from .validation import ProductionValidationReport as ProductionValidationReport
23
+ from .validation import ProductionValidationWarning as ProductionValidationWarning
15
24
 
16
25
  __all__ = [
17
26
  "ComponentRef",
27
+ "ComponentItem",
18
28
  "GraphEdge",
19
29
  "GraphNode",
30
+ "OperationItem",
20
31
  "PersistentMessageRegistration",
21
32
  "Port",
33
+ "ProcessItem",
22
34
  "Production",
23
35
  "ProductionDiff",
24
36
  "ProductionDiffEntry",
25
37
  "ProductionGraph",
38
+ "ProductionValidationError",
39
+ "ProductionValidationIssue",
40
+ "ProductionValidationReport",
41
+ "ProductionValidationWarning",
42
+ "Route",
43
+ "ServiceItem",
26
44
  "TargetSetting",
27
45
  "resolve_target",
28
46
  "target",
@@ -3,6 +3,30 @@ from __future__ import annotations
3
3
  import importlib
4
4
  from typing import Any
5
5
 
6
+ PRODUCTION_SETTING_FIELDS: dict[str, tuple[str, Any]] = {
7
+ "shutdown_timeout": ("ShutdownTimeout", 120),
8
+ "update_timeout": ("UpdateTimeout", 10),
9
+ "alert_notification_manager": ("AlertNotificationManager", ""),
10
+ "alert_notification_operation": ("AlertNotificationOperation", ""),
11
+ "alert_notification_recipients": ("AlertNotificationRecipients", ""),
12
+ "alert_action_window": ("AlertActionWindow", 60),
13
+ }
14
+
15
+ PRODUCTION_SETTING_NAMES: dict[str, str] = {
16
+ field_name: iris_name
17
+ for field_name, (iris_name, _default) in PRODUCTION_SETTING_FIELDS.items()
18
+ }
19
+
20
+ PRODUCTION_SETTING_FIELDS_BY_IRIS: dict[str, str] = {
21
+ iris_name: field_name
22
+ for field_name, (iris_name, _default) in PRODUCTION_SETTING_FIELDS.items()
23
+ }
24
+
25
+ SETTING_NAME_ALIASES = {
26
+ "target_config_name": "TargetConfigName",
27
+ "target_config_names": "TargetConfigNames",
28
+ }
29
+
6
30
 
7
31
  def _bool_text(value: bool | str) -> str:
8
32
  if isinstance(value, bool):