iris-pex-embedded-python 4.0.2b1__tar.gz → 4.1.0b1__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 (153) hide show
  1. {iris_pex_embedded_python-4.0.2b1/src/iris_pex_embedded_python.egg-info → iris_pex_embedded_python-4.1.0b1}/PKG-INFO +41 -5
  2. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/README.md +21 -2
  3. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/contributing.md +3 -2
  4. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/debug.md +1 -2
  5. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/index.md +2 -2
  6. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/python-api.md +4 -1
  7. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/pyproject.toml +26 -3
  8. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cli/formatting.py +2 -2
  9. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cli/main.py +2 -0
  10. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/components/async_request.py +36 -25
  11. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/components/debugpy.py +8 -2
  12. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/messages/persistent.py +146 -44
  13. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/messages/serialization.py +3 -0
  14. iris_pex_embedded_python-4.0.2b1/src/iop/migration/io.py → iris_pex_embedded_python-4.1.0b1/src/iop/migration/_conversion.py +0 -3
  15. iris_pex_embedded_python-4.1.0b1/src/iop/migration/_production_io.py +82 -0
  16. iris_pex_embedded_python-4.1.0b1/src/iop/migration/_registration.py +78 -0
  17. iris_pex_embedded_python-4.1.0b1/src/iop/migration/_settings.py +47 -0
  18. iris_pex_embedded_python-4.1.0b1/src/iop/migration/io.py +7 -0
  19. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/migration/manifest.py +2 -2
  20. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/migration/utils.py +40 -174
  21. iris_pex_embedded_python-4.1.0b1/src/iop/production/_authoring_methods.py +182 -0
  22. iris_pex_embedded_python-4.1.0b1/src/iop/production/_runtime_methods.py +159 -0
  23. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/production/actions.py +1 -0
  24. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/production/model.py +12 -313
  25. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/production/planning.py +4 -4
  26. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/runtime/director.py +2 -1
  27. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/runtime/remote/director.py +6 -1
  28. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1/src/iris_pex_embedded_python.egg-info}/PKG-INFO +41 -5
  29. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iris_pex_embedded_python.egg-info/SOURCES.txt +6 -0
  30. iris_pex_embedded_python-4.1.0b1/src/iris_pex_embedded_python.egg-info/requires.txt +26 -0
  31. iris_pex_embedded_python-4.0.2b1/src/iris_pex_embedded_python.egg-info/requires.txt +0 -7
  32. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/AGENTS.md +0 -0
  33. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/LICENSE +0 -0
  34. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/MANIFEST.in +0 -0
  35. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/agents-template.md +0 -0
  36. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/ai-coding.md +0 -0
  37. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/benchmarks.md +0 -0
  38. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/changelog.md +0 -0
  39. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/code-of-conduct.md +0 -0
  40. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/command-line.md +0 -0
  41. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/component-interaction.md +0 -0
  42. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/cookbooks/add-business-operation.md +0 -0
  43. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/cookbooks/add-business-process.md +0 -0
  44. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/cookbooks/add-polling-service.md +0 -0
  45. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/cookbooks/code-index.md +0 -0
  46. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/cookbooks/fhir-submission-python-client.md +0 -0
  47. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/cookbooks/hello-world-production.md +0 -0
  48. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/cookbooks/hl7v2-native-input.md +0 -0
  49. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/cookbooks/hl7v2-to-fhir-with-fhir-converter.md +0 -0
  50. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/cookbooks/index.md +0 -0
  51. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/cookbooks/production-settings-and-targets.md +0 -0
  52. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/cookbooks/remote-migration.md +0 -0
  53. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/credits.md +0 -0
  54. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/dtl.md +0 -0
  55. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/example.md +0 -0
  56. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/getting-started/first-steps.md +0 -0
  57. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/getting-started/installation.md +0 -0
  58. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/getting-started/register-component.md +0 -0
  59. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/healthcare-ai-coding.md +0 -0
  60. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/logging.md +0 -0
  61. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/prod-settings.md +0 -0
  62. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/production-change-workflow.md +0 -0
  63. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/production-graph.md +0 -0
  64. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/rest-api.md +0 -0
  65. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/settings.md +0 -0
  66. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/useful-links.md +0 -0
  67. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/venv.md +0 -0
  68. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/docs/wrapper.md +0 -0
  69. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/mkdocs.yml +0 -0
  70. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/setup.cfg +0 -0
  71. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/__init__.py +0 -0
  72. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/__main__.py +0 -0
  73. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cli/__init__.py +0 -0
  74. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cli/parser.py +0 -0
  75. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cli/types.py +0 -0
  76. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/BusinessOperation.cls +0 -0
  77. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/BusinessProcess.cls +0 -0
  78. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/BusinessService.cls +0 -0
  79. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/Common.cls +0 -0
  80. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/Director.cls +0 -0
  81. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/Duplex/Operation.cls +0 -0
  82. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/Duplex/Process.cls +0 -0
  83. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/Duplex/Service.cls +0 -0
  84. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/Generator/Message/Ack.cls +0 -0
  85. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/Generator/Message/Poll.cls +0 -0
  86. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/Generator/Message/Start.cls +0 -0
  87. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/Generator/Message/StartPickle.cls +0 -0
  88. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/Generator/Message/Stop.cls +0 -0
  89. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/InboundAdapter.cls +0 -0
  90. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/Message/JSONSchema.cls +0 -0
  91. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/Message.cls +0 -0
  92. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/OutboundAdapter.cls +0 -0
  93. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/PickleMessage.cls +0 -0
  94. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/PrivateSession/Duplex.cls +0 -0
  95. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/PrivateSession/Message/Ack.cls +0 -0
  96. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/PrivateSession/Message/Poll.cls +0 -0
  97. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/PrivateSession/Message/Start.cls +0 -0
  98. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/PrivateSession/Message/Stop.cls +0 -0
  99. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/Projection.cls +0 -0
  100. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/Service/Remote/Handler.cls +0 -0
  101. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/Service/Remote/Rest/v1.cls +0 -0
  102. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/Test.cls +0 -0
  103. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/Utils.cls +0 -0
  104. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/cls/IOP/Wrapper.cls +0 -0
  105. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/components/__init__.py +0 -0
  106. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/components/business_host.py +0 -0
  107. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/components/business_operation.py +0 -0
  108. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/components/business_process.py +0 -0
  109. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/components/business_service.py +0 -0
  110. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/components/common.py +0 -0
  111. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/components/generator_request.py +0 -0
  112. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/components/inbound_adapter.py +0 -0
  113. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/components/log_manager.py +0 -0
  114. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/components/outbound_adapter.py +0 -0
  115. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/components/polling_business_service.py +0 -0
  116. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/components/private_session_duplex.py +0 -0
  117. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/components/private_session_process.py +0 -0
  118. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/components/settings.py +0 -0
  119. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/messages/__init__.py +0 -0
  120. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/messages/base.py +0 -0
  121. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/messages/decorators.py +0 -0
  122. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/messages/dispatch.py +0 -0
  123. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/messages/validation.py +0 -0
  124. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/migration/__init__.py +0 -0
  125. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/migration/plans.py +0 -0
  126. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/production/__init__.py +0 -0
  127. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/production/common.py +0 -0
  128. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/production/component.py +0 -0
  129. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/production/declarations.py +0 -0
  130. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/production/declarative.py +0 -0
  131. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/production/diff.py +0 -0
  132. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/production/import_.py +0 -0
  133. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/production/inspection.py +0 -0
  134. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/production/reconstruction.py +0 -0
  135. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/production/rendering.py +0 -0
  136. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/production/runtime.py +0 -0
  137. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/production/source_inference.py +0 -0
  138. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/production/types.py +0 -0
  139. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/production/validation.py +0 -0
  140. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/py.typed +0 -0
  141. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/runtime/__init__.py +0 -0
  142. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/runtime/environment.py +0 -0
  143. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/runtime/iris.py +0 -0
  144. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/runtime/local.py +0 -0
  145. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/runtime/protocol.py +0 -0
  146. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/runtime/remote/__init__.py +0 -0
  147. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/runtime/remote/client.py +0 -0
  148. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/runtime/remote/migration.py +0 -0
  149. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/runtime/remote/settings.py +0 -0
  150. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iop/runtime/remote/setup.py +0 -0
  151. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iris_pex_embedded_python.egg-info/dependency_links.txt +0 -0
  152. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/src/iris_pex_embedded_python.egg-info/entry_points.txt +0 -0
  153. {iris_pex_embedded_python-4.0.2b1 → iris_pex_embedded_python-4.1.0b1}/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.2b1
