iris-pex-embedded-python 3.5.5b4__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.
Files changed (91) hide show
  1. grongier/__init__.py +0 -0
  2. grongier/cls/Grongier/PEX/BusinessOperation.cls +8 -0
  3. grongier/cls/Grongier/PEX/BusinessProcess.cls +13 -0
  4. grongier/cls/Grongier/PEX/BusinessService.cls +8 -0
  5. grongier/cls/Grongier/PEX/Common.cls +10 -0
  6. grongier/cls/Grongier/PEX/Director.cls +10 -0
  7. grongier/cls/Grongier/PEX/Duplex/Operation.cls +4 -0
  8. grongier/cls/Grongier/PEX/Duplex/Process.cls +13 -0
  9. grongier/cls/Grongier/PEX/Duplex/Service.cls +4 -0
  10. grongier/cls/Grongier/PEX/InboundAdapter.cls +8 -0
  11. grongier/cls/Grongier/PEX/Message.cls +13 -0
  12. grongier/cls/Grongier/PEX/OutboundAdapter.cls +8 -0
  13. grongier/cls/Grongier/PEX/PickleMessage.cls +13 -0
  14. grongier/cls/Grongier/PEX/PrivateSession/Duplex.cls +8 -0
  15. grongier/cls/Grongier/PEX/PrivateSession/Message/Ack.cls +14 -0
  16. grongier/cls/Grongier/PEX/PrivateSession/Message/Poll.cls +14 -0
  17. grongier/cls/Grongier/PEX/PrivateSession/Message/Start.cls +14 -0
  18. grongier/cls/Grongier/PEX/PrivateSession/Message/Stop.cls +14 -0
  19. grongier/cls/Grongier/PEX/Test.cls +10 -0
  20. grongier/cls/Grongier/PEX/Utils.cls +10 -0
  21. grongier/cls/Grongier/Service/WSGI.cls +4 -0
  22. grongier/pex/__init__.py +24 -0
  23. grongier/pex/__main__.py +4 -0
  24. grongier/pex/_business_host.py +1 -0
  25. grongier/pex/_cli.py +4 -0
  26. grongier/pex/_common.py +1 -0
  27. grongier/pex/_director.py +1 -0
  28. grongier/pex/_utils.py +1 -0
  29. grongier/pex/wsgi/handlers.py +104 -0
  30. iop/__init__.py +25 -0
  31. iop/__main__.py +4 -0
  32. iop/_async_request.py +67 -0
  33. iop/_business_host.py +256 -0
  34. iop/_business_operation.py +75 -0
  35. iop/_business_process.py +224 -0
  36. iop/_business_service.py +63 -0
  37. iop/_cli.py +247 -0
  38. iop/_common.py +334 -0
  39. iop/_debugpy.py +187 -0
  40. iop/_decorators.py +49 -0
  41. iop/_director.py +301 -0
  42. iop/_dispatch.py +136 -0
  43. iop/_generator_request.py +30 -0
  44. iop/_inbound_adapter.py +34 -0
  45. iop/_iris.py +8 -0
  46. iop/_log_manager.py +100 -0
  47. iop/_message.py +40 -0
  48. iop/_message_validator.py +49 -0
  49. iop/_outbound_adapter.py +23 -0
  50. iop/_private_session_duplex.py +103 -0
  51. iop/_private_session_process.py +41 -0
  52. iop/_remote.py +91 -0
  53. iop/_serialization.py +199 -0
  54. iop/_utils.py +671 -0
  55. iop/cls/IOP/BusinessOperation.cls +35 -0
  56. iop/cls/IOP/BusinessProcess.cls +156 -0
  57. iop/cls/IOP/BusinessService.cls +40 -0
  58. iop/cls/IOP/Common.cls +569 -0
  59. iop/cls/IOP/Director.cls +70 -0
  60. iop/cls/IOP/Duplex/Operation.cls +29 -0
  61. iop/cls/IOP/Duplex/Process.cls +229 -0
  62. iop/cls/IOP/Duplex/Service.cls +9 -0
  63. iop/cls/IOP/Generator/Message/Ack.cls +31 -0
  64. iop/cls/IOP/Generator/Message/Poll.cls +31 -0
  65. iop/cls/IOP/Generator/Message/Start.cls +15 -0
  66. iop/cls/IOP/Generator/Message/StartPickle.cls +15 -0
  67. iop/cls/IOP/Generator/Message/Stop.cls +32 -0
  68. iop/cls/IOP/InboundAdapter.cls +22 -0
  69. iop/cls/IOP/Message/JSONSchema.cls +125 -0
  70. iop/cls/IOP/Message.cls +754 -0
  71. iop/cls/IOP/OutboundAdapter.cls +36 -0
  72. iop/cls/IOP/PickleMessage.cls +58 -0
  73. iop/cls/IOP/PrivateSession/Duplex.cls +260 -0
  74. iop/cls/IOP/PrivateSession/Message/Ack.cls +32 -0
  75. iop/cls/IOP/PrivateSession/Message/Poll.cls +32 -0
  76. iop/cls/IOP/PrivateSession/Message/Start.cls +31 -0
  77. iop/cls/IOP/PrivateSession/Message/Stop.cls +48 -0
  78. iop/cls/IOP/Projection.cls +49 -0
  79. iop/cls/IOP/Service/Remote/Handler.cls +30 -0
  80. iop/cls/IOP/Service/Remote/Rest/v1.cls +97 -0
  81. iop/cls/IOP/Service/WSGI.cls +310 -0
  82. iop/cls/IOP/Test.cls +85 -0
  83. iop/cls/IOP/Utils.cls +503 -0
  84. iop/cls/IOP/Wrapper.cls +58 -0
  85. iop/wsgi/handlers.py +104 -0
  86. iris_pex_embedded_python-3.5.5b4.dist-info/METADATA +91 -0
  87. iris_pex_embedded_python-3.5.5b4.dist-info/RECORD +91 -0
  88. iris_pex_embedded_python-3.5.5b4.dist-info/WHEEL +5 -0
  89. iris_pex_embedded_python-3.5.5b4.dist-info/entry_points.txt +2 -0
  90. iris_pex_embedded_python-3.5.5b4.dist-info/licenses/LICENSE +21 -0
  91. iris_pex_embedded_python-3.5.5b4.dist-info/top_level.txt +2 -0
