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 ADDED
@@ -0,0 +1,62 @@
1
+ import asyncio
2
+ import iris
3
+ from typing import Any, Optional, Union
4
+ from iop._dispatch import dispatch_deserializer, dispatch_serializer
5
+ from iop._message import _Message as Message
6
+
7
+ class AsyncRequest(asyncio.Future):
8
+ _message_header_id: int = 0
9
+ _queue_name: str = ""
10
+ _end_time: int = 0
11
+ _response: Any = None
12
+ _done: bool = False
13
+
14
+ def __init__(self, target: str, request: Union[Message, Any],
15
+ timeout: int = -1, description: Optional[str] = None, host: Optional[Any] = None) -> None:
16
+ super().__init__()
17
+ self.target = target
18
+ self.request = request
19
+ self.timeout = timeout
20
+ self.description = description
21
+ self.host = host
22
+ self._iris_handle = host.iris_handle
23
+ asyncio.create_task(self.send())
24
+
25
+ async def send(self) -> None:
26
+ # init parameters
27
+ message_header_id = iris.ref()
28
+ queue_name = iris.ref()
29
+ end_time = iris.ref()
30
+ request = dispatch_serializer(self.request)
31
+
32
+ # send request
33
+ self._iris_handle.dispatchSendRequestAsyncNG(
34
+ self.target, request, self.timeout, self.description,
35
+ message_header_id, queue_name, end_time)
36
+
37
+ # get byref values
38
+ self._message_header_id = message_header_id.value
39
+ self._queue_name = queue_name.value
40
+ self._end_time = end_time.value
41
+
42
+ while not self._done:
43
+ await asyncio.sleep(0.1)
44
+ self.is_done()
45
+
46
+ self.set_result(self._response)
47
+
48
+ def is_done(self) -> None:
49
+ response = iris.ref()
50
+ status = self._iris_handle.dispatchIsRequestDone(self.timeout, self._end_time,
51
+ self._queue_name, self._message_header_id,
52
+ response)
53
+
54
+ self._response = dispatch_deserializer(response.value)
55
+
56
+ if status == 2: # message found
57
+ self._done = True
58
+ elif status == 1: # message not found
59
+ pass
60
+ else:
61
+ self._done = True
62
+ self.set_exception(RuntimeError(iris.system.Status.GetOneStatusText(status)))
iop/_business_host.py CHANGED
@@ -1,25 +1,13 @@
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
1
+ from inspect import getsource
2
+ from typing import Any,List, Optional, Tuple, Union
17
3
 
18
- from dacite import Config, from_dict
4
+ import iris
19
5
 
20
6
  from iop._common import _Common
21
7
  from iop._message import _Message as Message
22
- from iop._utils import _Utils
8
+ from iop._decorators import input_serializer_param, output_deserializer
9
+ from iop._dispatch import dispatch_serializer, dispatch_deserializer
10
+ from iop._async_request import AsyncRequest
23
11
 
24
12
  class _BusinessHost(_Common):
25
13
  """Base class for business components that defines common methods.
@@ -31,102 +19,8 @@ class _BusinessHost(_Common):
31
19
  buffer: int = 64000
32
20
  DISPATCH: List[Tuple[str, str]] = []
33
21
 
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
22
+ @input_serializer_param(1, 'request')
23
+ @output_deserializer
130
24
  def send_request_sync(self, target: str, request: Union[Message, Any],
131
25
  timeout: int = -1, description: Optional[str] = None) -> Any:
132
26
  """Send message synchronously to target component.
@@ -145,7 +39,7 @@ class _BusinessHost(_Common):
145
39
  """
146
40
  return self.iris_handle.dispatchSendRequestSync(target, request, timeout, description)
147
41
 
148
- @input_serialzer_param(1, 'request')
42
+ @input_serializer_param(1, 'request')
149
43
  def send_request_async(self, target: str, request: Union[Message, Any],
150
44
  description: Optional[str] = None) -> None:
151
45
  """Send message asynchronously to target component.
