From d0b6fe2627e3831568e09ded7a321976e4006b74 Mon Sep 17 00:00:00 2001 From: syntron Date: Sun, 23 Nov 2025 12:51:43 +0100 Subject: [PATCH 01/20] rename classes: *Process* => *Session* --- OMPython/ModelicaSystem.py | 14 +++++++------- OMPython/OMCSession.py | 32 ++++++++++++++++---------------- OMPython/__init__.py | 20 ++++++++++---------- tests/test_ModelicaSystem.py | 2 +- tests/test_ModelicaSystemDoE.py | 2 +- tests/test_OMCPath.py | 6 +++--- tests/test_ZMQ.py | 4 ++-- tests/test_docker.py | 6 +++--- 8 files changed, 43 insertions(+), 43 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index d6a3d6546..748550a67 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -52,8 +52,8 @@ OMCSessionException, OMCSessionRunData, OMCSessionZMQ, - OMCProcess, - OMCProcessLocal, + OMCSession, + OMCSessionLocal, OMCPath, ) @@ -333,7 +333,7 @@ def __init__( command_line_options: Optional[list[str]] = None, work_directory: Optional[str | os.PathLike] = None, omhome: Optional[str] = None, - omc_process: Optional[OMCProcess] = None, + omc_process: Optional[OMCSession] = None, ) -> None: """Create a ModelicaSystem instance. To define the model use model() or convertFmu2Mo(). @@ -461,13 +461,13 @@ def model( if model_file is not None: file_path = pathlib.Path(model_file) # special handling for OMCProcessLocal - consider a relative path - if isinstance(self._session.omc_process, OMCProcessLocal) and not file_path.is_absolute(): + if isinstance(self._session.omc_process, OMCSessionLocal) and not file_path.is_absolute(): file_path = pathlib.Path.cwd() / file_path if not file_path.is_file(): raise IOError(f"Model file {file_path} does not exist!") self._file_name = self.getWorkDirectory() / file_path.name - if (isinstance(self._session.omc_process, OMCProcessLocal) + if (isinstance(self._session.omc_process, OMCSessionLocal) and file_path.as_posix() == self._file_name.as_posix()): pass elif self._file_name.is_file(): @@ -1197,7 +1197,7 @@ def plot( plot is created by OMC which needs access to the local display. This is not the case for docker and WSL. """ - if not isinstance(self._session.omc_process, OMCProcessLocal): + if not isinstance(self._session.omc_process, OMCSessionLocal): raise ModelicaSystemError("Plot is using the OMC plot functionality; " "thus, it is only working if OMC is running locally!") @@ -1954,7 +1954,7 @@ def __init__( variable_filter: Optional[str] = None, work_directory: Optional[str | os.PathLike] = None, omhome: Optional[str] = None, - omc_process: Optional[OMCProcess] = None, + omc_process: Optional[OMCSession] = None, # simulation specific input # TODO: add more settings (simulation options, input options, ...) simargs: Optional[dict[str, Optional[str | dict[str, str] | numbers.Number]]] = None, diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index ee92ce9ff..a01fdedbc 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -293,7 +293,7 @@ class OMCPathReal(pathlib.PurePosixPath): errors as well as usage on a Windows system due to slightly different definitions (PureWindowsPath). """ - def __init__(self, *path, session: OMCProcess) -> None: + def __init__(self, *path, session: OMCSession) -> None: super().__init__(*path) self._session = session @@ -322,7 +322,7 @@ def is_absolute(self): Check if the path is an absolute path considering the possibility that we are running locally on Windows. This case needs special handling as the definition of is_absolute() differs. """ - if isinstance(self._session, OMCProcessLocal) and platform.system() == 'Windows': + if isinstance(self._session, OMCSessionLocal) and platform.system() == 'Windows': return pathlib.PureWindowsPath(self.as_posix()).is_absolute() return super().is_absolute() @@ -546,7 +546,7 @@ def __init__( self, timeout: float = 10.00, omhome: Optional[str] = None, - omc_process: Optional[OMCProcess] = None, + omc_process: Optional[OMCSession] = None, ) -> None: """ Initialisation for OMCSessionZMQ @@ -557,8 +557,8 @@ def __init__( stacklevel=2) if omc_process is None: - omc_process = OMCProcessLocal(omhome=omhome, timeout=timeout) - elif not isinstance(omc_process, OMCProcess): + omc_process = OMCSessionLocal(omhome=omhome, timeout=timeout) + elif not isinstance(omc_process, OMCSession): raise OMCSessionException("Invalid definition of the OMC process!") self.omc_process = omc_process @@ -570,7 +570,7 @@ def escape_str(value: str) -> str: """ Escape a string such that it can be used as string within OMC expressions, i.e. escape all double quotes. """ - return OMCProcess.escape_str(value=value) + return OMCSession.escape_str(value=value) def omcpath(self, *path) -> OMCPath: """ @@ -599,7 +599,7 @@ def run_model_executable(cmd_run_data: OMCSessionRunData) -> int: Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to keep instances of over classes around. """ - return OMCProcess.run_model_executable(cmd_run_data=cmd_run_data) + return OMCSession.run_model_executable(cmd_run_data=cmd_run_data) def execute(self, command: str): return self.omc_process.execute(command=command) @@ -641,7 +641,7 @@ def __call__(cls, *args, **kwargs): return obj -class OMCProcessMeta(abc.ABCMeta, PostInitCaller): +class OMCSessionMeta(abc.ABCMeta, PostInitCaller): """ Helper class to get a combined metaclass of ABCMeta and PostInitCaller. @@ -650,7 +650,7 @@ class OMCProcessMeta(abc.ABCMeta, PostInitCaller): """ -class OMCProcess(metaclass=OMCProcessMeta): +class OMCSession(metaclass=OMCSessionMeta): """ Base class for an OMC session. This class contains common functionality for all OMC sessions. @@ -771,7 +771,7 @@ def omcpath(self, *path) -> OMCPath: # fallback solution for Python < 3.12; a modified pathlib.Path object is used as OMCPath replacement if sys.version_info < (3, 12): - if isinstance(self, OMCProcessLocal): + if isinstance(self, OMCSessionLocal): # noinspection PyArgumentList return OMCPath(*path) raise OMCSessionException("OMCPath is supported for Python < 3.12 only if OMCProcessLocal is used!") @@ -1040,7 +1040,7 @@ def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunD raise NotImplementedError("This method must be implemented in subclasses!") -class OMCProcessPort(OMCProcess): +class OMCSessionPort(OMCSession): """ OMCProcess implementation which uses a port to connect to an already running OMC server. """ @@ -1059,7 +1059,7 @@ def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunD raise OMCSessionException("OMCProcessPort does not support omc_run_data_update()!") -class OMCProcessLocal(OMCProcess): +class OMCSessionLocal(OMCSession): """ OMCProcess implementation which runs the OMC server locally on the machine (Linux / Windows). """ @@ -1185,7 +1185,7 @@ def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunD return omc_run_data_copy -class OMCProcessDockerHelper(OMCProcess): +class OMCSessionDockerHelper(OMCSession): """ Base class for OMCProcess implementations which run the OMC server in a Docker container. """ @@ -1322,7 +1322,7 @@ def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunD return omc_run_data_copy -class OMCProcessDocker(OMCProcessDockerHelper): +class OMCSessionDocker(OMCSessionDockerHelper): """ OMC process running in a Docker container. """ @@ -1465,7 +1465,7 @@ def _docker_omc_start(self) -> Tuple[subprocess.Popen, DockerPopen, str]: return omc_process, docker_process, docker_cid -class OMCProcessDockerContainer(OMCProcessDockerHelper): +class OMCSessionDockerContainer(OMCSessionDockerHelper): """ OMC process running in a Docker container (by container ID). """ @@ -1558,7 +1558,7 @@ def _docker_omc_start(self) -> Tuple[subprocess.Popen, DockerPopen]: return omc_process, docker_process -class OMCProcessWSL(OMCProcess): +class OMCSessionWSL(OMCSession): """ OMC process running in Windows Subsystem for Linux (WSL). """ diff --git a/OMPython/__init__.py b/OMPython/__init__.py index 7d571a9b0..8831e57a3 100644 --- a/OMPython/__init__.py +++ b/OMPython/__init__.py @@ -48,11 +48,11 @@ OMCSessionException, OMCSessionRunData, OMCSessionZMQ, - OMCProcessPort, - OMCProcessLocal, - OMCProcessDocker, - OMCProcessDockerContainer, - OMCProcessWSL, + OMCSessionPort, + OMCSessionLocal, + OMCSessionDocker, + OMCSessionDockerContainer, + OMCSessionWSL, ) # global names imported if import 'from OMPython import *' is used @@ -67,9 +67,9 @@ 'OMCSessionException', 'OMCSessionRunData', 'OMCSessionZMQ', - 'OMCProcessPort', - 'OMCProcessLocal', - 'OMCProcessDocker', - 'OMCProcessDockerContainer', - 'OMCProcessWSL', + 'OMCSessionPort', + 'OMCSessionLocal', + 'OMCSessionDocker', + 'OMCSessionDockerContainer', + 'OMCSessionWSL', ] diff --git a/tests/test_ModelicaSystem.py b/tests/test_ModelicaSystem.py index 8567c426f..352006b5f 100644 --- a/tests/test_ModelicaSystem.py +++ b/tests/test_ModelicaSystem.py @@ -155,7 +155,7 @@ def test_customBuildDirectory(tmp_path, model_firstorder): @skip_on_windows @skip_python_older_312 def test_getSolutions_docker(model_firstorder): - omcp = OMPython.OMCProcessDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") + omcp = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") omc = OMPython.OMCSessionZMQ(omc_process=omcp) mod = OMPython.ModelicaSystem( diff --git a/tests/test_ModelicaSystemDoE.py b/tests/test_ModelicaSystemDoE.py index f9d700118..ef94a42ea 100644 --- a/tests/test_ModelicaSystemDoE.py +++ b/tests/test_ModelicaSystemDoE.py @@ -69,7 +69,7 @@ def test_ModelicaSystemDoE_local(tmp_path, model_doe, param_doe): @skip_on_windows @skip_python_older_312 def test_ModelicaSystemDoE_docker(tmp_path, model_doe, param_doe): - omcp = OMPython.OMCProcessDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") + omcp = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") omc = OMPython.OMCSessionZMQ(omc_process=omcp) assert omc.sendExpression("getVersion()") == "OpenModelica 1.25.0" diff --git a/tests/test_OMCPath.py b/tests/test_OMCPath.py index 4a0532870..b37e7c633 100644 --- a/tests/test_OMCPath.py +++ b/tests/test_OMCPath.py @@ -24,7 +24,7 @@ def test_OMCPath_OMCSessionZMQ(): def test_OMCPath_OMCProcessLocal(): - omp = OMPython.OMCProcessLocal() + omp = OMPython.OMCSessionLocal() om = OMPython.OMCSessionZMQ(omc_process=omp) _run_OMCPath_checks(om) @@ -35,7 +35,7 @@ def test_OMCPath_OMCProcessLocal(): @skip_on_windows @skip_python_older_312 def test_OMCPath_OMCProcessDocker(): - omcp = OMPython.OMCProcessDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") + omcp = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") om = OMPython.OMCSessionZMQ(omc_process=omcp) assert om.sendExpression("getVersion()") == "OpenModelica 1.25.0" @@ -48,7 +48,7 @@ def test_OMCPath_OMCProcessDocker(): @pytest.mark.skip(reason="Not able to run WSL on github") @skip_python_older_312 def test_OMCPath_OMCProcessWSL(): - omcp = OMPython.OMCProcessWSL( + omcp = OMPython.OMCSessionWSL( wsl_omc='omc', wsl_user='omc', timeout=30.0, diff --git a/tests/test_ZMQ.py b/tests/test_ZMQ.py index 45d517cdf..ba101560d 100644 --- a/tests/test_ZMQ.py +++ b/tests/test_ZMQ.py @@ -46,7 +46,7 @@ def test_execute(om): def test_omcprocessport_execute(om): port = om.omc_process.get_port() - omcp = OMPython.OMCProcessPort(omc_port=port) + omcp = OMPython.OMCSessionPort(omc_port=port) # run 1 om1 = OMPython.OMCSessionZMQ(omc_process=omcp) @@ -62,7 +62,7 @@ def test_omcprocessport_execute(om): def test_omcprocessport_simulate(om, model_time_str): port = om.omc_process.get_port() - omcp = OMPython.OMCProcessPort(omc_port=port) + omcp = OMPython.OMCSessionPort(omc_port=port) om = OMPython.OMCSessionZMQ(omc_process=omcp) assert om.sendExpression(f'loadString("{model_time_str}")') is True diff --git a/tests/test_docker.py b/tests/test_docker.py index 8d68f11f0..025c48e31 100644 --- a/tests/test_docker.py +++ b/tests/test_docker.py @@ -10,15 +10,15 @@ @skip_on_windows def test_docker(): - omcp = OMPython.OMCProcessDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") + omcp = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") om = OMPython.OMCSessionZMQ(omc_process=omcp) assert om.sendExpression("getVersion()") == "OpenModelica 1.25.0" - omcpInner = OMPython.OMCProcessDockerContainer(dockerContainer=omcp.get_docker_container_id()) + omcpInner = OMPython.OMCSessionDockerContainer(dockerContainer=omcp.get_docker_container_id()) omInner = OMPython.OMCSessionZMQ(omc_process=omcpInner) assert omInner.sendExpression("getVersion()") == "OpenModelica 1.25.0" - omcp2 = OMPython.OMCProcessDocker(docker="openmodelica/openmodelica:v1.25.0-minimal", port=11111) + omcp2 = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal", port=11111) om2 = OMPython.OMCSessionZMQ(omc_process=omcp2) assert om2.sendExpression("getVersion()") == "OpenModelica 1.25.0" From e4fd72563f67dc2fc70e2983ce19a20bb8fa9007 Mon Sep 17 00:00:00 2001 From: syntron Date: Sun, 23 Nov 2025 12:53:15 +0100 Subject: [PATCH 02/20] [ModelicaSystem*] omc_process => session --- OMPython/ModelicaSystem.py | 28 ++++++++++++++-------------- tests/test_ModelicaSystem.py | 2 +- tests/test_ModelicaSystemCmd.py | 2 +- tests/test_ModelicaSystemDoE.py | 2 +- tests/test_OMSessionCmd.py | 2 +- tests/test_optimization.py | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 748550a67..858667923 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -333,7 +333,7 @@ def __init__( command_line_options: Optional[list[str]] = None, work_directory: Optional[str | os.PathLike] = None, omhome: Optional[str] = None, - omc_process: Optional[OMCSession] = None, + session: Optional[OMCSession] = None, ) -> None: """Create a ModelicaSystem instance. To define the model use model() or convertFmu2Mo(). @@ -345,7 +345,7 @@ def __init__( files like the model executable. If left unspecified, a tmp directory will be created. omhome: path to OMC to be used when creating the OMC session (see OMCSessionZMQ). - omc_process: definition of a (local) OMC process to be used. If + session: definition of a (local) OMC session to be used. If unspecified, a new local session will be created. """ @@ -373,8 +373,8 @@ def __init__( self._linearized_outputs: list[str] = [] # linearization output list self._linearized_states: list[str] = [] # linearization states list - if omc_process is not None: - self._session = OMCSessionZMQ(omc_process=omc_process) + if session is not None: + self._session = OMCSessionZMQ(omc_process=session) else: self._session = OMCSessionZMQ(omhome=omhome) @@ -482,7 +482,7 @@ def model( if build: self.buildModel(variable_filter) - def session(self) -> OMCSessionZMQ: + def get_session(self) -> OMCSessionZMQ: """ Return the OMC session used for this class. """ @@ -1954,7 +1954,7 @@ def __init__( variable_filter: Optional[str] = None, work_directory: Optional[str | os.PathLike] = None, omhome: Optional[str] = None, - omc_process: Optional[OMCSession] = None, + session: Optional[OMCSession] = None, # simulation specific input # TODO: add more settings (simulation options, input options, ...) simargs: Optional[dict[str, Optional[str | dict[str, str] | numbers.Number]]] = None, @@ -1974,7 +1974,7 @@ def __init__( command_line_options=command_line_options, work_directory=work_directory, omhome=omhome, - omc_process=omc_process, + session=session, ) self._mod.model( model_file=model_file, @@ -1988,9 +1988,9 @@ def __init__( self._simargs = simargs if resultpath is None: - self._resultpath = self.session().omcpath_tempdir() + self._resultpath = self.get_session().omcpath_tempdir() else: - self._resultpath = self.session().omcpath(resultpath) + self._resultpath = self.get_session().omcpath(resultpath) if not self._resultpath.is_dir(): raise ModelicaSystemError("Argument resultpath must be set to a valid path within the environment used " f"for the OpenModelica session: {resultpath}!") @@ -2003,11 +2003,11 @@ def __init__( self._doe_def: Optional[dict[str, dict[str, Any]]] = None self._doe_cmd: Optional[dict[str, OMCSessionRunData]] = None - def session(self) -> OMCSessionZMQ: + def get_session(self) -> OMCSessionZMQ: """ Return the OMC session used for this class. """ - return self._mod.session() + return self._mod.get_session() def prepare(self) -> int: """ @@ -2046,7 +2046,7 @@ def prepare(self) -> int: pk_value = pc_structure[idx_structure] if isinstance(pk_value, str): - pk_value_str = self.session().escape_str(pk_value) + pk_value_str = self.get_session().escape_str(pk_value) expression = f"setParameterValue({self._model_name}, {pk_structure}, \"{pk_value_str}\")" elif isinstance(pk_value, bool): pk_value_bool_str = "true" if pk_value else "false" @@ -2167,12 +2167,12 @@ def worker(worker_id, task_queue): raise ModelicaSystemError("Missing simulation definition!") resultfile = cmd_definition.cmd_result_path - resultpath = self.session().omcpath(resultfile) + resultpath = self.get_session().omcpath(resultfile) logger.info(f"[Worker {worker_id}] Performing task: {resultpath.name}") try: - returncode = self.session().run_model_executable(cmd_run_data=cmd_definition) + returncode = self.get_session().run_model_executable(cmd_run_data=cmd_definition) logger.info(f"[Worker {worker_id}] Simulation {resultpath.name} " f"finished with return code: {returncode}") except ModelicaSystemError as ex: diff --git a/tests/test_ModelicaSystem.py b/tests/test_ModelicaSystem.py index 352006b5f..dcc55d0ba 100644 --- a/tests/test_ModelicaSystem.py +++ b/tests/test_ModelicaSystem.py @@ -159,7 +159,7 @@ def test_getSolutions_docker(model_firstorder): omc = OMPython.OMCSessionZMQ(omc_process=omcp) mod = OMPython.ModelicaSystem( - omc_process=omc.omc_process, + session=omc.omc_process, ) mod.model( model_file=model_firstorder, diff --git a/tests/test_ModelicaSystemCmd.py b/tests/test_ModelicaSystemCmd.py index 7eaf08ba3..2480aad97 100644 --- a/tests/test_ModelicaSystemCmd.py +++ b/tests/test_ModelicaSystemCmd.py @@ -24,7 +24,7 @@ def mscmd_firstorder(model_firstorder): model_name="M", ) mscmd = OMPython.ModelicaSystemCmd( - session=mod.session(), + session=mod.get_session(), runpath=mod.getWorkDirectory(), modelname=mod._model_name, ) diff --git a/tests/test_ModelicaSystemDoE.py b/tests/test_ModelicaSystemDoE.py index ef94a42ea..79c6e62d8 100644 --- a/tests/test_ModelicaSystemDoE.py +++ b/tests/test_ModelicaSystemDoE.py @@ -77,7 +77,7 @@ def test_ModelicaSystemDoE_docker(tmp_path, model_doe, param_doe): model_file=model_doe, model_name="M", parameters=param_doe, - omc_process=omcp, + session=omcp, simargs={"override": {'stopTime': 1.0}}, ) diff --git a/tests/test_OMSessionCmd.py b/tests/test_OMSessionCmd.py index be02136ad..2de03a5af 100644 --- a/tests/test_OMSessionCmd.py +++ b/tests/test_OMSessionCmd.py @@ -13,7 +13,7 @@ def test_isPackage2(): model_name="Modelica.Electrical.Analog.Examples.CauerLowPassAnalog", libraries=["Modelica"], ) - omccmd = OMPython.OMCSessionCmd(session=mod.session()) + omccmd = OMPython.OMCSessionCmd(session=mod.get_session()) assert omccmd.isPackage('Modelica') diff --git a/tests/test_optimization.py b/tests/test_optimization.py index be6945f37..d74942817 100644 --- a/tests/test_optimization.py +++ b/tests/test_optimization.py @@ -56,7 +56,7 @@ def test_optimization_example(tmp_path): r = mod.optimize() # it is necessary to specify resultfile, otherwise it wouldn't find it. resultfile_str = r["resultFile"] - resultfile_omcpath = mod.session().omcpath(resultfile_str) + resultfile_omcpath = mod.get_session().omcpath(resultfile_str) time, f, v = mod.getSolutions( varList=["time", "f", "v"], resultfile=resultfile_omcpath, From fa448ea3a37d3f790539683a1e25fb6edceceaa0 Mon Sep 17 00:00:00 2001 From: syntron Date: Sun, 23 Nov 2025 13:12:08 +0100 Subject: [PATCH 03/20] [OMCSession*] fix docstrings --- OMPython/OMCSession.py | 57 +++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index a01fdedbc..ee349ed26 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -286,8 +286,8 @@ def getClassNames(self, className=None, recursive=False, qualified=False, sort=F class OMCPathReal(pathlib.PurePosixPath): """ - Implementation of a basic (PurePosix)Path object which uses OMC as backend. The connection to OMC is provided via a - OMCSessionZMQ session object. + Implementation of a basic (PurePosix)Path object which uses OMC as backend. The connection to OMC is provided via an + instances of OMCSession* classes. PurePosixPath is selected to cover usage of OMC in docker or via WSL. Usage of specialised function could result in errors as well as usage on a Windows system due to slightly different definitions (PureWindowsPath). @@ -301,7 +301,7 @@ def with_segments(self, *pathsegments): """ Create a new OMCPath object with the given path segments. - The original definition of Path is overridden to ensure session is set. + The original definition of Path is overridden to ensure the OMC session is set. """ return type(self)(*pathsegments, session=self._session) @@ -539,7 +539,7 @@ def get_cmd(self) -> list[str]: class OMCSessionZMQ: """ - This class is handling an OMC session. It is a compatibility class for the new schema using OMCProcess* classes. + This class is a compatibility layer for the new schema using OMCSession* classes. """ def __init__( @@ -574,7 +574,7 @@ def escape_str(value: str) -> str: def omcpath(self, *path) -> OMCPath: """ - Create an OMCPath object based on the given path segments and the current OMC session. + Create an OMCPath object based on the given path segments and the current OMC process definition. """ return self.omc_process.omcpath(*path) @@ -652,23 +652,22 @@ class OMCSessionMeta(abc.ABCMeta, PostInitCaller): class OMCSession(metaclass=OMCSessionMeta): """ - Base class for an OMC session. This class contains common functionality for all OMC sessions. + Base class for an OMC session started via ZMQ. This class contains common functionality for all variants of an + OMC session definition. The main method is sendExpression() which is used to send commands to the OMC process. - The class expects an OMCProcess* on initialisation. It defines the type of OMC process to use: + The following variants are defined: - * OMCProcessLocal + * OMCSessionLocal - * OMCProcessPort + * OMCSessionPort - * OMCProcessDocker + * OMCSessionDocker - * OMCProcessDockerContainer + * OMCSessionDockerContainer - * OMCProcessWSL - - If no OMC process is defined, a local OMC process is initialized. + * OMCSessionWSL """ def __init__( @@ -677,12 +676,12 @@ def __init__( **kwargs, ) -> None: """ - Initialisation for OMCProcess + Initialisation for OMCSession """ # store variables self._timeout = timeout - # generate a random string for this session + # generate a random string for this instance of OMC self._random_string = uuid.uuid4().hex # get a temporary directory self._temp_dir = pathlib.Path(tempfile.gettempdir()) @@ -766,7 +765,7 @@ def escape_str(value: str) -> str: def omcpath(self, *path) -> OMCPath: """ - Create an OMCPath object based on the given path segments and the current OMC session. + Create an OMCPath object based on the given path segments and the current OMCSession* class. """ # fallback solution for Python < 3.12; a modified pathlib.Path object is used as OMCPath replacement @@ -774,7 +773,7 @@ def omcpath(self, *path) -> OMCPath: if isinstance(self, OMCSessionLocal): # noinspection PyArgumentList return OMCPath(*path) - raise OMCSessionException("OMCPath is supported for Python < 3.12 only if OMCProcessLocal is used!") + raise OMCSessionException("OMCPath is supported for Python < 3.12 only if OMCSessionLocal is used!") return OMCPath(*path, session=self) def omcpath_tempdir(self, tempdir_base: Optional[OMCPath] = None) -> OMCPath: @@ -868,7 +867,7 @@ def sendExpression(self, command: str, parsed: bool = True) -> Any: timeout = 1.0 if self._omc_zmq is None: - raise OMCSessionException("No OMC running. Please create a new instance of OMCProcess!") + raise OMCSessionException("No OMC running. Please create a new instance of OMCSession!") logger.debug("sendExpression(%r, parsed=%r)", command, parsed) @@ -998,7 +997,7 @@ def sendExpression(self, command: str, parsed: bool = True) -> Any: def get_port(self) -> Optional[str]: """ - Get the port to connect to the OMC process. + Get the port to connect to the OMC session. """ if not isinstance(self._omc_port, str): raise OMCSessionException(f"Invalid port to connect to OMC process: {self._omc_port}") @@ -1030,7 +1029,7 @@ def _get_portfile_path(self) -> Optional[pathlib.Path]: @abc.abstractmethod def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunData: """ - Update the OMCSessionRunData object based on the selected OMCProcess implementation. + Update the OMCSessionRunData object based on the selected OMCSession implementation. The main point is the definition of OMCSessionRunData.cmd_model_executable which contains the specific command to run depending on the selected system. @@ -1042,7 +1041,7 @@ def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunD class OMCSessionPort(OMCSession): """ - OMCProcess implementation which uses a port to connect to an already running OMC server. + OMCSession implementation which uses a port to connect to an already running OMC server. """ def __init__( @@ -1054,14 +1053,14 @@ def __init__( def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunData: """ - Update the OMCSessionRunData object based on the selected OMCProcess implementation. + Update the OMCSessionRunData object based on the selected OMCSession implementation. """ - raise OMCSessionException("OMCProcessPort does not support omc_run_data_update()!") + raise OMCSessionException("OMCSessionPort does not support omc_run_data_update()!") class OMCSessionLocal(OMCSession): """ - OMCProcess implementation which runs the OMC server locally on the machine (Linux / Windows). + OMCSession implementation which runs the OMC server locally on the machine (Linux / Windows). """ def __init__( @@ -1144,7 +1143,7 @@ def _omc_port_get(self) -> str: def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunData: """ - Update the OMCSessionRunData object based on the selected OMCProcess implementation. + Update the OMCSessionRunData object based on the selected OMCSession implementation. """ # create a copy of the data omc_run_data_copy = dataclasses.replace(omc_run_data) @@ -1187,7 +1186,7 @@ def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunD class OMCSessionDockerHelper(OMCSession): """ - Base class for OMCProcess implementations which run the OMC server in a Docker container. + Base class for OMCSession implementations which run the OMC server in a Docker container. """ def __init__( @@ -1301,7 +1300,7 @@ def get_docker_container_id(self) -> str: def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunData: """ - Update the OMCSessionRunData object based on the selected OMCProcess implementation. + Update the OMCSessionRunData object based on the selected OMCSession implementation. """ omc_run_data_copy = dataclasses.replace(omc_run_data) @@ -1646,7 +1645,7 @@ def _omc_port_get(self) -> str: def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunData: """ - Update the OMCSessionRunData object based on the selected OMCProcess implementation. + Update the OMCSessionRunData object based on the selected OMCSession implementation. """ omc_run_data_copy = dataclasses.replace(omc_run_data) From fc04903002ac2ad2d6b4bdb02281e3ab5650d55a Mon Sep 17 00:00:00 2001 From: syntron Date: Sun, 23 Nov 2025 12:54:39 +0100 Subject: [PATCH 04/20] [ModelicaSystem*] remove dependency on depreciated OMCSessionZMQ --- OMPython/ModelicaSystem.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 858667923..ea71df750 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -51,7 +51,6 @@ from OMPython.OMCSession import ( OMCSessionException, OMCSessionRunData, - OMCSessionZMQ, OMCSession, OMCSessionLocal, OMCPath, @@ -127,7 +126,7 @@ class ModelicaSystemCmd: def __init__( self, - session: OMCSessionZMQ, + session: OMCSession, runpath: OMCPath, modelname: Optional[str] = None, ) -> None: @@ -325,7 +324,7 @@ def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, Any] | n class ModelicaSystem: """ - Class to simulate a Modelica model using OpenModelica via OMCSessionZMQ. + Class to simulate a Modelica model using OpenModelica via OMCSession. """ def __init__( @@ -344,7 +343,7 @@ def __init__( work_directory: Path to a directory to be used for temporary files like the model executable. If left unspecified, a tmp directory will be created. - omhome: path to OMC to be used when creating the OMC session (see OMCSessionZMQ). + omhome: path to OMC to be used when creating the OMC session (see OMCSession). session: definition of a (local) OMC session to be used. If unspecified, a new local session will be created. """ @@ -374,9 +373,9 @@ def __init__( self._linearized_states: list[str] = [] # linearization states list if session is not None: - self._session = OMCSessionZMQ(omc_process=session) + self._session = session else: - self._session = OMCSessionZMQ(omhome=omhome) + self._session = OMCSession(omhome=omhome) # set commandLineOptions using default values or the user defined list if command_line_options is None: @@ -461,13 +460,13 @@ def model( if model_file is not None: file_path = pathlib.Path(model_file) # special handling for OMCProcessLocal - consider a relative path - if isinstance(self._session.omc_process, OMCSessionLocal) and not file_path.is_absolute(): + if isinstance(self._session, OMCSessionLocal) and not file_path.is_absolute(): file_path = pathlib.Path.cwd() / file_path if not file_path.is_file(): raise IOError(f"Model file {file_path} does not exist!") self._file_name = self.getWorkDirectory() / file_path.name - if (isinstance(self._session.omc_process, OMCSessionLocal) + if (isinstance(self._session, OMCSessionLocal) and file_path.as_posix() == self._file_name.as_posix()): pass elif self._file_name.is_file(): @@ -482,7 +481,7 @@ def model( if build: self.buildModel(variable_filter) - def get_session(self) -> OMCSessionZMQ: + def get_session(self) -> OMCSession: """ Return the OMC session used for this class. """ @@ -1197,7 +1196,7 @@ def plot( plot is created by OMC which needs access to the local display. This is not the case for docker and WSL. """ - if not isinstance(self._session.omc_process, OMCSessionLocal): + if not isinstance(self._session, OMCSessionLocal): raise ModelicaSystemError("Plot is using the OMC plot functionality; " "thus, it is only working if OMC is running locally!") @@ -2003,7 +2002,7 @@ def __init__( self._doe_def: Optional[dict[str, dict[str, Any]]] = None self._doe_cmd: Optional[dict[str, OMCSessionRunData]] = None - def get_session(self) -> OMCSessionZMQ: + def get_session(self) -> OMCSession: """ Return the OMC session used for this class. """ From 6313dd63aaf7bc60b722316dbb24db17f2c816c3 Mon Sep 17 00:00:00 2001 From: syntron Date: Sun, 23 Nov 2025 13:05:30 +0100 Subject: [PATCH 05/20] [OMCSessionCmd] use OMCSession (old OMCProcess) --- OMPython/OMCSession.py | 6 +++--- tests/test_OMSessionCmd.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index ee349ed26..a60631e4d 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -99,9 +99,9 @@ class OMCSessionCmd: Implementation of Open Modelica Compiler API functions. Depreciated! """ - def __init__(self, session: OMCSessionZMQ, readonly: bool = False): - if not isinstance(session, OMCSessionZMQ): - raise OMCSessionException("Invalid session definition!") + def __init__(self, session: OMCSession, readonly: bool = False): + if not isinstance(session, OMCSession): + raise OMCSessionException("Invalid OMC process definition!") self._session = session self._readonly = readonly self._omc_cache: dict[tuple[str, bool], Any] = {} diff --git a/tests/test_OMSessionCmd.py b/tests/test_OMSessionCmd.py index 2de03a5af..bff4afde0 100644 --- a/tests/test_OMSessionCmd.py +++ b/tests/test_OMSessionCmd.py @@ -3,7 +3,7 @@ def test_isPackage(): omczmq = OMPython.OMCSessionZMQ() - omccmd = OMPython.OMCSessionCmd(session=omczmq) + omccmd = OMPython.OMCSessionCmd(session=omczmq.omc_process) assert not omccmd.isPackage('Modelica') From 3ccb5fd39164e3cd8406e42e627e87271acb7a05 Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 26 Nov 2025 20:37:04 +0100 Subject: [PATCH 06/20] [ModelicaSystem] fix initialisation of default OMCSession - use *Local --- OMPython/ModelicaSystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index ea71df750..1a5edff5e 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -375,7 +375,7 @@ def __init__( if session is not None: self._session = session else: - self._session = OMCSession(omhome=omhome) + self._session = OMCSessionLocal(omhome=omhome) # set commandLineOptions using default values or the user defined list if command_line_options is None: From fa9785014c3d33fdda2a14e84f8fb6fec0bbd525 Mon Sep 17 00:00:00 2001 From: syntron Date: Sun, 23 Nov 2025 13:25:50 +0100 Subject: [PATCH 07/20] [unittests] use new definitions / remove OMCSessionZMQ --- tests/test_ArrayDimension.py | 16 ++++----- tests/test_FMIRegression.py | 12 +++---- tests/test_ModelicaSystem.py | 19 +++++----- tests/test_ModelicaSystemDoE.py | 7 ++-- tests/test_OMCPath.py | 43 ++++++++-------------- tests/test_OMSessionCmd.py | 4 +-- tests/test_ZMQ.py | 63 ++++++++++++++++----------------- tests/test_docker.py | 24 +++++-------- 8 files changed, 82 insertions(+), 106 deletions(-) diff --git a/tests/test_ArrayDimension.py b/tests/test_ArrayDimension.py index 13b3c11b9..6e80d53f0 100644 --- a/tests/test_ArrayDimension.py +++ b/tests/test_ArrayDimension.py @@ -2,18 +2,18 @@ def test_ArrayDimension(tmp_path): - omc = OMPython.OMCSessionZMQ() + omcs = OMPython.OMCSessionLocal() - omc.sendExpression(f'cd("{tmp_path.as_posix()}")') + omcs.sendExpression(f'cd("{tmp_path.as_posix()}")') - omc.sendExpression('loadString("model A Integer x[5+1,1+6]; end A;")') - omc.sendExpression("getErrorString()") + omcs.sendExpression('loadString("model A Integer x[5+1,1+6]; end A;")') + omcs.sendExpression("getErrorString()") - result = omc.sendExpression("getComponents(A)") + result = omcs.sendExpression("getComponents(A)") assert result[0][-1] == (6, 7), "array dimension does not match" - omc.sendExpression('loadString("model A Integer y = 5; Integer x[y+1,1+9]; end A;")') - omc.sendExpression("getErrorString()") + omcs.sendExpression('loadString("model A Integer y = 5; Integer x[y+1,1+9]; end A;")') + omcs.sendExpression("getErrorString()") - result = omc.sendExpression("getComponents(A)") + result = omcs.sendExpression("getComponents(A)") assert result[-1][-1] == ('y+1', 10), "array dimension does not match" diff --git a/tests/test_FMIRegression.py b/tests/test_FMIRegression.py index b61b8d497..8a91c5143 100644 --- a/tests/test_FMIRegression.py +++ b/tests/test_FMIRegression.py @@ -7,21 +7,21 @@ def buildModelFMU(modelName): - omc = OMPython.OMCSessionZMQ() + omcs = OMPython.OMCSessionLocal() tempdir = pathlib.Path(tempfile.mkdtemp()) try: - omc.sendExpression(f'cd("{tempdir.as_posix()}")') + omcs.sendExpression(f'cd("{tempdir.as_posix()}")') - omc.sendExpression("loadModel(Modelica)") - omc.sendExpression("getErrorString()") + omcs.sendExpression("loadModel(Modelica)") + omcs.sendExpression("getErrorString()") fileNamePrefix = modelName.split(".")[-1] exp = f'buildModelFMU({modelName}, fileNamePrefix="{fileNamePrefix}")' - fmu = omc.sendExpression(exp) + fmu = omcs.sendExpression(exp) assert os.path.exists(fmu) finally: - del omc + del omcs shutil.rmtree(tempdir, ignore_errors=True) diff --git a/tests/test_ModelicaSystem.py b/tests/test_ModelicaSystem.py index dcc55d0ba..dd0321ec8 100644 --- a/tests/test_ModelicaSystem.py +++ b/tests/test_ModelicaSystem.py @@ -47,14 +47,15 @@ def worker(): ) mod.simulate() mod.convertMo2Fmu(fmuType="me") + for _ in range(10): worker() def test_setParameters(): - omc = OMPython.OMCSessionZMQ() - model_path_str = omc.sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels" - model_path = omc.omcpath(model_path_str) + omcs = OMPython.OMCSessionLocal() + model_path_str = omcs.sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels" + model_path = omcs.omcpath(model_path_str) mod = OMPython.ModelicaSystem() mod.model( model_file=model_path / "BouncingBall.mo", @@ -87,9 +88,9 @@ def test_setParameters(): def test_setSimulationOptions(): - omc = OMPython.OMCSessionZMQ() - model_path_str = omc.sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels" - model_path = omc.omcpath(model_path_str) + omcs = OMPython.OMCSessionLocal() + model_path_str = omcs.sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels" + model_path = omcs.omcpath(model_path_str) mod = OMPython.ModelicaSystem() mod.model( model_file=model_path / "BouncingBall.mo", @@ -155,11 +156,9 @@ def test_customBuildDirectory(tmp_path, model_firstorder): @skip_on_windows @skip_python_older_312 def test_getSolutions_docker(model_firstorder): - omcp = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") - omc = OMPython.OMCSessionZMQ(omc_process=omcp) - + omcs = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") mod = OMPython.ModelicaSystem( - session=omc.omc_process, + session=omcs, ) mod.model( model_file=model_firstorder, diff --git a/tests/test_ModelicaSystemDoE.py b/tests/test_ModelicaSystemDoE.py index 79c6e62d8..0e8d6caae 100644 --- a/tests/test_ModelicaSystemDoE.py +++ b/tests/test_ModelicaSystemDoE.py @@ -69,15 +69,14 @@ def test_ModelicaSystemDoE_local(tmp_path, model_doe, param_doe): @skip_on_windows @skip_python_older_312 def test_ModelicaSystemDoE_docker(tmp_path, model_doe, param_doe): - omcp = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") - omc = OMPython.OMCSessionZMQ(omc_process=omcp) - assert omc.sendExpression("getVersion()") == "OpenModelica 1.25.0" + omcs = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") + assert omcs.sendExpression("getVersion()") == "OpenModelica 1.25.0" doe_mod = OMPython.ModelicaSystemDoE( model_file=model_doe, model_name="M", parameters=param_doe, - session=omcp, + session=omcs, simargs={"override": {'stopTime': 1.0}}, ) diff --git a/tests/test_OMCPath.py b/tests/test_OMCPath.py index b37e7c633..2ea8b8c8d 100644 --- a/tests/test_OMCPath.py +++ b/tests/test_OMCPath.py @@ -15,54 +15,41 @@ ) -def test_OMCPath_OMCSessionZMQ(): - om = OMPython.OMCSessionZMQ() - - _run_OMCPath_checks(om) - - del om - - def test_OMCPath_OMCProcessLocal(): - omp = OMPython.OMCSessionLocal() - om = OMPython.OMCSessionZMQ(omc_process=omp) + omcs = OMPython.OMCSessionLocal() - _run_OMCPath_checks(om) + _run_OMCPath_checks(omcs) - del om + del omcs @skip_on_windows @skip_python_older_312 def test_OMCPath_OMCProcessDocker(): - omcp = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") - om = OMPython.OMCSessionZMQ(omc_process=omcp) - assert om.sendExpression("getVersion()") == "OpenModelica 1.25.0" + omcs = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") + assert omcs.sendExpression("getVersion()") == "OpenModelica 1.25.0" - _run_OMCPath_checks(om) + _run_OMCPath_checks(omcs) - del omcp - del om + del omcs @pytest.mark.skip(reason="Not able to run WSL on github") @skip_python_older_312 def test_OMCPath_OMCProcessWSL(): - omcp = OMPython.OMCSessionWSL( + omcs = OMPython.OMCSessionWSL( wsl_omc='omc', wsl_user='omc', timeout=30.0, ) - om = OMPython.OMCSessionZMQ(omc_process=omcp) - _run_OMCPath_checks(om) + _run_OMCPath_checks(omcs) - del omcp - del om + del omcs -def _run_OMCPath_checks(om: OMPython.OMCSessionZMQ): - p1 = om.omcpath_tempdir() +def _run_OMCPath_checks(omcs: OMPython.OMCSession): + p1 = omcs.omcpath_tempdir() p2 = p1 / 'test' p2.mkdir() assert p2.is_dir() @@ -81,14 +68,14 @@ def _run_OMCPath_checks(om: OMPython.OMCSessionZMQ): def test_OMCPath_write_file(tmpdir): - om = OMPython.OMCSessionZMQ() + omcs = OMPython.OMCSessionLocal() data = "abc # \\t # \" # \\n # xyz" - p1 = om.omcpath_tempdir() + p1 = omcs.omcpath_tempdir() p2 = p1 / 'test.txt' p2.write_text(data=data) assert data == p2.read_text() - del om + del omcs diff --git a/tests/test_OMSessionCmd.py b/tests/test_OMSessionCmd.py index bff4afde0..d3997ecf3 100644 --- a/tests/test_OMSessionCmd.py +++ b/tests/test_OMSessionCmd.py @@ -2,8 +2,8 @@ def test_isPackage(): - omczmq = OMPython.OMCSessionZMQ() - omccmd = OMPython.OMCSessionCmd(session=omczmq.omc_process) + omcs = OMPython.OMCSessionLocal() + omccmd = OMPython.OMCSessionCmd(session=omcs) assert not omccmd.isPackage('Modelica') diff --git a/tests/test_ZMQ.py b/tests/test_ZMQ.py index ba101560d..1302a79da 100644 --- a/tests/test_ZMQ.py +++ b/tests/test_ZMQ.py @@ -14,58 +14,55 @@ def model_time_str(): @pytest.fixture -def om(tmp_path): +def omcs(tmp_path): origDir = pathlib.Path.cwd() os.chdir(tmp_path) - om = OMPython.OMCSessionZMQ() + omcs = OMPython.OMCSessionLocal() os.chdir(origDir) - return om + return omcs -def testHelloWorld(om): - assert om.sendExpression('"HelloWorld!"') == "HelloWorld!" +def testHelloWorld(omcs): + assert omcs.sendExpression('"HelloWorld!"') == "HelloWorld!" -def test_Translate(om, model_time_str): - assert om.sendExpression(model_time_str) == ("M",) - assert om.sendExpression('translateModel(M)') is True +def test_Translate(omcs, model_time_str): + assert omcs.sendExpression(model_time_str) == ("M",) + assert omcs.sendExpression('translateModel(M)') is True -def test_Simulate(om, model_time_str): - assert om.sendExpression(f'loadString("{model_time_str}")') is True - om.sendExpression('res:=simulate(M, stopTime=2.0)') - assert om.sendExpression('res.resultFile') +def test_Simulate(omcs, model_time_str): + assert omcs.sendExpression(f'loadString("{model_time_str}")') is True + omcs.sendExpression('res:=simulate(M, stopTime=2.0)') + assert omcs.sendExpression('res.resultFile') -def test_execute(om): +def test_execute(omcs): with pytest.deprecated_call(): - assert om.execute('"HelloWorld!"') == '"HelloWorld!"\n' - assert om.sendExpression('"HelloWorld!"', parsed=False) == '"HelloWorld!"\n' - assert om.sendExpression('"HelloWorld!"', parsed=True) == 'HelloWorld!' + assert omcs.execute('"HelloWorld!"') == '"HelloWorld!"\n' + assert omcs.sendExpression('"HelloWorld!"', parsed=False) == '"HelloWorld!"\n' + assert omcs.sendExpression('"HelloWorld!"', parsed=True) == 'HelloWorld!' -def test_omcprocessport_execute(om): - port = om.omc_process.get_port() - omcp = OMPython.OMCSessionPort(omc_port=port) +def test_omcprocessport_execute(omcs): + port = omcs.get_port() + omcs2 = OMPython.OMCSessionPort(omc_port=port) # run 1 - om1 = OMPython.OMCSessionZMQ(omc_process=omcp) - assert om1.sendExpression('"HelloWorld!"', parsed=False) == '"HelloWorld!"\n' + assert omcs.sendExpression('"HelloWorld!"', parsed=False) == '"HelloWorld!"\n' # run 2 - om2 = OMPython.OMCSessionZMQ(omc_process=omcp) - assert om2.sendExpression('"HelloWorld!"', parsed=False) == '"HelloWorld!"\n' + assert omcs2.sendExpression('"HelloWorld!"', parsed=False) == '"HelloWorld!"\n' - del om1 - del om2 + del omcs2 -def test_omcprocessport_simulate(om, model_time_str): - port = om.omc_process.get_port() - omcp = OMPython.OMCSessionPort(omc_port=port) +def test_omcprocessport_simulate(omcs, model_time_str): + port = omcs.get_port() + omcs2 = OMPython.OMCSessionPort(omc_port=port) - om = OMPython.OMCSessionZMQ(omc_process=omcp) - assert om.sendExpression(f'loadString("{model_time_str}")') is True - om.sendExpression('res:=simulate(M, stopTime=2.0)') - assert om.sendExpression('res.resultFile') != "" - del om + assert omcs2.sendExpression(f'loadString("{model_time_str}")') is True + omcs2.sendExpression('res:=simulate(M, stopTime=2.0)') + assert omcs2.sendExpression('res.resultFile') != "" + + del omcs2 diff --git a/tests/test_docker.py b/tests/test_docker.py index 025c48e31..f19735990 100644 --- a/tests/test_docker.py +++ b/tests/test_docker.py @@ -10,23 +10,17 @@ @skip_on_windows def test_docker(): - omcp = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") - om = OMPython.OMCSessionZMQ(omc_process=omcp) - assert om.sendExpression("getVersion()") == "OpenModelica 1.25.0" + omcs = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") + assert omcs.sendExpression("getVersion()") == "OpenModelica 1.25.0" - omcpInner = OMPython.OMCSessionDockerContainer(dockerContainer=omcp.get_docker_container_id()) - omInner = OMPython.OMCSessionZMQ(omc_process=omcpInner) - assert omInner.sendExpression("getVersion()") == "OpenModelica 1.25.0" + omcsInner = OMPython.OMCSessionDockerContainer(dockerContainer=omcs.get_docker_container_id()) + assert omcsInner.sendExpression("getVersion()") == "OpenModelica 1.25.0" - omcp2 = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal", port=11111) - om2 = OMPython.OMCSessionZMQ(omc_process=omcp2) - assert om2.sendExpression("getVersion()") == "OpenModelica 1.25.0" + omcs2 = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal", port=11111) + assert omcs2.sendExpression("getVersion()") == "OpenModelica 1.25.0" - del omcp2 - del om2 + del omcs2 - del omcpInner - del omInner + del omcsInner - del omcp - del om + del omcs From 8b19a463ce7967c657801c58eb9187f6441d9fef Mon Sep 17 00:00:00 2001 From: syntron Date: Sun, 23 Nov 2025 17:37:14 +0100 Subject: [PATCH 08/20] [OMCSessionPort] add missing function / catch possible errors OMCSessionPort is a limited version as we do not know how OMC is run. --- OMPython/OMCSession.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index a60631e4d..e21564cb2 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -1051,11 +1051,27 @@ def __init__( super().__init__() self._omc_port = omc_port + @staticmethod + def run_model_executable(cmd_run_data: OMCSessionRunData) -> int: + """ + Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to + keep instances of over classes around. + """ + raise OMCSessionException(f"({self.__class__.__name__}) does not support run_model_executable()!") + + def get_log(self) -> str: + """ + Get the log file content of the OMC session. + """ + log = f"No log available if OMC session is defined by port ({self.__class__.__name__})" + + return log + def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunData: """ Update the OMCSessionRunData object based on the selected OMCSession implementation. """ - raise OMCSessionException("OMCSessionPort does not support omc_run_data_update()!") + raise OMCSessionException(f"({self.__class__.__name__}) does not support omc_run_data_update()!") class OMCSessionLocal(OMCSession): From dd6740663c8335e4d7c0c8252f87d01b766570fc Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 26 Nov 2025 20:42:37 +0100 Subject: [PATCH 09/20] [OMCSessionPort] fix exception message --- OMPython/OMCSession.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index e21564cb2..2d65a327a 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -1057,7 +1057,7 @@ def run_model_executable(cmd_run_data: OMCSessionRunData) -> int: Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to keep instances of over classes around. """ - raise OMCSessionException(f"({self.__class__.__name__}) does not support run_model_executable()!") + raise OMCSessionException("OMCSessionPort does not support run_model_executable()!") def get_log(self) -> str: """ From 331e0a38b664dd4d1277a950956bb59b4ec482d1 Mon Sep 17 00:00:00 2001 From: syntron Date: Sun, 23 Nov 2025 17:37:29 +0100 Subject: [PATCH 10/20] [OMCSession] improve logging --- OMPython/OMCSession.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index a60631e4d..afe4eff90 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -731,8 +731,8 @@ def __del__(self): if isinstance(self._omc_zmq, zmq.Socket): try: self.sendExpression("quit()") - except OMCSessionException: - pass + except OMCSessionException as exc: + logger.warning(f"Exception on sending 'quit()' to OMC: {exc}! Continue nevertheless ...") finally: self._omc_zmq = None @@ -749,7 +749,7 @@ def __del__(self): self._omc_process.wait(timeout=2.0) except subprocess.TimeoutExpired: if self._omc_process: - logger.warning("OMC did not exit after being sent the quit() command; " + logger.warning("OMC did not exit after being sent the 'quit()' command; " "killing the process with pid=%s", self._omc_process.pid) self._omc_process.kill() self._omc_process.wait() From 73a38e5e80680d1e4b23327c2e611bf12bf65ed9 Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 26 Nov 2025 20:21:33 +0100 Subject: [PATCH 11/20] [OMCSession*] define set_timeout() --- OMPython/OMCSession.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index a60631e4d..35ac97579 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -517,8 +517,6 @@ class OMCSessionRunData: cmd_model_executable: Optional[str] = None # additional library search path; this is mainly needed if OMCProcessLocal is run on Windows cmd_library_path: Optional[str] = None - # command timeout - cmd_timeout: Optional[float] = 10.0 # working directory to be used on the *local* system cmd_cwd_local: Optional[str] = None @@ -593,13 +591,12 @@ def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunD """ return self.omc_process.omc_run_data_update(omc_run_data=omc_run_data) - @staticmethod - def run_model_executable(cmd_run_data: OMCSessionRunData) -> int: + def run_model_executable(self, cmd_run_data: OMCSessionRunData) -> int: """ Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to keep instances of over classes around. """ - return OMCSession.run_model_executable(cmd_run_data=cmd_run_data) + return self.omc_process.run_model_executable(cmd_run_data=cmd_run_data) def execute(self, command: str): return self.omc_process.execute(command=command) @@ -756,6 +753,19 @@ def __del__(self): finally: self._omc_process = None + def set_timeout(self, timeout: Optional[float] = None) -> float: + """ + Set the timeout to be used for OMC communication (OMCSession). + + The defined value is set and the current value is returned. If None is provided as argument, nothing is changed. + """ + retval = self._timeout + if timeout is not None: + if timeout <= 0.0: + raise OMCSessionException(f"Invalid timeout value: {timeout}!") + self._timeout = timeout + return retval + @staticmethod def escape_str(value: str) -> str: """ @@ -807,11 +817,9 @@ def omcpath_tempdir(self, tempdir_base: Optional[OMCPath] = None) -> OMCPath: return tempdir - @staticmethod - def run_model_executable(cmd_run_data: OMCSessionRunData) -> int: + def run_model_executable(self, cmd_run_data: OMCSessionRunData) -> int: """ - Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to - keep instances of over classes around. + Run the command defined in cmd_run_data. """ my_env = os.environ.copy() @@ -828,7 +836,7 @@ def run_model_executable(cmd_run_data: OMCSessionRunData) -> int: text=True, env=my_env, cwd=cmd_run_data.cmd_cwd_local, - timeout=cmd_run_data.cmd_timeout, + timeout=self._timeout, check=True, ) stdout = cmdres.stdout.strip() From fe1775e285865ecde381c06ceb5b628177b28834 Mon Sep 17 00:00:00 2001 From: syntron Date: Tue, 25 Nov 2025 22:26:36 +0100 Subject: [PATCH 12/20] [OMCSession*] align all usages of timeout to the same structure --- OMPython/OMCSession.py | 113 +++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 35ac97579..6e954feb4 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -868,34 +868,32 @@ def sendExpression(self, command: str, parsed: bool = True) -> Any: Caller should only check for OMCSessionException. """ - # this is needed if the class is not fully initialized or in the process of deletion - if hasattr(self, '_timeout'): - timeout = self._timeout - else: - timeout = 1.0 - if self._omc_zmq is None: raise OMCSessionException("No OMC running. Please create a new instance of OMCSession!") logger.debug("sendExpression(%r, parsed=%r)", command, parsed) + MAX_RETRIES = 50 attempts = 0 - while True: + while attempts < MAX_RETRIES: + attempts += 1 + try: self._omc_zmq.send_string(str(command), flags=zmq.NOBLOCK) break except zmq.error.Again: pass - attempts += 1 - if attempts >= 50: - # in the deletion process, the content is cleared. Thus, any access to a class attribute must be checked - try: - log_content = self.get_log() - except OMCSessionException: - log_content = 'log not available' - raise OMCSessionException(f"No connection with OMC (timeout={timeout}). " - f"Log-file says: \n{log_content}") - time.sleep(timeout / 50.0) + time.sleep(self._timeout / MAX_RETRIES) + else: + # in the deletion process, the content is cleared. Thus, any access to a class attribute must be checked + try: + log_content = self.get_log() + except OMCSessionException: + log_content = 'log not available' + + logger.error(f"Docker did not start. Log-file says:\n{log_content}") + raise OMCSessionException(f"No connection with OMC (timeout={self._timeout}).") + if command == "quit()": self._omc_zmq.close() self._omc_zmq = None @@ -1124,25 +1122,23 @@ def _omc_port_get(self) -> str: port = None # See if the omc server is running + MAX_RETRIES = 80 attempts = 0 - while True: - omc_portfile_path = self._get_portfile_path() + while attempts < MAX_RETRIES: + attempts += 1 + omc_portfile_path = self._get_portfile_path() if omc_portfile_path is not None and omc_portfile_path.is_file(): # Read the port file with open(file=omc_portfile_path, mode='r', encoding="utf-8") as f_p: port = f_p.readline() break - if port is not None: break - - attempts += 1 - if attempts == 80.0: - raise OMCSessionException(f"OMC Server did not start (timeout={self._timeout}). " - f"Could not open file {omc_portfile_path}. " - f"Log-file says:\n{self.get_log()}") - time.sleep(self._timeout / 80.0) + time.sleep(self._timeout / MAX_RETRIES) + else: + logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") + raise OMCSessionException(f"OMC Server did not start (timeout={self._timeout}).") logger.info(f"Local OMC Server is up and running at ZMQ port {port} " f"pid={self._omc_process.pid if isinstance(self._omc_process, subprocess.Popen) else '?'}") @@ -1224,7 +1220,11 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]: raise NotImplementedError("Docker not supported on win32!") docker_process = None - for _ in range(0, 40): + MAX_RETRIES = 40 + attempts = 0 + while attempts < MAX_RETRIES: + attempts += 1 + docker_top = subprocess.check_output(["docker", "top", docker_cid]).decode().strip() docker_process = None for line in docker_top.split("\n"): @@ -1235,10 +1235,12 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]: except psutil.NoSuchProcess as ex: raise OMCSessionException(f"Could not find PID {docker_top} - " "is this a docker instance spawned without --pid=host?") from ex - if docker_process is not None: break - time.sleep(self._timeout / 40.0) + time.sleep(self._timeout / MAX_RETRIES) + else: + logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") + raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}).") return docker_process @@ -1260,8 +1262,11 @@ def _omc_port_get(self) -> str: raise OMCSessionException(f"Invalid docker container ID: {self._docker_container_id}") # See if the omc server is running + MAX_RETRIES = 80 attempts = 0 - while True: + while attempts < MAX_RETRIES: + attempts += 1 + omc_portfile_path = self._get_portfile_path() if omc_portfile_path is not None: try: @@ -1272,16 +1277,12 @@ def _omc_port_get(self) -> str: port = output.decode().strip() except subprocess.CalledProcessError: pass - if port is not None: break - - attempts += 1 - if attempts == 80.0: - raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}). " - f"Could not open port file {omc_portfile_path}. " - f"Log-file says:\n{self.get_log()}") - time.sleep(self._timeout / 80.0) + time.sleep(self._timeout / MAX_RETRIES) + else: + logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") + raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}).") logger.info(f"Docker based OMC Server is up and running at port {port}") @@ -1449,25 +1450,28 @@ def _docker_omc_start(self) -> Tuple[subprocess.Popen, DockerPopen, str]: raise OMCSessionException(f"Invalid content for docker container ID file path: {docker_cid_file}") docker_cid = None - for _ in range(0, 40): + MAX_RETRIES = 40 + attempts = 0 + while attempts < MAX_RETRIES: + attempts += 1 + try: with open(file=docker_cid_file, mode="r", encoding="utf-8") as fh: docker_cid = fh.read().strip() except IOError: pass - if docker_cid: + if docker_cid is not None: break - time.sleep(self._timeout / 40.0) - - if docker_cid is None: + time.sleep(self._timeout / MAX_RETRIES) + else: logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") raise OMCSessionException(f"Docker did not start (timeout={self._timeout} might be too short " "especially if you did not docker pull the image before this command).") docker_process = self._docker_process_get(docker_cid=docker_cid) if docker_process is None: - raise OMCSessionException(f"Docker top did not contain omc process {self._random_string}. " - f"Log-file says:\n{self.get_log()}") + logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") + raise OMCSessionException(f"Docker top did not contain omc process {self._random_string}.") return omc_process, docker_process, docker_cid @@ -1623,8 +1627,11 @@ def _omc_port_get(self) -> str: port = None # See if the omc server is running + MAX_RETRIES = 80 attempts = 0 - while True: + while attempts < MAX_RETRIES: + attempts += 1 + try: omc_portfile_path = self._get_portfile_path() if omc_portfile_path is not None: @@ -1635,16 +1642,12 @@ def _omc_port_get(self) -> str: port = output.decode().strip() except subprocess.CalledProcessError: pass - if port is not None: break - - attempts += 1 - if attempts == 80.0: - raise OMCSessionException(f"WSL based OMC Server did not start (timeout={self._timeout}). " - f"Could not open port file {omc_portfile_path}. " - f"Log-file says:\n{self.get_log()}") - time.sleep(self._timeout / 80.0) + time.sleep(self._timeout / MAX_RETRIES) + else: + logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") + raise OMCSessionException(f"WSL based OMC Server did not start (timeout={self._timeout}).") logger.info(f"WSL based OMC Server is up and running at ZMQ port {port} " f"pid={self._omc_process.pid if isinstance(self._omc_process, subprocess.Popen) else '?'}") From 938a124263a1d33416d627c61759cba43473602c Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 26 Nov 2025 19:38:48 +0100 Subject: [PATCH 13/20] [OMCSession*] simplify code for timeout loops --- OMPython/OMCSession.py | 73 +++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 6e954feb4..048908c0e 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -753,6 +753,31 @@ def __del__(self): finally: self._omc_process = None + def _timeout_loop( + self, + timeout: Optional[float] = None, + timestep: float = 0.1, + ): + """ + Helper (using yield) for while loops to check OMC startup / response. The loop is executed as long as True is + returned, i.e. the first False will stop the while loop. + """ + + if timeout is None: + timeout = self._timeout + if timeout <= 0: + raise OMCSessionException(f"Invalid timeout: {timeout}") + + timer = 0.0 + yield True + while True: + timer += timestep + if timer > timeout: + break + time.sleep(timestep) + yield True + yield False + def set_timeout(self, timeout: Optional[float] = None) -> float: """ Set the timeout to be used for OMC communication (OMCSession). @@ -873,17 +898,13 @@ def sendExpression(self, command: str, parsed: bool = True) -> Any: logger.debug("sendExpression(%r, parsed=%r)", command, parsed) - MAX_RETRIES = 50 - attempts = 0 - while attempts < MAX_RETRIES: - attempts += 1 - + loop = self._timeout_loop(timestep=0.05) + while next(loop): try: self._omc_zmq.send_string(str(command), flags=zmq.NOBLOCK) break except zmq.error.Again: pass - time.sleep(self._timeout / MAX_RETRIES) else: # in the deletion process, the content is cleared. Thus, any access to a class attribute must be checked try: @@ -1122,11 +1143,8 @@ def _omc_port_get(self) -> str: port = None # See if the omc server is running - MAX_RETRIES = 80 - attempts = 0 - while attempts < MAX_RETRIES: - attempts += 1 - + loop = self._timeout_loop(timestep=0.1) + while next(loop): omc_portfile_path = self._get_portfile_path() if omc_portfile_path is not None and omc_portfile_path.is_file(): # Read the port file @@ -1135,7 +1153,6 @@ def _omc_port_get(self) -> str: break if port is not None: break - time.sleep(self._timeout / MAX_RETRIES) else: logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") raise OMCSessionException(f"OMC Server did not start (timeout={self._timeout}).") @@ -1220,11 +1237,8 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]: raise NotImplementedError("Docker not supported on win32!") docker_process = None - MAX_RETRIES = 40 - attempts = 0 - while attempts < MAX_RETRIES: - attempts += 1 - + loop = self._timeout_loop(timestep=0.2) + while next(loop): docker_top = subprocess.check_output(["docker", "top", docker_cid]).decode().strip() docker_process = None for line in docker_top.split("\n"): @@ -1237,7 +1251,6 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]: "is this a docker instance spawned without --pid=host?") from ex if docker_process is not None: break - time.sleep(self._timeout / MAX_RETRIES) else: logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}).") @@ -1262,11 +1275,8 @@ def _omc_port_get(self) -> str: raise OMCSessionException(f"Invalid docker container ID: {self._docker_container_id}") # See if the omc server is running - MAX_RETRIES = 80 - attempts = 0 - while attempts < MAX_RETRIES: - attempts += 1 - + loop = self._timeout_loop(timestep=0.1) + while next(loop): omc_portfile_path = self._get_portfile_path() if omc_portfile_path is not None: try: @@ -1279,7 +1289,6 @@ def _omc_port_get(self) -> str: pass if port is not None: break - time.sleep(self._timeout / MAX_RETRIES) else: logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}).") @@ -1450,11 +1459,8 @@ def _docker_omc_start(self) -> Tuple[subprocess.Popen, DockerPopen, str]: raise OMCSessionException(f"Invalid content for docker container ID file path: {docker_cid_file}") docker_cid = None - MAX_RETRIES = 40 - attempts = 0 - while attempts < MAX_RETRIES: - attempts += 1 - + loop = self._timeout_loop(timestep=0.1) + while next(loop): try: with open(file=docker_cid_file, mode="r", encoding="utf-8") as fh: docker_cid = fh.read().strip() @@ -1462,7 +1468,6 @@ def _docker_omc_start(self) -> Tuple[subprocess.Popen, DockerPopen, str]: pass if docker_cid is not None: break - time.sleep(self._timeout / MAX_RETRIES) else: logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") raise OMCSessionException(f"Docker did not start (timeout={self._timeout} might be too short " @@ -1627,11 +1632,8 @@ def _omc_port_get(self) -> str: port = None # See if the omc server is running - MAX_RETRIES = 80 - attempts = 0 - while attempts < MAX_RETRIES: - attempts += 1 - + loop = self._timeout_loop(timestep=0.1) + while next(loop): try: omc_portfile_path = self._get_portfile_path() if omc_portfile_path is not None: @@ -1644,7 +1646,6 @@ def _omc_port_get(self) -> str: pass if port is not None: break - time.sleep(self._timeout / MAX_RETRIES) else: logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") raise OMCSessionException(f"WSL based OMC Server did not start (timeout={self._timeout}).") From 9ad6f7c60780b6f19dd751995f633189c8fa9065 Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 27 Nov 2025 10:24:57 +0100 Subject: [PATCH 14/20] add OMCPath to the public interface --- OMPython/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OMPython/__init__.py b/OMPython/__init__.py index 7d571a9b0..8bdaaf852 100644 --- a/OMPython/__init__.py +++ b/OMPython/__init__.py @@ -44,6 +44,7 @@ ModelicaSystemError, ) from OMPython.OMCSession import ( + OMCPath, OMCSessionCmd, OMCSessionException, OMCSessionRunData, @@ -63,6 +64,8 @@ 'ModelicaSystemDoE', 'ModelicaSystemError', + 'OMCPath', + 'OMCSessionCmd', 'OMCSessionException', 'OMCSessionRunData', From 97e833351c6510491d25daf66aa066128527e4d8 Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 27 Nov 2025 10:21:54 +0100 Subject: [PATCH 15/20] [OMCSession] fix definiton of _timeout variable - use set_timeout() checks --- OMPython/OMCSession.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 048908c0e..29db14821 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -677,7 +677,9 @@ def __init__( """ # store variables - self._timeout = timeout + # set_timeout() is used to define the value of _timeout as it includes additional checks + self._timeout: float + self.set_timeout(timeout=timeout) # generate a random string for this instance of OMC self._random_string = uuid.uuid4().hex # get a temporary directory From 033e3a83954aa95ed8d7f302255af36380c8c62f Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 27 Nov 2025 09:04:49 +0100 Subject: [PATCH 16/20] use keyword arguments if possible (FKA100 - flake8-force-keyword-arguments) --- OMPython/ModelicaSystem.py | 14 ++++++++------ OMPython/OMCSession.py | 6 ++++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index d6a3d6546..cbd7fbd9d 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -291,8 +291,10 @@ def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, Any] | n The return data can be used as input for self.args_set(). """ - warnings.warn("The argument 'simflags' is depreciated and will be removed in future versions; " - "please use 'simargs' instead", DeprecationWarning, stacklevel=2) + warnings.warn(message="The argument 'simflags' is depreciated and will be removed in future versions; " + "please use 'simargs' instead", + category=DeprecationWarning, + stacklevel=2) simargs: dict[str, Optional[str | dict[str, Any] | numbers.Number]] = {} @@ -585,7 +587,7 @@ def buildModel(self, variableFilter: Optional[str] = None): def sendExpression(self, expr: str, parsed: bool = True) -> Any: try: - retval = self._session.sendExpression(expr, parsed) + retval = self._session.sendExpression(command=expr, parsed=parsed) except OMCSessionException as ex: raise ModelicaSystemError(f"Error executing {repr(expr)}: {ex}") from ex @@ -1612,9 +1614,9 @@ def _createCSVData(self, csvfile: Optional[OMCPath] = None) -> OMCPath: for signal_name, signal_values in inputs.items(): signal = np.array(signal_values) interpolated_inputs[signal_name] = np.interp( - all_times, - signal[:, 0], # times - signal[:, 1], # values + x=all_times, + xp=signal[:, 0], # times + fp=signal[:, 1], # values ) # Write CSV file diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index ee92ce9ff..2295eb062 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -848,8 +848,10 @@ def run_model_executable(cmd_run_data: OMCSessionRunData) -> int: return returncode def execute(self, command: str): - warnings.warn("This function is depreciated and will be removed in future versions; " - "please use sendExpression() instead", DeprecationWarning, stacklevel=2) + warnings.warn(message="This function is depreciated and will be removed in future versions; " + "please use sendExpression() instead", + category=DeprecationWarning, + stacklevel=2) return self.sendExpression(command, parsed=False) From cbb6e56755ea39e72592ab434d963b7c54abf5dd Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 27 Nov 2025 10:08:25 +0100 Subject: [PATCH 17/20] [OMCSession*] some additional cleanup (mypy / flake8) * remove not needed variable definitions * fix if condition for bool --- OMPython/OMCSession.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 29db14821..cdbe81aca 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -1012,7 +1012,7 @@ def sendExpression(self, command: str, parsed: bool = True) -> Any: raise OMCSessionException(f"OMC error occurred for 'sendExpression({command}, {parsed}):\n" f"{msg_long_str}") - if parsed is False: + if not parsed: return result try: @@ -1238,7 +1238,6 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]: if sys.platform == 'win32': raise NotImplementedError("Docker not supported on win32!") - docker_process = None loop = self._timeout_loop(timestep=0.2) while next(loop): docker_top = subprocess.check_output(["docker", "top", docker_cid]).decode().strip() @@ -1630,7 +1629,6 @@ def _omc_process_get(self) -> subprocess.Popen: return omc_process def _omc_port_get(self) -> str: - omc_portfile_path: Optional[pathlib.Path] = None port = None # See if the omc server is running From 223c8950fb0348f11a11579f90fd3b9d23b8e07a Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 27 Nov 2025 21:19:13 +0100 Subject: [PATCH 18/20] [OMCSession] move call to set_timeout() to __post_init__ --- OMPython/OMCSession.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index cdbe81aca..a73d633c1 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -677,9 +677,7 @@ def __init__( """ # store variables - # set_timeout() is used to define the value of _timeout as it includes additional checks - self._timeout: float - self.set_timeout(timeout=timeout) + self._timeout = timeout # generate a random string for this instance of OMC self._random_string = uuid.uuid4().hex # get a temporary directory @@ -713,6 +711,9 @@ def __post_init__(self) -> None: """ Create the connection to the OMC server using ZeroMQ. """ + # set_timeout() is used to define the value of _timeout as it includes additional checks + self.set_timeout(timeout=self._timeout) + port = self.get_port() if not isinstance(port, str): raise OMCSessionException(f"Invalid content for port: {port}") From fc43bdf88041849f5859453fbf698d6728e350a6 Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 27 Nov 2025 10:23:47 +0100 Subject: [PATCH 19/20] update README.md - replace OMCSessionZMQ with OMCSessionLocal --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2fd6baa19..07ff11f5d 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,8 @@ help(OMPython) ``` ```python -from OMPython import OMCSessionZMQ -omc = OMCSessionZMQ() +from OMPython import OMCSessionLocal +omc = OMCSessionLocal() omc.sendExpression("getVersion()") ``` From 2918597883bb67568c86bbfb4c5b16384d98cf4b Mon Sep 17 00:00:00 2001 From: syntron Date: Sun, 23 Nov 2025 13:17:25 +0100 Subject: [PATCH 20/20] [OMCSessionZMQ] cleanup / remove OMCSessionZMQ compatibility class --- OMPython/OMCSession.py | 76 ------------------------------------------ OMPython/__init__.py | 4 +-- 2 files changed, 2 insertions(+), 78 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index b9810962c..2d11b1360 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -535,82 +535,6 @@ def get_cmd(self) -> list[str]: return cmdl -class OMCSessionZMQ: - """ - This class is a compatibility layer for the new schema using OMCSession* classes. - """ - - def __init__( - self, - timeout: float = 10.00, - omhome: Optional[str] = None, - omc_process: Optional[OMCSession] = None, - ) -> None: - """ - Initialisation for OMCSessionZMQ - """ - warnings.warn(message="The class OMCSessionZMQ is depreciated and will be removed in future versions; " - "please use OMCProcess* classes instead!", - category=DeprecationWarning, - stacklevel=2) - - if omc_process is None: - omc_process = OMCSessionLocal(omhome=omhome, timeout=timeout) - elif not isinstance(omc_process, OMCSession): - raise OMCSessionException("Invalid definition of the OMC process!") - self.omc_process = omc_process - - def __del__(self): - del self.omc_process - - @staticmethod - def escape_str(value: str) -> str: - """ - Escape a string such that it can be used as string within OMC expressions, i.e. escape all double quotes. - """ - return OMCSession.escape_str(value=value) - - def omcpath(self, *path) -> OMCPath: - """ - Create an OMCPath object based on the given path segments and the current OMC process definition. - """ - return self.omc_process.omcpath(*path) - - def omcpath_tempdir(self, tempdir_base: Optional[OMCPath] = None) -> OMCPath: - """ - Get a temporary directory using OMC. It is our own implementation as non-local usage relies on OMC to run all - filesystem related access. - """ - return self.omc_process.omcpath_tempdir(tempdir_base=tempdir_base) - - def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunData: - """ - Modify data based on the selected OMCProcess implementation. - - Needs to be implemented in the subclasses. - """ - return self.omc_process.omc_run_data_update(omc_run_data=omc_run_data) - - def run_model_executable(self, cmd_run_data: OMCSessionRunData) -> int: - """ - Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to - keep instances of over classes around. - """ - return self.omc_process.run_model_executable(cmd_run_data=cmd_run_data) - - def execute(self, command: str): - return self.omc_process.execute(command=command) - - def sendExpression(self, command: str, parsed: 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 OMCSessionException. - """ - return self.omc_process.sendExpression(command=command, parsed=parsed) - - class PostInitCaller(type): """ Metaclass definition to define a new function __post_init__() which is called after all __init__() functions where diff --git a/OMPython/__init__.py b/OMPython/__init__.py index 888b7225c..069369bfd 100644 --- a/OMPython/__init__.py +++ b/OMPython/__init__.py @@ -48,7 +48,7 @@ OMCSessionCmd, OMCSessionException, OMCSessionRunData, - OMCSessionZMQ, + OMCSession, OMCSessionPort, OMCSessionLocal, OMCSessionDocker, @@ -69,7 +69,7 @@ 'OMCSessionCmd', 'OMCSessionException', 'OMCSessionRunData', - 'OMCSessionZMQ', + 'OMCSession', 'OMCSessionPort', 'OMCSessionLocal', 'OMCSessionDocker',