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.
- grongier/__init__.py +0 -0
- grongier/cls/Grongier/PEX/BusinessOperation.cls +8 -0
- grongier/cls/Grongier/PEX/BusinessProcess.cls +13 -0
- grongier/cls/Grongier/PEX/BusinessService.cls +8 -0
- grongier/cls/Grongier/PEX/Common.cls +10 -0
- grongier/cls/Grongier/PEX/Director.cls +10 -0
- grongier/cls/Grongier/PEX/Duplex/Operation.cls +4 -0
- grongier/cls/Grongier/PEX/Duplex/Process.cls +13 -0
- grongier/cls/Grongier/PEX/Duplex/Service.cls +4 -0
- grongier/cls/Grongier/PEX/InboundAdapter.cls +8 -0
- grongier/cls/Grongier/PEX/Message.cls +13 -0
- grongier/cls/Grongier/PEX/OutboundAdapter.cls +8 -0
- grongier/cls/Grongier/PEX/PickleMessage.cls +13 -0
- grongier/cls/Grongier/PEX/PrivateSession/Duplex.cls +8 -0
- grongier/cls/Grongier/PEX/PrivateSession/Message/Ack.cls +14 -0
- grongier/cls/Grongier/PEX/PrivateSession/Message/Poll.cls +14 -0
- grongier/cls/Grongier/PEX/PrivateSession/Message/Start.cls +14 -0
- grongier/cls/Grongier/PEX/PrivateSession/Message/Stop.cls +14 -0
- grongier/cls/Grongier/PEX/Test.cls +10 -0
- grongier/cls/Grongier/PEX/Utils.cls +10 -0
- grongier/cls/Grongier/Service/WSGI.cls +4 -0
- grongier/pex/__init__.py +24 -0
- grongier/pex/__main__.py +4 -0
- grongier/pex/_business_host.py +1 -0
- grongier/pex/_cli.py +4 -0
- grongier/pex/_common.py +1 -0
- grongier/pex/_director.py +1 -0
- grongier/pex/_utils.py +1 -0
- grongier/pex/wsgi/handlers.py +104 -0
- iop/__init__.py +25 -0
- iop/__main__.py +4 -0
- iop/_async_request.py +67 -0
- iop/_business_host.py +256 -0
- iop/_business_operation.py +75 -0
- iop/_business_process.py +224 -0
- iop/_business_service.py +63 -0
- iop/_cli.py +247 -0
- iop/_common.py +334 -0
- iop/_debugpy.py +187 -0
- iop/_decorators.py +49 -0
- iop/_director.py +301 -0
- iop/_dispatch.py +136 -0
- iop/_generator_request.py +30 -0
- iop/_inbound_adapter.py +34 -0
- iop/_iris.py +8 -0
- iop/_log_manager.py +100 -0
- iop/_message.py +40 -0
- iop/_message_validator.py +49 -0
- iop/_outbound_adapter.py +23 -0
- iop/_private_session_duplex.py +103 -0
- iop/_private_session_process.py +41 -0
- iop/_remote.py +91 -0
- iop/_serialization.py +199 -0
- iop/_utils.py +671 -0
- iop/cls/IOP/BusinessOperation.cls +35 -0
- iop/cls/IOP/BusinessProcess.cls +156 -0
- iop/cls/IOP/BusinessService.cls +40 -0
- iop/cls/IOP/Common.cls +569 -0
- iop/cls/IOP/Director.cls +70 -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/Generator/Message/Ack.cls +31 -0
- iop/cls/IOP/Generator/Message/Poll.cls +31 -0
- iop/cls/IOP/Generator/Message/Start.cls +15 -0
- iop/cls/IOP/Generator/Message/StartPickle.cls +15 -0
- iop/cls/IOP/Generator/Message/Stop.cls +32 -0
- iop/cls/IOP/InboundAdapter.cls +22 -0
- iop/cls/IOP/Message/JSONSchema.cls +125 -0
- iop/cls/IOP/Message.cls +754 -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 +31 -0
- iop/cls/IOP/PrivateSession/Message/Stop.cls +48 -0
- iop/cls/IOP/Projection.cls +49 -0
- iop/cls/IOP/Service/Remote/Handler.cls +30 -0
- iop/cls/IOP/Service/Remote/Rest/v1.cls +97 -0
- iop/cls/IOP/Service/WSGI.cls +310 -0
- iop/cls/IOP/Test.cls +85 -0
- iop/cls/IOP/Utils.cls +503 -0
- iop/cls/IOP/Wrapper.cls +58 -0
- iop/wsgi/handlers.py +104 -0
- iris_pex_embedded_python-3.5.5b4.dist-info/METADATA +91 -0
- iris_pex_embedded_python-3.5.5b4.dist-info/RECORD +91 -0
- iris_pex_embedded_python-3.5.5b4.dist-info/WHEEL +5 -0
- iris_pex_embedded_python-3.5.5b4.dist-info/entry_points.txt +2 -0
- iris_pex_embedded_python-3.5.5b4.dist-info/licenses/LICENSE +21 -0
- 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
|