From b733e4e19d21d3f012abb47df08cac7f386ec335 Mon Sep 17 00:00:00 2001 From: syntron Date: Fri, 2 May 2025 19:09:29 +0200 Subject: [PATCH 01/11] [OMCSessionBase] fix exception handling --- OMPython/OMCSession.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index ae3adced..83bca24a 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -114,7 +114,7 @@ def _ask(self, question: str, opt: Optional[list[str]] = None, parsed: Optional[ elif isinstance(opt, list): expression = f"{question}({','.join([str(x) for x in opt])})" else: - raise Exception(f"Invalid definition of options for {repr(question)}: {repr(opt)}") + raise OMCSessionException(f"Invalid definition of options for {repr(question)}: {repr(opt)}") p = (expression, parsed) @@ -127,9 +127,8 @@ def _ask(self, question: str, opt: Optional[list[str]] = None, parsed: Optional[ try: res = self.sendExpression(expression, parsed=parsed) - except OMCSessionException: - logger.error("OMC failed: %s, %s, parsed=%s", question, opt, parsed) - raise + except OMCSessionException as ex: + raise OMCSessionException("OMC _ask() failed: %s (parsed=%s)", expression, parsed) from ex # save response self._omc_cache[p] = res From 5400831cdb4321e870f9187473291e5943be635b Mon Sep 17 00:00:00 2001 From: syntron Date: Sat, 26 Apr 2025 22:38:49 +0200 Subject: [PATCH 02/11] rename [OMCSessionBase] => [OMCSessionCmd]; remove dependencies --- OMPython/ModelicaSystem.py | 2 +- OMPython/OMCSession.py | 31 +++++++++---------------------- OMPython/__init__.py | 4 ++-- 3 files changed, 12 insertions(+), 25 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 1ee8b993..31bdc8ed 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -46,7 +46,7 @@ from dataclasses import dataclass from typing import Optional -from OMPython.OMCSession import OMCSessionBase, OMCSessionZMQ +from OMPython.OMCSession import OMCSessionZMQ # define logger using the current module name as ID logger = logging.getLogger(__name__) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 83bca24a..04c21e11 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -3,6 +3,8 @@ Definition of an OMC session. """ +from __future__ import annotations + __license__ = """ This file is part of OpenModelica. @@ -33,7 +35,6 @@ """ import shutil -import abc import getpass import logging import json @@ -80,9 +81,12 @@ class OMCSessionException(Exception): pass -class OMCSessionBase(metaclass=abc.ABCMeta): +class OMCSessionCmd: - def __init__(self, readonly=False): + def __init__(self, session: OMCSessionZMQ, readonly: Optional[bool] = False): + if not isinstance(session, OMCSessionZMQ): + raise OMCSessionException("Invalid session definition!") + self._session = session self._readonly = readonly self._omc_cache = {} @@ -92,21 +96,6 @@ def execute(self, command): return self.sendExpression(command, parsed=False) - @abc.abstractmethod - def sendExpression(self, command, parsed=True): - """ - Sends an expression to the OpenModelica. The return type is parsed as if the - expression was part of the typed OpenModelica API (see ModelicaBuiltin.mo). - * Integer and Real are returned as Python numbers - * Strings, enumerations, and typenames are returned as Python strings - * Arrays, tuples, and MetaModelica lists are returned as tuples - * Records are returned as dicts (the name of the record is lost) - * Booleans are returned as True or False - * NONE() is returned as None - * SOME(value) is returned as value - """ - pass - def _ask(self, question: str, opt: Optional[list[str]] = None, parsed: Optional[bool] = True): if opt is None: @@ -126,7 +115,7 @@ def _ask(self, question: str, opt: Optional[list[str]] = None, parsed: Optional[ logger.debug('OMC ask: %s (parsed=%s)', expression, parsed) try: - res = self.sendExpression(expression, parsed=parsed) + res = self._session.sendExpression(expression, parsed=parsed) except OMCSessionException as ex: raise OMCSessionException("OMC _ask() failed: %s (parsed=%s)", expression, parsed) from ex @@ -282,7 +271,7 @@ def getClassNames(self, className=None, recursive=False, qualified=False, sort=F return value -class OMCSessionZMQ(OMCSessionBase): +class OMCSessionZMQ: def __init__(self, readonly=False, timeout=10.00, docker=None, dockerContainer=None, dockerExtraArgs=None, dockerOpenModelicaPath="omc", @@ -290,8 +279,6 @@ def __init__(self, readonly=False, timeout=10.00, if dockerExtraArgs is None: dockerExtraArgs = [] - super().__init__(readonly=readonly) - self.omhome = self._get_omhome(omhome=omhome) self._omc_process = None diff --git a/OMPython/__init__.py b/OMPython/__init__.py index eee36acc..0d4ab686 100644 --- a/OMPython/__init__.py +++ b/OMPython/__init__.py @@ -36,7 +36,7 @@ CONDITIONS OF OSMC-PL. """ -from OMPython.OMCSession import OMCSessionBase, OMCSessionZMQ, OMCSessionException +from OMPython.OMCSession import OMCSessionCmd, OMCSessionZMQ, OMCSessionException from OMPython.ModelicaSystem import ModelicaSystem, ModelicaSystemError, LinearizationResult # global names imported if import 'from OMPython import *' is used @@ -47,5 +47,5 @@ 'OMCSessionException', 'OMCSessionZMQ', - 'OMCSessionBase', + 'OMCSessionCmd', ] From 344a1368af552fa7e5b368d0fea1112f21be1c44 Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 28 Apr 2025 13:43:40 +0200 Subject: [PATCH 03/11] [OMCSessionZMQ] remove unused argument readonly --- OMPython/OMCSession.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 04c21e11..17b17dce 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -273,7 +273,7 @@ def getClassNames(self, className=None, recursive=False, qualified=False, sort=F class OMCSessionZMQ: - def __init__(self, readonly=False, timeout=10.00, + def __init__(self, timeout=10.00, docker=None, dockerContainer=None, dockerExtraArgs=None, dockerOpenModelicaPath="omc", dockerNetwork=None, port=None, omhome: str = None): if dockerExtraArgs is None: From ab33c18701ecad8739120166f637d50e2b980d4a Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 28 Apr 2025 09:01:46 +0200 Subject: [PATCH 04/11] [OMCSessionCmd] restore test_ZMQ - move execute() back into OMCSessionZMQ --- OMPython/OMCSession.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 17b17dce..a6a924d7 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -90,12 +90,6 @@ def __init__(self, session: OMCSessionZMQ, readonly: Optional[bool] = False): self._readonly = readonly self._omc_cache = {} - def execute(self, command): - warnings.warn("This function is depreciated and will be removed in future versions; " - "please use sendExpression() instead", DeprecationWarning, stacklevel=1) - - return self.sendExpression(command, parsed=False) - def _ask(self, question: str, opt: Optional[list[str]] = None, parsed: Optional[bool] = True): if opt is None: @@ -516,6 +510,12 @@ def _connect_to_omc(self, timeout): self._omc.setsockopt(zmq.IMMEDIATE, True) # Queue messages only to completed connections self._omc.connect(self._port) + def execute(self, command): + warnings.warn("This function is depreciated and will be removed in future versions; " + "please use sendExpression() instead", DeprecationWarning, stacklevel=1) + + return self.sendExpression(command, parsed=False) + def sendExpression(self, command, parsed=True): p = self._omc_process.poll() # check if process is running if p is not None: From 00bf6750af5e0800e0d94126a7820c338cc7921f Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 28 Apr 2025 13:54:46 +0200 Subject: [PATCH 05/11] [OMCSessionCmd] make sendExpression() available --- OMPython/OMCSession.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index a6a924d7..c00f2851 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -90,6 +90,9 @@ def __init__(self, session: OMCSessionZMQ, readonly: Optional[bool] = False): self._readonly = readonly self._omc_cache = {} + def sendExpression(self, command, parsed=True): + return self._session.sendExpression(command=command, parsed=parsed) + def _ask(self, question: str, opt: Optional[list[str]] = None, parsed: Optional[bool] = True): if opt is None: From eb7a6b7947792c40f529ce0a83a2608397d8e067 Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 28 Apr 2025 16:40:48 +0200 Subject: [PATCH 06/11] [tests] new test for OMCSessionCmd --- tests/test_OMSessionCmd.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/test_OMSessionCmd.py diff --git a/tests/test_OMSessionCmd.py b/tests/test_OMSessionCmd.py new file mode 100644 index 00000000..5e369636 --- /dev/null +++ b/tests/test_OMSessionCmd.py @@ -0,0 +1,24 @@ +import OMPython +import unittest + + +class OMCSessionCmdTester(unittest.TestCase): + def __init__(self, *args, **kwargs): + super(OMCSessionCmdTester, self).__init__(*args, **kwargs) + + def test_isPackage(self): + omczmq = OMPython.OMCSessionZMQ() + omccmd = OMPython.OMCSessionCmd(session=omczmq) + assert not omccmd.isPackage('Modelica') + + def test_isPackage2(self): + mod = OMPython.ModelicaSystem(modelName="Modelica.Electrical.Analog.Examples.CauerLowPassAnalog", + lmodel=["Modelica"]) + omccmd = OMPython.OMCSessionCmd(session=mod.getconn) + assert omccmd.isPackage('Modelica') + + # TODO: add more checks ... + + +if __name__ == '__main__': + unittest.main() From 449604722b2e44cf5b6f018db6ca41ce07f86711 Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 30 Apr 2025 19:14:06 +0200 Subject: [PATCH 07/11] [OMCSessionCmd] fix all try ... except ... usages - check for OMCSessionException --- OMPython/OMCSession.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index c00f2851..44f33c08 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -186,9 +186,11 @@ def getClassComment(self, className): try: return self._ask(question='getClassComment', opt=[className]) except pyparsing.ParseException as ex: - logger.warning("Method 'getClassComment' failed for %s", className) - logger.warning('OMTypedParser error: %s', ex.msg) + logger.warning("Method 'getClassComment(%s)' failed; OMTypedParser error: %s", + className, ex.msg) return 'No description available' + except OMCSessionException: + raise def getNthComponent(self, className, comp_id): """ returns with (type, name, description) """ @@ -217,13 +219,18 @@ def getParameterNames(self, className): logger.warning('OMPython error: %s', ex) # FIXME: OMC returns with a different structure for empty parameter set return [] + except OMCSessionException: + raise def getParameterValue(self, className, parameterName): try: return self._ask(question='getParameterValue', opt=[className, parameterName]) except pyparsing.ParseException as ex: - logger.warning('OMTypedParser error: %s', ex.msg) + logger.warning("Method 'getParameterValue(%s, %s)' failed; OMTypedParser error: %s", + className, parameterName, ex.msg) return "" + except OMCSessionException: + raise def getComponentModifierNames(self, className, componentName): return self._ask(question='getComponentModifierNames', opt=[className, componentName]) From de07e83a39cb6f63d1f24ef69146e232804a064a Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 30 Apr 2025 19:14:28 +0200 Subject: [PATCH 08/11] [OMCSessionCmd] improve code for getClassNames() --- OMPython/OMCSession.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 44f33c08..1e314be5 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -265,14 +265,12 @@ def getNthComponentModification(self, className, comp_id): # end getClassNames; def getClassNames(self, className=None, recursive=False, qualified=False, sort=False, builtin=False, showProtected=False): - value = self._ask(question='getClassNames', - opt=[className] if className else [] + [f'recursive={str(recursive).lower()}', - f'qualified={str(qualified).lower()}', - f'sort={str(sort).lower()}', - f'builtin={str(builtin).lower()}', - f'showProtected={str(showProtected).lower()}'] - ) - return value + opt = [className] if className else [] + [f'recursive={str(recursive).lower()}', + f'qualified={str(qualified).lower()}', + f'sort={str(sort).lower()}', + f'builtin={str(builtin).lower()}', + f'showProtected={str(showProtected).lower()}'] + return self._ask(question='getClassNames', opt=opt) class OMCSessionZMQ: From aab287b1ab97f1b064297e08ed58a7c5148e4736 Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 8 May 2025 22:23:15 +0200 Subject: [PATCH 09/11] [ModelicaSystem] fix rebase - OMCSessionBase] => OMCSessionZMQ --- OMPython/ModelicaSystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 31bdc8ed..fa0d2ba1 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -117,7 +117,7 @@ def __init__( variableFilter: Optional[str] = None, customBuildDirectory: Optional[str | os.PathLike] = None, omhome: Optional[str] = None, - session: Optional[OMCSessionBase] = None, + session: Optional[OMCSessionZMQ] = None, build: Optional[bool] = True ): """Initialize, load and build a model. From 1bbe52044140249809e4a4203f878e611c106668 Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 28 Apr 2025 13:45:50 +0200 Subject: [PATCH 10/11] [OMCSessionCmd] cleanup / remove sendExpression() as it is just a wrapper for _session.sendExpression() --- OMPython/OMCSession.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 1e314be5..175c0353 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -90,9 +90,6 @@ def __init__(self, session: OMCSessionZMQ, readonly: Optional[bool] = False): self._readonly = readonly self._omc_cache = {} - def sendExpression(self, command, parsed=True): - return self._session.sendExpression(command=command, parsed=parsed) - def _ask(self, question: str, opt: Optional[list[str]] = None, parsed: Optional[bool] = True): if opt is None: From ffffed17decd63e35af076739e5ba589d7222013 Mon Sep 17 00:00:00 2001 From: syntron Date: Tue, 13 May 2025 10:23:54 +0200 Subject: [PATCH 11/11] [OMCSessionZMQ] verify that _omc is not None in sendExpression() --- OMPython/OMCSession.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 175c0353..98ae8461 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -526,6 +526,9 @@ def sendExpression(self, command, parsed=True): if p is not None: raise OMCSessionException("Process Exited, No connection with OMC. Create a new instance of OMCSessionZMQ!") + if self._omc is None: + raise OMCSessionException("No OMC running. Create a new instance of OMCSessionZMQ!") + attempts = 0 while True: try: