Skip to content

Commit 1545ebc

Browse files
committed
[OMCSessionZMQ] split class - new: OMCProcess(Dummy|Local)
1 parent 09dea0e commit 1545ebc

1 file changed

Lines changed: 183 additions & 139 deletions

File tree

OMPython/OMCSession.py

Lines changed: 183 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,148 @@ def getClassNames(self, className=None, recursive=False, qualified=False, sort=F
271271

272272
class 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
547591
class OMCSessionZMQDocker(OMCSessionZMQ):

0 commit comments

Comments
 (0)