iop/_debugpy.py ADDED
@@ -0,0 +1,187 @@
1
+
2
+ import threading
3
+ import time
4
+ import contextlib
5
+ import socket
6
+ import os
7
+ import sys
8
+ from contextlib import closing
9
+ from typing import Optional, cast, Any, Dict
10
+
11
+ def find_free_port(start: Optional[int] = None, end: Optional[int] = None) -> int:
12
+ port = start
13
+ if port is None:
14
+ port = 0
15
+ if end is None:
16
+ end = port
17
+
18
+ try:
19
+ with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
20
+ with contextlib.suppress(Exception):
21
+ s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
22
+
23
+ s.bind(("0.0.0.0", port))
24
+
25
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
26
+ return cast(int, s.getsockname()[1])
27
+ except (SystemExit, KeyboardInterrupt):
28
+ raise
29
+ except BaseException:
30
+ if port and end > port:
31
+ return find_free_port(port + 1, end)
32
+ if start and start > 0:
33
+ return find_free_port(None)
34
+ raise
35
+
36
+ def is_debugpy_installed() -> bool:
37
+ try:
38
+ __import__("debugpy")
39
+ return True
40
+ except ImportError:
41
+ return False
42
+
43
+ def _get_python_interpreter_path(install_dir: Optional[str]) -> Optional[str]:
44
+ """Get the path to the Python interpreter."""
45
+ if not install_dir:
46
+ return None
47
+
48
+ python_exe = 'irispython.exe' if sys.platform == 'win32' else 'irispython'
49
+ python_path = os.path.join(install_dir, 'bin', python_exe)
50
+
51
+ return python_path if os.path.exists(python_path) else None
52
+
53
+ def _get_debugpy_config(python_path: str) -> Dict[str, str]:
54
+ """Get the debugpy configuration."""
55
+ return {"python": python_path}
56
+
57
+ def configure_debugpy(self, python_path: Optional[str] = None) -> bool:
58
+ """Configure debugpy with the appropriate Python interpreter."""
59
+ import debugpy
60
+
61
+ if not python_path:
62
+ install_dir = os.environ.get('IRISINSTALLDIR') or os.environ.get('ISC_PACKAGE_INSTALLDIR')
63
+ python_path = _get_python_interpreter_path(install_dir)
64
+
65
+ if not python_path:
66
+ self.log_alert("Could not determine Python interpreter path")
67
+ return False
68
+
69
+ try:
70
+ debugpy.configure(_get_debugpy_config(python_path))
71
+ self.log_info(f"Debugpy configured with Python interpreter: {python_path}")
72
+ return True
73
+ except Exception as e:
74
+ self.log_alert(f"Failed to configure debugpy: {e}")
75
+ return False
76
+
77
+ def wait_for_debugpy_connected(timeout: float = 30, port: int = 0) -> bool:
78
+ """Wait for debugpy client to connect."""
79
+ import debugpy
80
+
81
+ if not is_debugpy_installed():
82
+ return False
83
+
84
+ def timeout_handler():
85
+ time.sleep(timeout)
86
+ debugpy.wait_for_client.cancel() # type: ignore
87
+
88
+ threading.Thread(target=timeout_handler, daemon=True).start()
89
+
90
+ try:
91
+ debugpy.wait_for_client()
92
+ return debugpy.is_client_connected()
93
+ except Exception:
94
+ import pydevd # type: ignore
95
+ pydevd.stoptrace()
96
+ return False
97
+
98
+ def enable_debugpy(port: int, address: str = "0.0.0.0") -> None:
99
+ """Enable debugpy server on specified port and address."""
100
+ import debugpy
101
+ debugpy.listen((address, port))
102
+
103
+ def debugpython(self, host_object: Any) -> None:
104
+ """Enable and configure debugpy for debugging purposes."""
105
+ # hack to set __file__ for os module in debugpy
106
+ # This is a workaround for the issue where debugpy cannot find the __file__ attribute of the os module.
107
+ if not hasattr(os, '__file__'):
108
+ setattr(os, '__file__', __file__)
109
+
110
+
111
+ if host_object is None:
112
+ self.log_alert("No host object found, cannot enable debugpy.")
113
+ return
114
+
115
+ if host_object._enable != 1:
116
+ self.log_info("Debugpy is not enabled.")
117
+ return
118
+
119
+ if not is_debugpy_installed():
120
+ self.log_alert("Debugpy is not installed.")
121
+ return
122
+
123
+ # Configure Python interpreter
124
+ if host_object._PythonInterpreterPath != '':
125
+ success = configure_debugpy(self, host_object.PythonInterpreterPath)
126
+ else:
127
+ success = configure_debugpy(self)
128
+
129
+ if not success:
130
+ return
131
+
132
+ # Setup debugging server
133
+ port = host_object._port if host_object._port and host_object._port > 0 else find_free_port()
134
+
135
+ try:
136
+ enable_debugpy(port=port)
137
+ self.log_info(f"Debugpy enabled on port {port}")
138
+
139
+ self.trace(f"Waiting {host_object._timeout} seconds for debugpy connection...")
140
+ if wait_for_debugpy_connected(timeout=host_object._timeout, port=port):
141
+ self.log_info("Debugpy connected successfully")
142
+ else:
143
+ self.log_alert(f"Debugpy connection timed out after {host_object._timeout} seconds")
144
+ except Exception as e:
145
+ self.log_alert(f"Error enabling debugpy: {e}")
146
+
147
+ def debugpy_in_iris(iris_dir, port) -> bool:
148
+
149
+ if not hasattr(os, '__file__'):
150
+ setattr(os, '__file__', __file__)
151
+
152
+ if not is_debugpy_installed():
153
+ print("debugpy is not installed.")
154
+ return False
155
+ if not iris_dir:
156
+ print("IRIS directory is not specified.")
157
+ return False
158
+
159
+ import debugpy
160
+ python_path = _get_python_interpreter_path(mgr_dir_to_install_dir(iris_dir))
161
+ if not python_path:
162
+ return False
163
+ debugpy.configure(_get_debugpy_config(python_path))
164
+
165
+ try:
166
+ enable_debugpy(port=port)
167
+ except Exception as e:
168
+ print(f"Failed to enable debugpy: {e}")
169
+ return False
170
+
171
+ print(f"Debugpy is waiting for connection on port {port}...")
172
+ if wait_for_debugpy_connected(timeout=30, port=port):
173
+ print(f"Debugpy is connected on port {port}")
174
+ return True
175
+ else:
176
+ print(f"Debugpy connection timed out after 30 seconds on port {port}")
177
+ return False
178
+
179
+ def mgr_dir_to_install_dir(mgr_dir: str) -> Optional[str]:
180
+ """Convert manager directory to install directory."""
181
+ import os
182
+ if not mgr_dir:
183
+ return None
184
+ install_dir = os.path.dirname(os.path.dirname(mgr_dir))
185
+ if os.path.exists(install_dir):
186
+ return install_dir
187
+ return None
iop/_decorators.py ADDED
@@ -0,0 +1,49 @@
1
+ from functools import wraps
2
+ from typing import Any, Callable
3
+
4
+ from ._dispatch import dispatch_deserializer, dispatch_serializer
5
+
6
+ def input_serializer(fonction: Callable) -> Callable:
7
+ """Decorator that serializes all input arguments."""
8
+ def _dispatch_serializer(self, *params: Any, **param2: Any) -> Any:
9
+ serialized = [dispatch_serializer(param) for param in params]
10
+ param2 = {key: dispatch_serializer(value) for key, value in param2.items()}
11
+ return fonction(self, *serialized, **param2)
12
+ return _dispatch_serializer
13
+
14
+ def input_serializer_param(position: int, name: str) -> Callable:
15
+ """Decorator that serializes specific parameter by position or name."""
16
+ def _input_serializer_param(fonction: Callable) -> Callable:
17
+ @wraps(fonction)
18
+ def _dispatch_serializer(self, *params: Any, **param2: Any) -> Any:
19
+ serialized = [
20
+ dispatch_serializer(param) if i == position else param
21
+ for i, param in enumerate(params)
22
+ ]
23
+ param2 = {
24
+ key: dispatch_serializer(value) if key == name else value
25
+ for key, value in param2.items()
26
+ }
27
+ return fonction(self, *serialized, **param2)
28
+ return _dispatch_serializer
29
+ return _input_serializer_param
30
+
31
+ def output_deserializer(fonction: Callable) -> Callable:
32
+ """Decorator that deserializes function output."""
33
+ def _dispatch_deserializer(self, *params: Any, **param2: Any) -> Any:
34
+ return dispatch_deserializer(fonction(self, *params, **param2))
35
+ return _dispatch_deserializer
36
+
37
+ def input_deserializer(fonction: Callable) -> Callable:
38
+ """Decorator that deserializes all input arguments."""
39
+ def _dispatch_deserializer(self, *params: Any, **param2: Any) -> Any:
40
+ serialized = [dispatch_deserializer(param) for param in params]
41
+ param2 = {key: dispatch_deserializer(value) for key, value in param2.items()}
42
+ return fonction(self, *serialized, **param2)
43
+ return _dispatch_deserializer
44
+
45
+ def output_serializer(fonction: Callable) -> Callable:
46
+ """Decorator that serializes function output."""
47
+ def _dispatch_serializer(self, *params: Any, **param2: Any) -> Any:
48
+ return dispatch_serializer(fonction(self, *params, **param2))
49
+ return _dispatch_serializer
iop/_director.py ADDED
@@ -0,0 +1,301 @@
1
+ import asyncio
2
+ import datetime
3
+ import functools
4
+ import signal
5
+ from dataclasses import dataclass
6
+
7
+ from . import _iris
8
+ from ._dispatch import dispatch_deserializer, dispatch_serializer
9
+ from ._utils import _Utils
10
+
11
+ @dataclass
12
+ class SigintHandler():
13
+
14
+ sigint: bool = False
15
+ sigint_log: bool = False
16
+ log_only: bool = False
17
+
18
+ def signal_handler(self, signal, frame):
19
+ if self.sigint or self.log_only:
20
+ self.sigint_log = True
21
+ self.sigint = True
22
+
23
+ class _Director():
24
+ """ The Directorclass is used for nonpolling business services, that is, business services which are not automatically
25
+ called by the production framework (through the inbound adapter) at the call interval.
26
+ Instead these business services are created by a custom application by calling the Director.CreateBusinessService() method.
27
+ """
28
+
29
+ _bs = {}
30
+
31
+ def get_business_service(self,target,force_session_id=False):
32
+ """ get the business service
33
+ Parameters:
34
+ target: the name of the business service
35
+ force_session_id: if True, force the session id to be a new one
36
+ """
37
+ if target not in self._bs or self._bs[target] is None:
38
+ self._bs[target] = _Director.create_python_business_service(target)
39
+ if force_session_id:
40
+ self._bs[target].iris_handle._SessionId = ""
41
+ self._bs[target].iris_handle.ForceSessionId()
42
+ return self._bs[target]
43
+
44
+ @staticmethod
45
+ def CreateBusinessService(target):
46
+ """ DEPRECATED : use create_business_service
47
+ The CreateBusinessService() method initiates the specifiied business service.
48
+
49
+ Parameters:
50
+ connection: an IRISConnection object that specifies the connection to an IRIS instance for Java.
51
+ target: a string that specifies the name of the business service in the production definition.
52
+
53
+ Returns:
54
+ an object that contains an instance of IRISBusinessService
55
+ """
56
+ return _Director.create_business_service(target)
57
+
58
+ @staticmethod
59
+ def create_business_service(target):
60
+ """ The create_business_service() method initiates the specified business service.
61
+
62
+ Parameters:
63
+ connection: an IRISConnection object that specifies the connection to an IRIS instance for Java.
64
+ target: a string that specifies the name of the business service in the production definition.
65
+
66
+ Returns:
67
+ an object that contains an instance of IRISBusinessService
68
+ """
69
+ iris_object = _iris.get_iris().cls("IOP.Director").dispatchCreateBusinessService(target)
70
+ return iris_object
71
+
72
+ @staticmethod
73
+ def create_python_business_service(target):
74
+ """ The create_business_service() method initiates the specified business service.
75
+
76
+ Parameters:
77
+ connection: an IRISConnection object that specifies the connection to an IRIS instance for Java.
78
+ target: a string that specifies the name of the business service in the production definition.
79
+
80
+ Returns:
81
+ an object that contains an instance of IRISBusinessService
82
+ """
83
+ iris_object = _iris.get_iris().cls("IOP.Director").dispatchCreateBusinessService(target)
84
+ return iris_object.GetClass()
85
+
86
+ ### List of function to manage the production
87
+ ### start production
88
+ @staticmethod
89
+ def start_production_with_log(production_name=None):
90
+ if production_name is None or production_name == '':
91
+ production_name = _Director.get_default_production()
92
+ # create two async task
93
+ loop = asyncio.get_event_loop()
94
+ # add signal handler
95
+ handler = SigintHandler()
96
+ loop.add_signal_handler(signal.SIGINT, functools.partial(handler.signal_handler, signal.SIGINT, loop))
97
+ loop.run_until_complete(asyncio.gather(
98
+ _Director._start_production_async(production_name, handler),
99
+ _Director._log_production_async(handler)
100
+ ))
101
+ loop.close()
102
+
103
+ @staticmethod
104
+ async def _start_production_async(production_name:str, handler: SigintHandler):
105
+ _Director.start_production(production_name)
106
+ while True:
107
+ if handler.sigint:
108
+ _Director.stop_production()
109
+ break
110
+ await asyncio.sleep(1)
111
+
112
+ @staticmethod
113
+ def start_production(production_name=None):
114
+ if production_name is None or production_name == '':
115
+ production_name = _Director.get_default_production()
116
+ _iris.get_iris().cls('Ens.Director').StartProduction(production_name)
117
+
118
+ ### stop production
119
+ @staticmethod
120
+ def stop_production():
121
+ _iris.get_iris().cls('Ens.Director').StopProduction()
122
+
123
+ ### restart production
124
+ @staticmethod
125
+ def restart_production():
126
+ _iris.get_iris().cls('Ens.Director').RestartProduction()
127
+
128
+ ### shutdown production
129
+ @staticmethod
130
+ def shutdown_production():
131
+ _iris.get_iris().cls('Ens.Director').StopProduction(10,1)
132
+
133
+ ### update production
134
+ @staticmethod
135
+ def update_production():
136
+ _iris.get_iris().cls('Ens.Director').UpdateProduction()
137
+
138
+ ### list production
139
+ @staticmethod
140
+ def list_productions():
141
+ return _iris.get_iris().cls('IOP.Director').dispatchListProductions()
142
+
143
+ ### status production
144
+ @staticmethod
145
+ def status_production():
146
+ dikt = _iris.get_iris().cls('IOP.Director').StatusProduction()
147
+ if dikt['Production'] is None or dikt['Production'] == '':
148
+ dikt['Production'] = _Director.get_default_production()
149
+ return dikt
150
+
151
+ ### set default production
152
+ @staticmethod
153
+ def set_default_production(production_name=''):
154
+ #set ^Ens.Configuration("SuperUser","LastProduction")
155
+ glb = _iris.get_iris().gref("^Ens.Configuration")
156
+ glb['csp', "LastProduction"] = production_name
157
+
158
+ ### get default production
159
+ @staticmethod
160
+ def get_default_production():
161
+ glb = _iris.get_iris().gref("^Ens.Configuration")
162
+ default_production_name = glb['csp', "LastProduction"]
163
+ if default_production_name is None or default_production_name == '':
164
+ default_production_name = 'Not defined'
165
+ return default_production_name
166
+
167
+ @staticmethod
168
+ def format_log(row: list) -> str:
169
+ # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
170
+ # ID, ConfigName, Job, MessageId, SessionId, SourceClass, SourceMethod, Stack, Text, TimeLogged, TraceCat, Type
171
+ # yield all except stack aand tracecat
172
+ # in first position, the timelogged
173
+ # cast the result to string
174
+ # convert Type to its string value
175
+ # Assert,Error,Warning,Info,Trace,Alert
176
+ typ = row[11]
177
+ if typ == 1:
178
+ typ = 'Assert'
179
+ elif typ == 2:
180
+ typ = 'Error'
181
+ elif typ == 3:
182
+ typ = 'Warning'
183
+ elif typ == 4:
184
+ typ = 'Info'
185
+ elif typ == 5:
186
+ typ = 'Trace'
187
+ elif typ == 6:
188
+ typ = 'Alert'
189
+ return str(row[9]) + ' ' + typ + ' ' + str(row[1]) + ' ' + str(row[2]) + ' ' + str(row[3]) + ' ' + str(row[4]) + ' ' + str(row[5]) + ' ' + str(row[6]) + ' ' + str(row[8])
190
+
191
+ @staticmethod
192
+ def read_top_log(top) -> list:
193
+ sql = """
194
+ SELECT top ?
195
+ ID, ConfigName, Job, MessageId, SessionId, SourceClass, SourceMethod, Stack, Text, TimeLogged, TraceCat, Type
196
+ FROM Ens_Util.Log
197
+ order by id desc
198
+ """
199
+ result = []
200
+ stmt = _iris.get_iris().sql.prepare(sql)
201
+ rs = stmt.execute(top)
202
+ for row in rs:
203
+ result.append(_Director.format_log(row))
204
+ return result
205
+
206
+ @staticmethod
207
+ def read_log() -> list:
208
+ sql = """
209
+ SELECT
210
+ ID, ConfigName, Job, MessageId, SessionId, SourceClass, SourceMethod, Stack, Text, TimeLogged, TraceCat, Type
211
+ FROM Ens_Util.Log
212
+ where TimeLogged >= ?
213
+ order by id desc
214
+ """
215
+ result = []
216
+ stmt = _iris.get_iris().sql.prepare(sql)
217
+ time = datetime.datetime.now() - datetime.timedelta(seconds=1)
218
+ # convert to utc time
219
+ time = time.astimezone(datetime.timezone.utc)
220
+ rs = stmt.execute(time.isoformat(sep=' '))
221
+ for row in rs:
222
+ result.append(_Director.format_log(row))
223
+ return result
224
+
225
+ @staticmethod
226
+ async def _log_production_async(handler):
227
+ """ Log production
228
+ if ctrl+c is pressed, the log is stopped
229
+ """
230
+ while True:
231
+ for row in reversed(_Director.read_log()):
232
+ print(row)
233
+ if handler.sigint_log:
234
+ break
235
+ await asyncio.sleep(1)
236
+
237
+ @staticmethod
238
+ def log_production_top(top=10):
239
+ """
240
+ Log the top N logs of the production
241
+ Parameters:
242
+ top: the number of log to display
243
+ """
244
+ for row in reversed(_Director.read_top_log(top)):
245
+ print(row)
246
+
247
+ @staticmethod
248
+ def log_production():
249
+ """ Log production
250
+ if ctrl+c is pressed, the log is stopped
251
+ """
252
+ loop = asyncio.get_event_loop()
253
+ handler = SigintHandler(log_only=True)
254
+ loop.add_signal_handler(signal.SIGINT, functools.partial(handler.signal_handler, signal.SIGINT, loop))
255
+
256
+ for row in reversed(_Director.read_top_log( 10)):
257
+ print(row)
258
+
259
+ loop.run_until_complete(_Director._log_production_async(handler))
260
+ loop.close()
261
+
262
+ @staticmethod
263
+ def test_component(target,message=None,classname=None,body=None):
264
+ """
265
+ Test a component
266
+ Parameters:
267
+ target: the name of the component
268
+ classname: the name of the class to test
269
+ body: the body of the message
270
+ """
271
+ iris = _iris.get_iris()
272
+ if not message:
273
+ message = iris.cls('Ens.Request')._New()
274
+ if classname:
275
+ # if classname start with 'iris.' then create an iris object
276
+ if classname.startswith('iris.'):
277
+ # strip the iris. prefix
278
+ classname = classname[5:]
279
+ if body:
280
+ message = iris.cls(classname)._New(body)
281
+ else:
282
+ message = iris.cls(classname)._New()
283
+ # else create a python object
284
+ else:
285
+ # python message are casted to Grongier.PEX.Message
286
+ message = iris.cls("IOP.Message")._New()
287
+ message.classname = classname
288
+ if body:
289
+ message.json = body
290
+ else:
291
+ message.json = _Utils.string_to_stream("{}")
292
+ # serialize the message
293
+ serial_message = dispatch_serializer(message)
294
+ response = iris.cls('IOP.Utils').dispatchTestComponent(target,serial_message)
295
+ try:
296
+ deserialized_response = dispatch_deserializer(response)
297
+ except ImportError as e:
298
+ # can't import the class, return the string
299
+ deserialized_response = f'{response.classname} : {_Utils.stream_to_string(response.jstr)}'
300
+ return deserialized_response
301
+
iop/_dispatch.py ADDED
@@ -0,0 +1,136 @@
1
+ from inspect import signature, Parameter
2
+ from typing import Any, List, Tuple, Callable
3
+
4
+ from ._serialization import serialize_message, serialize_pickle_message, deserialize_message, deserialize_pickle_message, serialize_message_generator, serialize_pickle_message_generator
5
+ from ._message_validator import is_message_instance, is_pickle_message_instance, is_iris_object_instance
6
+
7
+ def dispatch_serializer(message: Any, is_generator: bool = False) -> Any:
8
+ """Serializes the message based on its type.
9
+
10
+ Args:
11
+ message: The message to serialize
12
+
13
+ Returns:
14
+ The serialized message
15
+
16
+ Raises:
17
+ TypeError: If message is invalid type
18
+ """
19
+ if message is not None:
20
+ if is_message_instance(message):
21
+ if is_generator:
22
+ return serialize_message_generator(message)
23
+ return serialize_message(message)
24
+ elif is_pickle_message_instance(message):
25
+ if is_generator:
26
+ return serialize_pickle_message_generator(message)
27
+ return serialize_pickle_message(message)
28
+ elif is_iris_object_instance(message):
29
+ return message
30
+
31
+ if message == "" or message is None:
32
+ return message
33
+
34
+ if hasattr(message, '__iter__'):
35
+ raise TypeError("You may have tried to invoke a generator function without using the 'send_generator_request' method. Please use that method to handle generator functions.")
36
+
37
+ raise TypeError("The message must be an instance of a class that is a subclass of Message or IRISObject %Persistent class.")
38
+
39
+ def dispatch_deserializer(serial: Any) -> Any:
40
+ """Deserializes the message based on its type.
41
+
42
+ Args:
43
+ serial: The serialized message
44
+
45
+ Returns:
46
+ The deserialized message
47
+ """
48
+ if (
49
+ serial is not None
50
+ and type(serial).__module__.startswith('iris')
51
+ and (
52
+ serial._IsA("IOP.Message")
53
+ or serial._IsA("Grongier.PEX.Message")
54
+ )
55
+ ):
56
+ return deserialize_message(serial)
57
+ elif (
58
+ serial is not None
59
+ and type(serial).__module__.startswith('iris')
60
+ and (
61
+ serial._IsA("IOP.PickleMessage")
62
+ or serial._IsA("Grongier.PEX.PickleMessage")
63
+ )
64
+ ):
65
+ return deserialize_pickle_message(serial)
66
+ else:
67
+ return serial
68
+
69
+ def dispach_message(host: Any, request: Any) -> Any:
70
+ """Dispatches the message to the appropriate method.
71
+
72
+ Args:
73
+ request: The request object
74
+
75
+ Returns:
76
+ The response object
77
+ """
78
+ call = 'on_message'
79
+
80
+ module = request.__class__.__module__
81
+ classname = request.__class__.__name__
82
+
83
+ for msg, method in host.DISPATCH:
84
+ if msg == module + "." + classname:
85
+ call = method
86
+
87
+ return getattr(host, call)(request)
88
+
89
+ def create_dispatch(host: Any) -> None:
90
+ """Creates a dispatch table mapping class names to their handler methods.
91
+ The dispatch table consists of tuples of (fully_qualified_class_name, method_name).
92
+ Only methods that take a single typed parameter are considered as handlers.
93
+ """
94
+ if len(host.DISPATCH) > 0:
95
+ return
96
+
97
+ for method_name in get_callable_methods(host):
98
+ handler_info = get_handler_info(host, method_name)
99
+ if handler_info:
100
+ host.DISPATCH.append(handler_info)
101
+
102
+ def get_callable_methods(host: Any) -> List[str]:
103
+ """Returns a list of callable method names that don't start with underscore."""
104
+ return [
105
+ func for func in dir(host)
106
+ if callable(getattr(host, func)) and not func.startswith("_")
107
+ ]
108
+
109
+ def get_handler_info(host: Any, method_name: str) -> Tuple[str, str] | None:
110
+ """Analyzes a method to determine if it's a valid message handler.
111
+ Returns a tuple of (fully_qualified_class_name, method_name) if valid,
112
+ None otherwise.
113
+ """
114
+ try:
115
+ params = signature(getattr(host, method_name)).parameters
116
+ if len(params) != 1:
117
+ return None
118
+
119
+ param: Parameter = next(iter(params.values()))
120
+ annotation = param.annotation
121
+
122
+ if isinstance(annotation, str):
123
+ # return it as is, assuming it's a fully qualified class name
124
+ return annotation, method_name
125
+
126
+ if is_iris_object_instance(annotation):
127
+ return f"{type(annotation).__module__}.{type(annotation).__name__}", method_name
128
+
129
+ if annotation == Parameter.empty or not isinstance(annotation, type):
130
+
131
+ return None
132
+
133
+ return f"{annotation.__module__}.{annotation.__name__}", method_name
134
+
135
+ except ValueError:
136
+ return None