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.

Files changed (85) hide show
  1. grongier/cls/Grongier/PEX/BusinessOperation.cls +1 -28
  2. grongier/cls/Grongier/PEX/BusinessProcess.cls +1 -100
  3. grongier/cls/Grongier/PEX/BusinessService.cls +1 -28
  4. grongier/cls/Grongier/PEX/Common.cls +1 -194
  5. grongier/cls/Grongier/PEX/Director.cls +1 -48
  6. grongier/cls/Grongier/PEX/Duplex/Operation.cls +1 -26
  7. grongier/cls/Grongier/PEX/Duplex/Process.cls +1 -217
  8. grongier/cls/Grongier/PEX/Duplex/Service.cls +1 -6
  9. grongier/cls/Grongier/PEX/InboundAdapter.cls +1 -15
  10. grongier/cls/Grongier/PEX/Message.cls +1 -116
  11. grongier/cls/Grongier/PEX/OutboundAdapter.cls +1 -29
  12. grongier/cls/Grongier/PEX/PickleMessage.cls +1 -46
  13. grongier/cls/Grongier/PEX/PrivateSession/Duplex.cls +1 -253
  14. grongier/cls/Grongier/PEX/PrivateSession/Message/Ack.cls +1 -19
  15. grongier/cls/Grongier/PEX/PrivateSession/Message/Poll.cls +1 -19
  16. grongier/cls/Grongier/PEX/PrivateSession/Message/Start.cls +1 -19
  17. grongier/cls/Grongier/PEX/PrivateSession/Message/Stop.cls +1 -35
  18. grongier/cls/Grongier/PEX/Test.cls +1 -53
  19. grongier/cls/Grongier/PEX/Utils.cls +1 -365
  20. grongier/cls/Grongier/Service/WSGI.cls +1 -307
  21. grongier/pex/__init__.py +11 -11
  22. grongier/pex/__main__.py +1 -1
  23. grongier/pex/_business_host.py +1 -511
  24. grongier/pex/_cli.py +2 -150
  25. grongier/pex/_common.py +1 -347
  26. grongier/pex/_director.py +1 -286
  27. grongier/pex/_utils.py +1 -369
  28. intersystems_iris/_ConnectionInformation.py +22 -20
  29. intersystems_iris/dbapi/_DBAPI.py +6 -1
  30. intersystems_iris/dbapi/_ResultSetRow.py +26 -15
  31. intersystems_iris/dbapi/preparser/_PreParser.py +4 -1
  32. iop/__init__.py +24 -0
  33. iop/__main__.py +4 -0
  34. iop/_business_host.py +675 -0
  35. iop/_business_operation.py +71 -0
  36. iop/_business_process.py +220 -0
  37. {grongier/pex → iop}/_business_service.py +2 -2
  38. iop/_cli.py +141 -0
  39. iop/_common.py +352 -0
  40. iop/_director.py +301 -0
  41. {grongier/pex → iop}/_inbound_adapter.py +1 -1
  42. iop/_log_manager.py +81 -0
  43. {grongier/pex → iop}/_message.py +1 -1
  44. {grongier/pex → iop}/_outbound_adapter.py +1 -1
  45. {grongier/pex → iop}/_private_session_duplex.py +3 -2
  46. {grongier/pex → iop}/_private_session_process.py +2 -2
  47. iop/_utils.py +458 -0
  48. iop/cls/IOP/BusinessOperation.cls +35 -0
  49. iop/cls/IOP/BusinessProcess.cls +124 -0
  50. iop/cls/IOP/BusinessService.cls +35 -0
  51. iop/cls/IOP/Common.cls +344 -0
  52. iop/cls/IOP/Director.cls +62 -0
  53. iop/cls/IOP/Duplex/Operation.cls +29 -0
  54. iop/cls/IOP/Duplex/Process.cls +229 -0
  55. iop/cls/IOP/Duplex/Service.cls +9 -0
  56. iop/cls/IOP/InboundAdapter.cls +22 -0
  57. iop/cls/IOP/Message/JSONSchema.cls +125 -0
  58. iop/cls/IOP/Message.cls +729 -0
  59. iop/cls/IOP/OutboundAdapter.cls +36 -0
  60. iop/cls/IOP/PickleMessage.cls +58 -0
  61. iop/cls/IOP/PrivateSession/Duplex.cls +260 -0
  62. iop/cls/IOP/PrivateSession/Message/Ack.cls +32 -0
  63. iop/cls/IOP/PrivateSession/Message/Poll.cls +32 -0
  64. iop/cls/IOP/PrivateSession/Message/Start.cls +32 -0
  65. iop/cls/IOP/PrivateSession/Message/Stop.cls +48 -0
  66. iop/cls/IOP/Service/WSGI.cls +310 -0
  67. iop/cls/IOP/Test.cls +85 -0
  68. iop/cls/IOP/Utils.cls +378 -0
  69. iop/wsgi/handlers.py +104 -0
  70. iris_pex_embedded_python-3.2.1b2.dist-info/METADATA +90 -0
  71. iris_pex_embedded_python-3.2.1b2.dist-info/RECORD +139 -0
  72. {iris_pex_embedded_python-2.3.27b2.dist-info → iris_pex_embedded_python-3.2.1b2.dist-info}/WHEEL +1 -1
  73. iris_pex_embedded_python-3.2.1b2.dist-info/entry_points.txt +2 -0
  74. {iris_pex_embedded_python-2.3.27b2.dist-info → iris_pex_embedded_python-3.2.1b2.dist-info}/top_level.txt +1 -1
  75. grongier/pex/_business_operation.py +0 -70
  76. grongier/pex/_business_process.py +0 -215
  77. iris/__init__.py +0 -60
  78. iris/__init__.pyi +0 -236
  79. iris/iris_ipm.py +0 -40
  80. iris/iris_ipm.pyi +0 -17
  81. iris_pex_embedded_python-2.3.27b2.dist-info/METADATA +0 -1384
  82. iris_pex_embedded_python-2.3.27b2.dist-info/RECORD +0 -113
  83. iris_pex_embedded_python-2.3.27b2.dist-info/entry_points.txt +0 -2
  84. {grongier/pex → iop}/_pickle_message.py +0 -0
  85. {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
@@ -1,4 +1,4 @@
1
- from grongier.pex._common import _Common
1
+ from iop._common import _Common
2
2
 
3
3
  class _InboundAdapter(_Common):
4
4
  """ Responsible for receiving the data from the external system, validating the data,