Skip to content

Commit b99ef6e

Browse files
committed
[OMCSession] update OMCPath to use OMPathABC as baseline and further cleanup
1 parent 90bb065 commit b99ef6e

1 file changed

Lines changed: 146 additions & 80 deletions

File tree

OMPython/OMCSession.py

Lines changed: 146 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -255,13 +255,15 @@ def getClassNames(self, className=None, recursive=False, qualified=False, sort=F
255255
return self._ask(question='getClassNames', opt=opt)
256256

257257

258-
class OMCPathReal(pathlib.PurePosixPath):
258+
class OMPathABC(pathlib.PurePosixPath, metaclass=abc.ABCMeta):
259259
"""
260-
Implementation of a basic (PurePosix)Path object which uses OMC as backend. The connection to OMC is provided via an
261-
instances of OMCSession* classes.
260+
Implementation of a basic (PurePosix)Path object to be used within OMPython. The derived classes can use OMC as
261+
backend and - thus - work on different configurations like docker or WSL. The connection to OMC is provided via an
262+
instances of classes derived from BaseSession.
262263
263-
PurePosixPath is selected to cover usage of OMC in docker or via WSL. Usage of specialised function could result in
264-
errors as well as usage on a Windows system due to slightly different definitions (PureWindowsPath).
264+
PurePosixPath is selected as it covers all but Windows systems (Linux, docker, WSL). However, the code is written
265+
such that possible Windows system are taken into account. Nevertheless, the overall functionality is limited
266+
compared to standard pathlib.Path objects.
265267
"""
266268

267269
def __init__(self, *path, session: OMCSession) -> None:
@@ -272,46 +274,122 @@ def with_segments(self, *pathsegments):
272274
"""
273275
Create a new OMCPath object with the given path segments.
274276
275-
The original definition of Path is overridden to ensure the OMC session is set.
277+
The original definition of Path is overridden to ensure the session data is set.
276278
"""
277279
return type(self)(*pathsegments, session=self._session)
278280

279-
def is_file(self, *, follow_symlinks=True) -> bool:
281+
@abc.abstractmethod
282+
def is_file(self) -> bool:
283+
"""
284+
Check if the path is a regular file.
285+
"""
286+
287+
@abc.abstractmethod
288+
def is_dir(self) -> bool:
289+
"""
290+
Check if the path is a directory.
291+
"""
292+
293+
@abc.abstractmethod
294+
def is_absolute(self):
295+
"""
296+
Check if the path is an absolute path.
297+
"""
298+
299+
@abc.abstractmethod
300+
def read_text(self) -> str:
301+
"""
302+
Read the content of the file represented by this path as text.
303+
"""
304+
305+
@abc.abstractmethod
306+
def write_text(self, data: str):
307+
"""
308+
Write text data to the file represented by this path.
309+
"""
310+
311+
@abc.abstractmethod
312+
def mkdir(self, parents: bool = True, exist_ok: bool = False):
313+
"""
314+
Create a directory at the path represented by this class.
315+
316+
The argument parents with default value True exists to ensure compatibility with the fallback solution for
317+
Python < 3.12. In this case, pathlib.Path is used directly and this option ensures, that missing parent
318+
directories are also created.
319+
"""
320+
321+
@abc.abstractmethod
322+
def cwd(self):
323+
"""
324+
Returns the current working directory as an OMPathBase object.
325+
"""
326+
327+
@abc.abstractmethod
328+
def unlink(self, missing_ok: bool = False) -> None:
329+
"""
330+
Unlink (delete) the file or directory represented by this path.
331+
"""
332+
333+
@abc.abstractmethod
334+
def resolve(self, strict: bool = False):
335+
"""
336+
Resolve the path to an absolute path.
337+
"""
338+
339+
def absolute(self):
340+
"""
341+
Resolve the path to an absolute path. Just a wrapper for resolve().
342+
"""
343+
return self.resolve()
344+
345+
def exists(self) -> bool:
346+
"""
347+
Semi replacement for pathlib.Path.exists().
348+
"""
349+
return self.is_file() or self.is_dir()
350+
351+
@abc.abstractmethod
352+
def size(self) -> int:
353+
"""
354+
Get the size of the file in bytes - this is an extra function and the best we can do using OMC.
355+
"""
356+
357+
358+
class _OMCPath(OMPathABC):
359+
"""
360+
Implementation of a OMPathBase using OMC as backend. The connection to OMC is provided via an instances of an
361+
OMCSession* classes.
362+
"""
363+
364+
def is_file(self) -> bool:
280365
"""
281366
Check if the path is a regular file.
282367
"""
283368
return self._session.sendExpression(f'regularFileExists("{self.as_posix()}")')
284369

285-
def is_dir(self, *, follow_symlinks=True) -> bool:
370+
def is_dir(self) -> bool:
286371
"""
287372
Check if the path is a directory.
288373
"""
289374
return self._session.sendExpression(f'directoryExists("{self.as_posix()}")')
290375

291376
def is_absolute(self):
292377
"""
293-
Check if the path is an absolute path considering the possibility that we are running locally on Windows. This
294-
case needs special handling as the definition of is_absolute() differs.
378+
Check if the path is an absolute path.
295379
"""
296380
if isinstance(self._session, OMCSessionLocal) and platform.system() == 'Windows':
297381
return pathlib.PureWindowsPath(self.as_posix()).is_absolute()
298382
return super().is_absolute()
299383

300-
def read_text(self, encoding=None, errors=None, newline=None) -> str:
384+
def read_text(self) -> str:
301385
"""
302386
Read the content of the file represented by this path as text.
303-
304-
The additional arguments `encoding`, `errors` and `newline` are only defined for compatibility with Path()
305-
definition.
306387
"""
307388
return self._session.sendExpression(f'readFile("{self.as_posix()}")')
308389

309-
def write_text(self, data: str, encoding=None, errors=None, newline=None):
390+
def write_text(self, data: str):
310391
"""
311392
Write text data to the file represented by this path.
312-
313-
The additional arguments `encoding`, `errors`, and `newline` are only defined for compatibility with Path()
314-
definitions.
315393
"""
316394
if not isinstance(data, str):
317395
raise TypeError(f"data must be str, not {data.__class__.__name__}")
@@ -321,11 +399,13 @@ def write_text(self, data: str, encoding=None, errors=None, newline=None):
321399

322400
return len(data)
323401

324-
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
402+
def mkdir(self, parents: bool = True, exist_ok: bool = False):
325403
"""
326-
Create a directory at the path represented by this OMCPath object.
404+
Create a directory at the path represented by this class.
327405
328-
The additional arguments `mode`, and `parents` are only defined for compatibility with Path() definitions.
406+
The argument parents with default value True exists to ensure compatibility with the fallback solution for
407+
Python < 3.12. In this case, pathlib.Path is used directly and this option ensures, that missing parent
408+
directories are also created.
329409
"""
330410
if self.is_dir() and not exist_ok:
331411
raise FileExistsError(f"Directory {self.as_posix()} already exists!")
@@ -334,7 +414,7 @@ def mkdir(self, mode=0o777, parents=False, exist_ok=False):
334414

335415
def cwd(self):
336416
"""
337-
Returns the current working directory as an OMCPath object.
417+
Returns the current working directory as an OMPathBase object.
338418
"""
339419
cwd_str = self._session.sendExpression('cd()')
340420
return OMCPath(cwd_str, session=self._session)
@@ -387,19 +467,6 @@ def _omc_resolve(self, pathstr: str) -> str:
387467

388468
return pathstr_resolved
389469

390-
def absolute(self):
391-
"""
392-
Resolve the path to an absolute path. This is done by calling resolve() as it is the best we can do
393-
using OMC functions.
394-
"""
395-
return self.resolve(strict=True)
396-
397-
def exists(self, follow_symlinks=True) -> bool:
398-
"""
399-
Semi replacement for pathlib.Path.exists().
400-
"""
401-
return self.is_file() or self.is_dir()
402-
403470
def size(self) -> int:
404471
"""
405472
Get the size of the file in bytes - this is an extra function and the best we can do using OMC.
@@ -414,47 +481,47 @@ def size(self) -> int:
414481
raise OMCSessionException(f"Error reading file size for path {self.as_posix()}!")
415482

416483

417-
if sys.version_info < (3, 12):
484+
class OMPathCompatibility(pathlib.Path):
485+
"""
486+
Compatibility class for OMPathBase in Python < 3.12. This allows to run all code which uses OMPathBase (mainly
487+
ModelicaSystem) on these Python versions. There are remaining limitation as only local execution is possible.
488+
"""
489+
490+
# modified copy of pathlib.Path.__new__() definition
491+
def __new__(cls, *args, **kwargs):
492+
logger.warning("Python < 3.12 - using a version of class OMCPath "
493+
"based on pathlib.Path for local usage only.")
494+
495+
if cls is OMPathCompatibility:
496+
cls = OMPathCompatibilityWindows if os.name == 'nt' else OMPathCompatibilityPosix
497+
self = cls._from_parts(args)
498+
if not self._flavour.is_supported:
499+
raise NotImplementedError(f"cannot instantiate {cls.__name__} on your system")
500+
return self
418501

419-
class OMCPathCompatibility(pathlib.Path):
502+
def size(self) -> int:
420503
"""
421-
Compatibility class for OMCPath in Python < 3.12. This allows to run all code which uses OMCPath (mainly
422-
ModelicaSystem) on these Python versions. There is one remaining limitation: only OMCProcessLocal will work as
423-
OMCPathCompatibility is based on the standard pathlib.Path implementation.
504+
Needed compatibility function to have the same interface as OMCPathReal
424505
"""
506+
return self.stat().st_size
425507

426-
# modified copy of pathlib.Path.__new__() definition
427-
def __new__(cls, *args, **kwargs):
428-
logger.warning("Python < 3.12 - using a version of class OMCPath "
429-
"based on pathlib.Path for local usage only.")
430-
431-
if cls is OMCPathCompatibility:
432-
cls = OMCPathCompatibilityWindows if os.name == 'nt' else OMCPathCompatibilityPosix
433-
self = cls._from_parts(args)
434-
if not self._flavour.is_supported:
435-
raise NotImplementedError(f"cannot instantiate {cls.__name__} on your system")
436-
return self
437508

438-
def size(self) -> int:
439-
"""
440-
Needed compatibility function to have the same interface as OMCPathReal
441-
"""
442-
return self.stat().st_size
509+
class OMPathCompatibilityPosix(pathlib.PosixPath, OMPathCompatibility):
510+
"""
511+
Compatibility class for OMCPath on Posix systems (Python < 3.12)
512+
"""
443513

444-
class OMCPathCompatibilityPosix(pathlib.PosixPath, OMCPathCompatibility):
445-
"""
446-
Compatibility class for OMCPath on Posix systems (Python < 3.12)
447-
"""
448514

449-
class OMCPathCompatibilityWindows(pathlib.WindowsPath, OMCPathCompatibility):
450-
"""
451-
Compatibility class for OMCPath on Windows systems (Python < 3.12)
452-
"""
515+
class OMPathCompatibilityWindows(pathlib.WindowsPath, OMPathCompatibility):
516+
"""
517+
Compatibility class for OMCPath on Windows systems (Python < 3.12)
518+
"""
453519

454-
OMCPath = OMCPathCompatibility
455520

521+
if sys.version_info < (3, 12):
522+
OMCPath = OMPathCompatibility
456523
else:
457-
OMCPath = OMCPathReal
524+
OMCPath = _OMCPath
458525

459526

460527
class ModelExecutionException(Exception):
@@ -576,13 +643,13 @@ def escape_str(value: str) -> str:
576643
"""
577644
return OMCSession.escape_str(value=value)
578645

579-
def omcpath(self, *path) -> OMCPath:
646+
def omcpath(self, *path) -> OMPathABC:
580647
"""
581648
Create an OMCPath object based on the given path segments and the current OMC process definition.
582649
"""
583650
return self.omc_process.omcpath(*path)
584651

585-
def omcpath_tempdir(self, tempdir_base: Optional[OMCPath] = None) -> OMCPath:
652+
def omcpath_tempdir(self, tempdir_base: Optional[OMPathABC] = None) -> OMPathABC:
586653
"""
587654
Get a temporary directory using OMC. It is our own implementation as non-local usage relies on OMC to run all
588655
filesystem related access.
@@ -755,11 +822,10 @@ def escape_str(value: str) -> str:
755822
"""
756823
return value.replace("\\", "\\\\").replace('"', '\\"')
757824

758-
def model_execution_prefix(self, cwd: Optional[OMCPath] = None) -> list[str]:
825+
def model_execution_prefix(self, cwd: Optional[OMPathABC] = None) -> list[str]:
759826
"""
760827
Helper function which returns a command prefix needed for docker and WSL. It defaults to an empty list.
761828
"""
762-
763829
return []
764830

765831
def get_version(self) -> str:
@@ -768,14 +834,14 @@ def get_version(self) -> str:
768834
"""
769835
return self.sendExpression("getVersion()", parsed=True)
770836

771-
def set_workdir(self, workdir: OMCPath) -> None:
837+
def set_workdir(self, workdir: OMPathABC) -> None:
772838
"""
773839
Set the workdir for this session.
774840
"""
775841
exp = f'cd("{workdir.as_posix()}")'
776842
self.sendExpression(exp)
777843

778-
def omcpath(self, *path) -> OMCPath:
844+
def omcpath(self, *path) -> OMPathABC:
779845
"""
780846
Create an OMCPath object based on the given path segments and the current OMCSession* class.
781847
"""
@@ -788,7 +854,7 @@ def omcpath(self, *path) -> OMCPath:
788854
raise OMCSessionException("OMCPath is supported for Python < 3.12 only if OMCSessionLocal is used!")
789855
return OMCPath(*path, session=self)
790856

791-
def omcpath_tempdir(self, tempdir_base: Optional[OMCPath] = None) -> OMCPath:
857+
def omcpath_tempdir(self, tempdir_base: Optional[OMPathABC] = None) -> OMPathABC:
792858
"""
793859
Get a temporary directory using OMC. It is our own implementation as non-local usage relies on OMC to run all
794860
filesystem related access.
@@ -805,10 +871,10 @@ def omcpath_tempdir(self, tempdir_base: Optional[OMCPath] = None) -> OMCPath:
805871
return self._tempdir(tempdir_base=tempdir_base)
806872

807873
@staticmethod
808-
def _tempdir(tempdir_base: OMCPath) -> OMCPath:
874+
def _tempdir(tempdir_base: OMPathABC) -> OMPathABC:
809875
names = [str(uuid.uuid4()) for _ in range(100)]
810876

811-
tempdir: Optional[OMCPath] = None
877+
tempdir: Optional[OMPathABC] = None
812878
for name in names:
813879
# create a unique temporary directory name
814880
tempdir = tempdir_base / name
@@ -1222,15 +1288,15 @@ def get_docker_container_id(self) -> str:
12221288

12231289
return self._docker_container_id
12241290

1225-
def model_execution_prefix(self, cwd: Optional[OMCPath] = None) -> list[str]:
1291+
def model_execution_prefix(self, cwd: Optional[OMPathABC] = None) -> list[str]:
12261292
"""
12271293
Helper function which returns a command prefix needed for docker and WSL. It defaults to an empty list.
12281294
"""
12291295
docker_cmd = [
12301296
"docker", "exec",
12311297
"--user", str(self._getuid()),
1232-
]
1233-
if isinstance(cwd, OMCPath):
1298+
]
1299+
if isinstance(cwd, OMPathABC):
12341300
docker_cmd += ["--workdir", cwd.as_posix()]
12351301
docker_cmd += self._docker_extra_args
12361302
if isinstance(self._docker_container_id, str):
@@ -1500,7 +1566,7 @@ def __init__(
15001566
# connect to the running omc instance using ZMQ
15011567
self._omc_port = self._omc_port_get()
15021568

1503-
def model_execution_prefix(self, cwd: Optional[OMCPath] = None) -> list[str]:
1569+
def model_execution_prefix(self, cwd: Optional[OMPathABC] = None) -> list[str]:
15041570
"""
15051571
Helper function which returns a command prefix needed for docker and WSL. It defaults to an empty list.
15061572
"""
@@ -1510,7 +1576,7 @@ def model_execution_prefix(self, cwd: Optional[OMCPath] = None) -> list[str]:
15101576
wsl_cmd += ['--distribution', self._wsl_distribution]
15111577
if isinstance(self._wsl_user, str):
15121578
wsl_cmd += ['--user', self._wsl_user]
1513-
if isinstance(cwd, OMCPath):
1579+
if isinstance(cwd, OMPathABC):
15141580
wsl_cmd += ['--cd', cwd.as_posix()]
15151581
wsl_cmd += ['--']
15161582

0 commit comments

Comments
 (0)