3
+ Version: 4.1.0b1
4
4
  Summary: Iris Interoperability based on Embedded Python
5
5
  Author-email: grongier <guillaume.rongier@intersystems.com>
6
6
  License: MIT License
@@ -46,10 +46,27 @@ License-File: LICENSE
46
46
  Requires-Dist: pydantic>=2.0.0
47
47
  Requires-Dist: xmltodict>=0.12.0
48
48
  Requires-Dist: iris-embedded-python-wrapper>=0.5.0
49
- Requires-Dist: iris-persistence>=0.1.1
49
+ Requires-Dist: iris-persistence>=0.3.0
50
50
  Requires-Dist: jsonpath-ng>=1.7.0
51
- Requires-Dist: debugpy>=1.8.0
52
51
  Requires-Dist: requests>=2.24.0
52
+ Provides-Extra: debug
53
+ Requires-Dist: debugpy>=1.8.0; extra == "debug"
54
+ Provides-Extra: dev
55
+ Requires-Dist: build; extra == "dev"
56
+ Requires-Dist: dataclasses-json; extra == "dev"
57
+ Requires-Dist: intersystems-irispython; extra == "dev"
58
+ Requires-Dist: mkdocs; extra == "dev"
59
+ Requires-Dist: mkdocs-gen-files; extra == "dev"
60
+ Requires-Dist: pymdown-extensions; extra == "dev"
61
+ Requires-Dist: pyright; extra == "dev"
62
+ Requires-Dist: pytest; extra == "dev"
63
+ Requires-Dist: pytest-asyncio; extra == "dev"
64
+ Requires-Dist: python-dotenv; extra == "dev"
65
+ Requires-Dist: ruff; extra == "dev"
66
+ Requires-Dist: tox>=4; extra == "dev"
67
+ Requires-Dist: twine; extra == "dev"
68
+ Requires-Dist: wheel; extra == "dev"
69
+ Requires-Dist: debugpy>=1.8.0; extra == "dev"
53
70
  Dynamic: license-file
