From 94d14a237845bf9886bf9c5790e834a318cfefc8 Mon Sep 17 00:00:00 2001 From: arun3688 Date: Fri, 17 Jul 2026 14:40:26 +0200 Subject: [PATCH 1/6] buildModel: allow recoverable 'error'-level OMC messages via raise_on_error --- OMPython/OMCSession.py | 4 ++-- OMPython/modelica_system_omc.py | 13 +++++++++---- OMPython/om_session_abc.py | 5 ++++- OMPython/om_session_omc.py | 15 ++++++++++++--- OMPython/om_session_runner.py | 2 +- 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index c5511923..24be4f7b 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -282,14 +282,14 @@ def omcpath_tempdir(self, tempdir_base: Optional[OMPathABC] = None) -> OMPathABC def execute(self, command: str): return self.omc_process.execute(command=command) - def sendExpression(self, command: str, parsed: bool = True) -> Any: + def sendExpression(self, command: str, parsed: bool = True, raise_on_error: bool = True) -> Any: """ Send an expression to the OMC server and return the result. The complete error handling of the OMC result is done within this method using '"getMessagesStringInternal()'. Caller should only check for OMSessionException. """ - return self.omc_process.sendExpression(expr=command, parsed=parsed) + return self.omc_process.sendExpression(expr=command, parsed=parsed, raise_on_error=raise_on_error) def get_version(self) -> str: return self.omc_process.get_version() diff --git a/OMPython/modelica_system_omc.py b/OMPython/modelica_system_omc.py index 34805e0f..e9c9409d 100644 --- a/OMPython/modelica_system_omc.py +++ b/OMPython/modelica_system_omc.py @@ -203,7 +203,11 @@ def buildModel(self, variableFilter: Optional[str] = None): else: var_filter = 'variableFilter=".*"' - build_model_result = self._requestApi(apiName="buildModel", entity=self._model_name, properties=var_filter) + # buildModel() can emit 'error'-level diagnostics (e.g. a structurally singular initialization + # system) that OMC itself recovers from without actually failing the build. Don't raise on those + # here; check_model_executable()/_xmlparse() below independently verify the build really succeeded. + build_model_result = self._requestApi(apiName="buildModel", entity=self._model_name, properties=var_filter, + raise_on_error=False) logger.debug("OM model build result: %s", build_model_result) # check if the executable exists ... @@ -212,12 +216,12 @@ def buildModel(self, variableFilter: Optional[str] = None): xml_file = self._session.omcpath(build_model_result[0]).parent / build_model_result[1] self._xmlparse(xml_file=xml_file) - def sendExpression(self, expr: str, parsed: bool = True) -> Any: + def sendExpression(self, expr: str, parsed: bool = True, raise_on_error: bool = True) -> Any: """ Wrapper for OMCSession.sendExpression(). """ try: - retval = self._session.sendExpression(expr=expr, parsed=parsed) + retval = self._session.sendExpression(expr=expr, parsed=parsed, raise_on_error=raise_on_error) except OMSessionException as ex: raise ModelicaSystemError(f"Error executing {repr(expr)}: {ex}") from ex @@ -231,6 +235,7 @@ def _requestApi( apiName: str, entity: Optional[str] = None, properties: Optional[str] = None, + raise_on_error: bool = True, ) -> Any: if entity is not None and properties is not None: expr = f'{apiName}({entity}, {properties})' @@ -242,7 +247,7 @@ def _requestApi( else: expr = f'{apiName}()' - return self.sendExpression(expr=expr) + return self.sendExpression(expr=expr, raise_on_error=raise_on_error) def getContinuousFinal( self, diff --git a/OMPython/om_session_abc.py b/OMPython/om_session_abc.py index 70e897d7..2def56e4 100644 --- a/OMPython/om_session_abc.py +++ b/OMPython/om_session_abc.py @@ -317,7 +317,10 @@ def _tempdir(tempdir_base: OMPathABC) -> OMPathABC: return tempdir @abc.abstractmethod - def sendExpression(self, expr: str, parsed: bool = True) -> Any: + def sendExpression(self, expr: str, parsed: bool = True, raise_on_error: bool = True) -> Any: """ Function needed to send expressions to the OMC server via ZMQ. + + If raise_on_error is False, 'error'-level OMC diagnostics are logged instead of raised as an + OMSessionException; use this only when the caller has its own, more precise way of verifying success. """ diff --git a/OMPython/om_session_omc.py b/OMPython/om_session_omc.py index 6626cd17..53b709e6 100644 --- a/OMPython/om_session_omc.py +++ b/OMPython/om_session_omc.py @@ -387,12 +387,18 @@ def execute(self, command: str): return self.sendExpression(command, parsed=False) - def sendExpression(self, expr: str, parsed: bool = True) -> Any: + def sendExpression(self, expr: str, parsed: bool = True, raise_on_error: bool = True) -> Any: """ Send an expression to the OMC server and return the result. The complete error handling of the OMC result is done within this method using 'getMessagesStringInternal()'. Caller should only check for OMSessionException. + + Some OMC API calls (e.g. buildModel) can emit 'error'-level diagnostics that are recoverable and don't + actually prevent the call from succeeding (e.g. a structurally singular initialization system that OMC + resolves via a fallback). Callers who have their own, more precise way of verifying success (such as + checking that the resulting files/executable actually exist) can pass raise_on_error=False to have such + messages logged instead of raised as an exception. """ if self._omc_zmq is None: @@ -509,8 +515,11 @@ def sendExpression(self, expr: str, parsed: bool = True) -> Any: msg_long_list.append(msg_long) if has_error: msg_long_str = '\n'.join(f"{idx:02d}: {msg}" for idx, msg in enumerate(msg_long_list)) - raise OMSessionException(f"OMC error occurred for 'sendExpression(expr={expr}, parsed={parsed}):\n" - f"{msg_long_str}") + if raise_on_error: + raise OMSessionException( + f"OMC error occurred for 'sendExpression(expr={expr}, parsed={parsed}):\n{msg_long_str}") + logger.warning("OMC reported 'error'-level messages for 'sendExpression(expr=%s, parsed=%s)', but " + "raise_on_error=False was requested; continuing:\n%s", expr, parsed, msg_long_str) if not parsed: return result diff --git a/OMPython/om_session_runner.py b/OMPython/om_session_runner.py index fc8e5ac8..470e2aaf 100644 --- a/OMPython/om_session_runner.py +++ b/OMPython/om_session_runner.py @@ -379,5 +379,5 @@ def omcpath_tempdir(self, tempdir_base: Optional[OMPathABC] = None) -> OMPathABC return self._tempdir(tempdir_base=tempdir_base) - def sendExpression(self, expr: str, parsed: bool = True) -> Any: + def sendExpression(self, expr: str, parsed: bool = True, raise_on_error: bool = True) -> Any: raise OMSessionException(f"{self.__class__.__name__} does not uses an OMC server!") From 96665d7cd5661d9fe20e02f49437eec19792f977 Mon Sep 17 00:00:00 2001 From: arun3688 Date: Fri, 17 Jul 2026 14:54:53 +0200 Subject: [PATCH 2/6] Trigger build From db95bf7a3d34398b5d36020b7a57e120851ab90c Mon Sep 17 00:00:00 2001 From: arun3688 Date: Mon, 20 Jul 2026 17:33:40 +0200 Subject: [PATCH 3/6] fix line intend --- OMPython/modelica_system_omc.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OMPython/modelica_system_omc.py b/OMPython/modelica_system_omc.py index e9c9409d..b6254b14 100644 --- a/OMPython/modelica_system_omc.py +++ b/OMPython/modelica_system_omc.py @@ -206,8 +206,7 @@ def buildModel(self, variableFilter: Optional[str] = None): # buildModel() can emit 'error'-level diagnostics (e.g. a structurally singular initialization # system) that OMC itself recovers from without actually failing the build. Don't raise on those # here; check_model_executable()/_xmlparse() below independently verify the build really succeeded. - build_model_result = self._requestApi(apiName="buildModel", entity=self._model_name, properties=var_filter, - raise_on_error=False) + build_model_result = self._requestApi(apiName="buildModel", entity=self._model_name, properties=var_filter, raise_on_error=False) logger.debug("OM model build result: %s", build_model_result) # check if the executable exists ... From 70d316d9e81f1cc92ef92e3edeb5da40c75fad56 Mon Sep 17 00:00:00 2001 From: arun3688 Date: Mon, 20 Jul 2026 17:44:49 +0200 Subject: [PATCH 4/6] fix line style --- OMPython/modelica_system_omc.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/OMPython/modelica_system_omc.py b/OMPython/modelica_system_omc.py index b6254b14..2d2088c0 100644 --- a/OMPython/modelica_system_omc.py +++ b/OMPython/modelica_system_omc.py @@ -206,7 +206,12 @@ def buildModel(self, variableFilter: Optional[str] = None): # buildModel() can emit 'error'-level diagnostics (e.g. a structurally singular initialization # system) that OMC itself recovers from without actually failing the build. Don't raise on those # here; check_model_executable()/_xmlparse() below independently verify the build really succeeded. - build_model_result = self._requestApi(apiName="buildModel", entity=self._model_name, properties=var_filter, raise_on_error=False) + build_model_result = self._requestApi( + apiName="buildModel", + entity=self._model_name, + properties=var_filter, + raise_on_error=False, + ) logger.debug("OM model build result: %s", build_model_result) # check if the executable exists ... From b94516f82d2efa2718a172e6c0af161988873b0b Mon Sep 17 00:00:00 2001 From: arun3688 Date: Mon, 20 Jul 2026 22:03:41 +0200 Subject: [PATCH 5/6] update docker image to latest --- tests/test_docker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_docker.py b/tests/test_docker.py index 50d2763a..72d007fa 100644 --- a/tests/test_docker.py +++ b/tests/test_docker.py @@ -12,7 +12,7 @@ @skip_on_windows def test_docker(): - omcs = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") + omcs = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.27.0-ompython") omversion = omcs.sendExpression("getVersion()") assert isinstance(omversion, str) and omversion.startswith("OpenModelica") @@ -20,7 +20,7 @@ def test_docker(): omversion = omcsInner.sendExpression("getVersion()") assert isinstance(omversion, str) and omversion.startswith("OpenModelica") - omcs2 = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal", port=11111) + omcs2 = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.27.0-ompython", port=11111) omversion = omcs2.sendExpression("getVersion()") assert isinstance(omversion, str) and omversion.startswith("OpenModelica") From adc7e7c3b791332f868905717bef66becd696e1b Mon Sep 17 00:00:00 2001 From: arun3688 Date: Tue, 21 Jul 2026 15:10:26 +0200 Subject: [PATCH 6/6] fix docker test --- OMPython/om_session_omc.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/OMPython/om_session_omc.py b/OMPython/om_session_omc.py index 53b709e6..24b74fc1 100644 --- a/OMPython/om_session_omc.py +++ b/OMPython/om_session_omc.py @@ -875,17 +875,20 @@ def _docker_omc_start( loop = self._timeout_loop(timestep=0.1) while next(loop): try: - with open(file=docker_cid_file, mode="r", encoding="utf-8") as fh: + with open(docker_cid_file, "r", encoding="utf-8") as fh: docker_cid = fh.read().strip() except IOError: - pass - if docker_cid is not None: + continue + + if docker_cid: break - if docker_cid is None: - raise OMSessionException(f"Docker did not start (timeout={self._timeout:.2f}s might be too short " - "especially if you did not docker pull the image before this command). " - f"Log-file says:\n{self.get_log()}") + if not docker_cid: + raise OMSessionException( + f"Docker did not start (timeout={self._timeout:.2f}s might be too short " + "especially if you did not docker pull the image before this command). " + f"Log-file says:\n{self.get_log()}" + ) docker_process = self._docker_process_get(docker_cid=docker_cid) if docker_process is None: