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/_common.py
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import dataclasses
|
|
3
|
+
import inspect
|
|
4
|
+
import iris
|
|
5
|
+
import traceback
|
|
6
|
+
from typing import Any, ClassVar, Dict, List, Optional, Tuple, Type
|
|
7
|
+
from iop._log_manager import LogManager
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
class _Common(metaclass=abc.ABCMeta):
|
|
11
|
+
"""Base class that defines common methods for all component types.
|
|
12
|
+
|
|
13
|
+
Provides core functionality like initialization, teardown, connection handling
|
|
14
|
+
and message type checking that is shared across component types.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
INFO_URL: ClassVar[str]
|
|
18
|
+
ICON_URL: ClassVar[str]
|
|
19
|
+
iris_handle: Any = None
|
|
20
|
+
log_to_console: bool = False
|
|
21
|
+
|
|
22
|
+
def on_init(self) -> None:
|
|
23
|
+
"""Initialize component when started.
|
|
24
|
+
|
|
25
|
+
Called when component starts. Use to initialize required structures.
|
|
26
|
+
"""
|
|
27
|
+
return self.OnInit()
|
|
28
|
+
|
|
29
|
+
def on_tear_down(self) -> None:
|
|
30
|
+
"""Clean up component before termination.
|
|
31
|
+
|
|
32
|
+
Called before component terminates. Use to free resources.
|
|
33
|
+
"""
|
|
34
|
+
return self.OnTearDown()
|
|
35
|
+
|
|
36
|
+
def on_connected(self) -> None:
|
|
37
|
+
"""Handle component connection/reconnection.
|
|
38
|
+
|
|
39
|
+
Called when component connects or reconnects after disconnection.
|
|
40
|
+
Use to initialize connection-dependent structures.
|
|
41
|
+
"""
|
|
42
|
+
return self.OnConnected()
|
|
43
|
+
|
|
44
|
+
def _dispatch_on_connected(self, host_object: Any) -> None:
|
|
45
|
+
"""Internal dispatch for connection handling."""
|
|
46
|
+
self.on_connected()
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
def _dispatch_on_init(self, host_object: Any) -> None:
|
|
50
|
+
"""Internal dispatch for initialization."""
|
|
51
|
+
self.on_init()
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
def _dispatch_on_tear_down(self, host_object: Any) -> None:
|
|
55
|
+
"""Internal dispatch for teardown."""
|
|
56
|
+
self.on_tear_down()
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
def _set_iris_handles(self, handle_current: Any, handle_partner: Any) -> None:
|
|
60
|
+
"""Internal method to set IRIS handles."""
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def _is_message_instance(cls, obj: Any) -> bool:
|
|
65
|
+
"""Check if object is a valid Message instance.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
obj: Object to check
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
True if object is a Message instance
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
TypeError: If object is Message class but not a dataclass
|
|
75
|
+
"""
|
|
76
|
+
if cls._is_message_class(type(obj)):
|
|
77
|
+
if not dataclasses.is_dataclass(obj):
|
|
78
|
+
raise TypeError(f"{type(obj).__module__}.{type(obj).__qualname__} must be a dataclass")
|
|
79
|
+
return True
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def _is_pickle_message_instance(cls, obj: Any) -> bool:
|
|
84
|
+
"""Check if object is a PickleMessage instance."""
|
|
85
|
+
if cls._is_pickel_message_class(type(obj)):
|
|
86
|
+
return True
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def _is_iris_object_instance(cls, obj: Any) -> bool:
|
|
91
|
+
"""Check if object is an IRIS persistent object."""
|
|
92
|
+
if (obj is not None and type(obj).__module__.find('iris') == 0) and obj._IsA("%Persistent"):
|
|
93
|
+
return True
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def _is_message_class(cls, klass: Type) -> bool:
|
|
98
|
+
name = klass.__module__ + '.' + klass.__qualname__
|
|
99
|
+
if name == "iop.Message" or name == "grongier.pex.Message":
|
|
100
|
+
return True
|
|
101
|
+
for c in klass.__bases__:
|
|
102
|
+
if cls._is_message_class(c):
|
|
103
|
+
return True
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def _is_pickel_message_class(cls, klass: Type) -> bool:
|
|
108
|
+
name = klass.__module__ + '.' + klass.__qualname__
|
|
109
|
+
if name == "iop.PickleMessage" or name == "grongier.pex.PickleMessage":
|
|
110
|
+
return True
|
|
111
|
+
for c in klass.__bases__:
|
|
112
|
+
if cls._is_pickel_message_class(c):
|
|
113
|
+
return True
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def _get_info(cls) -> List[str]:
|
|
118
|
+
"""Get component configuration information.
|
|
119
|
+
|
|
120
|
+
Returns information used to display in Production config UI including:
|
|
121
|
+
- Superclass
|
|
122
|
+
- Description
|
|
123
|
+
- InfoURL
|
|
124
|
+
- IconURL
|
|
125
|
+
- Adapter type (for Business Services/Operations)
|
|
126
|
+
"""
|
|
127
|
+
ret = []
|
|
128
|
+
desc = ""
|
|
129
|
+
info_url = ""
|
|
130
|
+
icon_url = ""
|
|
131
|
+
super_class = ""
|
|
132
|
+
adapter = ""
|
|
133
|
+
try:
|
|
134
|
+
# Get tuple of the class's base classes and loop through them until we find one of the PEX component classes
|
|
135
|
+
classes = inspect.getmro(cls)
|
|
136
|
+
for cl in classes:
|
|
137
|
+
classname = str(cl)[7:-1]
|
|
138
|
+
if classname in ["'iop.BusinessService'","'iop.BusinessOperation'","'iop.DuplexOperation'","'iop.DuplexService'",
|
|
139
|
+
"'grongier.pex.BusinessService'","'grongier.pex.BusinessOperation'","'grongier.pex.DuplexOperation'","'grongier.pex.DuplexService'"] :
|
|
140
|
+
# Remove the apostrophes and set as super_class, then find if it uses an adapter
|
|
141
|
+
super_class = classname[1:-1]
|
|
142
|
+
adapter = cls.get_adapter_type()
|
|
143
|
+
if adapter is None:
|
|
144
|
+
adapter = cls.getAdapterType()
|
|
145
|
+
break
|
|
146
|
+
elif classname in ["'iop.BusinessProcess'","'iop.DuplexProcess'","'iop.InboundAdapter'","'iop.OutboundAdapter'",
|
|
147
|
+
"'grongier.pex.BusinessProcess'","'grongier.pex.DuplexProcess'","'grongier.pex.InboundAdapter'","'grongier.pex.OutboundAdapter'"] :
|
|
148
|
+
# Remove the apostrophes and set as super_class
|
|
149
|
+
super_class = classname[1:-1]
|
|
150
|
+
break
|
|
151
|
+
|
|
152
|
+
if ""==super_class:
|
|
153
|
+
return ""
|
|
154
|
+
ret.append(super_class)
|
|
155
|
+
|
|
156
|
+
# Get the class documentation, if any
|
|
157
|
+
class_desc = inspect.getdoc(cls)
|
|
158
|
+
super_desc = inspect.getdoc(classes[1])
|
|
159
|
+
if class_desc!=super_desc:
|
|
160
|
+
desc = class_desc
|
|
161
|
+
ret.append(str(desc))
|
|
162
|
+
|
|
163
|
+
info_url = inspect.getattr_static(cls,"INFO_URL","")
|
|
164
|
+
icon_url = inspect.getattr_static(cls,"ICON_URL","")
|
|
165
|
+
|
|
166
|
+
ret.append(info_url)
|
|
167
|
+
ret.append(icon_url)
|
|
168
|
+
|
|
169
|
+
if ""!=adapter:
|
|
170
|
+
ret.append(adapter)
|
|
171
|
+
except Exception as e:
|
|
172
|
+
raise e
|
|
173
|
+
return ret
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def _get_properties(cls) -> List[List[Any]]:
|
|
177
|
+
"""Get component properties for Production configuration.
|
|
178
|
+
|
|
179
|
+
Returns list of property definitions containing:
|
|
180
|
+
- Property name
|
|
181
|
+
- Data type
|
|
182
|
+
- Default value
|
|
183
|
+
- Required flag
|
|
184
|
+
- Category
|
|
185
|
+
- Description
|
|
186
|
+
|
|
187
|
+
Only includes non-private class attributes and properties.
|
|
188
|
+
"""
|
|
189
|
+
ret = []
|
|
190
|
+
try:
|
|
191
|
+
# getmembers() returns all the members of an obj
|
|
192
|
+
for member in inspect.getmembers(cls):
|
|
193
|
+
# remove private and protected functions
|
|
194
|
+
if not member[0].startswith('_'):
|
|
195
|
+
# remove other methods and functions
|
|
196
|
+
if not inspect.ismethod(member[1]) and not inspect.isfunction(member[1]) and not inspect.isclass(member[1]):
|
|
197
|
+
if member[0] not in ('INFO_URL','ICON_URL','PERSISTENT_PROPERTY_LIST') :
|
|
198
|
+
name = member[0]
|
|
199
|
+
req = 0
|
|
200
|
+
cat = "Additional"
|
|
201
|
+
desc = ""
|
|
202
|
+
# get value, set to "" if None or a @property
|
|
203
|
+
val = member[1]
|
|
204
|
+
if isinstance(val,property) or (val is None):
|
|
205
|
+
val = ""
|
|
206
|
+
dt = str(type(val))[8:-2]
|
|
207
|
+
# get datatype from attribute definition, default to String
|
|
208
|
+
data_type = {'int':'Integer','float':'Numeric','complex':'Numeric','bool':'Boolean'}.get(dt,'String')
|
|
209
|
+
# if the user has created a attr_info function, then check the annotation on the return from that for more information about this attribute
|
|
210
|
+
if hasattr(cls,name + '_info') :
|
|
211
|
+
func = getattr(cls,name+'_info')
|
|
212
|
+
if callable(func) :
|
|
213
|
+
annotations = func.__annotations__['return']
|
|
214
|
+
if annotations is not None:
|
|
215
|
+
if bool(annotations.get("ExcludeFromSettings")) :
|
|
216
|
+
# don't add this attribute to the settings list
|
|
217
|
+
continue
|
|
218
|
+
req = bool(annotations.get("IsRequired"))
|
|
219
|
+
cat = annotations.get("Category","Additional")
|
|
220
|
+
desc = annotations.get("Description")
|
|
221
|
+
dt = annotations.get("DataType")
|
|
222
|
+
# only override DataType found
|
|
223
|
+
if (dt is not None) and ("" != dt):
|
|
224
|
+
data_type = {int:'Integer',float:'Number',complex:'Number',bool:'Boolean',str:'String'}.get(dt,str(dt))
|
|
225
|
+
default = func()
|
|
226
|
+
if default is not None:
|
|
227
|
+
val = default
|
|
228
|
+
# create list of information for this specific property
|
|
229
|
+
info = []
|
|
230
|
+
info.append(name) # Name
|
|
231
|
+
info.append(data_type) # DataType
|
|
232
|
+
info.append(val) # Default Value
|
|
233
|
+
info.append(req) # Required
|
|
234
|
+
info.append(cat) # Category
|
|
235
|
+
info.append(desc) # Description
|
|
236
|
+
# add this property to the list
|
|
237
|
+
ret.append(info)
|
|
238
|
+
except:
|
|
239
|
+
pass
|
|
240
|
+
return ret
|
|
241
|
+
|
|
242
|
+
def _log(self) -> Tuple[str, Optional[str]]:
|
|
243
|
+
"""Get class and method name for logging.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Tuple of (class_name, method_name)
|
|
247
|
+
"""
|
|
248
|
+
current_class = self.__class__.__name__
|
|
249
|
+
current_method = None
|
|
250
|
+
try:
|
|
251
|
+
frame = traceback.extract_stack()[-4]
|
|
252
|
+
current_method = frame.name
|
|
253
|
+
except:
|
|
254
|
+
pass
|
|
255
|
+
return current_class, current_method
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def logger(self) -> logging.Logger:
|
|
259
|
+
"""Get a logger instance for this component.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Logger configured for IRIS integration
|
|
263
|
+
"""
|
|
264
|
+
class_name, method_name = self._log()
|
|
265
|
+
return LogManager.get_logger(class_name, method_name, self.log_to_console)
|
|
266
|
+
|
|
267
|
+
def trace(self, message: str, to_console: Optional[bool] = None) -> None:
|
|
268
|
+
"""Write trace log entry.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
message: Message to log
|
|
272
|
+
to_console: If True, log to console instead of IRIS
|
|
273
|
+
"""
|
|
274
|
+
self.logger.debug(message, extra={'to_console': to_console})
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def log_info(self, message: str, to_console: Optional[bool] = None) -> None:
|
|
278
|
+
"""Write info log entry.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
message: Message to log
|
|
282
|
+
to_console: If True, log to console instead of IRIS
|
|
283
|
+
"""
|
|
284
|
+
self.logger.info(message, extra={'to_console': to_console})
|
|
285
|
+
|
|
286
|
+
def log_alert(self, message: str, to_console: Optional[bool] = None) -> None:
|
|
287
|
+
"""Write alert log entry.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
message: Message to log
|
|
291
|
+
to_console: If True, log to console instead of IRIS
|
|
292
|
+
"""
|
|
293
|
+
self.logger.critical(message, extra={'to_console': to_console})
|
|
294
|
+
|
|
295
|
+
def log_warning(self, message: str, to_console: Optional[bool] = None) -> None:
|
|
296
|
+
"""Write warning log entry.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
message: Message to log
|
|
300
|
+
to_console: If True, log to console instead of IRIS
|
|
301
|
+
"""
|
|
302
|
+
self.logger.warning(message, extra={'to_console': to_console})
|
|
303
|
+
|
|
304
|
+
def log_error(self, message: str, to_console: Optional[bool] = None) -> None:
|
|
305
|
+
"""Write error log entry.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
message: Message to log
|
|
309
|
+
to_console: If True, log to console instead of IRIS
|
|
310
|
+
"""
|
|
311
|
+
self.logger.error(message, extra={'to_console': to_console})
|
|
312
|
+
|
|
313
|
+
def log_assert(self, message: str) -> None:
|
|
314
|
+
"""Write a log entry of type "assert". Log entries can be viewed in the management portal.
|
|
315
|
+
|
|
316
|
+
Parameters:
|
|
317
|
+
message: a string that is written to the log.
|
|
318
|
+
"""
|
|
319
|
+
current_class, current_method = self._log()
|
|
320
|
+
iris.cls("Ens.Util.Log").LogAssert(current_class, current_method, message)
|
|
321
|
+
|
|
322
|
+
def LOGINFO(self, message: str) -> None:
|
|
323
|
+
"""DEPRECATED: Use log_info."""
|
|
324
|
+
return self.log_info(message=message)
|
|
325
|
+
|
|
326
|
+
def LOGALERT(self, message: str) -> None:
|
|
327
|
+
"""DEPRECATED: Use log_alert."""
|
|
328
|
+
return self.log_alert(message)
|
|
329
|
+
|
|
330
|
+
def LOGWARNING(self, message: str) -> None:
|
|
331
|
+
"""DEPRECATED: Use log_warning."""
|
|
332
|
+
return self.log_warning(message)
|
|
333
|
+
|
|
334
|
+
def LOGERROR(self, message: str) -> None:
|
|
335
|
+
"""DEPRECATED: Use log_error."""
|
|
336
|
+
return self.log_error(message)
|
|
337
|
+
|
|
338
|
+
def LOGASSERT(self, message: str) -> None:
|
|
339
|
+
"""DEPRECATED: Use log_assert."""
|
|
340
|
+
return self.log_assert(message)
|
|
341
|
+
|
|
342
|
+
def OnInit(self) -> None:
|
|
343
|
+
"""DEPRECATED: Use on_init."""
|
|
344
|
+
return
|
|
345
|
+
|
|
346
|
+
def OnTearDown(self) -> None:
|
|
347
|
+
"""DEPRECATED: Use on_tear_down."""
|
|
348
|
+
return
|
|
349
|
+
|
|
350
|
+
def OnConnected(self) -> None:
|
|
351
|
+
"""DEPRECATED: Use on_connected."""
|
|
352
|
+
return
|
iop/_director.py
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import datetime
|
|
3
|
+
import functools
|
|
4
|
+
import iris
|
|
5
|
+
import intersystems_iris.dbapi._DBAPI as irisdbapi
|
|
6
|
+
import signal
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from iop._business_host import _BusinessHost
|
|
10
|
+
from iop._utils import _Utils
|
|
11
|
+
|
|
12
|
+
class _Director():
|
|
13
|
+
""" The Directorclass is used for nonpolling business services, that is, business services which are not automatically
|
|
14
|
+
called by the production framework (through the inbound adapter) at the call interval.
|
|
15
|
+
Instead these business services are created by a custom application by calling the Director.CreateBusinessService() method.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
_bs = {}
|
|
19
|
+
|
|
20
|
+
def get_business_service(self,target,force_session_id=False):
|
|
21
|
+
""" get the business service
|
|
22
|
+
Parameters:
|
|
23
|
+
target: the name of the business service
|
|
24
|
+
force_session_id: if True, force the session id to be a new one
|
|
25
|
+
"""
|
|
26
|
+
if target not in self._bs or self._bs[target] is None:
|
|
27
|
+
self._bs[target] = _Director.create_python_business_service(target)
|
|
28
|
+
if force_session_id:
|
|
29
|
+
self._bs[target].iris_handle._SessionId = ""
|
|
30
|
+
self._bs[target].iris_handle.ForceSessionId()
|
|
31
|
+
return self._bs[target]
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def CreateBusinessService(target):
|
|
35
|
+
""" DEPRECATED : use create_business_service
|
|
36
|
+
The CreateBusinessService() method initiates the specifiied business service.
|
|
37
|
+
|
|
38
|
+
Parameters:
|
|
39
|
+
connection: an IRISConnection object that specifies the connection to an IRIS instance for Java.
|
|
40
|
+
target: a string that specifies the name of the business service in the production definition.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
an object that contains an instance of IRISBusinessService
|
|
44
|
+
"""
|
|
45
|
+
return _Director.create_business_service(target)
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def create_business_service(target):
|
|
49
|
+
""" The create_business_service() method initiates the specified business service.
|
|
50
|
+
|
|
51
|
+
Parameters:
|
|
52
|
+
connection: an IRISConnection object that specifies the connection to an IRIS instance for Java.
|
|
53
|
+
target: a string that specifies the name of the business service in the production definition.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
an object that contains an instance of IRISBusinessService
|
|
57
|
+
"""
|
|
58
|
+
iris_object = iris.cls("IOP.Director").dispatchCreateBusinessService(target)
|
|
59
|
+
return iris_object
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def create_python_business_service(target):
|
|
63
|
+
""" The create_business_service() method initiates the specified business service.
|
|
64
|
+
|
|
65
|
+
Parameters:
|
|
66
|
+
connection: an IRISConnection object that specifies the connection to an IRIS instance for Java.
|
|
67
|
+
target: a string that specifies the name of the business service in the production definition.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
an object that contains an instance of IRISBusinessService
|
|
71
|
+
"""
|
|
72
|
+
iris_object = iris.cls("IOP.Director").dispatchCreateBusinessService(target)
|
|
73
|
+
return iris_object.GetClass()
|
|
74
|
+
|
|
75
|
+
### List of function to manage the production
|
|
76
|
+
### start production
|
|
77
|
+
@staticmethod
|
|
78
|
+
def start_production_with_log(production_name=None):
|
|
79
|
+
if production_name is None or production_name == '':
|
|
80
|
+
production_name = _Director.get_default_production()
|
|
81
|
+
# create two async task
|
|
82
|
+
loop = asyncio.get_event_loop()
|
|
83
|
+
# add signal handler
|
|
84
|
+
handler = SigintHandler()
|
|
85
|
+
loop.add_signal_handler(signal.SIGINT, functools.partial(handler.signal_handler, signal.SIGINT, loop))
|
|
86
|
+
loop.run_until_complete(asyncio.gather(
|
|
87
|
+
_Director._start_production_async(production_name, handler),
|
|
88
|
+
_Director._log_production_async(handler)
|
|
89
|
+
))
|
|
90
|
+
loop.close()
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
async def _start_production_async(production_name=None, handler=None):
|
|
94
|
+
_Director.start_production(production_name)
|
|
95
|
+
while True:
|
|
96
|
+
if handler.sigint:
|
|
97
|
+
_Director.stop_production()
|
|
98
|
+
break
|
|
99
|
+
await asyncio.sleep(1)
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def start_production(production_name=None):
|
|
103
|
+
if production_name is None or production_name == '':
|
|
104
|
+
production_name = _Director.get_default_production()
|
|
105
|
+
iris.cls('Ens.Director').StartProduction(production_name)
|
|
106
|
+
|
|
107
|
+
### stop production
|
|
108
|
+
@staticmethod
|
|
109
|
+
def stop_production():
|
|
110
|
+
iris.cls('Ens.Director').StopProduction()
|
|
111
|
+
|
|
112
|
+
### restart production
|
|
113
|
+
@staticmethod
|
|
114
|
+
def restart_production():
|
|
115
|
+
iris.cls('Ens.Director').RestartProduction()
|
|
116
|
+
|
|
117
|
+
### shutdown production
|
|
118
|
+
@staticmethod
|
|
119
|
+
def shutdown_production():
|
|
120
|
+
iris.cls('Ens.Director').StopProduction(10,1)
|
|
121
|
+
|
|
122
|
+
### update production
|
|
123
|
+
@staticmethod
|
|
124
|
+
def update_production():
|
|
125
|
+
iris.cls('Ens.Director').UpdateProduction()
|
|
126
|
+
|
|
127
|
+
### list production
|
|
128
|
+
@staticmethod
|
|
129
|
+
def list_productions():
|
|
130
|
+
return iris.cls('IOP.Director').dispatchListProductions()
|
|
131
|
+
|
|
132
|
+
### status production
|
|
133
|
+
@staticmethod
|
|
134
|
+
def status_production():
|
|
135
|
+
dikt = iris.cls('IOP.Director').StatusProduction()
|
|
136
|
+
if dikt['Production'] is None or dikt['Production'] == '':
|
|
137
|
+
dikt['Production'] = _Director.get_default_production()
|
|
138
|
+
return dikt
|
|
139
|
+
|
|
140
|
+
### set default production
|
|
141
|
+
@staticmethod
|
|
142
|
+
def set_default_production(production_name=''):
|
|
143
|
+
#set ^Ens.Configuration("SuperUser","LastProduction")
|
|
144
|
+
glb = iris.gref("^Ens.Configuration")
|
|
145
|
+
glb['csp', "LastProduction"] = production_name
|
|
146
|
+
|
|
147
|
+
### get default production
|
|
148
|
+
@staticmethod
|
|
149
|
+
def get_default_production():
|
|
150
|
+
glb = iris.gref("^Ens.Configuration")
|
|
151
|
+
default_production_name = glb['csp', "LastProduction"]
|
|
152
|
+
if default_production_name is None or default_production_name == '':
|
|
153
|
+
default_production_name = 'Not defined'
|
|
154
|
+
return default_production_name
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def format_log(row: list) -> str:
|
|
158
|
+
# 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
|
|
159
|
+
# ID, ConfigName, Job, MessageId, SessionId, SourceClass, SourceMethod, Stack, Text, TimeLogged, TraceCat, Type
|
|
160
|
+
# yield all except stack aand tracecat
|
|
161
|
+
# in first position, the timelogged
|
|
162
|
+
# cast the result to string
|
|
163
|
+
# convert Type to its string value
|
|
164
|
+
# Assert,Error,Warning,Info,Trace,Alert
|
|
165
|
+
typ = row[11]
|
|
166
|
+
if typ == 1:
|
|
167
|
+
typ = 'Assert'
|
|
168
|
+
elif typ == 2:
|
|
169
|
+
typ = 'Error'
|
|
170
|
+
elif typ == 3:
|
|
171
|
+
typ = 'Warning'
|
|
172
|
+
elif typ == 4:
|
|
173
|
+
typ = 'Info'
|
|
174
|
+
elif typ == 5:
|
|
175
|
+
typ = 'Trace'
|
|
176
|
+
elif typ == 6:
|
|
177
|
+
typ = 'Alert'
|
|
178
|
+
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])
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def read_top_log(cursor,top) -> list:
|
|
182
|
+
sql = """
|
|
183
|
+
SELECT top ?
|
|
184
|
+
ID, ConfigName, Job, MessageId, SessionId, SourceClass, SourceMethod, Stack, Text, TimeLogged, TraceCat, Type
|
|
185
|
+
FROM Ens_Util.Log
|
|
186
|
+
order by id desc
|
|
187
|
+
"""
|
|
188
|
+
result = []
|
|
189
|
+
cursor.execute(sql, top)
|
|
190
|
+
for row in cursor:
|
|
191
|
+
result.append(_Director.format_log(row))
|
|
192
|
+
return result
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def read_log(cursor) -> list:
|
|
196
|
+
sql = """
|
|
197
|
+
SELECT
|
|
198
|
+
ID, ConfigName, Job, MessageId, SessionId, SourceClass, SourceMethod, Stack, Text, TimeLogged, TraceCat, Type
|
|
199
|
+
FROM Ens_Util.Log
|
|
200
|
+
where TimeLogged >= ?
|
|
201
|
+
order by id desc
|
|
202
|
+
"""
|
|
203
|
+
result = []
|
|
204
|
+
cursor.execute(sql, (datetime.datetime.now() - datetime.timedelta(seconds=1),))
|
|
205
|
+
for row in cursor:
|
|
206
|
+
result.append(_Director.format_log(row))
|
|
207
|
+
return result
|
|
208
|
+
|
|
209
|
+
@staticmethod
|
|
210
|
+
async def _log_production_async(handler):
|
|
211
|
+
""" Log production
|
|
212
|
+
if ctrl+c is pressed, the log is stopped
|
|
213
|
+
"""
|
|
214
|
+
with irisdbapi.connect(embedded=True) as conn:
|
|
215
|
+
with conn.cursor() as cursor:
|
|
216
|
+
while True:
|
|
217
|
+
for row in reversed(_Director.read_log(cursor)):
|
|
218
|
+
print(row)
|
|
219
|
+
if handler.sigint_log:
|
|
220
|
+
break
|
|
221
|
+
await asyncio.sleep(1)
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
def log_production_top(top):
|
|
225
|
+
"""
|
|
226
|
+
Log the top N logs of the production
|
|
227
|
+
Parameters:
|
|
228
|
+
top: the number of log to display
|
|
229
|
+
"""
|
|
230
|
+
with irisdbapi.connect(embedded=True) as conn:
|
|
231
|
+
with conn.cursor() as cursor:
|
|
232
|
+
for row in reversed(_Director.read_top_log(cursor, top)):
|
|
233
|
+
print(row)
|
|
234
|
+
|
|
235
|
+
@staticmethod
|
|
236
|
+
def log_production():
|
|
237
|
+
""" Log production
|
|
238
|
+
if ctrl+c is pressed, the log is stopped
|
|
239
|
+
"""
|
|
240
|
+
loop = asyncio.get_event_loop()
|
|
241
|
+
handler = SigintHandler(log_only=True)
|
|
242
|
+
loop.add_signal_handler(signal.SIGINT, functools.partial(handler.signal_handler, signal.SIGINT, loop))
|
|
243
|
+
with irisdbapi.connect(embedded=True) as conn:
|
|
244
|
+
with conn.cursor() as cursor:
|
|
245
|
+
for row in reversed(_Director.read_top_log(cursor, 10)):
|
|
246
|
+
print(row)
|
|
247
|
+
loop.run_until_complete(_Director._log_production_async(handler))
|
|
248
|
+
loop.close()
|
|
249
|
+
|
|
250
|
+
@staticmethod
|
|
251
|
+
def test_component(target,message=None,classname=None,body=None):
|
|
252
|
+
"""
|
|
253
|
+
Test a component
|
|
254
|
+
Parameters:
|
|
255
|
+
target: the name of the component
|
|
256
|
+
classname: the name of the class to test
|
|
257
|
+
body: the body of the message
|
|
258
|
+
"""
|
|
259
|
+
if not message:
|
|
260
|
+
message = iris.cls('Ens.Request')._New()
|
|
261
|
+
if classname:
|
|
262
|
+
# if classname start with 'iris.' then create an iris object
|
|
263
|
+
if classname.startswith('iris.'):
|
|
264
|
+
# strip the iris. prefix
|
|
265
|
+
classname = classname[5:]
|
|
266
|
+
if body:
|
|
267
|
+
message = iris.cls(classname)._New(body)
|
|
268
|
+
else:
|
|
269
|
+
message = iris.cls(classname)._New()
|
|
270
|
+
# else create a python object
|
|
271
|
+
else:
|
|
272
|
+
# python message are casted to Grongier.PEX.Message
|
|
273
|
+
message = iris.cls("IOP.Message")._New()
|
|
274
|
+
message.classname = classname
|
|
275
|
+
if body:
|
|
276
|
+
message.json = body
|
|
277
|
+
else:
|
|
278
|
+
message.json = _Utils.string_to_stream("{}")
|
|
279
|
+
# serialize the message
|
|
280
|
+
business_host = _BusinessHost()
|
|
281
|
+
serial_message = business_host._dispatch_serializer(message)
|
|
282
|
+
response = iris.cls('IOP.Utils').dispatchTestComponent(target,serial_message)
|
|
283
|
+
try:
|
|
284
|
+
deserialized_response = business_host._dispatch_deserializer(response)
|
|
285
|
+
except ImportError as e:
|
|
286
|
+
# can't import the class, return the string
|
|
287
|
+
deserialized_response = f'{response.classname} : {_Utils.stream_to_string(response.jstr)}'
|
|
288
|
+
return deserialized_response
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@dataclass
|
|
292
|
+
class SigintHandler():
|
|
293
|
+
|
|
294
|
+
sigint: bool = False
|
|
295
|
+
sigint_log: bool = False
|
|
296
|
+
log_only: bool = False
|
|
297
|
+
|
|
298
|
+
def signal_handler(self, signal, frame):
|
|
299
|
+
if self.sigint or self.log_only:
|
|
300
|
+
self.sigint_log = True
|
|
301
|
+
self.sigint = True
|