54
71
 
55
72
  # IoP (Interoperability On Python)
@@ -60,7 +77,7 @@ Dynamic: license-file
60
77
  [![PyPI - License](https://img.shields.io/pypi/l/iris-pex-embedded-python)](https://pypi.org/project/iris-pex-embedded-python/)
61
78
  ![GitHub last commit](https://img.shields.io/github/last-commit/grongierisc/interoperability-embedded-python)
62
79
 
63
- Welcome to the **Interoperability On Python (IoP)** proof of concept! This project demonstrates how the **IRIS Interoperability Framework** can be utilized with a **Python-first approach**.
80
+ Welcome to **Interoperability On Python (IoP)**, a production-stable Python-first toolkit for building on the **IRIS Interoperability Framework**.
64
81
 
65
82
  Documentation can be found [here](https://grongierisc.github.io/interoperability-embedded-python/).
66
83
  For prompt-driven workflows, see [AI-assisted coding with IoP](https://grongierisc.github.io/interoperability-embedded-python/ai-coding/).
@@ -105,7 +122,7 @@ PRODUCTIONS = [prod]
105
122
 
106
123
  ## Installation
107
124
 
108
- To start using this proof of concept, install it using pip:
125
+ Install the production-stable package using pip:
109
126
 
110
127
  ```bash
111
128
  pip install iris-pex-embedded-python
@@ -120,3 +137,22 @@ For concrete workflows, use the [IoP cookbooks](https://grongierisc.github.io/in
120
137
  For healthcare productions, also see [Healthcare AI-assisted coding](https://grongierisc.github.io/interoperability-embedded-python/healthcare-ai-coding/).
121
138
 
122
139
  Happy coding!
140
+
141
+ ## Compatibility and support
142
+
143
+ IoP supports Python 3.10 through 3.14 and InterSystems IRIS 2021.2 or newer.
144
+ Changes follow semantic versioning:
145
+ backward-compatible features and deprecations ship in minor releases, while
146
+ removals are reserved for major releases and are announced in the changelog.
147
+ IRIS compatibility is continuously checked against the latest Community image;
148
+ the preview image is tested nightly to identify upcoming incompatibilities.
149
+
150
+ ## Contributing
151
+
152
+ Create a Python 3.10 or newer virtual environment, then install and verify the
153
+ development environment with:
154
+
155
+ ```bash
156
+ python -m pip install -e ".[dev]"
157
+ tox
158
+ ```
@@ -6,7 +6,7 @@
6
6
  [![PyPI - License](https://img.shields.io/pypi/l/iris-pex-embedded-python)](https://pypi.org/project/iris-pex-embedded-python/)
7
7
  ![GitHub last commit](https://img.shields.io/github/last-commit/grongierisc/interoperability-embedded-python)
8
8
 
9
- Welcome to the **Interoperability On Python (IoP)** proof of concept! This project demonstrates how the **IRIS Interoperability Framework** can be utilized with a **Python-first approach**.
9
+ Welcome to **Interoperability On Python (IoP)**, a production-stable Python-first toolkit for building on the **IRIS Interoperability Framework**.
10
10
 
11
11
  Documentation can be found [here](https://grongierisc.github.io/interoperability-embedded-python/).
12
12
  For prompt-driven workflows, see [AI-assisted coding with IoP](https://grongierisc.github.io/interoperability-embedded-python/ai-coding/).
@@ -51,7 +51,7 @@ PRODUCTIONS = [prod]
51
51
 
52
52
  ## Installation
53
53
 
54
- To start using this proof of concept, install it using pip:
54
+ Install the production-stable package using pip:
55
55
 
56
56
  ```bash
57
57
  pip install iris-pex-embedded-python
@@ -66,3 +66,22 @@ For concrete workflows, use the [IoP cookbooks](https://grongierisc.github.io/in
66
66
  For healthcare productions, also see [Healthcare AI-assisted coding](https://grongierisc.github.io/interoperability-embedded-python/healthcare-ai-coding/).
67
67
 
68
68
  Happy coding!
69
+
70
+ ## Compatibility and support
71
+
72
+ IoP supports Python 3.10 through 3.14 and InterSystems IRIS 2021.2 or newer.
73
+ Changes follow semantic versioning:
74
+ backward-compatible features and deprecations ship in minor releases, while
75
+ removals are reserved for major releases and are announced in the changelog.
76
+ IRIS compatibility is continuously checked against the latest Community image;
77
+ the preview image is tested nightly to identify upcoming incompatibilities.
78
+
79
+ ## Contributing
80
+
81
+ Create a Python 3.10 or newer virtual environment, then install and verify the
82
+ development environment with:
83
+
84
+ ```bash
85
+ python -m pip install -e ".[dev]"
86
+ tox
87
+ ```
@@ -14,7 +14,8 @@ source .venv/bin/activate
14
14
  ### Install the dependencies.
15
15
 
16
16
  ```bash
17
- pip install -r requirements-dev.txt
17
+ python -m pip install -e ".[dev]"
18
+ tox
18
19
  ```
19
20
 
20
21
  ### Have a running IRIS instance.
@@ -45,7 +46,7 @@ ln -s <your_git_dir>/src/iop $IRISINSTALLDIR/python/iop
45
46
  ##### Run the unit tests.
46
47
 
47
48
  ```bash
48
- pytest
49
+ tox -e py
49
50
  ```
50
51
 
51
52
  #### Docker installation of IRIS
@@ -153,7 +153,7 @@ And select the virtual environment in VsCode.
153
153
  4. Install the dependencies.
154
154
 
155
155
  ```bash
156
- pip install -r requirements.txt
156
+ python -m pip install -e ".[debug]"
157
157
  ```
158
158
 
159
159
  5. Run the code in debug mode.
@@ -219,4 +219,3 @@ With tracebacks enabled:
219
219
  With tracebacks disabled:
220
220
 
221
221
  ![Traceback disabled](./img/traceback_disable.png)
222
-
@@ -6,7 +6,7 @@
6
6
  [![PyPI - License](https://img.shields.io/pypi/l/iris-pex-embedded-python)](https://pypi.org/project/iris-pex-embedded-python/)
7
7
  ![GitHub last commit](https://img.shields.io/github/last-commit/grongierisc/interoperability-embedded-python)
8
8
 
9
- Welcome to the **Interoperability On Python (IoP)** proof of concept! This project demonstrates how the **IRIS Interoperability Framework** can be utilized with a **Python-first approach**.
9
+ Welcome to **Interoperability On Python (IoP)**, a production-stable Python-first toolkit for building on the **IRIS Interoperability Framework**.
10
10
 
11
11
  Documentation can be found [here](https://grongierisc.github.io/interoperability-embedded-python/).
12
12
  For prompt-driven workflows, see [AI-assisted coding with IoP](https://grongierisc.github.io/interoperability-embedded-python/ai-coding/).
@@ -51,7 +51,7 @@ PRODUCTIONS = [prod]
51
51
 
52
52
  ## Installation
53
53
 
54
- To start using this proof of concept, install it using pip:
54
+ Install the production-stable package using pip:
55
55
 
56
56
  ```bash
57
57
  pip install iris-pex-embedded-python
@@ -35,7 +35,10 @@ class MyRequest(PydanticMessage):
35
35
 
36
36
  Base class for native IRIS message body classes backed by `iris-persistence`. Fields are declared directly on the Python class.
37
37
 
38
- By default, a `PersistentMessage` generates an IRIS class that extends `Ens.MessageBody`, uses schema mode `extend`, and enables runtime auto-sync in extend mode.
38
+ By default, a `PersistentMessage` generates an IRIS class that extends
39
+ `Ens.MessageBody`, uses schema mode `managed`, and enables runtime auto-sync in
40
+ managed mode. This is the replacement for the `extend` mode removed in
41
+ `iris-persistence` 0.3.
39
42
 
40
43
  **Example:**
41
44
  ```python
@@ -3,7 +3,7 @@ requires = ["setuptools", "wheel"]
3
3
 
4
4
  [project]
5
5
  name = "iris_pex_embedded_python"
6
- version = "4.0.2b1"
6
+ version = "4.1.0b1"
7
7
  description = "Iris Interoperability based on Embedded Python"
8
8
  readme = "README.md"
9
9
  requires-python = ">=3.10"
@@ -29,14 +29,35 @@ dependencies = [
29
29
  "pydantic>=2.0.0",
30
30
  "xmltodict>=0.12.0",
31
31
  "iris-embedded-python-wrapper>=0.5.0",
32
- "iris-persistence>=0.1.1",
32
+ "iris-persistence>=0.3.0",
33
33
  "jsonpath-ng>=1.7.0",
34
- "debugpy>=1.8.0",
35
34
  "requests>=2.24.0"
36
35
  ]
37
36
 
38
37
  license = { file = "LICENSE" }
39
38
 
39
+ [project.optional-dependencies]
40
+ debug = [
41
+ "debugpy>=1.8.0",
42
+ ]
43
+ dev = [
44
+ "build",
45
+ "dataclasses-json",
46
+ "intersystems-irispython",
47
+ "mkdocs",
48
+ "mkdocs-gen-files",
49
+ "pymdown-extensions",
50
+ "pyright",
51
+ "pytest",
52
+ "pytest-asyncio",
53
+ "python-dotenv",
54
+ "ruff",
55
+ "tox>=4",
56
+ "twine",
57
+ "wheel",
58
+ "debugpy>=1.8.0",
59
+ ]
60
+
40
61
  [project.urls]
41
62
  homepage = "https://github.com/grongierisc/interoperability-embedded-python"
42
63
  documentation = "https://github.com/grongierisc/interoperability-embedded-python/blob/master/README.md"
@@ -66,10 +87,12 @@ markers = [
66
87
 
67
88
  [tool.pyright]
68
89
  pythonVersion = "3.10"
90
+ include = ["src/iop"]
69
91
 
70
92
  [tool.ruff]
71
93
  target-version = "py310"
72
94
  line-length = 88
95
+ exclude = ["src/tests"]
73
96
 
74
97
  [tool.ruff.lint]
75
98
  select = ["E", "F", "I", "B", "UP"]
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import dataclasses
4
4
  import json
5
- from typing import Any
5
+ from typing import Any, cast
6
6
 
7
7
 
8
8
  def format_test_response(response: Any) -> str:
@@ -41,5 +41,5 @@ def format_test_response(response: Any) -> str:
41
41
  return response
42
42
 
43
43
  if dataclasses.is_dataclass(response):
44
- return json.dumps(dataclasses.asdict(response), indent=4)
44
+ return json.dumps(dataclasses.asdict(cast(Any, response)), indent=4)
45
45
  return str(response)
@@ -241,6 +241,8 @@ class Command:
241
241
  if self.args.export == "not_set"
242
242
  else self.args.export
243
243
  )
244
+ if not export_name:
245
+ raise ValueError("No production name was provided or configured.")
244
246
  export_format = self.args.export_format or "json"
245
247
  if export_format == "json":
246
248
  print(json.dumps(self.director.export_production(export_name), indent=4))
@@ -30,37 +30,48 @@ class AsyncRequest(asyncio.Future):
30
30
  if host is None:
31
31
  raise ValueError("host parameter cannot be None")
32
32
  self._iris_handle = host.iris_handle
33
- asyncio.create_task(self.send())
33
+ self._message_header_id = 0
34
+ self._queue_name = ""
35
+ self._end_time = 0
36
+ self._response = None
37
+ self._done = False
38
+ self._send_task = asyncio.create_task(self.send())
34
39
 
35
40
  async def send(self) -> None:
36
- # init parameters
37
- iris = _iris.get_iris()
38
- message_header_id = iris.ref()
39
- queue_name = iris.ref()
40
- end_time = iris.ref()
41
- request = dispatch_serializer(self.request)
41
+ try:
42
+ iris = _iris.get_iris()
43
+ message_header_id = iris.ref()
44
+ queue_name = iris.ref()
45
+ end_time = iris.ref()
46
+ request = dispatch_serializer(self.request)
42
47
 
43
- # send request
44
- self._iris_handle.dispatchSendRequestAsyncNG(
45
- self.target,
46
- request,
47
- self.timeout,
48
- self.description,
49
- message_header_id,
50
- queue_name,
51
- end_time,
52
- )
48
+ self._iris_handle.dispatchSendRequestAsyncNG(
49
+ self.target,
50
+ request,
51
+ self.timeout,
52
+ self.description,
53
+ message_header_id,
54
+ queue_name,
55
+ end_time,
56
+ )
53
57
 
54
- # get byref values
55
- self._message_header_id = message_header_id.value
56
- self._queue_name = queue_name.value
57
- self._end_time = end_time.value
58
+ self._message_header_id = message_header_id.value
59
+ self._queue_name = queue_name.value
60
+ self._end_time = end_time.value
58
61
 
59
- while not self._done:
60
- await asyncio.sleep(0.1)
61
- self.is_done()
62
+ while not self._done:
63
+ await asyncio.sleep(0.1)
64
+ self.is_done()
62
65
 
63
- self.set_result(self._response)
66
+ if not self.done():
67
+ self.set_result(self._response)
68
+ except asyncio.CancelledError:
69
+ if not self.done():
70
+ self.cancel()
71
+ raise
72
+ except Exception as exc:
73
+ if not self.done():
74
+ self.set_exception(exc)
64
75
 
65
76
  def is_done(self) -> None:
66
77
  iris = _iris.get_iris()
@@ -127,7 +127,10 @@ def debugpython(self, host_object: Any) -> None:
127
127
  return
128
128
 
129
129
  if not is_debugpy_installed():
130
- self.log_alert("Debugpy is not installed.")
130
+ self.log_alert(
131
+ "Debugpy is not installed. Install iris-pex-embedded-python[debug] "
132
+ "to enable remote debugging."
133
+ )
131
134
  return
132
135
 
133
136
  # Configure Python interpreter
@@ -167,7 +170,10 @@ def debugpy_in_iris(iris_dir, port) -> bool:
167
170
  os.__file__ = __file__
168
171
 
169
172
  if not is_debugpy_installed():
170
- print("debugpy is not installed.")
173
+ print(
174
+ "debugpy is not installed. Install iris-pex-embedded-python[debug] "
175
+ "to enable remote debugging."
176
+ )
171
177
  return False
172
178
  if not iris_dir:
173
179
  print("IRIS directory is not specified.")
@@ -4,18 +4,19 @@ import importlib
4
4
  import inspect
5
5
  import os
6
6
  import sys
7
+ from dataclasses import dataclass
8
+ from enum import Enum
7
9
  from typing import Any
8
10
 
9
- import iris_persistence
10
11
  from iris_persistence import Field as Field
11
- from iris_persistence import Model
12
+ from iris_persistence import Model as _PersistenceModel
12
13
  from iris_persistence.models import ModelMeta
13
14
  from iris_persistence.runtime import get_runtime
14
15
 
15
16
  from ..runtime.environment import prepend_sys_path
16
17
 
17
18
  DEFAULT_SUPERCLASS = "Ens.MessageBody"
18
- DEFAULT_SYNC_MODE = "extend"
19
+ DEFAULT_SYNC_MODE = "managed"
19
20
  MESSAGE_KIND_PARAMETER = "IOP_MESSAGE_KIND"
20
21
  MESSAGE_KIND_VALUE = "PersistentMessage"
21
22
  PYTHON_CLASS_PARAMETER = "IOP_PYTHON_CLASS"
@@ -28,13 +29,46 @@ _IRIS_TO_PYTHON_STRICT_CACHE: dict[str, bool] = {}
28
29
  _IRIS_TO_MESSAGE_CLASS_CACHE: dict[str, type] = {}
29
30
  _IRIS_PARAMETER_CACHE: dict[tuple[str, str], str | None] = {}
30
31
  _AUTO_SYNCED: set[tuple[type, str]] = set()
32
+ _CACHE_VALUE_UNSET = object()
31
33
 
32
34
 
33
35
  class PersistentMessageError(Exception):
34
36
  """Raised when a native persistent message cannot be materialized."""
35
37
 
36
38
 
37
- class _PersistentMessageMeta(ModelMeta):
39
+ class _PersistentMessageResolutionState(Enum):
40
+ UNMAPPED = "unmapped"
41
+ RESOLVED = "resolved"
42
+ INVALID = "invalid"
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class _PersistentMessageResolution:
47
+ state: _PersistentMessageResolutionState
48
+ message_class: type | None = None
49
+ error: PersistentMessageError | None = None
50
+
51
+
52
+ def _materialize_deferred_annotations(namespace: dict) -> None:
53
+ """Expose Python 3.14 deferred annotations to iris-persistence."""
54
+ if "__annotations__" in namespace:
55
+ return
56
+ annotate = namespace.get("__annotate_func__")
57
+ if annotate is not None:
58
+ namespace["__annotations__"] = annotate(1)
59
+
60
+
61
+ class _ModelMeta(ModelMeta):
62
+ def __new__(mcs, name: str, bases: tuple, namespace: dict, **kwargs: Any):
63
+ _materialize_deferred_annotations(namespace)
64
+ return super().__new__(mcs, name, bases, namespace, **kwargs)
65
+
66
+
67
+ class Model(_PersistenceModel, metaclass=_ModelMeta):
68
+ """Python 3.10–3.14 compatible public persistence model base."""
69
+
70
+
71
+ class _PersistentMessageMeta(_ModelMeta):
38
72
  def __init__(cls, name: str, bases: tuple, namespace: dict, **kwargs: Any):
39
73
  super().__init__(name, bases, namespace, **kwargs)
40
74
 
@@ -199,8 +233,7 @@ def serialize_persistent_message(
199
233
  iris_classname = resolve_iris_classname(msg_cls)
200
234
  _ensure_schema(msg_cls, iris_classname)
201
235
 
202
- iris_obj = iris_persistence.materialize(
203
- message,
236
+ iris_obj = message.to_iris(
204
237
  auto_sync=False,
205
238
  validate=False,
206
239
  )
@@ -215,36 +248,14 @@ def deserialize_persistent_message(
215
248
  if not iris_classname:
216
249
  return serial
217
250
 
218
- msg_cls = _IRIS_TO_MESSAGE_CLASS_CACHE.get(iris_classname)
219
- if msg_cls is None:
220
- python_classname, python_classpath, strict = _resolve_python_message_metadata(
221
- iris_classname
222
- )
223
- if not python_classname:
224
- return serial
225
-
226
- try:
227
- msg_cls = load_python_class(python_classname, python_classpath)
228
- except (AttributeError, ModuleNotFoundError, PersistentMessageError) as exc:
229
- if strict:
230
- raise PersistentMessageError(
231
- f"IRIS class {iris_classname!r} is marked as a PersistentMessage for "
232
- f"Python class {python_classname!r}, but that Python class could not "
233
- "be imported. Ensure the message class is importable, or register it "
234
- "through CLASSES so migration writes IOP_PYTHON_CLASSPATH."
235
- ) from exc
236
- return serial
237
-
238
- if not is_persistent_message_class(msg_cls):
239
- if strict:
240
- raise PersistentMessageError(
241
- f"IRIS class {iris_classname!r} maps to {python_classname!r}, "
242
- "but that class is not a PersistentMessage subclass."
243
- )
244
- return serial
245
-
246
- _prepare_message_class(msg_cls, iris_classname, registered=False)
247
- _IRIS_TO_MESSAGE_CLASS_CACHE[iris_classname] = msg_cls
251
+ resolution = _resolve_persistent_message_class(iris_classname)
252
+ if resolution.state is _PersistentMessageResolutionState.UNMAPPED:
253
+ return serial
254
+ if resolution.state is _PersistentMessageResolutionState.INVALID:
255
+ assert resolution.error is not None
256
+ raise resolution.error
257
+ msg_cls = resolution.message_class
258
+ assert msg_cls is not None
248
259
 
249
260
  known_pk = _safe_get_object_id(serial)
250
261
  return msg_cls.from_iris(serial, known_pk=known_pk or "")
@@ -353,7 +364,7 @@ def _ensure_schema(msg_cls: type, iris_classname: str) -> None:
353
364
  raise PersistentMessageError(
354
365
  f"{get_python_classname(msg_cls)} has auto_sync=True but mode="
355
366
  f"{getattr(msg_cls, '_sync_mode', None)!r}. Runtime auto-sync is only allowed "
356
- "with mode='extend'."
367
+ "with mode='managed'."
357
368
  )
358
369
 
359
370
  _prepare_message_class(msg_cls, iris_classname, registered=False)
@@ -372,8 +383,12 @@ def _prepare_message_class(
372
383
  if registered:
373
384
  msg_cls._iop_registered_classname = iris_classname
374
385
  _set_message_parameters(msg_cls)
375
- _cache_mapping(get_python_classname(msg_cls), iris_classname)
376
- _IRIS_TO_MESSAGE_CLASS_CACHE[iris_classname] = msg_cls
386
+ _cache_mapping(
387
+ get_python_classname(msg_cls),
388
+ iris_classname,
389
+ python_classpath=get_python_classpath(msg_cls),
390
+ message_class=msg_cls,
391
+ )
377
392
 
378
393
 
379
394
  def _set_message_parameters(msg_cls: type) -> None:
@@ -388,10 +403,37 @@ def _set_message_parameters(msg_cls: type) -> None:
388
403
  msg_cls._parameters = parameters
389
404
 
390
405
 
391
- def _cache_mapping(python_classname: str, iris_classname: str) -> None:
406
+ def _cache_mapping(
407
+ python_classname: str,
408
+ iris_classname: str,
409
+ *,
410
+ python_classpath: str | None | object = _CACHE_VALUE_UNSET,
411
+ strict: bool = True,
412
+ message_class: type | None = None,
413
+ ) -> None:
414
+ previous_iris = _PYTHON_TO_IRIS_CACHE.get(python_classname)
415
+ if previous_iris and previous_iris != iris_classname:
416
+ _IRIS_TO_PYTHON_CACHE.pop(previous_iris, None)
417
+ _IRIS_TO_PYTHON_CLASSPATH_CACHE.pop(previous_iris, None)
418
+ _IRIS_TO_PYTHON_STRICT_CACHE.pop(previous_iris, None)
419
+ _IRIS_TO_MESSAGE_CLASS_CACHE.pop(previous_iris, None)
420
+
421
+ previous_python = _IRIS_TO_PYTHON_CACHE.get(iris_classname)
422
+ if previous_python and previous_python != python_classname:
423
+ if _PYTHON_TO_IRIS_CACHE.get(previous_python) == iris_classname:
424
+ _PYTHON_TO_IRIS_CACHE.pop(previous_python, None)
425
+ _IRIS_TO_MESSAGE_CLASS_CACHE.pop(iris_classname, None)
426
+
392
427
  _PYTHON_TO_IRIS_CACHE[python_classname] = iris_classname
393
428
  _IRIS_TO_PYTHON_CACHE[iris_classname] = python_classname
394
- _IRIS_TO_PYTHON_STRICT_CACHE[iris_classname] = True
429
+ _IRIS_TO_PYTHON_STRICT_CACHE[iris_classname] = strict
430
+ if python_classpath is not _CACHE_VALUE_UNSET:
431
+ if python_classpath:
432
+ _IRIS_TO_PYTHON_CLASSPATH_CACHE[iris_classname] = str(python_classpath)
433
+ else:
434
+ _IRIS_TO_PYTHON_CLASSPATH_CACHE.pop(iris_classname, None)
435
+ if message_class is not None:
436
+ _IRIS_TO_MESSAGE_CLASS_CACHE[iris_classname] = message_class
395
437
 
396
438
 
397
439
  def _resolve_python_message_metadata(
@@ -416,13 +458,73 @@ def _resolve_python_message_metadata(
416
458
  python_classname = iris_classname_to_python_classname(iris_classname)
417
459
 
418
460
  if python_classname and strict:
419
- _cache_mapping(python_classname, iris_classname)
420
- if python_classpath:
421
- _IRIS_TO_PYTHON_CLASSPATH_CACHE[iris_classname] = python_classpath
461
+ _cache_mapping(
462
+ python_classname,
463
+ iris_classname,
464
+ python_classpath=python_classpath,
465
+ strict=strict,
466
+ )
422
467
 
423
468
  return python_classname, python_classpath, strict
424
469
 
425
470
 
471
+ def _resolve_persistent_message_class(
472
+ iris_classname: str,
473
+ ) -> _PersistentMessageResolution:
474
+ cached = _IRIS_TO_MESSAGE_CLASS_CACHE.get(iris_classname)
475
+ if cached is not None:
476
+ return _PersistentMessageResolution(
477
+ _PersistentMessageResolutionState.RESOLVED,
478
+ message_class=cached,
479
+ )
480
+
481
+ python_classname, python_classpath, strict = _resolve_python_message_metadata(
482
+ iris_classname
483
+ )
484
+ if not python_classname:
485
+ return _PersistentMessageResolution(
486
+ _PersistentMessageResolutionState.UNMAPPED
487
+ )
488
+
489
+ try:
490
+ msg_cls = load_python_class(python_classname, python_classpath)
491
+ except (AttributeError, ModuleNotFoundError, PersistentMessageError) as exc:
492
+ if not strict:
493
+ return _PersistentMessageResolution(
494
+ _PersistentMessageResolutionState.UNMAPPED
495
+ )
496
+ error = PersistentMessageError(
497
+ f"IRIS class {iris_classname!r} is marked as a PersistentMessage for "
498
+ f"Python class {python_classname!r}, but that Python class could not "
499
+ "be imported. Ensure the message class is importable, or register it "
500
+ "through CLASSES so migration writes IOP_PYTHON_CLASSPATH."
501
+ )
502
+ error.__cause__ = exc
503
+ return _PersistentMessageResolution(
504
+ _PersistentMessageResolutionState.INVALID,
505
+ error=error,
506
+ )
507
+
508
+ if not is_persistent_message_class(msg_cls):
509
+ if not strict:
510
+ return _PersistentMessageResolution(
511
+ _PersistentMessageResolutionState.UNMAPPED
512
+ )
513
+ return _PersistentMessageResolution(
514
+ _PersistentMessageResolutionState.INVALID,
515
+ error=PersistentMessageError(
516
+ f"IRIS class {iris_classname!r} maps to {python_classname!r}, "
517
+ "but that class is not a PersistentMessage subclass."
518
+ ),
519
+ )
520
+
521
+ _prepare_message_class(msg_cls, iris_classname, registered=False)
522
+ return _PersistentMessageResolution(
523
+ _PersistentMessageResolutionState.RESOLVED,
524
+ message_class=msg_cls,
525
+ )
526
+
527
+
426
528
  def get_iris_class_parameter(
427
529
  iris_classname: str,
428
530
  parameter_name: str,
@@ -104,6 +104,9 @@ class MessageSerializer:
104
104
  f"Failed to load class {serial.classname}: {str(e)}"
105
105
  ) from e
106
106
 
107
+ if not isinstance(msg_class, type):
108
+ raise SerializationError(f"Class {msg_class} must be a class")
109
+
107
110
  if is_dataclass(msg_class) and issubclass(msg_class, BaseModel):
108
111
  raise SerializationError(
109
112
  f"Class '{msg_class.__name__}' combines @dataclass with PydanticMessage, which are incompatible. "
@@ -14,8 +14,6 @@ def dict_to_xml(data: dict[str, Any]) -> str:
14
14
 
15
15
 
16
16
  def xml_to_json(xml_string: str) -> str:
17
- """Convert an XML production export to the JSON shape used by Production."""
18
-
19
17
  def postprocessor(path, key, value):
20
18
  return key, "" if value is None else value
21
19
 
@@ -44,7 +42,6 @@ def string_to_stream(iris, string: str, buffer=1000000):
44
42
 
45
43
 
46
44
  def guess_path(module: str, path: str) -> str:
47
- """Determine the full file path for a module name under a base path."""
48
45
  if not module:
49
46
  raise ValueError("Module name cannot be empty")
50
47