From 1193499f9025e0376c8e3c57e25a635291afba58 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 2807538de..320fd7a29 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -271,6 +271,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__( @@ -325,6 +350,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 01af4bab2e51d9b9184631c774bf5b25b1c259dd 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 320fd7a29..62935d34a 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -285,12 +285,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 873ab70cb1761bd1c4ef3751c17650dd7b8e4813 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 000000000..106f1cc7b --- /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 3019ac375d503cd33d63bce4f7ba78b18b5b5d44 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 62935d34a..b03b8f491 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -277,6 +277,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 @@ -366,6 +370,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 5c92c20a30a271544fe4400c8ec05585ee43081c 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 106f1cc7b..bae417142 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 e3ecb695175f9063f143e0a0b4d9560c04f0036a 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 bae417142..c0249df6a 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 5b3543866fb22e437ff56e8afdb1286db9f06c5a 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 c0249df6a..9621b54bd 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 234e4c9dbd09ddb7f1818aa9adc91183d66e89cd 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 b03b8f491..10282359d 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -280,6 +280,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 676e2787d54b3855fb9779b9e4ad10bf7437348a 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 9621b54bd..bef2b4732 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 99893cd02e35343b9912bde4608ec934a3ee1530 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 10282359d..fcf91429a 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -287,23 +287,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 c23e57f3e4ee0a61752af92cc1d720898e6d934e 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 fcf91429a..029d2746c 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -277,11 +277,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 @@ -420,15 +415,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 1acd916f9b87ca9f5bd33fdca725ca879fa13b84 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 029d2746c..2f4cac491 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -416,6 +416,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 7a50441f201310c67c886159d20c121b41cf75ad 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 2f4cac491..6bcb08364 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -481,8 +481,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 b1efdb18770e59eab55fd6eb1b557a820a321604 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 6bcb08364..efeda3f65 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -492,6 +492,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 e8bd3d87dccb59ff209c05fbc6d273fb9f7dd964 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 efeda3f65..834fad005 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -422,8 +422,7 @@ class OMCPathCompatibility(pathlib.Path): def size(self) -> int: return self.stat().st_size - - OMCPath = OMCPathCompatibility + OMCPath = OMCPathCompatibility # noqa: F811 class OMCSessionZMQ: From 84c73becbeb46bac36e5b12680a406d99e6d1b48 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 834fad005..7523b09fe 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -417,6 +417,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 3fa97b0c336b0afb94901c89b3f7377aa12730cc 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 7523b09fe..5334f9ec2 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -271,7 +271,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. @@ -423,13 +423,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: @@ -494,8 +497,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 84c7671a8717c4f46d9b70a2186460f85cfc37f4 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 bef2b4732..8cc2c88ce 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 f25688872b975feed55ed8528d6bc3163bf9c02a 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 8cc2c88ce..67f1316cc 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 458b875371d1259e694899d3cbbb5d84b21c4ca1 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 5334f9ec2..608f8c3c2 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -375,7 +375,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 9a16d44bd1c4a419faf25b5b15171d36eb4cde1c 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 67f1316cc..8d323ddf1 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 0ea522eb510c28e367eafc5d2f0f2ce334c640e0 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 8d323ddf1..ea87f53c8 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 0a67b3587af2c3be078ea35377a7e38dbfd376f8 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 ea87f53c8..456896227 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 7f86ff60fd1fc172e501c9078c23173785dc2b2d 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 608f8c3c2..5e3f5e118 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -404,7 +404,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 f2cd7180047a03205c868718889fd1e26b7b331d 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 5e3f5e118..eed1b001e 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -277,7 +277,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 @@ -289,27 +289,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. @@ -340,16 +341,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. """ @@ -365,7 +365,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. @@ -389,7 +389,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. @@ -417,18 +417,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 @@ -495,19 +519,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 63042fb45bca53006f8b4d2f20a5980cbb559657 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 eed1b001e..21e6155e8 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -443,15 +443,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 6328a17922ad92f6a90e66edf0fe294fb9778f56 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 21e6155e8..5ae937582 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -526,18 +526,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 ad7d54fc15f68c56d1ce1f1bc4d437e999be621c 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 5ae937582..d9b8333a7 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -418,7 +418,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 769bd67fd2dba77a4c235dd7d76220b155e40ebe 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 d9b8333a7..7b110c739 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -427,7 +427,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 553933a94f9b3b4511a73e473dacb01032411cad 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 456896227..449108acf 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 662967307fe6506a927ff6541531a6c6545f2446 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 7b110c739..2f2af10d5 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -396,7 +396,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 6f70ad289343105823ec46c9dea8753f4485cd02 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 449108acf..b8e937f3c 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