iris-pex-embedded-python 3.2.1b2__py3-none-any.whl → 3.3.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.
Potentially problematic release.
This version of iris-pex-embedded-python might be problematic. Click here for more details.
- iop/_async_request.py +62 -0
- iop/_business_host.py +36 -489
- iop/_business_operation.py +6 -4
- iop/_business_process.py +12 -10
- iop/_business_service.py +3 -2
- iop/_cli.py +161 -104
- iop/_common.py +15 -80
- iop/_decorators.py +48 -0
- iop/_director.py +4 -3
- iop/_dispatch.py +219 -0
- iop/_log_manager.py +20 -12
- iop/_message_validator.py +41 -0
- iop/_private_session_duplex.py +11 -10
- iop/_private_session_process.py +7 -7
- iop/_serialization.py +196 -0
- iop/_utils.py +27 -19
- iop/cls/IOP/Common.cls +6 -1
- iop/cls/IOP/Message.cls +1 -0
- {iris_pex_embedded_python-3.2.1b2.dist-info → iris_pex_embedded_python-3.3.0.dist-info}/METADATA +1 -1
- {iris_pex_embedded_python-3.2.1b2.dist-info → iris_pex_embedded_python-3.3.0.dist-info}/RECORD +24 -19
- {iris_pex_embedded_python-3.2.1b2.dist-info → iris_pex_embedded_python-3.3.0.dist-info}/LICENSE +0 -0
- {iris_pex_embedded_python-3.2.1b2.dist-info → iris_pex_embedded_python-3.3.0.dist-info}/WHEEL +0 -0
- {iris_pex_embedded_python-3.2.1b2.dist-info → iris_pex_embedded_python-3.3.0.dist-info}/entry_points.txt +0 -0
- {iris_pex_embedded_python-3.2.1b2.dist-info → iris_pex_embedded_python-3.3.0.dist-info}/top_level.txt +0 -0
iop/_dispatch.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import codecs
|
|
2
|
+
import importlib
|
|
3
|
+
from inspect import signature
|
|
4
|
+
import json
|
|
5
|
+
import pickle
|
|
6
|
+
from typing import Any, Dict, List, Type
|
|
7
|
+
|
|
8
|
+
import iris
|
|
9
|
+
from dacite import Config, from_dict
|
|
10
|
+
|
|
11
|
+
from iop._utils import _Utils
|
|
12
|
+
from iop._serialization import IrisJSONEncoder, IrisJSONDecoder
|
|
13
|
+
from iop._message_validator import is_message_instance, is_pickle_message_instance, is_iris_object_instance
|
|
14
|
+
|
|
15
|
+
def serialize_pickle_message(message: Any) -> iris.cls:
|
|
16
|
+
"""Converts a python dataclass message into an iris iop.message.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
message: The message to serialize, an instance of a class that is a subclass of Message.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
The message in json format.
|
|
23
|
+
"""
|
|
24
|
+
pickle_string = codecs.encode(pickle.dumps(message), "base64").decode()
|
|
25
|
+
module = message.__class__.__module__
|
|
26
|
+
classname = message.__class__.__name__
|
|
27
|
+
|
|
28
|
+
msg = iris.cls('IOP.PickleMessage')._New()
|
|
29
|
+
msg.classname = module + "." + classname
|
|
30
|
+
|
|
31
|
+
stream = _Utils.string_to_stream(pickle_string)
|
|
32
|
+
msg.jstr = stream
|
|
33
|
+
|
|
34
|
+
return msg
|
|
35
|
+
|
|
36
|
+
def dispatch_serializer(message: Any) -> Any:
|
|
37
|
+
"""Serializes the message based on its type.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
message: The message to serialize
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
The serialized message
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
TypeError: If message is invalid type
|
|
47
|
+
"""
|
|
48
|
+
if message is not None:
|
|
49
|
+
if is_message_instance(message):
|
|
50
|
+
return serialize_message(message)
|
|
51
|
+
elif is_pickle_message_instance(message):
|
|
52
|
+
return serialize_pickle_message(message)
|
|
53
|
+
elif is_iris_object_instance(message):
|
|
54
|
+
return message
|
|
55
|
+
|
|
56
|
+
if message == "" or message is None:
|
|
57
|
+
return message
|
|
58
|
+
|
|
59
|
+
raise TypeError("The message must be an instance of a class that is a subclass of Message or IRISObject %Persistent class.")
|
|
60
|
+
|
|
61
|
+
def serialize_message(message: Any) -> iris.cls:
|
|
62
|
+
"""Converts a python dataclass message into an iris iop.message.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
message: The message to serialize, an instance of a class that is a subclass of Message.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
The message in json format.
|
|
69
|
+
"""
|
|
70
|
+
json_string = json.dumps(message, cls=IrisJSONEncoder, ensure_ascii=False)
|
|
71
|
+
module = message.__class__.__module__
|
|
72
|
+
classname = message.__class__.__name__
|
|
73
|
+
|
|
74
|
+
msg = iris.cls('IOP.Message')._New()
|
|
75
|
+
msg.classname = module + "." + classname
|
|
76
|
+
|
|
77
|
+
if hasattr(msg, 'buffer') and len(json_string) > msg.buffer:
|
|
78
|
+
msg.json = _Utils.string_to_stream(json_string, msg.buffer)
|
|
79
|
+
else:
|
|
80
|
+
msg.json = json_string
|
|
81
|
+
|
|
82
|
+
return msg
|
|
83
|
+
|
|
84
|
+
def deserialize_pickle_message(serial: iris.cls) -> Any:
|
|
85
|
+
"""Converts an iris iop.message into a python dataclass message.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
serial: The serialized message
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
The deserialized message
|
|
92
|
+
"""
|
|
93
|
+
string = _Utils.stream_to_string(serial.jstr)
|
|
94
|
+
msg = pickle.loads(codecs.decode(string.encode(), "base64"))
|
|
95
|
+
return msg
|
|
96
|
+
|
|
97
|
+
def dispatch_deserializer(serial: Any) -> Any:
|
|
98
|
+
"""Deserializes the message based on its type.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
serial: The serialized message
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
The deserialized message
|
|
105
|
+
"""
|
|
106
|
+
if (
|
|
107
|
+
serial is not None
|
|
108
|
+
and type(serial).__module__.startswith('iris')
|
|
109
|
+
and (
|
|
110
|
+
serial._IsA("IOP.Message")
|
|
111
|
+
or serial._IsA("Grongier.PEX.Message")
|
|
112
|
+
)
|
|
113
|
+
):
|
|
114
|
+
return deserialize_message(serial)
|
|
115
|
+
elif (
|
|
116
|
+
serial is not None
|
|
117
|
+
and type(serial).__module__.startswith('iris')
|
|
118
|
+
and (
|
|
119
|
+
serial._IsA("IOP.PickleMessage")
|
|
120
|
+
or serial._IsA("Grongier.PEX.PickleMessage")
|
|
121
|
+
)
|
|
122
|
+
):
|
|
123
|
+
return deserialize_pickle_message(serial)
|
|
124
|
+
else:
|
|
125
|
+
return serial
|
|
126
|
+
|
|
127
|
+
def deserialize_message(serial: iris.cls) -> Any:
|
|
128
|
+
"""Converts an iris iop.message into a python dataclass message.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
serial: The serialized message
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
The deserialized message
|
|
135
|
+
"""
|
|
136
|
+
if (serial.classname is None):
|
|
137
|
+
raise ValueError("JSON message malformed, must include classname")
|
|
138
|
+
classname = serial.classname
|
|
139
|
+
|
|
140
|
+
j = classname.rindex(".")
|
|
141
|
+
if (j <= 0):
|
|
142
|
+
raise ValueError("Classname must include a module: " + classname)
|
|
143
|
+
try:
|
|
144
|
+
module = importlib.import_module(classname[:j])
|
|
145
|
+
msg = getattr(module, classname[j+1:])
|
|
146
|
+
except Exception:
|
|
147
|
+
raise ImportError("Class not found: " + classname)
|
|
148
|
+
|
|
149
|
+
string = ""
|
|
150
|
+
if (serial.type == 'Stream'):
|
|
151
|
+
string = _Utils.stream_to_string(serial.json)
|
|
152
|
+
else:
|
|
153
|
+
string = serial.json
|
|
154
|
+
|
|
155
|
+
jdict = json.loads(string, cls=IrisJSONDecoder)
|
|
156
|
+
return dataclass_from_dict(msg, jdict)
|
|
157
|
+
|
|
158
|
+
def dataclass_from_dict(klass: Type, dikt: Dict) -> Any:
|
|
159
|
+
"""Converts a dictionary to a dataclass instance.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
klass: The dataclass to convert to
|
|
163
|
+
dikt: The dictionary to convert to a dataclass
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
A dataclass object with the fields of the dataclass and the fields of the dictionary.
|
|
167
|
+
"""
|
|
168
|
+
ret = from_dict(klass, dikt, Config(check_types=False))
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
fieldtypes = klass.__annotations__
|
|
172
|
+
except Exception as e:
|
|
173
|
+
fieldtypes = []
|
|
174
|
+
|
|
175
|
+
for key, val in dikt.items():
|
|
176
|
+
if key not in fieldtypes:
|
|
177
|
+
setattr(ret, key, val)
|
|
178
|
+
return ret
|
|
179
|
+
|
|
180
|
+
def dispach_message(host, request: Any) -> Any:
|
|
181
|
+
"""Dispatches the message to the appropriate method.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
request: The request object
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
The response object
|
|
188
|
+
"""
|
|
189
|
+
call = 'on_message'
|
|
190
|
+
|
|
191
|
+
module = request.__class__.__module__
|
|
192
|
+
classname = request.__class__.__name__
|
|
193
|
+
|
|
194
|
+
for msg, method in host.DISPATCH:
|
|
195
|
+
if msg == module + "." + classname:
|
|
196
|
+
call = method
|
|
197
|
+
|
|
198
|
+
return getattr(host, call)(request)
|
|
199
|
+
|
|
200
|
+
def create_dispatch(host) -> None:
|
|
201
|
+
"""Creates a list of tuples, where each tuple contains the name of a class and the name of a method
|
|
202
|
+
that takes an instance of that class as its only argument.
|
|
203
|
+
"""
|
|
204
|
+
if len(host.DISPATCH) == 0:
|
|
205
|
+
method_list = [func for func in dir(host) if callable(getattr(host, func)) and not func.startswith("_")]
|
|
206
|
+
for method in method_list:
|
|
207
|
+
try:
|
|
208
|
+
param = signature(getattr(host, method)).parameters
|
|
209
|
+
except ValueError as e:
|
|
210
|
+
param = ''
|
|
211
|
+
if (len(param) == 1):
|
|
212
|
+
annotation = str(param[list(param)[0]].annotation)
|
|
213
|
+
i = annotation.find("'")
|
|
214
|
+
j = annotation.rfind("'")
|
|
215
|
+
if j == -1:
|
|
216
|
+
j = None
|
|
217
|
+
classname = annotation[i+1:j]
|
|
218
|
+
host.DISPATCH.append((classname, method))
|
|
219
|
+
return
|
iop/_log_manager.py
CHANGED
|
@@ -46,6 +46,23 @@ class IRISLogHandler(logging.Handler):
|
|
|
46
46
|
self.method_name = method_name
|
|
47
47
|
self.to_console = to_console
|
|
48
48
|
|
|
49
|
+
# Map Python logging levels to IRIS logging methods
|
|
50
|
+
self.level_map = {
|
|
51
|
+
logging.DEBUG: iris.cls("Ens.Util.Log").LogTrace,
|
|
52
|
+
logging.INFO: iris.cls("Ens.Util.Log").LogInfo,
|
|
53
|
+
logging.WARNING: iris.cls("Ens.Util.Log").LogWarning,
|
|
54
|
+
logging.ERROR: iris.cls("Ens.Util.Log").LogError,
|
|
55
|
+
logging.CRITICAL: iris.cls("Ens.Util.Log").LogAlert,
|
|
56
|
+
}
|
|
57
|
+
# Map Python logging levels to IRIS logging Console level
|
|
58
|
+
self.level_map_console = {
|
|
59
|
+
logging.DEBUG: -1,
|
|
60
|
+
logging.INFO: 0,
|
|
61
|
+
logging.WARNING: 1,
|
|
62
|
+
logging.ERROR: 2,
|
|
63
|
+
logging.CRITICAL: 3,
|
|
64
|
+
}
|
|
65
|
+
|
|
49
66
|
def format(self, record: logging.LogRecord) -> str:
|
|
50
67
|
"""Format the log record into a string.
|
|
51
68
|
|
|
@@ -55,8 +72,6 @@ class IRISLogHandler(logging.Handler):
|
|
|
55
72
|
Returns:
|
|
56
73
|
Formatted log message
|
|
57
74
|
"""
|
|
58
|
-
if self.to_console:
|
|
59
|
-
return f"{record}"
|
|
60
75
|
return record.getMessage()
|
|
61
76
|
|
|
62
77
|
def emit(self, record: logging.LogRecord) -> None:
|
|
@@ -65,17 +80,10 @@ class IRISLogHandler(logging.Handler):
|
|
|
65
80
|
Args:
|
|
66
81
|
record: The logging record to emit
|
|
67
82
|
"""
|
|
68
|
-
# Map Python logging levels to IRIS logging methods
|
|
69
|
-
level_map = {
|
|
70
|
-
logging.DEBUG: iris.cls("Ens.Util.Log").LogTrace,
|
|
71
|
-
logging.INFO: iris.cls("Ens.Util.Log").LogInfo,
|
|
72
|
-
logging.WARNING: iris.cls("Ens.Util.Log").LogWarning,
|
|
73
|
-
logging.ERROR: iris.cls("Ens.Util.Log").LogError,
|
|
74
|
-
logging.CRITICAL: iris.cls("Ens.Util.Log").LogAlert,
|
|
75
|
-
}
|
|
76
83
|
|
|
77
|
-
log_func = level_map.get(record.levelno, iris.cls("Ens.Util.Log").LogInfo)
|
|
84
|
+
log_func = self.level_map.get(record.levelno, iris.cls("Ens.Util.Log").LogInfo)
|
|
78
85
|
if self.to_console or (hasattr(record, "to_console") and record.to_console):
|
|
79
|
-
iris.cls("%SYS.System").WriteToConsoleLog(self.format(record),
|
|
86
|
+
iris.cls("%SYS.System").WriteToConsoleLog(self.format(record),
|
|
87
|
+
0,self.level_map_console.get(record.levelno, 0),record.name)
|
|
80
88
|
else:
|
|
81
89
|
log_func(self.class_name, self.method_name, self.format(record))
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
from typing import Any, Type
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def is_message_instance(obj: Any) -> bool:
|
|
6
|
+
"""Check if object is a valid Message instance."""
|
|
7
|
+
if is_message_class(type(obj)):
|
|
8
|
+
if not dataclasses.is_dataclass(obj):
|
|
9
|
+
raise TypeError(f"{type(obj).__module__}.{type(obj).__qualname__} must be a dataclass")
|
|
10
|
+
return True
|
|
11
|
+
return False
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def is_pickle_message_instance(obj: Any) -> bool:
|
|
15
|
+
"""Check if object is a PickleMessage instance."""
|
|
16
|
+
if is_pickle_message_class(type(obj)):
|
|
17
|
+
return True
|
|
18
|
+
return False
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def is_iris_object_instance(obj: Any) -> bool:
|
|
22
|
+
"""Check if object is an IRIS persistent object."""
|
|
23
|
+
return (obj is not None and
|
|
24
|
+
type(obj).__module__.startswith('iris') and
|
|
25
|
+
obj._IsA("%Persistent"))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def is_message_class(klass: Type) -> bool:
|
|
29
|
+
"""Check if class is a Message type."""
|
|
30
|
+
name = f"{klass.__module__}.{klass.__qualname__}"
|
|
31
|
+
if name in ("iop.Message", "grongier.pex.Message"):
|
|
32
|
+
return True
|
|
33
|
+
return any(is_message_class(c) for c in klass.__bases__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def is_pickle_message_class(klass: Type) -> bool:
|
|
37
|
+
"""Check if class is a PickleMessage type."""
|
|
38
|
+
name = f"{klass.__module__}.{klass.__qualname__}"
|
|
39
|
+
if name in ("iop.PickleMessage", "grongier.pex.PickleMessage"):
|
|
40
|
+
return True
|
|
41
|
+
return any(is_pickle_message_class(c) for c in klass.__bases__)
|
iop/_private_session_duplex.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import importlib
|
|
2
2
|
from iop._business_host import _BusinessHost
|
|
3
|
+
from iop._decorators import input_deserializer, input_serializer_param, output_serializer, input_serializer, output_deserializer
|
|
3
4
|
|
|
4
5
|
class _PrivateSessionDuplex(_BusinessHost):
|
|
5
6
|
|
|
@@ -21,8 +22,8 @@ class _PrivateSessionDuplex(_BusinessHost):
|
|
|
21
22
|
"""
|
|
22
23
|
pass
|
|
23
24
|
|
|
24
|
-
@
|
|
25
|
-
@
|
|
25
|
+
@input_deserializer
|
|
26
|
+
@output_serializer
|
|
26
27
|
def _dispatch_on_message(self, request):
|
|
27
28
|
""" For internal use only. """
|
|
28
29
|
return self._dispach_message(request)
|
|
@@ -38,8 +39,8 @@ class _PrivateSessionDuplex(_BusinessHost):
|
|
|
38
39
|
self.Adapter = self.adapter = handle_partner
|
|
39
40
|
return
|
|
40
41
|
|
|
41
|
-
@
|
|
42
|
-
@
|
|
42
|
+
@input_deserializer
|
|
43
|
+
@output_serializer
|
|
43
44
|
def _dispatch_on_process_input(self, request):
|
|
44
45
|
""" For internal use only. """
|
|
45
46
|
return self.on_process_input(request)
|
|
@@ -55,8 +56,8 @@ class _PrivateSessionDuplex(_BusinessHost):
|
|
|
55
56
|
"""
|
|
56
57
|
return
|
|
57
58
|
|
|
58
|
-
@
|
|
59
|
-
@
|
|
59
|
+
@input_serializer_param(0,'document')
|
|
60
|
+
@output_deserializer
|
|
60
61
|
def send_document_to_process(self, document):
|
|
61
62
|
""" Send the specified message to the target business process or business operation synchronously.
|
|
62
63
|
|
|
@@ -75,8 +76,8 @@ class _PrivateSessionDuplex(_BusinessHost):
|
|
|
75
76
|
|
|
76
77
|
return self.iris_handle.dispatchSendDocumentToProcess(document)
|
|
77
78
|
|
|
78
|
-
@
|
|
79
|
-
@
|
|
79
|
+
@input_deserializer
|
|
80
|
+
@output_serializer
|
|
80
81
|
def _dispatch_on_private_session_started(self, source_config_name,self_generated):
|
|
81
82
|
""" For internal use only. """
|
|
82
83
|
|
|
@@ -87,8 +88,8 @@ class _PrivateSessionDuplex(_BusinessHost):
|
|
|
87
88
|
def on_private_session_started(self,source_config_name,self_generated):
|
|
88
89
|
pass
|
|
89
90
|
|
|
90
|
-
@
|
|
91
|
-
@
|
|
91
|
+
@input_deserializer
|
|
92
|
+
@output_serializer
|
|
92
93
|
def _dispatch_on_private_session_stopped(self, source_config_name,self_generated,message):
|
|
93
94
|
""" For internal use only. """
|
|
94
95
|
|
iop/_private_session_process.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from iop._business_process import _BusinessProcess
|
|
2
|
-
from iop.
|
|
2
|
+
from iop._decorators import input_deserializer, output_serializer, input_serializer, output_deserializer
|
|
3
3
|
|
|
4
4
|
class _PrivateSessionProcess(_BusinessProcess):
|
|
5
5
|
|
|
6
|
-
@
|
|
7
|
-
@
|
|
6
|
+
@input_deserializer
|
|
7
|
+
@output_serializer
|
|
8
8
|
def _dispatch_on_document(self, host_object,source_config_name, request):
|
|
9
9
|
""" For internal use only. """
|
|
10
10
|
self._restore_persistent_properties(host_object)
|
|
@@ -16,8 +16,8 @@ class _PrivateSessionProcess(_BusinessProcess):
|
|
|
16
16
|
pass
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
@
|
|
20
|
-
@
|
|
19
|
+
@input_deserializer
|
|
20
|
+
@output_serializer
|
|
21
21
|
def _dispatch_on_private_session_started(self, host_object, source_config_name,self_generated):
|
|
22
22
|
""" For internal use only. """
|
|
23
23
|
self._restore_persistent_properties(host_object)
|
|
@@ -28,8 +28,8 @@ class _PrivateSessionProcess(_BusinessProcess):
|
|
|
28
28
|
def on_private_session_started(self,source_config_name,self_generated):
|
|
29
29
|
pass
|
|
30
30
|
|
|
31
|
-
@
|
|
32
|
-
@
|
|
31
|
+
@input_deserializer
|
|
32
|
+
@output_serializer
|
|
33
33
|
def _dispatch_on_private_session_stopped(self, host_object, source_config_name,self_generated,message):
|
|
34
34
|
""" For internal use only. """
|
|
35
35
|
self._restore_persistent_properties(host_object)
|
iop/_serialization.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import codecs
|
|
3
|
+
import datetime
|
|
4
|
+
import decimal
|
|
5
|
+
import importlib
|
|
6
|
+
import json
|
|
7
|
+
import pickle
|
|
8
|
+
import uuid
|
|
9
|
+
from typing import Any, Dict, Type
|
|
10
|
+
|
|
11
|
+
from dacite import Config, from_dict
|
|
12
|
+
import iris
|
|
13
|
+
|
|
14
|
+
from iop._utils import _Utils
|
|
15
|
+
|
|
16
|
+
class IrisJSONEncoder(json.JSONEncoder):
|
|
17
|
+
"""JSONEncoder that handles dates, decimals, UUIDs, etc."""
|
|
18
|
+
|
|
19
|
+
def default(self, o: Any) -> Any:
|
|
20
|
+
if o.__class__.__name__ == 'DataFrame':
|
|
21
|
+
return 'dataframe:' + o.to_json(orient="table")
|
|
22
|
+
elif isinstance(o, datetime.datetime):
|
|
23
|
+
r = o.isoformat()
|
|
24
|
+
if o.microsecond:
|
|
25
|
+
r = r[:23] + r[26:]
|
|
26
|
+
if r.endswith("+00:00"):
|
|
27
|
+
r = r[:-6] + "Z"
|
|
28
|
+
return 'datetime:' + r
|
|
29
|
+
elif isinstance(o, datetime.date):
|
|
30
|
+
return 'date:' + o.isoformat()
|
|
31
|
+
elif isinstance(o, datetime.time):
|
|
32
|
+
r = o.isoformat()
|
|
33
|
+
if o.microsecond:
|
|
34
|
+
r = r[:12]
|
|
35
|
+
return 'time:' + r
|
|
36
|
+
elif isinstance(o, decimal.Decimal):
|
|
37
|
+
return 'decimal:' + str(o)
|
|
38
|
+
elif isinstance(o, uuid.UUID):
|
|
39
|
+
return 'uuid:' + str(o)
|
|
40
|
+
elif isinstance(o, bytes):
|
|
41
|
+
return 'bytes:' + base64.b64encode(o).decode("UTF-8")
|
|
42
|
+
elif hasattr(o, '__dict__'):
|
|
43
|
+
return o.__dict__
|
|
44
|
+
return super().default(o)
|
|
45
|
+
|
|
46
|
+
class IrisJSONDecoder(json.JSONDecoder):
|
|
47
|
+
"""JSONDecoder that handles special type annotations."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
50
|
+
json.JSONDecoder.__init__(
|
|
51
|
+
self, object_hook=self.object_hook, *args, **kwargs)
|
|
52
|
+
|
|
53
|
+
def object_hook(self, obj: Dict) -> Dict:
|
|
54
|
+
ret = {}
|
|
55
|
+
for key, value in obj.items():
|
|
56
|
+
i = 0
|
|
57
|
+
if isinstance(value, str):
|
|
58
|
+
i = value.find(":")
|
|
59
|
+
if i > 0:
|
|
60
|
+
typ = value[:i]
|
|
61
|
+
val = value[i+1:]
|
|
62
|
+
ret[key] = self._convert_typed_value(typ, val)
|
|
63
|
+
else:
|
|
64
|
+
ret[key] = value
|
|
65
|
+
return ret
|
|
66
|
+
|
|
67
|
+
def _convert_typed_value(self, typ: str, val: str) -> Any:
|
|
68
|
+
if typ == 'datetime':
|
|
69
|
+
return datetime.datetime.fromisoformat(val)
|
|
70
|
+
elif typ == 'date':
|
|
71
|
+
return datetime.date.fromisoformat(val)
|
|
72
|
+
elif typ == 'time':
|
|
73
|
+
return datetime.time.fromisoformat(val)
|
|
74
|
+
elif typ == 'dataframe':
|
|
75
|
+
module = importlib.import_module('pandas')
|
|
76
|
+
return module.read_json(val, orient="table")
|
|
77
|
+
elif typ == 'decimal':
|
|
78
|
+
return decimal.Decimal(val)
|
|
79
|
+
elif typ == 'uuid':
|
|
80
|
+
return uuid.UUID(val)
|
|
81
|
+
elif typ == 'bytes':
|
|
82
|
+
return base64.b64decode(val.encode("UTF-8"))
|
|
83
|
+
return val
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def serialize_pickle_message(message: Any) -> iris.cls:
|
|
87
|
+
"""Converts a python dataclass message into an iris iop.message.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
message: The message to serialize, an instance of a class that is a subclass of Message.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
The message in json format.
|
|
94
|
+
"""
|
|
95
|
+
pickle_string = codecs.encode(pickle.dumps(message), "base64").decode()
|
|
96
|
+
module = message.__class__.__module__
|
|
97
|
+
classname = message.__class__.__name__
|
|
98
|
+
|
|
99
|
+
msg = iris.cls('IOP.PickleMessage')._New()
|
|
100
|
+
msg.classname = module + "." + classname
|
|
101
|
+
|
|
102
|
+
stream = _Utils.string_to_stream(pickle_string)
|
|
103
|
+
msg.jstr = stream
|
|
104
|
+
|
|
105
|
+
return msg
|
|
106
|
+
|
|
107
|
+
def serialize_message(message: Any) -> iris.cls:
|
|
108
|
+
"""Converts a python dataclass message into an iris iop.message.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
message: The message to serialize, an instance of a class that is a subclass of Message.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
The message in json format.
|
|
115
|
+
"""
|
|
116
|
+
json_string = json.dumps(message, cls=IrisJSONEncoder, ensure_ascii=False)
|
|
117
|
+
module = message.__class__.__module__
|
|
118
|
+
classname = message.__class__.__name__
|
|
119
|
+
|
|
120
|
+
msg = iris.cls('IOP.Message')._New()
|
|
121
|
+
msg.classname = module + "." + classname
|
|
122
|
+
|
|
123
|
+
if hasattr(msg, 'buffer') and len(json_string) > msg.buffer:
|
|
124
|
+
msg.json = _Utils.string_to_stream(json_string, msg.buffer)
|
|
125
|
+
else:
|
|
126
|
+
msg.json = json_string
|
|
127
|
+
|
|
128
|
+
return msg
|
|
129
|
+
|
|
130
|
+
def deserialize_pickle_message(serial: iris.cls) -> Any:
|
|
131
|
+
"""Converts an iris iop.message into a python dataclass message.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
serial: The serialized message
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
The deserialized message
|
|
138
|
+
"""
|
|
139
|
+
string = _Utils.stream_to_string(serial.jstr)
|
|
140
|
+
|
|
141
|
+
msg = pickle.loads(codecs.decode(string.encode(), "base64"))
|
|
142
|
+
return msg
|
|
143
|
+
|
|
144
|
+
def deserialize_message(serial: iris.cls) -> Any:
|
|
145
|
+
"""Converts an iris iop.message into a python dataclass message.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
serial: The serialized message
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
The deserialized message
|
|
152
|
+
"""
|
|
153
|
+
if (serial.classname is None):
|
|
154
|
+
raise ValueError("JSON message malformed, must include classname")
|
|
155
|
+
classname = serial.classname
|
|
156
|
+
|
|
157
|
+
j = classname.rindex(".")
|
|
158
|
+
if (j <= 0):
|
|
159
|
+
raise ValueError("Classname must include a module: " + classname)
|
|
160
|
+
try:
|
|
161
|
+
module = importlib.import_module(classname[:j])
|
|
162
|
+
msg = getattr(module, classname[j+1:])
|
|
163
|
+
except Exception:
|
|
164
|
+
raise ImportError("Class not found: " + classname)
|
|
165
|
+
|
|
166
|
+
string = ""
|
|
167
|
+
if (serial.type == 'Stream'):
|
|
168
|
+
string = _Utils.stream_to_string(serial.json)
|
|
169
|
+
else:
|
|
170
|
+
string = serial.json
|
|
171
|
+
|
|
172
|
+
jdict = json.loads(string, cls=IrisJSONDecoder)
|
|
173
|
+
msg = dataclass_from_dict(msg, jdict)
|
|
174
|
+
return msg
|
|
175
|
+
|
|
176
|
+
def dataclass_from_dict(klass: Type, dikt: Dict) -> Any:
|
|
177
|
+
"""Converts a dictionary to a dataclass instance.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
klass: The dataclass to convert to
|
|
181
|
+
dikt: The dictionary to convert to a dataclass
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
A dataclass object with the fields of the dataclass and the fields of the dictionary.
|
|
185
|
+
"""
|
|
186
|
+
ret = from_dict(klass, dikt, Config(check_types=False))
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
fieldtypes = klass.__annotations__
|
|
190
|
+
except Exception as e:
|
|
191
|
+
fieldtypes = []
|
|
192
|
+
|
|
193
|
+
for key, val in dikt.items():
|
|
194
|
+
if key not in fieldtypes:
|
|
195
|
+
setattr(ret, key, val)
|
|
196
|
+
return ret
|
iop/_utils.py
CHANGED
|
@@ -436,23 +436,31 @@ class _Utils():
|
|
|
436
436
|
return stream
|
|
437
437
|
|
|
438
438
|
@staticmethod
|
|
439
|
-
def guess_path(module,path):
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
439
|
+
def guess_path(module: str, path: str) -> str:
|
|
440
|
+
"""Determines the full file path for a given module.
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
module: Module name/path (e.g. 'foo.bar' or '.foo.bar')
|
|
444
|
+
path: Base directory path
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
Full path to the module's .py file
|
|
448
|
+
"""
|
|
449
|
+
if not module:
|
|
450
|
+
raise ValueError("Module name cannot be empty")
|
|
451
|
+
|
|
452
|
+
if module.startswith("."):
|
|
453
|
+
# Handle relative imports
|
|
454
|
+
dot_count = len(module) - len(module.lstrip("."))
|
|
455
|
+
module = module[dot_count:]
|
|
456
|
+
|
|
457
|
+
# Go up directory tree based on dot count
|
|
458
|
+
for _ in range(dot_count - 1):
|
|
459
|
+
path = os.path.dirname(path)
|
|
460
|
+
|
|
461
|
+
# Convert module path to file path
|
|
462
|
+
if module.endswith(".py"):
|
|
463
|
+
module_path = module.replace(".", os.sep)
|
|
457
464
|
else:
|
|
458
|
-
|
|
465
|
+
module_path = module.replace(".", os.sep) + ".py"
|
|
466
|
+
return os.path.join(path, module_path)
|
iop/cls/IOP/Common.cls
CHANGED
|
@@ -193,7 +193,12 @@ ClassMethod OnGetConnections(
|
|
|
193
193
|
pItem As Ens.Config.Item)
|
|
194
194
|
{
|
|
195
195
|
// finds any settings of type Ens.DataType.ConfigName
|
|
196
|
-
|
|
196
|
+
Try {
|
|
197
|
+
do ..GetPropertyConnections(.pArray,pItem)
|
|
198
|
+
}
|
|
199
|
+
Catch ex {
|
|
200
|
+
}
|
|
201
|
+
|
|
197
202
|
|
|
198
203
|
// Get settings
|
|
199
204
|
do pItem.GetModifiedSetting("%classpaths", .tClasspaths)
|
iop/cls/IOP/Message.cls
CHANGED