@@ -173,10 +67,10 @@ class _BusinessHost(_Common):
173
67
  Returns:
174
68
  Response from target component
175
69
  """
176
- return await _send_request_async_ng(target, request, timeout, description, self)
70
+ return await AsyncRequest(target, request, timeout, description, self)
177
71
 
178
72
  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]]:
73
+ timeout: int = -1, description: Optional[str] = None) -> List[Tuple[str, Union[Message, Any], Any, int]]:
180
74
  """Send multiple messages synchronously to target components.
181
75
 
182
76
  Args:
@@ -186,255 +80,39 @@ class _BusinessHost(_Common):
186
80
 
187
81
  Returns:
188
82
  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
83
 
251
84
  Raises:
252
- TypeError: If message is invalid type
85
+ TypeError: If target_request is not a list of tuples
86
+ ValueError: If target_request is empty
253
87
  """
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))
88
+ self._validate_target_request(target_request)
379
89
 
380
- try:
381
- fieldtypes = klass.__annotations__
382
- except Exception as e:
383
- fieldtypes = []
90
+ call_list = [self._create_call_structure(target, request)
91
+ for target, request in target_request]
384
92
 
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.
93
+ response_list = self.iris_handle.dispatchSendRequestSyncMultiple(call_list, timeout)
392
94
 
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
95
+ return [(target_request[i][0],
96
+ target_request[i][1],
97
+ dispatch_deserializer(response_list[i].Response),
98
+ response_list[i].ResponseCode)
99
+ for i in range(len(target_request))]
407
100
 
408
- return getattr(self, call)(request)
101
+ def _validate_target_request(self, target_request: List[Tuple[str, Union[Message, Any]]]) -> None:
102
+ """Validate the target_request parameter structure."""
103
+ if not isinstance(target_request, list):
104
+ raise TypeError("target_request must be a list")
105
+ if not target_request:
106
+ raise ValueError("target_request must not be empty")
107
+ if not all(isinstance(item, tuple) and len(item) == 2 for item in target_request):
108
+ raise TypeError("target_request must contain tuples of (target, request)")
409
109
 
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
110
+ def _create_call_structure(self, target: str, request: Union[Message, Any]) -> Any:
111
+ """Create an Ens.CallStructure object for the request."""
112
+ call = iris.cls("Ens.CallStructure")._New()
113
+ call.TargetDispatchName = target
114
+ call.Request = dispatch_serializer(request)
115
+ return call
438
116
 
439
117
  @staticmethod
440
118
  def OnGetConnections() -> Optional[List[str]]:
@@ -542,134 +220,3 @@ class _BusinessHost(_Common):
542
220
  pass
543
221
 
544
222
  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)))
@@ -1,6 +1,8 @@
1
1
  import importlib
2
2
  from typing import Any, List, Optional, Union, Tuple
3
3
  from iop._business_host import _BusinessHost
4
+ from iop._decorators import input_deserializer, output_serializer, input_serializer, output_deserializer
5
+ from iop._dispatch import create_dispatch, dispach_message
4
6
 
5
7
  class _BusinessOperation(_BusinessHost):
6
8
  """Business operation component that handles outbound communication.
@@ -45,15 +47,15 @@ class _BusinessOperation(_BusinessHost):
45
47
 
46
48
  def _dispatch_on_init(self, host_object: Any) -> None:
47
49
  """For internal use only."""
48
- self._create_dispatch()
50
+ create_dispatch(self)
49
51
  self.on_init()
50
52
  return
51
53
 
52
- @_BusinessHost.input_deserialzer
53
- @_BusinessHost.output_serialzer
54
+ @input_deserializer
55
+ @output_serializer
54
56
  def _dispatch_on_message(self, request: Any) -> Any:
55
57
  """For internal use only."""
56
- return self._dispach_message(request)
58
+ return dispach_message(self,request)
57
59
 
58
60
  def OnMessage(self, request: Any) -> Any:
59
61
  """DEPRECATED : use on_message