Skip to content
Prev Previous commit
Next Next commit
[OMCSessionZMQ] use 'getMessagesStringInternal()' to check for OMC er…
…rors for each command using sendExpression()
  • Loading branch information
syntron committed May 21, 2025
commit efc1d60cd953eb5b707535b70def517e9ac9a6a4
67 changes: 58 additions & 9 deletions OMPython/OMCSession.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import os
import pathlib
import psutil
import re
import signal
import subprocess
import sys
Expand Down Expand Up @@ -325,6 +326,9 @@ def __init__(self, timeout=10.00,
# connect to the running omc instance using ZMQ
self._connect_to_omc(timeout)

self._re_log_entries = None
self._re_log_raw = None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to make them class members?


def __del__(self):
try:
self.sendExpression("quit()")
Expand Down Expand Up @@ -550,15 +554,60 @@ def sendExpression(self, command, parsed=True):
else:
result = self._omc.recv_string()

# allways check for error
self._omc.send_string("getErrorString()", flags=zmq.NOBLOCK)
error_raw = self._omc.recv_string()
error_str = om_parser_typed(error_raw)
if error_str:
if "Error" in error_str:
raise OMCSessionException(f"OM Error for 'sendExpression({command}, {parsed})': {error_str}")
else:
logger.warning(f"[OM]: {error_str}")
if command == "getErrorString()":
# no error handling if 'getErrorString()' is called
pass
elif command == "getMessagesStringInternal()":
# no error handling if 'getMessagesStringInternal()' is called; parsing NOT possible!
if parsed:
logger.warning("Result of 'getMessagesStringInternal()' cannot be parsed - set parsed to False!")
parsed = False
else:
# allways check for error
self._omc.send_string('getMessagesStringInternal()', flags=zmq.NOBLOCK)
error_raw = self._omc.recv_string()
# run error handling only if there is something to check
if error_raw != "{}\n":
if not self._re_log_entries:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self._re_log_entries and self._re_log_raw can be local variables here.

@syntron syntron May 21, 2025 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I defined them as class variables to do the initialisation once (if needed at all). Later, the compiled re struture is just reused ... However, if the expected numbers of calls to this part is low, it makes sense to run this each time.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actual re compilation is run each time, its just they are initialized to none only once. I don't think initializing them to none locally is expensive.

@syntron syntron May 21, 2025 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in OMCSession.py line 584ff (definition of sendExpression()):

if not self._re_log_entries:
    self._re_log_entries = re.compile(...)

if not self._re_log_raw:
    self._re_log_raw = re.compile(...)

Compilation is done only once (on first call; self._re* class variables are None); after that, the variables are set (not None) and, thus, the compiled version is used ...

self._re_log_entries = re.compile(pattern=r'record OpenModelica\.Scripting\.ErrorMessage'
'(.*?)'
r'end OpenModelica\.Scripting\.ErrorMessage;',
flags=re.MULTILINE | re.DOTALL)
if not self._re_log_raw:
self._re_log_raw = re.compile(
pattern=r"\s+message = \"(.*?)\",\n" # message
r"\s+kind = .OpenModelica.Scripting.ErrorKind.(.*?),\n" # kind
r"\s+level = .OpenModelica.Scripting.ErrorLevel.(.*?),\n" # level
r"\s+id = (.*?)" # id
"(,\n|\n)", # end marker
flags=re.MULTILINE | re.DOTALL)

# extract all ErrorMessage records
log_entries = self._re_log_entries.findall(string=error_raw)
for log_entry in reversed(log_entries):
log_raw = self._re_log_raw.findall(string=log_entry)
if len(log_raw) != 1 or len(log_raw[0]) != 5:
logger.warning("Invalid ErrorMessage record returned by 'getMessagesStringInternal()':"
f" {repr(log_entry)}!")

log_message = log_raw[0][0].encode().decode('unicode_escape')
log_kind = log_raw[0][1]
log_level = log_raw[0][2]
log_id = log_raw[0][3]

msg = (f"[OMC log for 'sendExpression({command}, {parsed})']: "
f"[{log_kind}:{log_level}:{log_id}] {log_message}")

# response according to the used log level
# see: https://build.openmodelica.org/Documentation/OpenModelica.Scripting.ErrorLevel.html
if log_level == 'error':
raise OMCSessionException(msg)
elif log_level == 'warning':
logger.warning(msg)
elif log_level == 'notification':
logger.info(msg)
else: # internal
logger.debug(msg)

if parsed is True:
try:
Expand Down