@@ -271,6 +271,148 @@ def getClassNames(self, className=None, recursive=False, qualified=False, sort=F
271271
272272class OMCSessionZMQ :
273273
274+ def __init__ (self ,
275+ timeout : float = 10.00 ,
276+ omhome : Optional [str ] = None ,
277+ omc_process : Optional [OMCProcess ] = None ):
278+
279+ if omc_process is None :
280+ omc_process = OMCProcessLocal (omhome = omhome , timeout = timeout )
281+ elif not isinstance (omc_process , OMCProcess ):
282+ raise OMCSessionException ("Invalid definition of the OMC process!" )
283+ self ._omc_process = omc_process
284+
285+ # variables to store compiled re expressions use in self.sendExpression()
286+ self ._re_log_entries = None
287+ self ._re_log_raw = None
288+
289+ # Create the ZeroMQ socket and connect to OMC server
290+ context = zmq .Context .instance ()
291+ omc = context .socket (zmq .REQ )
292+ omc .setsockopt (zmq .LINGER , 0 ) # Dismisses pending messages if closed
293+ omc .setsockopt (zmq .IMMEDIATE , True ) # Queue messages only to completed connections
294+ omc .connect (self ._omc_process .get_port ())
295+
296+ self ._omc = omc
297+
298+ def __del__ (self ):
299+ if self ._omc :
300+ try :
301+ self .sendExpression ("quit()" )
302+ except OMCSessionException :
303+ pass
304+
305+ del self ._omc
306+
307+ self ._omc = None
308+
309+ def execute (self , command ):
310+ warnings .warn ("This function is depreciated and will be removed in future versions; "
311+ "please use sendExpression() instead" , DeprecationWarning , stacklevel = 1 )
312+
313+ return self .sendExpression (command , parsed = False )
314+
315+ def sendExpression (self , command , parsed = True ):
316+ p = self ._omc_process .poll () # check if process is running
317+ if p is not None :
318+ raise OMCSessionException ("Process Exited, No connection with OMC. Create a new instance of OMCSessionZMQ!" )
319+
320+ if self ._omc is None :
321+ raise OMCSessionException ("No OMC running. Create a new instance of OMCSessionZMQ!" )
322+
323+ logger .debug ("sendExpression(%r, parsed=%r)" , command , parsed )
324+
325+ attempts = 0
326+ while True :
327+ try :
328+ self ._omc .send_string (str (command ), flags = zmq .NOBLOCK )
329+ break
330+ except zmq .error .Again :
331+ pass
332+ attempts += 1
333+ if attempts >= 50 :
334+ self ._omc_filehandle_log .seek (0 )
335+ log = self ._omc_filehandle_log .read ()
336+ self ._omc_filehandle_log .close ()
337+ raise OMCSessionException (f"No connection with OMC (timeout={ self ._timeout } ). Log-file says: \n { log } " )
338+ time .sleep (self ._timeout / 50.0 )
339+ if command == "quit()" :
340+ self ._omc .close ()
341+ self ._omc = None
342+ return None
343+ else :
344+ result = self ._omc .recv_string ()
345+
346+ if command == "getErrorString()" :
347+ # no error handling if 'getErrorString()' is called
348+ pass
349+ elif command == "getMessagesStringInternal()" :
350+ # no error handling if 'getMessagesStringInternal()' is called; parsing NOT possible!
351+ if parsed :
352+ logger .warning ("Result of 'getMessagesStringInternal()' cannot be parsed - set parsed to False!" )
353+ parsed = False
354+ else :
355+ # allways check for error
356+ self ._omc .send_string ('getMessagesStringInternal()' , flags = zmq .NOBLOCK )
357+ error_raw = self ._omc .recv_string ()
358+ # run error handling only if there is something to check
359+ if error_raw != "{}\n " :
360+ if not self ._re_log_entries :
361+ self ._re_log_entries = re .compile (pattern = r'record OpenModelica\.Scripting\.ErrorMessage'
362+ '(.*?)'
363+ r'end OpenModelica\.Scripting\.ErrorMessage;' ,
364+ flags = re .MULTILINE | re .DOTALL )
365+ if not self ._re_log_raw :
366+ self ._re_log_raw = re .compile (
367+ pattern = r"\s+message = \"(.*?)\",\n" # message
368+ r"\s+kind = .OpenModelica.Scripting.ErrorKind.(.*?),\n" # kind
369+ r"\s+level = .OpenModelica.Scripting.ErrorLevel.(.*?),\n" # level
370+ r"\s+id = (.*?)" # id
371+ "(,\n |\n )" , # end marker
372+ flags = re .MULTILINE | re .DOTALL )
373+
374+ # extract all ErrorMessage records
375+ log_entries = self ._re_log_entries .findall (string = error_raw )
376+ for log_entry in reversed (log_entries ):
377+ log_raw = self ._re_log_raw .findall (string = log_entry )
378+ if len (log_raw ) != 1 or len (log_raw [0 ]) != 5 :
379+ logger .warning ("Invalid ErrorMessage record returned by 'getMessagesStringInternal()':"
380+ f" { repr (log_entry )} !" )
381+
382+ log_message = log_raw [0 ][0 ].encode ().decode ('unicode_escape' )
383+ log_kind = log_raw [0 ][1 ]
384+ log_level = log_raw [0 ][2 ]
385+ log_id = log_raw [0 ][3 ]
386+
387+ msg = (f"[OMC log for 'sendExpression({ command } , { parsed } )']: "
388+ f"[{ log_kind } :{ log_level } :{ log_id } ] { log_message } " )
389+
390+ # response according to the used log level
391+ # see: https://build.openmodelica.org/Documentation/OpenModelica.Scripting.ErrorLevel.html
392+ if log_level == 'error' :
393+ raise OMCSessionException (msg )
394+ elif log_level == 'warning' :
395+ logger .warning (msg )
396+ elif log_level == 'notification' :
397+ logger .info (msg )
398+ else : # internal
399+ logger .debug (msg )
400+
401+ if parsed is True :
402+ try :
403+ return om_parser_typed (result )
404+ except pyparsing .ParseException as ex :
405+ logger .warning ('OMTypedParser error: %s. Returning the basic parser result.' , ex .msg )
406+ try :
407+ return om_parser_basic (result )
408+ except (TypeError , UnboundLocalError ) as ex :
409+ raise OMCSessionException ("Cannot parse OMC result" ) from ex
410+ else :
411+ return result
412+
413+
414+ class OMCProcess :
415+
274416 def __init__ (self ,
275417 timeout : float = 10.00 ,
276418 omhome : Optional [str ] = None ):
@@ -279,9 +421,47 @@ def __init__(self,
279421 self ._omhome = self ._omc_home_get (omhome = omhome )
280422 self ._timeout = timeout
281423
282- # variables to store compiled re expressions use in self.sendExpression()
283- self ._re_log_entries = None
284- self ._re_log_raw = None
424+ self ._omc_port : Optional [str ] = None
425+
426+ @staticmethod
427+ def _omc_home_get (omhome : Optional [str ] = None ):
428+ # use the provided path
429+ if omhome is not None :
430+ return pathlib .Path (omhome )
431+
432+ # check the environment variable
433+ omhome = os .environ .get ('OPENMODELICAHOME' )
434+ if omhome is not None :
435+ return pathlib .Path (omhome )
436+
437+ # Get the path to the OMC executable, if not installed this will be None
438+ path_to_omc = shutil .which ("omc" )
439+ if path_to_omc is not None :
440+ return pathlib .Path (path_to_omc ).parents [1 ]
441+
442+ raise OMCSessionException ("Cannot find OpenModelica executable, please install from openmodelica.org" )
443+
444+ def get_port (self ) -> str :
445+ if not isinstance (self ._omc_port , str ):
446+ raise OMCSessionException (f"Invalid port to connect to OMC process: { self ._omc_port } " )
447+ return self ._omc_port
448+
449+
450+ class OMCProcessDummy (OMCProcess ):
451+
452+ def __init__ (self ,
453+ omc_port : str ):
454+ super ().__init__ ()
455+ self ._omc_port = omc_port
456+
457+
458+ class OMCProcessLocal (OMCProcess ):
459+
460+ def __init__ (self ,
461+ timeout : float = 10.00 ,
462+ omhome : Optional [str ] = None ):
463+
464+ super ().__init__ (timeout = timeout , omhome = omhome )
285465
286466 # generate a random string for this session
287467 self ._random_string = uuid .uuid4 ().hex
@@ -319,21 +499,7 @@ def __init__(self,
319499 # connect to the running omc instance using ZMQ
320500 self ._omc_port = self ._omc_port_get (timeout )
321501
322- # Create the ZeroMQ socket and connect to OMC server
323- context = zmq .Context .instance ()
324- omc = context .socket (zmq .REQ )
325- omc .setsockopt (zmq .LINGER , 0 ) # Dismisses pending messages if closed
326- omc .setsockopt (zmq .IMMEDIATE , True ) # Queue messages only to completed connections
327- omc .connect (self ._omc_port )
328-
329- self ._omc = omc
330-
331502 def __del__ (self ):
332- try :
333- self .sendExpression ("quit()" )
334- except OMCSessionException :
335- pass
336-
337503 if self ._omc_filehandle_log is not None :
338504 self ._omc_filehandle_log .close ()
339505
@@ -379,24 +545,6 @@ def _omc_command_get(self, omc_path_and_args_list) -> list:
379545
380546 return omc_command
381547
382- @staticmethod
383- def _omc_home_get (omhome : Optional [str ] = None ):
384- # use the provided path
385- if omhome is not None :
386- return pathlib .Path (omhome )
387-
388- # check the environment variable
389- omhome = os .environ .get ('OPENMODELICAHOME' )
390- if omhome is not None :
391- return pathlib .Path (omhome )
392-
393- # Get the path to the OMC executable, if not installed this will be None
394- path_to_omc = shutil .which ("omc" )
395- if path_to_omc is not None :
396- return pathlib .Path (path_to_omc ).parents [1 ]
397-
398- raise OMCSessionException ("Cannot find OpenModelica executable, please install from openmodelica.org" )
399-
400548 def _omc_path_get (self ) -> pathlib .Path :
401549 return self ._omhome / "bin" / "omc"
402550
@@ -438,110 +586,6 @@ def _omc_port_get_main(self) -> Optional[str]:
438586
439587 return port
440588
441- def execute (self , command ):
442- warnings .warn ("This function is depreciated and will be removed in future versions; "
443- "please use sendExpression() instead" , DeprecationWarning , stacklevel = 1 )
444-
445- return self .sendExpression (command , parsed = False )
446-
447- def sendExpression (self , command , parsed = True ):
448- p = self ._omc_process .poll () # check if process is running
449- if p is not None :
450- raise OMCSessionException ("Process Exited, No connection with OMC. Create a new instance of OMCSessionZMQ!" )
451-
452- if self ._omc is None :
453- raise OMCSessionException ("No OMC running. Create a new instance of OMCSessionZMQ!" )
454-
455- logger .debug ("sendExpression(%r, parsed=%r)" , command , parsed )
456-
457- attempts = 0
458- while True :
459- try :
460- self ._omc .send_string (str (command ), flags = zmq .NOBLOCK )
461- break
462- except zmq .error .Again :
463- pass
464- attempts += 1
465- if attempts >= 50 :
466- self ._omc_filehandle_log .seek (0 )
467- log = self ._omc_filehandle_log .read ()
468- self ._omc_filehandle_log .close ()
469- raise OMCSessionException (f"No connection with OMC (timeout={ self ._timeout } ). Log-file says: \n { log } " )
470- time .sleep (self ._timeout / 50.0 )
471- if command == "quit()" :
472- self ._omc .close ()
473- self ._omc = None
474- return None
475- else :
476- result = self ._omc .recv_string ()
477-
478- if command == "getErrorString()" :
479- # no error handling if 'getErrorString()' is called
480- pass
481- elif command == "getMessagesStringInternal()" :
482- # no error handling if 'getMessagesStringInternal()' is called; parsing NOT possible!
483- if parsed :
484- logger .warning ("Result of 'getMessagesStringInternal()' cannot be parsed - set parsed to False!" )
485- parsed = False
486- else :
487- # allways check for error
488- self ._omc .send_string ('getMessagesStringInternal()' , flags = zmq .NOBLOCK )
489- error_raw = self ._omc .recv_string ()
490- # run error handling only if there is something to check
491- if error_raw != "{}\n " :
492- if not self ._re_log_entries :
493- self ._re_log_entries = re .compile (pattern = r'record OpenModelica\.Scripting\.ErrorMessage'
494- '(.*?)'
495- r'end OpenModelica\.Scripting\.ErrorMessage;' ,
496- flags = re .MULTILINE | re .DOTALL )
497- if not self ._re_log_raw :
498- self ._re_log_raw = re .compile (
499- pattern = r"\s+message = \"(.*?)\",\n" # message
500- r"\s+kind = .OpenModelica.Scripting.ErrorKind.(.*?),\n" # kind
501- r"\s+level = .OpenModelica.Scripting.ErrorLevel.(.*?),\n" # level
502- r"\s+id = (.*?)" # id
503- "(,\n |\n )" , # end marker
504- flags = re .MULTILINE | re .DOTALL )
505-
506- # extract all ErrorMessage records
507- log_entries = self ._re_log_entries .findall (string = error_raw )
508- for log_entry in reversed (log_entries ):
509- log_raw = self ._re_log_raw .findall (string = log_entry )
510- if len (log_raw ) != 1 or len (log_raw [0 ]) != 5 :
511- logger .warning ("Invalid ErrorMessage record returned by 'getMessagesStringInternal()':"
512- f" { repr (log_entry )} !" )
513-
514- log_message = log_raw [0 ][0 ].encode ().decode ('unicode_escape' )
515- log_kind = log_raw [0 ][1 ]
516- log_level = log_raw [0 ][2 ]
517- log_id = log_raw [0 ][3 ]
518-
519- msg = (f"[OMC log for 'sendExpression({ command } , { parsed } )']: "
520- f"[{ log_kind } :{ log_level } :{ log_id } ] { log_message } " )
521-
522- # response according to the used log level
523- # see: https://build.openmodelica.org/Documentation/OpenModelica.Scripting.ErrorLevel.html
524- if log_level == 'error' :
525- raise OMCSessionException (msg )
526- elif log_level == 'warning' :
527- logger .warning (msg )
528- elif log_level == 'notification' :
529- logger .info (msg )
530- else : # internal
531- logger .debug (msg )
532-
533- if parsed is True :
534- try :
535- return om_parser_typed (result )
536- except pyparsing .ParseException as ex :
537- logger .warning ('OMTypedParser error: %s. Returning the basic parser result.' , ex .msg )
538- try :
539- return om_parser_basic (result )
540- except (TypeError , UnboundLocalError ) as ex :
541- raise OMCSessionException ("Cannot parse OMC result" ) from ex
542- else :
543- return result
544-
545589
546590# noinspection PyPep8Naming
547591class OMCSessionZMQDocker (OMCSessionZMQ ):
0 commit comments