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..2d2088c0 100644 --- a/OMPython/modelica_system_omc.py +++ b/OMPython/modelica_system_omc.py @@ -203,7 +203,15 @@ 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 +220,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 +239,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 +251,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..24b74fc1 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 @@ -866,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: 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!") 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")