iris-pex-embedded-python 2.3.27b2__py3-none-any.whl → 3.2.1b2__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.
- grongier/cls/Grongier/PEX/BusinessOperation.cls +1 -28
- grongier/cls/Grongier/PEX/BusinessProcess.cls +1 -100
- grongier/cls/Grongier/PEX/BusinessService.cls +1 -28
- grongier/cls/Grongier/PEX/Common.cls +1 -194
- grongier/cls/Grongier/PEX/Director.cls +1 -48
- grongier/cls/Grongier/PEX/Duplex/Operation.cls +1 -26
- grongier/cls/Grongier/PEX/Duplex/Process.cls +1 -217
- grongier/cls/Grongier/PEX/Duplex/Service.cls +1 -6
- grongier/cls/Grongier/PEX/InboundAdapter.cls +1 -15
- grongier/cls/Grongier/PEX/Message.cls +1 -116
- grongier/cls/Grongier/PEX/OutboundAdapter.cls +1 -29
- grongier/cls/Grongier/PEX/PickleMessage.cls +1 -46
- grongier/cls/Grongier/PEX/PrivateSession/Duplex.cls +1 -253
- grongier/cls/Grongier/PEX/PrivateSession/Message/Ack.cls +1 -19
- grongier/cls/Grongier/PEX/PrivateSession/Message/Poll.cls +1 -19
- grongier/cls/Grongier/PEX/PrivateSession/Message/Start.cls +1 -19
- grongier/cls/Grongier/PEX/PrivateSession/Message/Stop.cls +1 -35
- grongier/cls/Grongier/PEX/Test.cls +1 -53
- grongier/cls/Grongier/PEX/Utils.cls +1 -365
- grongier/cls/Grongier/Service/WSGI.cls +1 -307
- grongier/pex/__init__.py +11 -11
- grongier/pex/__main__.py +1 -1
- grongier/pex/_business_host.py +1 -511
- grongier/pex/_cli.py +2 -150
- grongier/pex/_common.py +1 -347
- grongier/pex/_director.py +1 -286
- grongier/pex/_utils.py +1 -369
- intersystems_iris/_ConnectionInformation.py +22 -20
- intersystems_iris/dbapi/_DBAPI.py +6 -1
- intersystems_iris/dbapi/_ResultSetRow.py +26 -15
- intersystems_iris/dbapi/preparser/_PreParser.py +4 -1
- iop/__init__.py +24 -0
- iop/__main__.py +4 -0
- iop/_business_host.py +675 -0
- iop/_business_operation.py +71 -0
- iop/_business_process.py +220 -0
- {grongier/pex → iop}/_business_service.py +2 -2
- iop/_cli.py +141 -0
- iop/_common.py +352 -0
- iop/_director.py +301 -0
- {grongier/pex → iop}/_inbound_adapter.py +1 -1
- iop/_log_manager.py +81 -0
- {grongier/pex → iop}/_message.py +1 -1
- {grongier/pex → iop}/_outbound_adapter.py +1 -1
- {grongier/pex → iop}/_private_session_duplex.py +3 -2
- {grongier/pex → iop}/_private_session_process.py +2 -2
- iop/_utils.py +458 -0
- iop/cls/IOP/BusinessOperation.cls +35 -0
- iop/cls/IOP/BusinessProcess.cls +124 -0
- iop/cls/IOP/BusinessService.cls +35 -0
- iop/cls/IOP/Common.cls +344 -0
- iop/cls/IOP/Director.cls +62 -0
- iop/cls/IOP/Duplex/Operation.cls +29 -0
- iop/cls/IOP/Duplex/Process.cls +229 -0
- iop/cls/IOP/Duplex/Service.cls +9 -0
- iop/cls/IOP/InboundAdapter.cls +22 -0
- iop/cls/IOP/Message/JSONSchema.cls +125 -0
- iop/cls/IOP/Message.cls +729 -0
- iop/cls/IOP/OutboundAdapter.cls +36 -0
- iop/cls/IOP/PickleMessage.cls +58 -0
- iop/cls/IOP/PrivateSession/Duplex.cls +260 -0
- iop/cls/IOP/PrivateSession/Message/Ack.cls +32 -0
- iop/cls/IOP/PrivateSession/Message/Poll.cls +32 -0
- iop/cls/IOP/PrivateSession/Message/Start.cls +32 -0
- iop/cls/IOP/PrivateSession/Message/Stop.cls +48 -0
- iop/cls/IOP/Service/WSGI.cls +310 -0
- iop/cls/IOP/Test.cls +85 -0
- iop/cls/IOP/Utils.cls +378 -0
- iop/wsgi/handlers.py +104 -0
- iris_pex_embedded_python-3.2.1b2.dist-info/METADATA +90 -0
- iris_pex_embedded_python-3.2.1b2.dist-info/RECORD +139 -0
- {iris_pex_embedded_python-2.3.27b2.dist-info → iris_pex_embedded_python-3.2.1b2.dist-info}/WHEEL +1 -1
- iris_pex_embedded_python-3.2.1b2.dist-info/entry_points.txt +2 -0
- {iris_pex_embedded_python-2.3.27b2.dist-info → iris_pex_embedded_python-3.2.1b2.dist-info}/top_level.txt +1 -1
- grongier/pex/_business_operation.py +0 -70
- grongier/pex/_business_process.py +0 -215
- iris/__init__.py +0 -60
- iris/__init__.pyi +0 -236
- iris/iris_ipm.py +0 -40
- iris/iris_ipm.pyi +0 -17
- iris_pex_embedded_python-2.3.27b2.dist-info/METADATA +0 -1384
- iris_pex_embedded_python-2.3.27b2.dist-info/RECORD +0 -113
- iris_pex_embedded_python-2.3.27b2.dist-info/entry_points.txt +0 -2
- {grongier/pex → iop}/_pickle_message.py +0 -0
- {iris_pex_embedded_python-2.3.27b2.dist-info → iris_pex_embedded_python-3.2.1b2.dist-info}/LICENSE +0 -0
iop/_business_host.py
ADDED
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import asyncio
|
|
3
|
+
import base64
|
|
4
|
+
import codecs
|
|
5
|
+
import datetime
|
|
6
|
+
import decimal
|
|
7
|
+
import importlib
|
|
8
|
+
import inspect
|
|
9
|
+
import iris
|
|
10
|
+
import json
|
|
11
|
+
import pickle
|
|
12
|
+
import uuid
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from functools import wraps
|
|
15
|
+
from inspect import getsource, signature
|
|
16
|
+
from typing import Any, Callable, ClassVar, Dict, List, Optional, Tuple, Type, Union
|
|
17
|
+
|
|
18
|
+
from dacite import Config, from_dict
|
|
19
|
+
|
|
20
|
+
from iop._common import _Common
|
|
21
|
+
from iop._message import _Message as Message
|
|
22
|
+
from iop._utils import _Utils
|
|
23
|
+
|
|
24
|
+
class _BusinessHost(_Common):
|
|
25
|
+
"""Base class for business components that defines common methods.
|
|
26
|
+
|
|
27
|
+
This is a superclass for BusinessService, BusinessProcess, and BusinessOperation that
|
|
28
|
+
defines common functionality like message serialization/deserialization and request handling.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
buffer: int = 64000
|
|
32
|
+
DISPATCH: List[Tuple[str, str]] = []
|
|
33
|
+
|
|
34
|
+
def input_serialzer(fonction: Callable) -> Callable:
|
|
35
|
+
"""Decorator that serializes input arguments before passing to function.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
fonction: Function to decorate
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Decorated function that handles serialization
|
|
42
|
+
"""
|
|
43
|
+
def dispatch_serializer(self, *params: Any, **param2: Any) -> Any:
|
|
44
|
+
# Handle positional arguments using list comprehension
|
|
45
|
+
serialized = [self._dispatch_serializer(param) for param in params]
|
|
46
|
+
|
|
47
|
+
# Handle keyword arguments using dictionary comprehension
|
|
48
|
+
param2 = {key: self._dispatch_serializer(value) for key, value in param2.items()}
|
|
49
|
+
|
|
50
|
+
return fonction(self, *serialized, **param2)
|
|
51
|
+
return dispatch_serializer
|
|
52
|
+
|
|
53
|
+
def input_serialzer_param(position: int, name: str) -> Callable:
|
|
54
|
+
"""Decorator that serializes specific parameter by position or name.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
position: Position of parameter to serialize
|
|
58
|
+
name: Name of parameter to serialize
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Decorator function
|
|
62
|
+
"""
|
|
63
|
+
def input_serialzer_param(fonction: Callable) -> Callable:
|
|
64
|
+
@wraps(fonction)
|
|
65
|
+
def dispatch_serializer(self, *params: Any, **param2: Any) -> Any:
|
|
66
|
+
# Handle positional arguments using list comprehension
|
|
67
|
+
serialized = [
|
|
68
|
+
self._dispatch_serializer(param) if i == position else param
|
|
69
|
+
for i, param in enumerate(params)
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
# Handle keyword arguments using dictionary comprehension
|
|
73
|
+
param2 = {
|
|
74
|
+
key: self._dispatch_serializer(value) if key == name else value
|
|
75
|
+
for key, value in param2.items()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return fonction(self, *serialized, **param2)
|
|
79
|
+
return dispatch_serializer
|
|
80
|
+
return input_serialzer_param
|
|
81
|
+
|
|
82
|
+
def output_deserialzer(fonction: Callable) -> Callable:
|
|
83
|
+
"""Decorator that deserializes output of function.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
fonction: Function to decorate
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Decorated function that handles deserialization
|
|
90
|
+
"""
|
|
91
|
+
def dispatch_deserializer(self, *params: Any, **param2: Any) -> Any:
|
|
92
|
+
return self._dispatch_deserializer(fonction(self, *params, **param2))
|
|
93
|
+
|
|
94
|
+
return dispatch_deserializer
|
|
95
|
+
|
|
96
|
+
def input_deserialzer(fonction: Callable) -> Callable:
|
|
97
|
+
"""Decorator that deserializes input arguments before passing to function.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
fonction: Function to decorate
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Decorated function that handles deserialization
|
|
104
|
+
"""
|
|
105
|
+
def dispatch_deserializer(self, *params: Any, **param2: Any) -> Any:
|
|
106
|
+
# Handle positional arguments using list comprehension
|
|
107
|
+
serialized = [self._dispatch_deserializer(param) for param in params]
|
|
108
|
+
|
|
109
|
+
# Handle keyword arguments using dictionary comprehension
|
|
110
|
+
param2 = {key: self._dispatch_deserializer(value) for key, value in param2.items()}
|
|
111
|
+
|
|
112
|
+
return fonction(self, *serialized, **param2)
|
|
113
|
+
return dispatch_deserializer
|
|
114
|
+
|
|
115
|
+
def output_serialzer(fonction: Callable) -> Callable:
|
|
116
|
+
"""Decorator that serializes output of function.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
fonction: Function to decorate
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Decorated function that handles serialization
|
|
123
|
+
"""
|
|
124
|
+
def dispatch_serializer(self, *params: Any, **param2: Any) -> Any:
|
|
125
|
+
return self._dispatch_serializer(fonction(self, *params, **param2))
|
|
126
|
+
return dispatch_serializer
|
|
127
|
+
|
|
128
|
+
@input_serialzer_param(1, 'request')
|
|
129
|
+
@output_deserialzer
|
|
130
|
+
def send_request_sync(self, target: str, request: Union[Message, Any],
|
|
131
|
+
timeout: int = -1, description: Optional[str] = None) -> Any:
|
|
132
|
+
"""Send message synchronously to target component.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
target: Name of target component
|
|
136
|
+
request: Message to send
|
|
137
|
+
timeout: Timeout in seconds, -1 means wait forever
|
|
138
|
+
description: Optional description for logging
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Response from target component
|
|
142
|
+
|
|
143
|
+
Raises:
|
|
144
|
+
TypeError: If request is invalid type
|
|
145
|
+
"""
|
|
146
|
+
return self.iris_handle.dispatchSendRequestSync(target, request, timeout, description)
|
|
147
|
+
|
|
148
|
+
@input_serialzer_param(1, 'request')
|
|
149
|
+
def send_request_async(self, target: str, request: Union[Message, Any],
|
|
150
|
+
description: Optional[str] = None) -> None:
|
|
151
|
+
"""Send message asynchronously to target component.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
target: Name of target component
|
|
155
|
+
request: Message to send
|
|
156
|
+
description: Optional description for logging
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
TypeError: If request is invalid type
|
|
160
|
+
"""
|
|
161
|
+
return self.iris_handle.dispatchSendRequestAsync(target, request, description)
|
|
162
|
+
|
|
163
|
+
async def send_request_async_ng(self, target: str, request: Union[Message, Any],
|
|
164
|
+
timeout: int = -1, description: Optional[str] = None) -> Any:
|
|
165
|
+
"""Send message asynchronously to target component with asyncio.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
target: Name of target component
|
|
169
|
+
request: Message to send
|
|
170
|
+
timeout: Timeout in seconds, -1 means wait forever
|
|
171
|
+
description: Optional description for logging
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Response from target component
|
|
175
|
+
"""
|
|
176
|
+
return await _send_request_async_ng(target, request, timeout, description, self)
|
|
177
|
+
|
|
178
|
+
def send_multi_request_sync(self, target_request: List[Tuple[str, Union[Message, Any]]],
|
|
179
|
+
timeout: int = -1, description: Optional[str] = None) -> List[Tuple[str, Union[Message, Any], Any, int]]:
|
|
180
|
+
"""Send multiple messages synchronously to target components.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
target_request: List of tuples (target, request) to send
|
|
184
|
+
timeout: Timeout in seconds, -1 means wait forever
|
|
185
|
+
description: Optional description for logging
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
List of tuples (target, request, response, status)
|
|
189
|
+
"""
|
|
190
|
+
# create a list of iris.Ens.CallStructure for each target_request
|
|
191
|
+
call_list = []
|
|
192
|
+
# sanity check
|
|
193
|
+
if not isinstance(target_request, list):
|
|
194
|
+
raise TypeError("The target_request parameter must be a list")
|
|
195
|
+
if len(target_request) == 0:
|
|
196
|
+
raise ValueError("The target_request parameter must not be empty")
|
|
197
|
+
# check if the target_request is a list of tuple of 2 elements
|
|
198
|
+
if not all(isinstance(item, tuple) and len(item) == 2 for item in target_request):
|
|
199
|
+
raise TypeError("The target_request parameter must be a list of tuple of 2 elements")
|
|
200
|
+
|
|
201
|
+
for target, request in target_request:
|
|
202
|
+
call = iris.cls("Ens.CallStructure")._New()
|
|
203
|
+
call.TargetDispatchName = target
|
|
204
|
+
call.Request = self._dispatch_serializer(request)
|
|
205
|
+
call_list.append(call)
|
|
206
|
+
# call the dispatchSendMultiRequestSync method
|
|
207
|
+
response_list = self.iris_handle.dispatchSendRequestSyncMultiple(call_list, timeout)
|
|
208
|
+
# create a list of tuple (target, request, response, status)
|
|
209
|
+
result = []
|
|
210
|
+
for i in range(len(target_request)):
|
|
211
|
+
result.append(
|
|
212
|
+
(target_request[i][0],
|
|
213
|
+
target_request[i][1],
|
|
214
|
+
self._dispatch_deserializer(response_list[i].Response),
|
|
215
|
+
response_list[i].ResponseCode
|
|
216
|
+
))
|
|
217
|
+
return result
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _serialize_pickle_message(self, message: Any) -> iris.cls:
|
|
221
|
+
"""Converts a python dataclass message into an iris iop.message.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
message: The message to serialize, an instance of a class that is a subclass of Message.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
The message in json format.
|
|
228
|
+
"""
|
|
229
|
+
pickle_string = codecs.encode(pickle.dumps(message), "base64").decode()
|
|
230
|
+
module = message.__class__.__module__
|
|
231
|
+
classname = message.__class__.__name__
|
|
232
|
+
|
|
233
|
+
msg = iris.cls('IOP.PickleMessage')._New()
|
|
234
|
+
msg.classname = module + "." + classname
|
|
235
|
+
|
|
236
|
+
stream = _Utils.string_to_stream(pickle_string)
|
|
237
|
+
msg.jstr = stream
|
|
238
|
+
|
|
239
|
+
return msg
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _dispatch_serializer(self, message: Any) -> Any:
|
|
243
|
+
"""Serializes the message based on its type.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
message: The message to serialize
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
The serialized message
|
|
250
|
+
|
|
251
|
+
Raises:
|
|
252
|
+
TypeError: If message is invalid type
|
|
253
|
+
"""
|
|
254
|
+
if message is not None:
|
|
255
|
+
if self._is_message_instance(message):
|
|
256
|
+
return self._serialize_message(message)
|
|
257
|
+
elif self._is_pickle_message_instance(message):
|
|
258
|
+
return self._serialize_pickle_message(message)
|
|
259
|
+
elif self._is_iris_object_instance(message):
|
|
260
|
+
return message
|
|
261
|
+
|
|
262
|
+
if message == "" or message is None:
|
|
263
|
+
return message
|
|
264
|
+
|
|
265
|
+
# todo : decorator takes care of all the parameters, so this should never happen
|
|
266
|
+
# return message
|
|
267
|
+
raise TypeError("The message must be an instance of a class that is a subclass of Message or IRISObject %Persistent class.")
|
|
268
|
+
|
|
269
|
+
def _serialize_message(self, message: Any) -> iris.cls:
|
|
270
|
+
"""Converts a python dataclass message into an iris iop.message.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
message: The message to serialize, an instance of a class that is a subclass of Message.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
The message in json format.
|
|
277
|
+
"""
|
|
278
|
+
json_string = json.dumps(message, cls=IrisJSONEncoder, ensure_ascii=False)
|
|
279
|
+
module = message.__class__.__module__
|
|
280
|
+
classname = message.__class__.__name__
|
|
281
|
+
|
|
282
|
+
msg = iris.cls('IOP.Message')._New()
|
|
283
|
+
msg.classname = module + "." + classname
|
|
284
|
+
|
|
285
|
+
if hasattr(msg, 'buffer') and len(json_string) > msg.buffer:
|
|
286
|
+
msg.json = _Utils.string_to_stream(json_string, msg.buffer)
|
|
287
|
+
else:
|
|
288
|
+
msg.json = json_string
|
|
289
|
+
|
|
290
|
+
return msg
|
|
291
|
+
|
|
292
|
+
def _deserialize_pickle_message(self, serial: iris.cls) -> Any:
|
|
293
|
+
"""Converts an iris iop.message into a python dataclass message.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
serial: The serialized message
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
The deserialized message
|
|
300
|
+
"""
|
|
301
|
+
string = _Utils.stream_to_string(serial.jstr)
|
|
302
|
+
|
|
303
|
+
msg = pickle.loads(codecs.decode(string.encode(), "base64"))
|
|
304
|
+
return msg
|
|
305
|
+
|
|
306
|
+
def _dispatch_deserializer(self, serial: Any) -> Any:
|
|
307
|
+
"""Deserializes the message based on its type.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
serial: The serialized message
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
The deserialized message
|
|
314
|
+
"""
|
|
315
|
+
if (
|
|
316
|
+
serial is not None
|
|
317
|
+
and type(serial).__module__.startswith('iris')
|
|
318
|
+
and (
|
|
319
|
+
serial._IsA("IOP.Message")
|
|
320
|
+
or serial._IsA("Grongier.PEX.Message")
|
|
321
|
+
)
|
|
322
|
+
):
|
|
323
|
+
return self._deserialize_message(serial)
|
|
324
|
+
elif (
|
|
325
|
+
serial is not None
|
|
326
|
+
and type(serial).__module__.startswith('iris')
|
|
327
|
+
and (
|
|
328
|
+
serial._IsA("IOP.PickleMessage")
|
|
329
|
+
or serial._IsA("Grongier.PEX.PickleMessage")
|
|
330
|
+
)
|
|
331
|
+
):
|
|
332
|
+
return self._deserialize_pickle_message(serial)
|
|
333
|
+
else:
|
|
334
|
+
return serial
|
|
335
|
+
|
|
336
|
+
def _deserialize_message(self, serial: iris.cls) -> Any:
|
|
337
|
+
"""Converts an iris iop.message into a python dataclass message.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
serial: The serialized message
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
The deserialized message
|
|
344
|
+
"""
|
|
345
|
+
if (serial.classname is None):
|
|
346
|
+
raise ValueError("JSON message malformed, must include classname")
|
|
347
|
+
classname = serial.classname
|
|
348
|
+
|
|
349
|
+
j = classname.rindex(".")
|
|
350
|
+
if (j <= 0):
|
|
351
|
+
raise ValueError("Classname must include a module: " + classname)
|
|
352
|
+
try:
|
|
353
|
+
module = importlib.import_module(classname[:j])
|
|
354
|
+
msg = getattr(module, classname[j+1:])
|
|
355
|
+
except Exception:
|
|
356
|
+
raise ImportError("Class not found: " + classname)
|
|
357
|
+
|
|
358
|
+
string = ""
|
|
359
|
+
if (serial.type == 'Stream'):
|
|
360
|
+
string = _Utils.stream_to_string(serial.json)
|
|
361
|
+
else:
|
|
362
|
+
string = serial.json
|
|
363
|
+
|
|
364
|
+
jdict = json.loads(string, cls=IrisJSONDecoder)
|
|
365
|
+
msg = self._dataclass_from_dict(msg, jdict)
|
|
366
|
+
return msg
|
|
367
|
+
|
|
368
|
+
def _dataclass_from_dict(self, klass: Type, dikt: Dict) -> Any:
|
|
369
|
+
"""Converts a dictionary to a dataclass instance.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
klass: The dataclass to convert to
|
|
373
|
+
dikt: The dictionary to convert to a dataclass
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
A dataclass object with the fields of the dataclass and the fields of the dictionary.
|
|
377
|
+
"""
|
|
378
|
+
ret = from_dict(klass, dikt, Config(check_types=False))
|
|
379
|
+
|
|
380
|
+
try:
|
|
381
|
+
fieldtypes = klass.__annotations__
|
|
382
|
+
except Exception as e:
|
|
383
|
+
fieldtypes = []
|
|
384
|
+
|
|
385
|
+
for key, val in dikt.items():
|
|
386
|
+
if key not in fieldtypes:
|
|
387
|
+
setattr(ret, key, val)
|
|
388
|
+
return ret
|
|
389
|
+
|
|
390
|
+
def _dispach_message(self, request: Any) -> Any:
|
|
391
|
+
"""Dispatches the message to the appropriate method.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
request: The request object
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
The response object
|
|
398
|
+
"""
|
|
399
|
+
call = 'on_message'
|
|
400
|
+
|
|
401
|
+
module = request.__class__.__module__
|
|
402
|
+
classname = request.__class__.__name__
|
|
403
|
+
|
|
404
|
+
for msg, method in self.DISPATCH:
|
|
405
|
+
if msg == module + "." + classname:
|
|
406
|
+
call = method
|
|
407
|
+
|
|
408
|
+
return getattr(self, call)(request)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _create_dispatch(self) -> None:
|
|
412
|
+
"""Creates a list of tuples, where each tuple contains the name of a class and the name of a method
|
|
413
|
+
that takes an instance of that class as its only argument.
|
|
414
|
+
"""
|
|
415
|
+
if len(self.DISPATCH) == 0:
|
|
416
|
+
# get all function in current BO
|
|
417
|
+
method_list = [func for func in dir(self) if callable(getattr(self, func)) and not func.startswith("_")]
|
|
418
|
+
for method in method_list:
|
|
419
|
+
# get signature of current function
|
|
420
|
+
try:
|
|
421
|
+
param = signature(getattr(self, method)).parameters
|
|
422
|
+
# Handle staticmethod
|
|
423
|
+
except ValueError as e:
|
|
424
|
+
param = ''
|
|
425
|
+
# one parameter
|
|
426
|
+
if (len(param) == 1):
|
|
427
|
+
# get parameter type
|
|
428
|
+
annotation = str(param[list(param)[0]].annotation)
|
|
429
|
+
# trim annotation format <class 'toto'>
|
|
430
|
+
i = annotation.find("'")
|
|
431
|
+
j = annotation.rfind("'")
|
|
432
|
+
# if end is not found
|
|
433
|
+
if j == -1:
|
|
434
|
+
j = None
|
|
435
|
+
classname = annotation[i+1:j]
|
|
436
|
+
self.DISPATCH.append((classname, method))
|
|
437
|
+
return
|
|
438
|
+
|
|
439
|
+
@staticmethod
|
|
440
|
+
def OnGetConnections() -> Optional[List[str]]:
|
|
441
|
+
"""The OnGetConnections() method returns all of the targets of any SendRequestSync or SendRequestAsync
|
|
442
|
+
calls for the class. Implement this method to allow connections between components to show up in
|
|
443
|
+
the interoperability UI.
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
An IRISList containing all targets for this class. Default is None.
|
|
447
|
+
"""
|
|
448
|
+
return None
|
|
449
|
+
|
|
450
|
+
def SendRequestSync(self, target: str, request: Union[Message, Any],
|
|
451
|
+
timeout: int = -1, description: Optional[str] = None) -> Any:
|
|
452
|
+
"""DEPRECATED: use send_request_sync.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
target: The target of the request
|
|
456
|
+
request: The request to send
|
|
457
|
+
timeout: The timeout in seconds, -1 means wait forever
|
|
458
|
+
description: A string that describes the request
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
The response from the target component
|
|
462
|
+
"""
|
|
463
|
+
return self.send_request_sync(target, request, timeout, description)
|
|
464
|
+
|
|
465
|
+
def SendRequestAsync(self, target: str, request: Union[Message, Any],
|
|
466
|
+
description: Optional[str] = None) -> None:
|
|
467
|
+
"""DEPRECATED: use send_request_async.
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
target: The target of the request
|
|
471
|
+
request: The request to send
|
|
472
|
+
description: A string that describes the request
|
|
473
|
+
"""
|
|
474
|
+
return self.send_request_async(target, request, description)
|
|
475
|
+
|
|
476
|
+
@staticmethod
|
|
477
|
+
def getAdapterType() -> Optional[str]:
|
|
478
|
+
"""DEPRECATED: use get_adapter_type.
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
Name of the registered Adapter
|
|
482
|
+
"""
|
|
483
|
+
return
|
|
484
|
+
|
|
485
|
+
@staticmethod
|
|
486
|
+
def get_adapter_type() -> Optional[str]:
|
|
487
|
+
"""Returns the name of the registered Adapter.
|
|
488
|
+
|
|
489
|
+
Returns:
|
|
490
|
+
Name of the registered Adapter
|
|
491
|
+
"""
|
|
492
|
+
return
|
|
493
|
+
|
|
494
|
+
def on_get_connections(self) -> List[str]:
|
|
495
|
+
"""The OnGetConnections() method returns all of the targets of any SendRequestSync or SendRequestAsync
|
|
496
|
+
calls for the class. Implement this method to allow connections between components to show up in
|
|
497
|
+
the interoperability UI.
|
|
498
|
+
|
|
499
|
+
Returns:
|
|
500
|
+
A list containing all targets for this class.
|
|
501
|
+
"""
|
|
502
|
+
## Parse the class code to find all invocations of send_request_sync and send_request_async
|
|
503
|
+
## and return the targets
|
|
504
|
+
targer_list = []
|
|
505
|
+
# get the source code of the class
|
|
506
|
+
source = getsource(self.__class__)
|
|
507
|
+
# find all invocations of send_request_sync and send_request_async
|
|
508
|
+
for method in ['send_request_sync', 'send_request_async', 'SendRequestSync', 'SendRequestAsync']:
|
|
509
|
+
i = source.find(method)
|
|
510
|
+
while i != -1:
|
|
511
|
+
j = source.find("(", i)
|
|
512
|
+
if j != -1:
|
|
513
|
+
k = source.find(",", j)
|
|
514
|
+
if k != -1:
|
|
515
|
+
target = source[j+1:k]
|
|
516
|
+
if target.find("=") != -1:
|
|
517
|
+
# it's a keyword argument, remove the keyword
|
|
518
|
+
target = target[target.find("=")+1:].strip()
|
|
519
|
+
if target not in targer_list:
|
|
520
|
+
targer_list.append(target)
|
|
521
|
+
i = source.find(method, i+1)
|
|
522
|
+
|
|
523
|
+
for target in targer_list:
|
|
524
|
+
# if target is a string, remove the quotes
|
|
525
|
+
if target[0] == "'" and target[-1] == "'":
|
|
526
|
+
targer_list[targer_list.index(target)] = target[1:-1]
|
|
527
|
+
elif target[0] == '"' and target[-1] == '"':
|
|
528
|
+
targer_list[targer_list.index(target)] = target[1:-1]
|
|
529
|
+
# if target is a variable, try to find the value of the variable
|
|
530
|
+
else:
|
|
531
|
+
self.on_init()
|
|
532
|
+
try:
|
|
533
|
+
if target.find("self.") != -1:
|
|
534
|
+
# it's a class variable
|
|
535
|
+
targer_list[targer_list.index(target)] = getattr(self, target[target.find(".")+1:])
|
|
536
|
+
elif target.find(".") != -1:
|
|
537
|
+
# it's a class variable
|
|
538
|
+
targer_list[targer_list.index(target)] = getattr(getattr(self, target[:target.find(".")]), target[target.find(".")+1:])
|
|
539
|
+
else:
|
|
540
|
+
targer_list[targer_list.index(target)] = getattr(self, target)
|
|
541
|
+
except Exception as e:
|
|
542
|
+
pass
|
|
543
|
+
|
|
544
|
+
return targer_list
|
|
545
|
+
|
|
546
|
+
# It's a subclass of the standard JSONEncoder class that knows how to encode date/time, decimal types,
|
|
547
|
+
# and UUIDs.
|
|
548
|
+
class IrisJSONEncoder(json.JSONEncoder):
|
|
549
|
+
"""
|
|
550
|
+
JSONEncoder subclass that knows how to encode date/time, decimal types, and
|
|
551
|
+
UUIDs.
|
|
552
|
+
"""
|
|
553
|
+
|
|
554
|
+
def default(self, o: Any) -> Any:
|
|
555
|
+
if o.__class__.__name__ == 'DataFrame':
|
|
556
|
+
return 'dataframe:' + o.to_json(orient="table")
|
|
557
|
+
elif isinstance(o, datetime.datetime):
|
|
558
|
+
r = o.isoformat()
|
|
559
|
+
if o.microsecond:
|
|
560
|
+
r = r[:23] + r[26:]
|
|
561
|
+
if r.endswith("+00:00"):
|
|
562
|
+
r = r[:-6] + "Z"
|
|
563
|
+
return 'datetime:' + r
|
|
564
|
+
elif isinstance(o, datetime.date):
|
|
565
|
+
return 'date:' + o.isoformat()
|
|
566
|
+
elif isinstance(o, datetime.time):
|
|
567
|
+
r = o.isoformat()
|
|
568
|
+
if o.microsecond:
|
|
569
|
+
r = r[:12]
|
|
570
|
+
return 'time:' + r
|
|
571
|
+
elif isinstance(o, decimal.Decimal):
|
|
572
|
+
return 'decimal:' + str(o)
|
|
573
|
+
elif isinstance(o, uuid.UUID):
|
|
574
|
+
return 'uuid:' + str(o)
|
|
575
|
+
elif isinstance(o, bytes):
|
|
576
|
+
return 'bytes:' + base64.b64encode(o).decode("UTF-8")
|
|
577
|
+
elif hasattr(o, '__dict__'):
|
|
578
|
+
return o.__dict__
|
|
579
|
+
else:
|
|
580
|
+
return super().default(o)
|
|
581
|
+
|
|
582
|
+
# It's a JSON decoder that looks for a colon in the value of a key/value pair. If it finds one, it
|
|
583
|
+
# assumes the value is a string that represents a type and a value. It then converts the value to the
|
|
584
|
+
# appropriate type
|
|
585
|
+
class IrisJSONDecoder(json.JSONDecoder):
|
|
586
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
587
|
+
json.JSONDecoder.__init__(
|
|
588
|
+
self, object_hook=self.object_hook, *args, **kwargs)
|
|
589
|
+
|
|
590
|
+
def object_hook(self, obj: Dict) -> Dict:
|
|
591
|
+
ret = {}
|
|
592
|
+
for key, value in obj.items():
|
|
593
|
+
i = 0
|
|
594
|
+
if isinstance(value, str):
|
|
595
|
+
i = value.find(":")
|
|
596
|
+
if (i > 0):
|
|
597
|
+
typ = value[:i]
|
|
598
|
+
if typ == 'datetime':
|
|
599
|
+
ret[key] = datetime.datetime.fromisoformat(value[i+1:])
|
|
600
|
+
elif typ == 'date':
|
|
601
|
+
ret[key] = datetime.date.fromisoformat(value[i+1:])
|
|
602
|
+
elif typ == 'time':
|
|
603
|
+
ret[key] = datetime.time.fromisoformat(value[i+1:])
|
|
604
|
+
elif typ == 'dataframe':
|
|
605
|
+
module = importlib.import_module('pandas')
|
|
606
|
+
ret[key] = module.read_json(value[i+1:], orient="table")
|
|
607
|
+
elif typ == 'decimal':
|
|
608
|
+
ret[key] = decimal.Decimal(value[i+1:])
|
|
609
|
+
elif typ == 'uuid':
|
|
610
|
+
ret[key] = uuid.UUID(value[i+1:])
|
|
611
|
+
elif typ == 'bytes':
|
|
612
|
+
ret[key] = base64.b64decode((value[i+1:].encode("UTF-8")))
|
|
613
|
+
else:
|
|
614
|
+
ret[key] = value
|
|
615
|
+
else:
|
|
616
|
+
ret[key] = value
|
|
617
|
+
return ret
|
|
618
|
+
|
|
619
|
+
class _send_request_async_ng(asyncio.Future):
|
|
620
|
+
|
|
621
|
+
_message_header_id: int = 0
|
|
622
|
+
_queue_name: str = ""
|
|
623
|
+
_end_time: int = 0
|
|
624
|
+
_response: Any = None
|
|
625
|
+
_done: bool = False
|
|
626
|
+
|
|
627
|
+
def __init__(self, target: str, request: Union[Message, Any],
|
|
628
|
+
timeout: int = -1, description: Optional[str] = None, host: Optional[_BusinessHost] = None) -> None:
|
|
629
|
+
super().__init__()
|
|
630
|
+
self.target = target
|
|
631
|
+
self.request = request
|
|
632
|
+
self.timeout = timeout
|
|
633
|
+
self.description = description
|
|
634
|
+
self.host = host
|
|
635
|
+
self._iris_handle = host.iris_handle
|
|
636
|
+
asyncio.create_task(self.send())
|
|
637
|
+
|
|
638
|
+
async def send(self) -> None:
|
|
639
|
+
# init parameters
|
|
640
|
+
message_header_id = iris.ref()
|
|
641
|
+
queue_name = iris.ref()
|
|
642
|
+
end_time = iris.ref()
|
|
643
|
+
request = self.host._dispatch_serializer(self.request)
|
|
644
|
+
|
|
645
|
+
# send request
|
|
646
|
+
self._iris_handle.dispatchSendRequestAsyncNG(
|
|
647
|
+
self.target, request, self.timeout, self.description,
|
|
648
|
+
message_header_id, queue_name, end_time)
|
|
649
|
+
|
|
650
|
+
# get byref values
|
|
651
|
+
self._message_header_id = message_header_id.value
|
|
652
|
+
self._queue_name = queue_name.value
|
|
653
|
+
self._end_time = end_time.value
|
|
654
|
+
|
|
655
|
+
while not self._done:
|
|
656
|
+
await asyncio.sleep(0.1)
|
|
657
|
+
self.is_done()
|
|
658
|
+
|
|
659
|
+
self.set_result(self._response)
|
|
660
|
+
|
|
661
|
+
def is_done(self) -> None:
|
|
662
|
+
response = iris.ref()
|
|
663
|
+
status = self._iris_handle.dispatchIsRequestDone(self.timeout, self._end_time,
|
|
664
|
+
self._queue_name, self._message_header_id,
|
|
665
|
+
response)
|
|
666
|
+
|
|
667
|
+
self._response = self.host._dispatch_deserializer(response.value)
|
|
668
|
+
|
|
669
|
+
if status == 2: # message found
|
|
670
|
+
self._done = True
|
|
671
|
+
elif status == 1: # message not found
|
|
672
|
+
pass
|
|
673
|
+
else:
|
|
674
|
+
self._done = True
|
|
675
|
+
self.set_exception(RuntimeError(iris.system.Status.GetOneStatusText(status)))
|