From c12c2774ee9b2e09ccbcb579bd50f7405e58ebab Mon Sep 17 00:00:00 2001 From: syntron Date: Sat, 28 Jun 2025 20:44:15 +0200 Subject: [PATCH 01/32] [OMCPath] add class --- OMPython/OMCSession.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index ac99dc05..41a96a67 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -268,6 +268,31 @@ def getClassNames(self, className=None, recursive=False, qualified=False, sort=F return self._ask(question='getClassNames', opt=opt) +class OMCPath(pathlib.PurePosixPath): + """ + Implementation of a basic Path object which uses OMC as backend. The connection to OMC is provided via a + OMCSessionZMQ session object. + """ + + def __init__(self, *path, session: OMCSessionZMQ): + super().__init__(*path) + self._session = session + + def with_segments(self, *pathsegments): + # overwrite this function of PurePosixPath to ensure session is set + return type(self)(*pathsegments, session=self._session) + + # TODO: implement needed methods from pathlib._abc.PathBase: + # is_dir() + # is_file() + # read_text() + binary()? + # write_text() + binary()? + # unlink() + # resolve() + # ... more ... + # ??? test if local (write OMC => READ local and the other way) and use shortcuts ??? + + class OMCSessionZMQ: def __init__( @@ -322,6 +347,9 @@ def __del__(self): self.omc_zmq = None + def omcpath(self, *path) -> OMCPath: + return OMCPath(*path, session=self) + 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) From 908a239db424e2ddcf41409cc1db6a276c380a01 Mon Sep 17 00:00:00 2001 From: syntron Date: Fri, 27 Jun 2025 22:53:08 +0200 Subject: [PATCH 02/32] [OMCPath] add implementation using OMC via sendExpression() --- OMPython/OMCSession.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 41a96a67..fb865ba4 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -282,12 +282,27 @@ def with_segments(self, *pathsegments): # overwrite this function of PurePosixPath to ensure session is set return type(self)(*pathsegments, session=self._session) + def is_file(self) -> bool: + return self._session.sendExpression(f'regularFileExists("{self.as_posix()}")') + + def is_dir(self) -> bool: + return self._session.sendExpression(f'directoryExists("{self.as_posix()}")') + + def read_text(self) -> str: + return self._session.sendExpression(f'readFile("{self.as_posix()}")') + + def write_text(self, data: str) -> bool: + return self._session.sendExpression(f'writeFile("{self.as_posix()}", "{data}", false)') + + def unlink(self) -> bool: + return self._session.sendExpression(f'deleteFile("{self.as_posix()}")') + # TODO: implement needed methods from pathlib._abc.PathBase: - # is_dir() - # is_file() - # read_text() + binary()? - # write_text() + binary()? - # unlink() + # OK - is_dir() + # OK - is_file() + # OK - read_text() + binary()? + # OK - write_text() + binary()? + # OK - unlink() # resolve() # ... more ... # ??? test if local (write OMC => READ local and the other way) and use shortcuts ??? From 00d016f8227425a88692baefb615b6029239f64b Mon Sep 17 00:00:00 2001 From: syntron Date: Sat, 28 Jun 2025 20:47:05 +0200 Subject: [PATCH 03/32] [OMCPath] add pytest (only docker at the moment) --- tests/test_OMCPath.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/test_OMCPath.py diff --git a/tests/test_OMCPath.py b/tests/test_OMCPath.py new file mode 100644 index 00000000..106f1cc7 --- /dev/null +++ b/tests/test_OMCPath.py @@ -0,0 +1,26 @@ +import OMPython + + +def test_OMCPath_docker(): + omcp = OMPython.OMCProcessDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") + om = OMPython.OMCSessionZMQ(omc_process=omcp) + assert om.sendExpression("getVersion()") == "OpenModelica 1.25.0" + + p1 = om.omcpath('/tmp') + assert str(p1) == "/tmp" + p2 = p1 / 'test.txt' + assert str(p2) == "/tmp/test.txt" + assert p2.write_text('test') + assert p2.read_text() == "test" + assert p2.is_file() + assert p2.parent.is_dir() + assert p2.unlink() + assert p2.is_file() == False + + del omcp + del om + + +if __name__ == '__main__': + test_OMCPath_docker() + print('DONE') \ No newline at end of file From a449445a6112d2253b75a7b18bbbeb10379b5bfc Mon Sep 17 00:00:00 2001 From: syntron Date: Sat, 28 Jun 2025 20:47:15 +0200 Subject: [PATCH 04/32] [OMCPath] TODO items --- OMPython/OMCSession.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index fb865ba4..a03b7f57 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -274,6 +274,10 @@ class OMCPath(pathlib.PurePosixPath): OMCSessionZMQ session object. """ + # TODO: need to handle PurePosixPath and PureWindowsPath + # PureOMCPath => OMCPathPosix(PureOMCPath, PurePosixPath) + # => OMCPathWindows(PureOMCPath, PureWindowsPath) + def __init__(self, *path, session: OMCSessionZMQ): super().__init__(*path) self._session = session @@ -363,6 +367,8 @@ def __del__(self): self.omc_zmq = None def omcpath(self, *path) -> OMCPath: + # TODO: need to handle PurePosixPath and PureWindowsPath + # define it here based on the backend (omc_process) used? return OMCPath(*path, session=self) def execute(self, command: str): From 17c9ee80cfec9ee0c9fb6dc01867a7dda9725859 Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 2 Jul 2025 22:34:46 +0200 Subject: [PATCH 05/32] [test_OMCPath] mypy fix --- tests/test_OMCPath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_OMCPath.py b/tests/test_OMCPath.py index 106f1cc7..bae41714 100644 --- a/tests/test_OMCPath.py +++ b/tests/test_OMCPath.py @@ -15,7 +15,7 @@ def test_OMCPath_docker(): assert p2.is_file() assert p2.parent.is_dir() assert p2.unlink() - assert p2.is_file() == False + assert p2.is_file() is False del omcp del om From 942635e00ef5ad888086ddacdd82a435d3c4895d Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 2 Jul 2025 22:52:38 +0200 Subject: [PATCH 06/32] [test_OMCPath] fix end of file --- tests/test_OMCPath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_OMCPath.py b/tests/test_OMCPath.py index bae41714..c0249df6 100644 --- a/tests/test_OMCPath.py +++ b/tests/test_OMCPath.py @@ -23,4 +23,4 @@ def test_OMCPath_docker(): if __name__ == '__main__': test_OMCPath_docker() - print('DONE') \ No newline at end of file + print('DONE') From 11b252403ce92f44350d2a19f71373935180617d Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 3 Jul 2025 09:21:35 +0200 Subject: [PATCH 07/32] [test_OMCPath] define test using OMCSessionZMQ() locally --- tests/test_OMCPath.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_OMCPath.py b/tests/test_OMCPath.py index c0249df6..9621b54b 100644 --- a/tests/test_OMCPath.py +++ b/tests/test_OMCPath.py @@ -1,6 +1,8 @@ import OMPython +import pytest +@pytest.mark.skip(reason="This test would fail (no docker on github)") def test_OMCPath_docker(): omcp = OMPython.OMCProcessDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") om = OMPython.OMCSessionZMQ(omc_process=omcp) @@ -21,6 +23,23 @@ def test_OMCPath_docker(): del om +def test_OMCPath_local(): + om = OMPython.OMCSessionZMQ() + + p1 = om.omcpath('/tmp') + assert str(p1) == "/tmp" + p2 = p1 / 'test.txt' + assert str(p2) == "/tmp/test.txt" + assert p2.write_text('test') + assert p2.read_text() == "test" + assert p2.is_file() + assert p2.parent.is_dir() + assert p2.unlink() + assert p2.is_file() is False + + del om + + if __name__ == '__main__': test_OMCPath_docker() print('DONE') From 05b3c4d284ef8fe083bac47697e14ed6bfce5947 Mon Sep 17 00:00:00 2001 From: syntron Date: Sun, 6 Jul 2025 21:54:01 +0200 Subject: [PATCH 08/32] add TODO - need to check Python versions * not working: 3.10 * working: 3.12 --- OMPython/OMCSession.py | 1 + 1 file changed, 1 insertion(+) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index a03b7f57..f0505383 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -277,6 +277,7 @@ class OMCPath(pathlib.PurePosixPath): # TODO: need to handle PurePosixPath and PureWindowsPath # PureOMCPath => OMCPathPosix(PureOMCPath, PurePosixPath) # => OMCPathWindows(PureOMCPath, PureWindowsPath) + # TODO: only working for Python 3.12+ (not working for 3.10!; 3.11?) def __init__(self, *path, session: OMCSessionZMQ): super().__init__(*path) From 79e118ac90167c0a96ee2a471d27502316d27248 Mon Sep 17 00:00:00 2001 From: syntron Date: Sun, 6 Jul 2025 21:54:24 +0200 Subject: [PATCH 09/32] [test_OMCPath] activate docker based on test_docker --- tests/test_OMCPath.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_OMCPath.py b/tests/test_OMCPath.py index 9621b54b..bef2b473 100644 --- a/tests/test_OMCPath.py +++ b/tests/test_OMCPath.py @@ -1,8 +1,14 @@ +import sys import OMPython import pytest +skip_on_windows = pytest.mark.skipif( + sys.platform.startswith("win"), + reason="OpenModelica Docker image is Linux-only; skipping on Windows.", +) -@pytest.mark.skip(reason="This test would fail (no docker on github)") + +@skip_on_windows def test_OMCPath_docker(): omcp = OMPython.OMCProcessDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") om = OMPython.OMCSessionZMQ(omc_process=omcp) From e0cb45393fc95f27e91b063d76e672d1782fe1cd Mon Sep 17 00:00:00 2001 From: syntron Date: Fri, 11 Jul 2025 21:18:56 +0200 Subject: [PATCH 10/32] [OMCPath] add more functionality and docstrings --- OMPython/OMCSession.py | 125 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 120 insertions(+), 5 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index f0505383..d8a17af7 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -284,23 +284,138 @@ def __init__(self, *path, session: OMCSessionZMQ): self._session = session def with_segments(self, *pathsegments): - # overwrite this function of PurePosixPath to ensure session is set + """ + Create a new OMCPath object with the given path segments. + + The original definition of Path is overridden to ensure session is set. + """ return type(self)(*pathsegments, session=self._session) def is_file(self) -> bool: + """ + Check if the path is a regular file. + """ return self._session.sendExpression(f'regularFileExists("{self.as_posix()}")') def is_dir(self) -> bool: + """ + Check if the path is a directory. + """ return self._session.sendExpression(f'directoryExists("{self.as_posix()}")') - def read_text(self) -> str: + def read_text(self, encoding=None, errors=None) -> str: + """ + Read the content of the file represented by this path as text. + + The additional arguments `encoding` and `errors` are only defined for compatibility with Path() definitions. + """ return self._session.sendExpression(f'readFile("{self.as_posix()}")') - def write_text(self, data: str) -> bool: + def write_text(self, data: str, encoding=None, errors=None, newline=None) -> bool: + """ + Write text data to the file represented by this path. + + The additional arguments `encoding`, `errors`, and `newline` are only defined for compatibility with Path() + definitions. + """ + if not isinstance(data, str): + raise TypeError('data must be str, not %s' % + data.__class__.__name__) + return self._session.sendExpression(f'writeFile("{self.as_posix()}", "{data}", false)') - def unlink(self) -> bool: - return self._session.sendExpression(f'deleteFile("{self.as_posix()}")') + def mkdir(self, mode=0o777, parents=False, exist_ok=False): + """ + Create a directory at the path represented by this OMCPath object. + + The additional arguments `mode`, and `parents` are only defined for compatibility with Path() definitions. + """ + if self.is_dir() and not exist_ok: + raise FileExistsError(f"Directory {self.as_posix()} already exists!") + + return self._session.sendExpression(f'mkdir("{self.as_posix()}")') + + def cwd(self): + """ + Returns the current working directory as an OMCPath object. + """ + cwd_str = self._session.sendExpression('cd()') + return OMCPath(cwd_str, session=self._session) + + def unlink(self, missing_ok: bool = False) -> bool: + """ + Unlink (delete) the file or directory represented by this path. + """ + res = self._session.sendExpression(f'deleteFile("{self.as_posix()}")') + if not res and not missing_ok: + raise FileNotFoundError(f"Cannot delete file {self.as_posix()} - it does not exists!") + return res + + def resolve(self, strict: bool = False) -> OMCPath: + """ + Resolve the path to an absolute path. This is done based on available OMC functions. + """ + if strict and not (self.is_file() or self.is_dir()): + raise OMCSessionException(f"Path {self.as_posix()} does not exist!") + + if self.is_file(): + omcpath = self._omc_resolve(self.parent.as_posix()) / self.name + elif self.is_dir(): + omcpath = self._omc_resolve(self.as_posix()) + else: + raise OMCSessionException(f"Path {self.as_posix()} is neither a file nor a directory!") + + return omcpath + + def _omc_resolve(self, pathstr: str) -> OMCPath: + """ + Internal function to resolve the path of the OMCPath object using OMC functions *WITHOUT* changing the cwd + within OMC. + """ + expression = ('omcpath_cwd := cd(); ' + f'omcpath_check := cd("{pathstr}"); ' # check requested pathstring + 'cd(omcpath_cwd)') + + try: + result = self._session.sendExpression(expression) + result_parts = result.split('\n') + pathstr_resolved = result_parts[1] + pathstr_resolved = pathstr_resolved[1:-1] # remove quotes + + omcpath_resolved = self._session.omcpath(pathstr_resolved) + except OMCSessionException as ex: + raise OMCSessionException(f"OMCPath resolve failed for {pathstr}!") from ex + + if not omcpath_resolved.is_file() and not omcpath_resolved.is_dir(): + raise OMCSessionException(f"OMCPath resolve failed for {pathstr} - path does not exist!") + + return omcpath_resolved + + def absolute(self) -> OMCPath: + """ + Resolve the path to an absolute path. This is done by calling resolve() as it is the best we can do + using OMC functions. + """ + return self.resolve(strict=True) + + def exists(self) -> bool: + """ + Semi replacement for pathlib.Path.exists(). + """ + return self.is_file() or self.is_dir() + + def size(self) -> int: + """ + Get the size of the file in bytes - this is a extra function and the best we can do using OMC. + """ + if not self.is_file(): + raise OMCSessionException(f"Path {self.as_posix()} is not a file!") + + res = self._session.sendExpression(f'stat("{self.as_posix()}")') + if res[0]: + return int(res[1]) + + raise OMCSessionException(f"Error reading file size for path {self.as_posix()}!") # TODO: implement needed methods from pathlib._abc.PathBase: # OK - is_dir() From 2b864420ba2a25675ce5d5981030aa86fe65010c Mon Sep 17 00:00:00 2001 From: syntron Date: Fri, 11 Jul 2025 21:19:51 +0200 Subject: [PATCH 11/32] [OMCPath] remove TODO entries --- OMPython/OMCSession.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index d8a17af7..3cd38475 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -274,11 +274,6 @@ class OMCPath(pathlib.PurePosixPath): OMCSessionZMQ session object. """ - # TODO: need to handle PurePosixPath and PureWindowsPath - # PureOMCPath => OMCPathPosix(PureOMCPath, PurePosixPath) - # => OMCPathWindows(PureOMCPath, PureWindowsPath) - # TODO: only working for Python 3.12+ (not working for 3.10!; 3.11?) - def __init__(self, *path, session: OMCSessionZMQ): super().__init__(*path) self._session = session @@ -417,15 +412,6 @@ def size(self) -> int: raise OMCSessionException(f"Error reading file size for path {self.as_posix()}!") - # TODO: implement needed methods from pathlib._abc.PathBase: - # OK - is_dir() - # OK - is_file() - # OK - read_text() + binary()? - # OK - write_text() + binary()? - # OK - unlink() - # resolve() - # ... more ... - # ??? test if local (write OMC => READ local and the other way) and use shortcuts ??? class OMCSessionZMQ: From a9fb9f9a703dee3d178b4ae0cebf1248d38e3a18 Mon Sep 17 00:00:00 2001 From: syntron Date: Fri, 11 Jul 2025 21:22:17 +0200 Subject: [PATCH 12/32] [OMCPath] define limited compatibility for Python < 3.12 * use modified pathlib.Path as OMCPath --- OMPython/OMCSession.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 3cd38475..966709bc 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -413,6 +413,15 @@ def size(self) -> int: raise OMCSessionException(f"Error reading file size for path {self.as_posix()}!") +if sys.version_info < (3, 12): + class OMCPathCompatibility(pathlib.Path): + + def size(self) -> int: + return self.stat().st_size + + + OMCPath = OMCPathCompatibility + class OMCSessionZMQ: From fcb8571a6812a9c58d4643730fd6588ef472ec30 Mon Sep 17 00:00:00 2001 From: syntron Date: Fri, 11 Jul 2025 21:23:24 +0200 Subject: [PATCH 13/32] [OMCSEssionZMQ] use OMCpath --- OMPython/OMCSession.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 966709bc..a6541eae 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -478,8 +478,15 @@ def __del__(self): self.omc_zmq = None def omcpath(self, *path) -> OMCPath: - # TODO: need to handle PurePosixPath and PureWindowsPath - # define it here based on the backend (omc_process) used? + """ + Create an OMCPath object based on the given path segments and the current OMC session. + """ + + # fallback solution for Python < 3.12; a modified pathlib.Path object is used as OMCPath replacement + if sys.version_info < (3, 12): + # noinspection PyArgumentList + return OMCPath(*path) + return OMCPath(*path, session=self) def execute(self, command: str): From 51b916091759f899f929fc537f256b990a5c0151 Mon Sep 17 00:00:00 2001 From: syntron Date: Fri, 11 Jul 2025 21:28:31 +0200 Subject: [PATCH 14/32] [OMCSessionZMQ] create a tempdir using omcpath_tempdir() --- OMPython/OMCSession.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index a6541eae..d8a73a3b 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -489,6 +489,30 @@ def omcpath(self, *path) -> OMCPath: return OMCPath(*path, session=self) + def omcpath_tempdir(self) -> OMCPath: + """ + Get a temporary directory using OMC. + """ + names = [str(uuid.uuid4()) for _ in range(100)] + + tempdir_str = self.sendExpression("getTempDirectoryPath()") + tempdir_base = self.omcpath(tempdir_str) + tempdir: Optional[OMCPath] = None + for name in names: + # create a unique temporary directory name + tempdir = tempdir_base / name + + if tempdir.exists(): + continue + + tempdir.mkdir(parents=True, exist_ok=False) + break + + if tempdir is None or not tempdir.is_dir(): + raise OMCSessionException("Cannot create a temporary directory!") + + return tempdir + 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) From dd9afedf0d0922e4ef64faa75d255ade4eef9453 Mon Sep 17 00:00:00 2001 From: syntron Date: Fri, 11 Jul 2025 21:34:24 +0200 Subject: [PATCH 15/32] [OMCPath] fix mypy --- OMPython/OMCSession.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index d8a73a3b..2250d21d 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -419,8 +419,7 @@ class OMCPathCompatibility(pathlib.Path): def size(self) -> int: return self.stat().st_size - - OMCPath = OMCPathCompatibility + OMCPath = OMCPathCompatibility # noqa: F811 class OMCSessionZMQ: From dc0393eafb3f397fec575bbe8f1a14da30f58fdd Mon Sep 17 00:00:00 2001 From: syntron Date: Fri, 11 Jul 2025 21:38:27 +0200 Subject: [PATCH 16/32] [OMCPath] add warning message for Python < 3.12 --- OMPython/OMCSession.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 2250d21d..8b3a3b28 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -414,6 +414,12 @@ def size(self) -> int: if sys.version_info < (3, 12): + warnings.warn( + message="Python < 3.12 - using a limited compatibility class as OMCPath replacement.", + category=DeprecationWarning, + stacklevel=1, + ) + class OMCPathCompatibility(pathlib.Path): def size(self) -> int: From 6ff54d3a0abfc46ed2bb26e15852a4a0d8a379f9 Mon Sep 17 00:00:00 2001 From: syntron Date: Fri, 11 Jul 2025 22:20:23 +0200 Subject: [PATCH 17/32] [OMCPath] try to make mypy happy ... --- OMPython/OMCSession.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 8b3a3b28..3107320d 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -268,7 +268,7 @@ def getClassNames(self, className=None, recursive=False, qualified=False, sort=F return self._ask(question='getClassNames', opt=opt) -class OMCPath(pathlib.PurePosixPath): +class OMCPathReal(pathlib.PurePosixPath): """ Implementation of a basic Path object which uses OMC as backend. The connection to OMC is provided via a OMCSessionZMQ session object. @@ -420,13 +420,16 @@ def size(self) -> int: stacklevel=1, ) - class OMCPathCompatibility(pathlib.Path): + class OMCPathCompatibility(pathlib.PosixPath): def size(self) -> int: return self.stat().st_size OMCPath = OMCPathCompatibility # noqa: F811 +else: + OMCPath = OMCPathReal + class OMCSessionZMQ: @@ -491,8 +494,8 @@ def omcpath(self, *path) -> OMCPath: if sys.version_info < (3, 12): # noinspection PyArgumentList return OMCPath(*path) - - return OMCPath(*path, session=self) + else: + return OMCPath(*path, session=self) def omcpath_tempdir(self) -> OMCPath: """ From c4323b36c5e3296bf7a35f1a0919d34d8c13c0be Mon Sep 17 00:00:00 2001 From: syntron Date: Fri, 11 Jul 2025 21:17:00 +0200 Subject: [PATCH 18/32] [test_OMCPath] only for Python >= 3.12 --- tests/test_OMCPath.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_OMCPath.py b/tests/test_OMCPath.py index bef2b473..8cc2c88c 100644 --- a/tests/test_OMCPath.py +++ b/tests/test_OMCPath.py @@ -7,8 +7,14 @@ reason="OpenModelica Docker image is Linux-only; skipping on Windows.", ) +skip_python_older_312 = pytest.mark.skipif( + sys.version_info < (3, 12), + reason="OMCPath only working for Python >= 3.12 (definition of pathlib.PurePath).", +) + @skip_on_windows +@skip_python_older_312 def test_OMCPath_docker(): omcp = OMPython.OMCProcessDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") om = OMPython.OMCSessionZMQ(omc_process=omcp) @@ -29,6 +35,7 @@ def test_OMCPath_docker(): del om +@skip_python_older_312 def test_OMCPath_local(): om = OMPython.OMCSessionZMQ() From ec552dd8d3a1498de01fb191743150b9386c8998 Mon Sep 17 00:00:00 2001 From: syntron Date: Fri, 11 Jul 2025 21:17:35 +0200 Subject: [PATCH 19/32] [test_OMCPath] update test --- tests/test_OMCPath.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/tests/test_OMCPath.py b/tests/test_OMCPath.py index 8cc2c88c..67f1316c 100644 --- a/tests/test_OMCPath.py +++ b/tests/test_OMCPath.py @@ -20,11 +20,16 @@ def test_OMCPath_docker(): om = OMPython.OMCSessionZMQ(omc_process=omcp) assert om.sendExpression("getVersion()") == "OpenModelica 1.25.0" - p1 = om.omcpath('/tmp') - assert str(p1) == "/tmp" - p2 = p1 / 'test.txt' - assert str(p2) == "/tmp/test.txt" + tempdir = '/tmp' + + p1 = om.omcpath(tempdir).resolve().absolute() + assert str(p1) == tempdir + p2 = p1 / '..' / p1.name / 'test.txt' + assert p2.is_file() is False assert p2.write_text('test') + assert p2.is_file() + p2 = p2.resolve().absolute() + assert str(p2) == f"{tempdir}/test.txt" assert p2.read_text() == "test" assert p2.is_file() assert p2.parent.is_dir() @@ -39,11 +44,20 @@ def test_OMCPath_docker(): def test_OMCPath_local(): om = OMPython.OMCSessionZMQ() - p1 = om.omcpath('/tmp') - assert str(p1) == "/tmp" - p2 = p1 / 'test.txt' - assert str(p2) == "/tmp/test.txt" + # use different tempdir for Windows and Linux + if sys.platform.startswith("win"): + tempdir = 'C:/temp' + else: + tempdir = '/tmp' + + p1 = om.omcpath(tempdir).resolve().absolute() + assert str(p1) == tempdir + p2 = p1 / '..' / p1.name / 'test.txt' + assert p2.is_file() is False assert p2.write_text('test') + assert p2.is_file() + p2 = p2.resolve().absolute() + assert str(p2) == f"{tempdir}/test.txt" assert p2.read_text() == "test" assert p2.is_file() assert p2.parent.is_dir() From 3b99f2fd181dcd2680acaf1e9859fc93c7e7ef46 Mon Sep 17 00:00:00 2001 From: syntron Date: Sat, 12 Jul 2025 12:53:28 +0200 Subject: [PATCH 20/32] [OMCPath._omc_resolve] use sendExpression() with parsed=False * this is scripting output and, thus, it cannot be parsed --- OMPython/OMCSession.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 3107320d..a012ae87 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -372,7 +372,7 @@ def _omc_resolve(self, pathstr: str) -> OMCPath: 'cd(omcpath_cwd)') try: - result = self._session.sendExpression(expression) + result = self._session.sendExpression(command=expression, parsed=False) result_parts = result.split('\n') pathstr_resolved = result_parts[1] pathstr_resolved = pathstr_resolved[1:-1] # remove quotes From a54b796475f8631f30ab210c3b3a9665f3782b80 Mon Sep 17 00:00:00 2001 From: syntron Date: Sat, 12 Jul 2025 15:42:45 +0200 Subject: [PATCH 21/32] [test_OMCPath] cleanup; use the same code for local OMC and docker based OMC --- tests/test_OMCPath.py | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/tests/test_OMCPath.py b/tests/test_OMCPath.py index 67f1316c..8d323ddf 100644 --- a/tests/test_OMCPath.py +++ b/tests/test_OMCPath.py @@ -22,19 +22,7 @@ def test_OMCPath_docker(): tempdir = '/tmp' - p1 = om.omcpath(tempdir).resolve().absolute() - assert str(p1) == tempdir - p2 = p1 / '..' / p1.name / 'test.txt' - assert p2.is_file() is False - assert p2.write_text('test') - assert p2.is_file() - p2 = p2.resolve().absolute() - assert str(p2) == f"{tempdir}/test.txt" - assert p2.read_text() == "test" - assert p2.is_file() - assert p2.parent.is_dir() - assert p2.unlink() - assert p2.is_file() is False + _run_OMCPath_checks(tempdir, om) del omcp del om @@ -50,6 +38,12 @@ def test_OMCPath_local(): else: tempdir = '/tmp' + _run_OMCPath_checks(tempdir, om) + + del om + + +def _run_OMCPath_checks(tempdir: str, om: OMPython.OMCSessionZMQ): p1 = om.omcpath(tempdir).resolve().absolute() assert str(p1) == tempdir p2 = p1 / '..' / p1.name / 'test.txt' @@ -63,10 +57,3 @@ def test_OMCPath_local(): assert p2.parent.is_dir() assert p2.unlink() assert p2.is_file() is False - - del om - - -if __name__ == '__main__': - test_OMCPath_docker() - print('DONE') From 02c40bc64cabba415bdc87cb8e8c18d10c424df6 Mon Sep 17 00:00:00 2001 From: syntron Date: Sat, 12 Jul 2025 15:45:00 +0200 Subject: [PATCH 22/32] [test_OMCPath] define test for WSL --- tests/test_OMCPath.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_OMCPath.py b/tests/test_OMCPath.py index 8d323ddf..ea87f53c 100644 --- a/tests/test_OMCPath.py +++ b/tests/test_OMCPath.py @@ -43,6 +43,23 @@ def test_OMCPath_local(): del om +@pytest.mark.skip(reason="Not able to run WSL on github") +def test_OMCPath_WSL(): + omcp = OMPython.OMCProcessWSL( + wsl_omc='omc', + wsl_user='omc', + timeout=30.0, + ) + om = OMPython.OMCSessionZMQ(omc_process=omcp) + + tempdir = '/tmp' + + _run_OMCPath_checks(tempdir, om) + + del omcp + del om + + def _run_OMCPath_checks(tempdir: str, om: OMPython.OMCSessionZMQ): p1 = om.omcpath(tempdir).resolve().absolute() assert str(p1) == tempdir From 4feccf2c64e724823873a5e7fa693117b5a84fc8 Mon Sep 17 00:00:00 2001 From: syntron Date: Sat, 12 Jul 2025 15:57:02 +0200 Subject: [PATCH 23/32] [test_OMCPath] use omcpath_tempdir() instead of hard-coded tempdir definition --- tests/test_OMCPath.py | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/tests/test_OMCPath.py b/tests/test_OMCPath.py index ea87f53c..45689622 100644 --- a/tests/test_OMCPath.py +++ b/tests/test_OMCPath.py @@ -20,9 +20,7 @@ def test_OMCPath_docker(): om = OMPython.OMCSessionZMQ(omc_process=omcp) assert om.sendExpression("getVersion()") == "OpenModelica 1.25.0" - tempdir = '/tmp' - - _run_OMCPath_checks(tempdir, om) + _run_OMCPath_checks(om) del omcp del om @@ -32,13 +30,7 @@ def test_OMCPath_docker(): def test_OMCPath_local(): om = OMPython.OMCSessionZMQ() - # use different tempdir for Windows and Linux - if sys.platform.startswith("win"): - tempdir = 'C:/temp' - else: - tempdir = '/tmp' - - _run_OMCPath_checks(tempdir, om) + _run_OMCPath_checks(om) del om @@ -52,23 +44,20 @@ def test_OMCPath_WSL(): ) om = OMPython.OMCSessionZMQ(omc_process=omcp) - tempdir = '/tmp' - - _run_OMCPath_checks(tempdir, om) + _run_OMCPath_checks(om) del omcp del om -def _run_OMCPath_checks(tempdir: str, om: OMPython.OMCSessionZMQ): - p1 = om.omcpath(tempdir).resolve().absolute() - assert str(p1) == tempdir +def _run_OMCPath_checks(om: OMPython.OMCSessionZMQ): + p1 = om.omcpath_tempdir() p2 = p1 / '..' / p1.name / 'test.txt' assert p2.is_file() is False assert p2.write_text('test') assert p2.is_file() p2 = p2.resolve().absolute() - assert str(p2) == f"{tempdir}/test.txt" + assert str(p2) == f"{str(p1)}/test.txt" assert p2.read_text() == "test" assert p2.is_file() assert p2.parent.is_dir() From 362580737387494d1b0b4b3d5569a968c810e1a1 Mon Sep 17 00:00:00 2001 From: syntron Date: Tue, 15 Jul 2025 21:46:39 +0200 Subject: [PATCH 24/32] [OMCPath] spelling fix see commit ID: aa74b367f0fa35b81905d646bbf1beefd3a89595 - [OMCPath] add more functionality and docstrings --- OMPython/OMCSession.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index a012ae87..afb2b8cd 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -401,7 +401,7 @@ def exists(self) -> bool: def size(self) -> int: """ - Get the size of the file in bytes - this is a extra function and the best we can do using OMC. + Get the size of the file in bytes - this is an extra function and the best we can do using OMC. """ if not self.is_file(): raise OMCSessionException(f"Path {self.as_posix()} is not a file!") From 930cff1426b0cce5a16cb6bc2a42a08ac4fff7bf Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 16 Jul 2025 21:08:03 +0200 Subject: [PATCH 25/32] [OMCPath] implementation version 3 * differentiate between * Python >= 3.12 uses OMCPath based on OMC for filesystem operation * Python < 3.12 uses a pathlib.Path based implementation which is limited to OMCProcessLocal --- OMPython/OMCSession.py | 81 ++++++++++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 23 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index afb2b8cd..9be3dbf9 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -274,7 +274,7 @@ class OMCPathReal(pathlib.PurePosixPath): OMCSessionZMQ session object. """ - def __init__(self, *path, session: OMCSessionZMQ): + def __init__(self, *path, session: OMCSessionZMQ) -> None: super().__init__(*path) self._session = session @@ -286,27 +286,28 @@ def with_segments(self, *pathsegments): """ return type(self)(*pathsegments, session=self._session) - def is_file(self) -> bool: + def is_file(self, *, follow_symlinks=True) -> bool: """ Check if the path is a regular file. """ return self._session.sendExpression(f'regularFileExists("{self.as_posix()}")') - def is_dir(self) -> bool: + def is_dir(self, *, follow_symlinks=True) -> bool: """ Check if the path is a directory. """ return self._session.sendExpression(f'directoryExists("{self.as_posix()}")') - def read_text(self, encoding=None, errors=None) -> str: + def read_text(self, encoding=None, errors=None, newline=None) -> str: """ Read the content of the file represented by this path as text. - The additional arguments `encoding` and `errors` are only defined for compatibility with Path() definitions. + The additional arguments `encoding`, `errors` and `newline` are only defined for compatibility with Path() + definition. """ return self._session.sendExpression(f'readFile("{self.as_posix()}")') - def write_text(self, data: str, encoding=None, errors=None, newline=None) -> bool: + def write_text(self, data: str, encoding=None, errors=None, newline=None): """ Write text data to the file represented by this path. @@ -337,16 +338,15 @@ def cwd(self): cwd_str = self._session.sendExpression('cd()') return OMCPath(cwd_str, session=self._session) - def unlink(self, missing_ok: bool = False) -> bool: + def unlink(self, missing_ok: bool = False) -> None: """ Unlink (delete) the file or directory represented by this path. """ res = self._session.sendExpression(f'deleteFile("{self.as_posix()}")') if not res and not missing_ok: raise FileNotFoundError(f"Cannot delete file {self.as_posix()} - it does not exists!") - return res - def resolve(self, strict: bool = False) -> OMCPath: + def resolve(self, strict: bool = False): """ Resolve the path to an absolute path. This is done based on available OMC functions. """ @@ -362,7 +362,7 @@ def resolve(self, strict: bool = False) -> OMCPath: return omcpath - def _omc_resolve(self, pathstr: str) -> OMCPath: + def _omc_resolve(self, pathstr: str): """ Internal function to resolve the path of the OMCPath object using OMC functions *WITHOUT* changing the cwd within OMC. @@ -386,7 +386,7 @@ def _omc_resolve(self, pathstr: str) -> OMCPath: return omcpath_resolved - def absolute(self) -> OMCPath: + def absolute(self): """ Resolve the path to an absolute path. This is done by calling resolve() as it is the best we can do using OMC functions. @@ -414,18 +414,42 @@ def size(self) -> int: if sys.version_info < (3, 12): - warnings.warn( - message="Python < 3.12 - using a limited compatibility class as OMCPath replacement.", - category=DeprecationWarning, - stacklevel=1, - ) - class OMCPathCompatibility(pathlib.PosixPath): + class OMCPathCompatibility: + """ + Compatibility class for OMCPath in Python < 3.12. This allows to run all code which uses OMCPath (mainly + ModelicaSystem) on these Python versions. There is one remaining limitation: only OMCProcessLocal will work as + OMCPathCompatibility is based on the standard pathlib.Path implementation. + """ + + # modified copy of pathlib.Path.__new__() definition + def __new__(cls, *args, **kwargs): + logger.warning("Python < 3.12 - using a limited version of class OMCPath.") + + if cls is OMCPathCompatibility: + cls = OMCPathCompatibilityWindows if os.name == 'nt' else OMCPathCompatibilityPosix + self = cls._from_parts(args) + if not self._flavour.is_supported: + raise NotImplementedError("cannot instantiate %r on your system" + % (cls.__name__,)) + return self def size(self) -> int: + """ + Needed compatibility function to have the same interface as OMCPathReal + """ return self.stat().st_size - OMCPath = OMCPathCompatibility # noqa: F811 + + class OMCPathCompatibilityPosix(pathlib.PosixPath, OMCPathCompatibility): + pass + + + class OMCPathCompatibilityWindows(pathlib.WindowsPath, OMCPathCompatibility): + pass + + + OMCPath = OMCPathCompatibility else: OMCPath = OMCPathReal @@ -492,19 +516,30 @@ 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): - # noinspection PyArgumentList - return OMCPath(*path) + if isinstance(self.omc_process, OMCProcessLocal): + # noinspection PyArgumentList + return OMCPath(*path) + else: + raise OMCSessionException("OMCPath is supported for Python < 3.12 only if OMCProcessLocal is used!") else: return OMCPath(*path, session=self) - def omcpath_tempdir(self) -> OMCPath: + def omcpath_tempdir(self, tempdir_base: Optional[OMCPath] = None) -> OMCPath: """ Get a temporary directory using OMC. """ + # fallback solution for Python < 3.12; a modified pathlib.Path object is used as OMCPath replacement + if sys.version_info < (3, 12): + tempdir_str = tempfile.gettempdir() + # noinspection PyArgumentList + return OMCPath(tempdir_str) + names = [str(uuid.uuid4()) for _ in range(100)] - tempdir_str = self.sendExpression("getTempDirectoryPath()") - tempdir_base = self.omcpath(tempdir_str) + if tempdir_base is None: + tempdir_str = self.sendExpression("getTempDirectoryPath()") + tempdir_base = self.omcpath(tempdir_str) + tempdir: Optional[OMCPath] = None for name in names: # create a unique temporary directory name From 1de0e3b4be5a5c20aa5d4d0e23c5c449f7115aef Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 16 Jul 2025 23:20:37 +0200 Subject: [PATCH 26/32] [OMCSession*] fix flake8 (PyCharm likes the empty lines) --- OMPython/OMCSession.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 9be3dbf9..ca82abb5 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -440,15 +440,12 @@ def size(self) -> int: """ return self.stat().st_size - class OMCPathCompatibilityPosix(pathlib.PosixPath, OMCPathCompatibility): pass - class OMCPathCompatibilityWindows(pathlib.WindowsPath, OMCPathCompatibility): pass - OMCPath = OMCPathCompatibility else: From 8f38def82f2d4590bc8fdf47f8395946d6ffc33f Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 16 Jul 2025 23:28:54 +0200 Subject: [PATCH 27/32] [OMCSessionZMQ] more generic definiton for omcpath_tempdir() --- OMPython/OMCSession.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index ca82abb5..4c10ecbd 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -523,18 +523,17 @@ def omcpath(self, *path) -> OMCPath: def omcpath_tempdir(self, tempdir_base: Optional[OMCPath] = None) -> OMCPath: """ - Get a temporary directory using OMC. + Get a temporary directory using OMC. It is our own implementation as non-local usage relies on OMC to run all + filesystem related access. """ - # fallback solution for Python < 3.12; a modified pathlib.Path object is used as OMCPath replacement - if sys.version_info < (3, 12): - tempdir_str = tempfile.gettempdir() - # noinspection PyArgumentList - return OMCPath(tempdir_str) - names = [str(uuid.uuid4()) for _ in range(100)] if tempdir_base is None: - tempdir_str = self.sendExpression("getTempDirectoryPath()") + # fallback solution for Python < 3.12; a modified pathlib.Path object is used as OMCPath replacement + if sys.version_info < (3, 12): + tempdir_str = tempfile.gettempdir() + else: + tempdir_str = self.sendExpression("getTempDirectoryPath()") tempdir_base = self.omcpath(tempdir_str) tempdir: Optional[OMCPath] = None From 9dc161b1aa3f23b4c17dd0caddd843d89fd7be1b Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 16 Jul 2025 23:35:44 +0200 Subject: [PATCH 28/32] [OMCPathCompatibility] mypy on github ... --- OMPython/OMCSession.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 4c10ecbd..744068e3 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -415,7 +415,7 @@ def size(self) -> int: if sys.version_info < (3, 12): - class OMCPathCompatibility: + class OMCPathCompatibility(pathlib.Path): """ Compatibility class for OMCPath in Python < 3.12. This allows to run all code which uses OMCPath (mainly ModelicaSystem) on these Python versions. There is one remaining limitation: only OMCProcessLocal will work as From fd906cc023c939237893489f5a5203679629923a Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 16 Jul 2025 23:47:23 +0200 Subject: [PATCH 29/32] [OMCPathCompatibility] improve log messages --- OMPython/OMCSession.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 744068e3..1398aa50 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -424,7 +424,8 @@ class OMCPathCompatibility(pathlib.Path): # modified copy of pathlib.Path.__new__() definition def __new__(cls, *args, **kwargs): - logger.warning("Python < 3.12 - using a limited version of class OMCPath.") + logger.warning("Python < 3.12 - using a version of class OMCPath " + "based on pathlib.Path for local usage only.") if cls is OMCPathCompatibility: cls = OMCPathCompatibilityWindows if os.name == 'nt' else OMCPathCompatibilityPosix From 90f8c98490797bcb7abe0661138ecb2bf90ff540 Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 16 Jul 2025 23:18:33 +0200 Subject: [PATCH 30/32] [test_OMCPath] update --- tests/test_OMCPath.py | 57 ++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/tests/test_OMCPath.py b/tests/test_OMCPath.py index 45689622..449108ac 100644 --- a/tests/test_OMCPath.py +++ b/tests/test_OMCPath.py @@ -9,34 +9,43 @@ skip_python_older_312 = pytest.mark.skipif( sys.version_info < (3, 12), - reason="OMCPath only working for Python >= 3.12 (definition of pathlib.PurePath).", + reason="OMCPath(non-local) only working for Python >= 3.12.", ) -@skip_on_windows -@skip_python_older_312 -def test_OMCPath_docker(): - omcp = OMPython.OMCProcessDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") - om = OMPython.OMCSessionZMQ(omc_process=omcp) - assert om.sendExpression("getVersion()") == "OpenModelica 1.25.0" +def test_OMCPath_OMCSessionZMQ(): + om = OMPython.OMCSessionZMQ() + + _run_OMCPath_checks(om) + + del om + + +def test_OMCPath_OMCProcessLocal(): + omp = OMPython.OMCProcessLocal() + om = OMPython.OMCSessionZMQ(omc_process=omp) _run_OMCPath_checks(om) - del omcp del om +@skip_on_windows @skip_python_older_312 -def test_OMCPath_local(): - om = OMPython.OMCSessionZMQ() +def test_OMCPath_OMCProcessDocker(): + omcp = OMPython.OMCProcessDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") + om = OMPython.OMCSessionZMQ(omc_process=omcp) + assert om.sendExpression("getVersion()") == "OpenModelica 1.25.0" _run_OMCPath_checks(om) + del omcp del om @pytest.mark.skip(reason="Not able to run WSL on github") -def test_OMCPath_WSL(): +@skip_python_older_312 +def test_OMCPath_OMCProcessWSL(): omcp = OMPython.OMCProcessWSL( wsl_omc='omc', wsl_user='omc', @@ -52,14 +61,18 @@ def test_OMCPath_WSL(): def _run_OMCPath_checks(om: OMPython.OMCSessionZMQ): p1 = om.omcpath_tempdir() - p2 = p1 / '..' / p1.name / 'test.txt' - assert p2.is_file() is False - assert p2.write_text('test') - assert p2.is_file() - p2 = p2.resolve().absolute() - assert str(p2) == f"{str(p1)}/test.txt" - assert p2.read_text() == "test" - assert p2.is_file() - assert p2.parent.is_dir() - assert p2.unlink() - assert p2.is_file() is False + p2 = p1 / 'test' + p2.mkdir() + assert p2.is_dir() + p3 = p2 / '..' / p2.name / 'test.txt' + assert p3.is_file() is False + assert p3.write_text('test') + assert p3.is_file() + assert p3.size() > 0 + p3 = p3.resolve().absolute() + assert str(p3) == str((p2 / 'test.txt').resolve().absolute()) + assert p3.read_text() == "test" + assert p3.is_file() + assert p3.parent.is_dir() + assert p3.unlink() is None + assert p3.is_file() is False From b2a9190bc8cfb85796f0c95c93eb4da7485fcff2 Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 24 Jul 2025 20:55:01 +0200 Subject: [PATCH 31/32] [OMCPathReal] align exists() to the definition used in pathlib --- OMPython/OMCSession.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 1398aa50..d5badb4b 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -393,7 +393,7 @@ def absolute(self): """ return self.resolve(strict=True) - def exists(self) -> bool: + def exists(self, follow_symlinks=True) -> bool: """ Semi replacement for pathlib.Path.exists(). """ From b11bfa98761e93119a9448a1343694d4ba27952f Mon Sep 17 00:00:00 2001 From: syntron Date: Sat, 26 Jul 2025 15:47:20 +0200 Subject: [PATCH 32/32] [test_OMCPath] fix error error: "unlink" of "OMCPathReal" does not return a value (it only ever returns None) [func-returns-value] --- tests/test_OMCPath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_OMCPath.py b/tests/test_OMCPath.py index 449108ac..b8e937f3 100644 --- a/tests/test_OMCPath.py +++ b/tests/test_OMCPath.py @@ -74,5 +74,5 @@ def _run_OMCPath_checks(om: OMPython.OMCSessionZMQ): assert p3.read_text() == "test" assert p3.is_file() assert p3.parent.is_dir() - assert p3.unlink() is None + p3.unlink() assert p3.is_file() is False