iris-pex-embedded-python 3.7.2b2__py3-none-any.whl → 4.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iop/__init__.py +276 -18
- iop/__main__.py +3 -3
- iop/cli/__init__.py +1 -0
- iop/cli/formatting.py +45 -0
- iop/{_cli.py → cli/main.py} +238 -216
- iop/cli/parser.py +165 -0
- iop/cli/types.py +73 -0
- iop/cls/IOP/Common.cls +11 -2
- iop/cls/IOP/PrivateSession/Duplex.cls +1 -1
- iop/cls/IOP/Service/Remote/Rest/v1.cls +152 -5
- iop/cls/IOP/Utils.cls +814 -7
- iop/cls/IOP/Wrapper.cls +1 -1
- iop/components/__init__.py +16 -0
- iop/{_async_request.py → components/async_request.py} +7 -7
- iop/components/business_host.py +363 -0
- iop/components/business_operation.py +84 -0
- iop/{_business_process.py → components/business_process.py} +145 -125
- iop/components/business_service.py +106 -0
- iop/{_common.py → components/common.py} +160 -164
- iop/{_debugpy.py → components/debugpy.py} +11 -11
- iop/{_generator_request.py → components/generator_request.py} +4 -4
- iop/{_inbound_adapter.py → components/inbound_adapter.py} +10 -18
- iop/{_log_manager.py → components/log_manager.py} +1 -1
- iop/{_outbound_adapter.py → components/outbound_adapter.py} +1 -1
- iop/components/polling_business_service.py +48 -0
- iop/{_private_session_duplex.py → components/private_session_duplex.py} +7 -9
- iop/{_private_session_process.py → components/private_session_process.py} +2 -2
- iop/{_settings.py → components/settings.py} +23 -3
- iop/messages/__init__.py +7 -0
- iop/messages/base.py +61 -0
- iop/{_decorators.py → messages/decorators.py} +8 -2
- iop/messages/dispatch.py +351 -0
- iop/{_persistent_message.py → messages/persistent.py} +24 -19
- iop/{_serialization.py → messages/serialization.py} +34 -21
- iop/{_message_validator.py → messages/validation.py} +15 -13
- iop/migration/__init__.py +1 -0
- iop/migration/io.py +61 -0
- iop/migration/manifest.py +534 -0
- iop/migration/plans.py +80 -0
- iop/migration/utils.py +1126 -0
- iop/production/__init__.py +55 -0
- iop/production/actions.py +525 -0
- iop/production/common.py +103 -0
- iop/production/component.py +234 -0
- iop/production/declarations.py +190 -0
- iop/production/declarative.py +214 -0
- iop/production/diff.py +327 -0
- iop/production/import_.py +296 -0
- iop/production/inspection.py +92 -0
- iop/production/model.py +1143 -0
- iop/production/planning.py +678 -0
- iop/production/reconstruction.py +341 -0
- iop/production/rendering.py +610 -0
- iop/production/runtime.py +91 -0
- iop/production/source_inference.py +729 -0
- iop/production/types.py +546 -0
- iop/production/validation.py +452 -0
- iop/runtime/__init__.py +1 -0
- iop/runtime/director.py +415 -0
- iop/runtime/environment.py +59 -0
- iop/{_iris.py → runtime/iris.py} +2 -2
- iop/runtime/local.py +145 -0
- iop/{_director_protocol.py → runtime/protocol.py} +25 -8
- iop/runtime/remote/__init__.py +12 -0
- iop/runtime/remote/client.py +90 -0
- iop/runtime/remote/director.py +283 -0
- iop/runtime/remote/migration.py +57 -0
- iop/runtime/remote/settings.py +69 -0
- iop/runtime/remote/setup.py +72 -0
- {iris_pex_embedded_python-3.7.2b2.dist-info → iris_pex_embedded_python-4.0.0.dist-info}/METADATA +11 -2
- iris_pex_embedded_python-4.0.0.dist-info/RECORD +99 -0
- iris_pex_embedded_python-4.0.0.dist-info/entry_points.txt +2 -0
- iris_pex_embedded_python-4.0.0.dist-info/top_level.txt +1 -0
- grongier/__init__.py +0 -0
- grongier/cls/Grongier/PEX/BusinessOperation.cls +0 -8
- grongier/cls/Grongier/PEX/BusinessProcess.cls +0 -13
- grongier/cls/Grongier/PEX/BusinessService.cls +0 -8
- grongier/cls/Grongier/PEX/Common.cls +0 -10
- grongier/cls/Grongier/PEX/Director.cls +0 -10
- grongier/cls/Grongier/PEX/Duplex/Operation.cls +0 -4
- grongier/cls/Grongier/PEX/Duplex/Process.cls +0 -13
- grongier/cls/Grongier/PEX/Duplex/Service.cls +0 -4
- grongier/cls/Grongier/PEX/InboundAdapter.cls +0 -8
- grongier/cls/Grongier/PEX/Message.cls +0 -13
- grongier/cls/Grongier/PEX/OutboundAdapter.cls +0 -8
- grongier/cls/Grongier/PEX/PickleMessage.cls +0 -13
- grongier/cls/Grongier/PEX/PrivateSession/Duplex.cls +0 -8
- grongier/cls/Grongier/PEX/PrivateSession/Message/Ack.cls +0 -14
- grongier/cls/Grongier/PEX/PrivateSession/Message/Poll.cls +0 -14
- grongier/cls/Grongier/PEX/PrivateSession/Message/Start.cls +0 -14
- grongier/cls/Grongier/PEX/PrivateSession/Message/Stop.cls +0 -14
- grongier/cls/Grongier/PEX/Test.cls +0 -10
- grongier/cls/Grongier/PEX/Utils.cls +0 -10
- grongier/cls/Grongier/Service/WSGI.cls +0 -4
- grongier/pex/__init__.py +0 -75
- grongier/pex/__main__.py +0 -4
- grongier/pex/_business_host.py +0 -1
- grongier/pex/_cli.py +0 -4
- grongier/pex/_common.py +0 -1
- grongier/pex/_director.py +0 -1
- grongier/pex/_utils.py +0 -1
- grongier/pex/wsgi/handlers.py +0 -104
- iop/_business_host.py +0 -322
- iop/_business_operation.py +0 -77
- iop/_business_service.py +0 -67
- iop/_director.py +0 -333
- iop/_dispatch.py +0 -194
- iop/_local.py +0 -106
- iop/_message.py +0 -45
- iop/_polling_business_service.py +0 -6
- iop/_remote.py +0 -473
- iop/_utils.py +0 -912
- iop/cls/IOP/Service/WSGI.cls +0 -310
- iop/wsgi/handlers.py +0 -112
- iris_pex_embedded_python-3.7.2b2.dist-info/RECORD +0 -96
- iris_pex_embedded_python-3.7.2b2.dist-info/entry_points.txt +0 -2
- iris_pex_embedded_python-3.7.2b2.dist-info/top_level.txt +0 -2
- {iris_pex_embedded_python-3.7.2b2.dist-info → iris_pex_embedded_python-4.0.0.dist-info}/WHEEL +0 -0
- {iris_pex_embedded_python-3.7.2b2.dist-info → iris_pex_embedded_python-4.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,7 +4,7 @@ import importlib
|
|
|
4
4
|
import inspect
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any
|
|
8
8
|
|
|
9
9
|
import iris_persistence
|
|
10
10
|
from iris_persistence import Field as Field
|
|
@@ -12,6 +12,7 @@ from iris_persistence import Model
|
|
|
12
12
|
from iris_persistence.models import ModelMeta
|
|
13
13
|
from iris_persistence.runtime import get_runtime
|
|
14
14
|
|
|
15
|
+
from ..runtime.environment import prepend_sys_path
|
|
15
16
|
|
|
16
17
|
DEFAULT_SUPERCLASS = "Ens.MessageBody"
|
|
17
18
|
DEFAULT_SYNC_MODE = "extend"
|
|
@@ -25,7 +26,7 @@ _IRIS_TO_PYTHON_CACHE: dict[str, str] = {}
|
|
|
25
26
|
_IRIS_TO_PYTHON_CLASSPATH_CACHE: dict[str, str] = {}
|
|
26
27
|
_IRIS_TO_PYTHON_STRICT_CACHE: dict[str, bool] = {}
|
|
27
28
|
_IRIS_TO_MESSAGE_CLASS_CACHE: dict[str, type] = {}
|
|
28
|
-
_IRIS_PARAMETER_CACHE: dict[tuple[str, str],
|
|
29
|
+
_IRIS_PARAMETER_CACHE: dict[tuple[str, str], str | None] = {}
|
|
29
30
|
_AUTO_SYNCED: set[tuple[type, str]] = set()
|
|
30
31
|
|
|
31
32
|
|
|
@@ -54,18 +55,18 @@ class _PersistentMessageMeta(ModelMeta):
|
|
|
54
55
|
cls._iop_persistent_message_abstract = False
|
|
55
56
|
|
|
56
57
|
|
|
57
|
-
def _explicit_meta_classname(cls: type) ->
|
|
58
|
+
def _explicit_meta_classname(cls: type) -> str | None:
|
|
58
59
|
meta = cls.__dict__.get("Meta")
|
|
59
60
|
if meta is None or not hasattr(meta, "classname"):
|
|
60
61
|
return None
|
|
61
|
-
value =
|
|
62
|
+
value = meta.classname
|
|
62
63
|
return str(value) if value else None
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
def _apply_persistent_message_defaults(
|
|
66
67
|
cls: type,
|
|
67
|
-
namespace:
|
|
68
|
-
kwargs:
|
|
68
|
+
namespace: dict | None = None,
|
|
69
|
+
kwargs: dict | None = None,
|
|
69
70
|
) -> None:
|
|
70
71
|
namespace = namespace or {}
|
|
71
72
|
kwargs = kwargs or {}
|
|
@@ -90,9 +91,17 @@ def _apply_persistent_message_defaults(
|
|
|
90
91
|
|
|
91
92
|
|
|
92
93
|
class _PersistentMessage(Model, metaclass=_PersistentMessageMeta):
|
|
94
|
+
"""Base class for native persistent IRIS message bodies.
|
|
95
|
+
|
|
96
|
+
Use PersistentMessage when IRIS needs a generated native message class or
|
|
97
|
+
persistent message body. Prefer Message or PydanticMessage for Python-only
|
|
98
|
+
production routing. See docs/getting-started/register-component.md and
|
|
99
|
+
docs/settings.md.
|
|
100
|
+
"""
|
|
101
|
+
|
|
93
102
|
_iop_persistent_message_abstract = True
|
|
94
103
|
|
|
95
|
-
def get_iris_id(self) ->
|
|
104
|
+
def get_iris_id(self) -> str | None:
|
|
96
105
|
if self.pk:
|
|
97
106
|
return self.pk
|
|
98
107
|
|
|
@@ -200,7 +209,7 @@ def serialize_persistent_message(
|
|
|
200
209
|
|
|
201
210
|
def deserialize_persistent_message(
|
|
202
211
|
serial: Any,
|
|
203
|
-
iris_classname:
|
|
212
|
+
iris_classname: str | None = None,
|
|
204
213
|
) -> Any:
|
|
205
214
|
iris_classname = iris_classname or get_iris_object_classname(serial)
|
|
206
215
|
if not iris_classname:
|
|
@@ -263,7 +272,7 @@ def resolve_iris_classname(msg_cls: type) -> str:
|
|
|
263
272
|
return iris_classname
|
|
264
273
|
|
|
265
274
|
|
|
266
|
-
def resolve_python_classname(iris_classname: str) ->
|
|
275
|
+
def resolve_python_classname(iris_classname: str) -> str | None:
|
|
267
276
|
python_classname, _python_classpath, _strict = _resolve_python_message_metadata(
|
|
268
277
|
iris_classname
|
|
269
278
|
)
|
|
@@ -271,7 +280,7 @@ def resolve_python_classname(iris_classname: str) -> Optional[str]:
|
|
|
271
280
|
|
|
272
281
|
|
|
273
282
|
def load_python_class(
|
|
274
|
-
python_classname: str, python_classpath:
|
|
283
|
+
python_classname: str, python_classpath: str | None = None
|
|
275
284
|
) -> type:
|
|
276
285
|
module_name, _, class_name = python_classname.rpartition(".")
|
|
277
286
|
if not module_name:
|
|
@@ -306,7 +315,7 @@ def get_python_classpath(klass: type) -> str:
|
|
|
306
315
|
return classpath
|
|
307
316
|
|
|
308
317
|
|
|
309
|
-
def get_iris_object_classname(obj: Any) ->
|
|
318
|
+
def get_iris_object_classname(obj: Any) -> str | None:
|
|
310
319
|
for method_name in ("%ClassName", "_ClassName"):
|
|
311
320
|
try:
|
|
312
321
|
method = getattr(obj, method_name)
|
|
@@ -322,7 +331,7 @@ def get_iris_object_classname(obj: Any) -> Optional[str]:
|
|
|
322
331
|
return None
|
|
323
332
|
|
|
324
333
|
|
|
325
|
-
def _safe_get_object_id(obj: Any) ->
|
|
334
|
+
def _safe_get_object_id(obj: Any) -> str | None:
|
|
326
335
|
for method_name in ("_Id", "Id", "%Id"):
|
|
327
336
|
try:
|
|
328
337
|
method = getattr(obj, method_name)
|
|
@@ -387,7 +396,7 @@ def _cache_mapping(python_classname: str, iris_classname: str) -> None:
|
|
|
387
396
|
|
|
388
397
|
def _resolve_python_message_metadata(
|
|
389
398
|
iris_classname: str,
|
|
390
|
-
) -> tuple[
|
|
399
|
+
) -> tuple[str | None, str | None, bool]:
|
|
391
400
|
cached = _IRIS_TO_PYTHON_CACHE.get(iris_classname)
|
|
392
401
|
if cached:
|
|
393
402
|
return (
|
|
@@ -417,7 +426,7 @@ def _resolve_python_message_metadata(
|
|
|
417
426
|
def get_iris_class_parameter(
|
|
418
427
|
iris_classname: str,
|
|
419
428
|
parameter_name: str,
|
|
420
|
-
) ->
|
|
429
|
+
) -> str | None:
|
|
421
430
|
key = (iris_classname, parameter_name)
|
|
422
431
|
if key in _IRIS_PARAMETER_CACHE:
|
|
423
432
|
return _IRIS_PARAMETER_CACHE[key]
|
|
@@ -511,11 +520,7 @@ def _decode_iris_identifier(value: str) -> str:
|
|
|
511
520
|
|
|
512
521
|
|
|
513
522
|
def _prepend_sys_path(path: str) -> None:
|
|
514
|
-
|
|
515
|
-
while normalized_path in sys.path:
|
|
516
|
-
sys.path.remove(normalized_path)
|
|
517
|
-
sys.path.insert(0, normalized_path)
|
|
518
|
-
importlib.invalidate_caches()
|
|
523
|
+
prepend_sys_path(path)
|
|
519
524
|
|
|
520
525
|
|
|
521
526
|
def _import_module_from_classpath(module_name: str, python_classpath: str):
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
import codecs
|
|
4
|
+
import copy
|
|
3
5
|
import importlib
|
|
4
6
|
import inspect
|
|
5
|
-
import pickle
|
|
6
7
|
import json
|
|
8
|
+
import pickle
|
|
7
9
|
from dataclasses import is_dataclass
|
|
8
|
-
from typing import Any
|
|
10
|
+
from typing import Any
|
|
9
11
|
|
|
10
12
|
from pydantic import BaseModel, TypeAdapter, ValidationError
|
|
11
13
|
|
|
12
|
-
from
|
|
13
|
-
from
|
|
14
|
-
from .
|
|
14
|
+
from ..migration import utils as _migration_utils
|
|
15
|
+
from ..runtime import iris as _iris
|
|
16
|
+
from .base import _Message
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
class SerializationError(Exception):
|
|
@@ -20,6 +22,12 @@ class SerializationError(Exception):
|
|
|
20
22
|
pass
|
|
21
23
|
|
|
22
24
|
|
|
25
|
+
class MessageClassImportError(SerializationError, ImportError):
|
|
26
|
+
"""Raised when a JSON message's Python class cannot be imported."""
|
|
27
|
+
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
23
31
|
class TempPydanticModel(BaseModel):
|
|
24
32
|
model_config = {"arbitrary_types_allowed": True, "extra": "allow"}
|
|
25
33
|
|
|
@@ -32,7 +40,6 @@ class MessageSerializer:
|
|
|
32
40
|
message: Any, use_pickle: bool = False, is_generator: bool = False
|
|
33
41
|
) -> Any:
|
|
34
42
|
"""Serializes a message to IRIS format."""
|
|
35
|
-
message = remove_iris_id(message)
|
|
36
43
|
if use_pickle:
|
|
37
44
|
return MessageSerializer._serialize_pickle(message, is_generator)
|
|
38
45
|
return MessageSerializer._serialize_json(message, is_generator)
|
|
@@ -48,7 +55,7 @@ class MessageSerializer:
|
|
|
48
55
|
msg.classname = f"{message.__class__.__module__}.{message.__class__.__name__}"
|
|
49
56
|
|
|
50
57
|
if hasattr(msg, "buffer") and len(json_string) > msg.buffer:
|
|
51
|
-
msg.json =
|
|
58
|
+
msg.json = _migration_utils.string_to_stream(json_string, msg.buffer)
|
|
52
59
|
else:
|
|
53
60
|
msg.json = json_string
|
|
54
61
|
return msg
|
|
@@ -63,7 +70,7 @@ class MessageSerializer:
|
|
|
63
70
|
else:
|
|
64
71
|
msg = _iris.get_iris().cls("IOP.PickleMessage")._New()
|
|
65
72
|
msg.classname = f"{message.__class__.__module__}.{message.__class__.__name__}"
|
|
66
|
-
msg.jstr =
|
|
73
|
+
msg.jstr = _migration_utils.string_to_stream(pickle_string)
|
|
67
74
|
return msg
|
|
68
75
|
|
|
69
76
|
@staticmethod
|
|
@@ -92,10 +99,10 @@ class MessageSerializer:
|
|
|
92
99
|
)
|
|
93
100
|
module = importlib.import_module(module_name)
|
|
94
101
|
msg_class = getattr(module, class_name)
|
|
95
|
-
except
|
|
96
|
-
raise
|
|
102
|
+
except (ModuleNotFoundError, AttributeError, ValueError) as e:
|
|
103
|
+
raise MessageClassImportError(
|
|
97
104
|
f"Failed to load class {serial.classname}: {str(e)}"
|
|
98
|
-
)
|
|
105
|
+
) from e
|
|
99
106
|
|
|
100
107
|
if is_dataclass(msg_class) and issubclass(msg_class, BaseModel):
|
|
101
108
|
raise SerializationError(
|
|
@@ -105,7 +112,7 @@ class MessageSerializer:
|
|
|
105
112
|
)
|
|
106
113
|
|
|
107
114
|
json_string = (
|
|
108
|
-
|
|
115
|
+
_migration_utils.stream_to_string(serial.json)
|
|
109
116
|
if serial.type == "Stream"
|
|
110
117
|
else serial.json
|
|
111
118
|
)
|
|
@@ -120,18 +127,21 @@ class MessageSerializer:
|
|
|
120
127
|
f"Class {msg_class} must be a Pydantic model or dataclass"
|
|
121
128
|
)
|
|
122
129
|
except Exception as e:
|
|
123
|
-
raise SerializationError(f"Failed to deserialize JSON: {str(e)}")
|
|
130
|
+
raise SerializationError(f"Failed to deserialize JSON: {str(e)}") from e
|
|
124
131
|
|
|
125
132
|
@staticmethod
|
|
126
133
|
def _deserialize_pickle(serial: Any) -> Any:
|
|
127
|
-
string =
|
|
134
|
+
string = _migration_utils.stream_to_string(serial.jstr)
|
|
128
135
|
return pickle.loads(codecs.decode(string.encode(), "base64"))
|
|
129
136
|
|
|
130
137
|
@staticmethod
|
|
131
138
|
def _parse_classname(classname: str) -> tuple[str, str]:
|
|
132
|
-
|
|
139
|
+
try:
|
|
140
|
+
j = classname.rindex(".")
|
|
141
|
+
except ValueError as e:
|
|
142
|
+
raise ValueError(f"Classname must include a module: {classname}") from e
|
|
133
143
|
if j <= 0:
|
|
134
|
-
raise
|
|
144
|
+
raise ValueError(f"Classname must include a module: {classname}")
|
|
135
145
|
return classname[:j], classname[j + 1 :]
|
|
136
146
|
|
|
137
147
|
@staticmethod
|
|
@@ -156,18 +166,19 @@ class MessageSerializer:
|
|
|
156
166
|
|
|
157
167
|
|
|
158
168
|
def remove_iris_id(message: Any) -> Any:
|
|
169
|
+
message = copy.copy(message)
|
|
159
170
|
try:
|
|
160
|
-
|
|
171
|
+
delattr(message, "_iris_id")
|
|
161
172
|
except AttributeError:
|
|
162
173
|
pass
|
|
163
174
|
return message
|
|
164
175
|
|
|
165
176
|
|
|
166
|
-
def dataclass_from_dict(klass:
|
|
177
|
+
def dataclass_from_dict(klass: type | Any, dikt: dict) -> Any:
|
|
167
178
|
"""Converts a dictionary to a dataclass instance.
|
|
168
179
|
Handles non attended fields and nested dataclasses."""
|
|
169
180
|
|
|
170
|
-
def process_field(value: Any, field_type:
|
|
181
|
+
def process_field(value: Any, field_type: type) -> Any:
|
|
171
182
|
if value is None:
|
|
172
183
|
return None
|
|
173
184
|
if is_dataclass(field_type):
|
|
@@ -203,11 +214,13 @@ def dataclass_from_dict(klass: Type | Any, dikt: Dict) -> Any:
|
|
|
203
214
|
return instance
|
|
204
215
|
|
|
205
216
|
|
|
206
|
-
def dataclass_to_dict(instance: Any) ->
|
|
217
|
+
def dataclass_to_dict(instance: Any) -> dict:
|
|
207
218
|
"""Converts a class instance to a dictionary.
|
|
208
219
|
Handles non attended fields."""
|
|
209
220
|
result = {}
|
|
210
221
|
for field in instance.__dict__:
|
|
222
|
+
if field == "_iris_id":
|
|
223
|
+
continue
|
|
211
224
|
value = getattr(instance, field)
|
|
212
225
|
if is_dataclass(value):
|
|
213
226
|
result[field] = dataclass_to_dict(value)
|
|
@@ -227,7 +240,7 @@ def dataclass_to_dict(instance: Any) -> Dict:
|
|
|
227
240
|
return result
|
|
228
241
|
|
|
229
242
|
|
|
230
|
-
#
|
|
243
|
+
# Convenience wrappers used by dispatch and tests.
|
|
231
244
|
def serialize_pickle_message(msg):
|
|
232
245
|
return MessageSerializer.serialize(msg, use_pickle=True, is_generator=False)
|
|
233
246
|
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import dataclasses
|
|
2
|
-
from typing import Any
|
|
2
|
+
from typing import Any
|
|
3
3
|
|
|
4
|
-
from .
|
|
4
|
+
from .base import _Message, _PickleMessage, _PydanticMessage, _PydanticPickleMessage
|
|
5
|
+
|
|
6
|
+
_PYDANTIC_BASE = _PydanticMessage.__mro__[1]
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
def is_message_instance(obj: Any) -> bool:
|
|
8
10
|
"""Check if object is a valid Message instance."""
|
|
9
|
-
if isinstance(obj,
|
|
11
|
+
if isinstance(obj, _PYDANTIC_BASE):
|
|
10
12
|
return True
|
|
11
13
|
if is_message_class(type(obj)):
|
|
12
14
|
if not dataclasses.is_dataclass(obj):
|
|
@@ -36,17 +38,17 @@ def is_iris_object_instance(obj: Any) -> bool:
|
|
|
36
38
|
# Stream.Object are used for HTTP InboundAdapter/OutboundAdapter
|
|
37
39
|
|
|
38
40
|
|
|
39
|
-
def is_message_class(klass:
|
|
41
|
+
def is_message_class(klass: type) -> bool:
|
|
40
42
|
"""Check if class is a Message type."""
|
|
41
|
-
|
|
42
|
-
return
|
|
43
|
-
|
|
43
|
+
try:
|
|
44
|
+
return issubclass(klass, (_Message, _PYDANTIC_BASE))
|
|
45
|
+
except TypeError:
|
|
46
|
+
return False
|
|
44
47
|
|
|
45
48
|
|
|
46
|
-
def is_pickle_message_class(klass:
|
|
49
|
+
def is_pickle_message_class(klass: type) -> bool:
|
|
47
50
|
"""Check if class is a PickleMessage type."""
|
|
48
|
-
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
return
|
|
52
|
-
return False
|
|
51
|
+
try:
|
|
52
|
+
return issubclass(klass, (_PickleMessage, _PydanticPickleMessage))
|
|
53
|
+
except TypeError:
|
|
54
|
+
return False
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .utils import _Utils as _Utils
|
iop/migration/io.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import xmltodict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def dict_to_xml(data: dict[str, Any]) -> str:
|
|
11
|
+
xml = xmltodict.unparse(data, pretty=True)
|
|
12
|
+
xml = xml.replace('<?xml version="1.0" encoding="utf-8"?>', "")
|
|
13
|
+
return xml[1:]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def xml_to_json(xml_string: str) -> str:
|
|
17
|
+
"""Convert an XML production export to the JSON shape used by Production."""
|
|
18
|
+
|
|
19
|
+
def postprocessor(path, key, value):
|
|
20
|
+
return key, "" if value is None else value
|
|
21
|
+
|
|
22
|
+
data = xmltodict.parse(xml_string, postprocessor=postprocessor)
|
|
23
|
+
if "Production" in data:
|
|
24
|
+
production_obj = data["Production"]
|
|
25
|
+
production_name = production_obj.get("@Name", "Production")
|
|
26
|
+
data = {production_name: production_obj}
|
|
27
|
+
return json.dumps(data)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def stream_to_string(stream, buffer=1000000) -> str:
|
|
31
|
+
string = ""
|
|
32
|
+
stream.Rewind()
|
|
33
|
+
while not stream.AtEnd:
|
|
34
|
+
string += stream.Read(buffer)
|
|
35
|
+
return string
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def string_to_stream(iris, string: str, buffer=1000000):
|
|
39
|
+
stream = iris.cls("%Stream.GlobalCharacter")._New()
|
|
40
|
+
chunks = [string[i : i + buffer] for i in range(0, len(string), buffer)]
|
|
41
|
+
for chunk in chunks:
|
|
42
|
+
stream.Write(chunk)
|
|
43
|
+
return stream
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def guess_path(module: str, path: str) -> str:
|
|
47
|
+
"""Determine the full file path for a module name under a base path."""
|
|
48
|
+
if not module:
|
|
49
|
+
raise ValueError("Module name cannot be empty")
|
|
50
|
+
|
|
51
|
+
if module.startswith("."):
|
|
52
|
+
dot_count = len(module) - len(module.lstrip("."))
|
|
53
|
+
module = module[dot_count:]
|
|
54
|
+
for _ in range(dot_count - 1):
|
|
55
|
+
path = os.path.dirname(path)
|
|
56
|
+
|
|
57
|
+
if module.endswith(".py"):
|
|
58
|
+
module_path = module.replace(".", os.sep)
|
|
59
|
+
else:
|
|
60
|
+
module_path = module.replace(".", os.sep) + ".py"
|
|
61
|
+
return os.path.join(path, module_path)
|