Skip to content

Commit f406c47

Browse files
committed
[OMCSession] update OMCPath to use OMPathABC as baseline and further cleanup
1 parent 7d131ff commit f406c47

1 file changed

Lines changed: 145 additions & 78 deletions

File tree

OMPython/OMCSession.py

Lines changed: 145 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -249,13 +249,15 @@ def getClassNames(self, className=None, recursive=False, qualified=False, sort=F
249249
return self._ask(question='getClassNames', opt=opt)
250250

251251

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

261263
def __init__(self, *path, session: OMCSession) -> None:
@@ -266,46 +268,122 @@ def with_segments(self, *pathsegments):
266268
"""
267269
Create a new OMCPath object with the given path segments.
268270
269-
The original definition of Path is overridden to ensure the OMC session is set.
271+
The original definition of Path is overridden to ensure the session data is set.
270272
"""
271273
return type(self)(*pathsegments, session=self._session)
272274

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

279-
def is_dir(self, *, follow_symlinks=True) -> bool:
364+
def is_dir(self) -> bool:
280365
"""
281366
Check if the path is a directory.
282367
"""
283368
return self._session.sendExpression(expr=f'directoryExists("{self.as_posix()}")')
284369

285370
def is_absolute(self):
286371
"""
287-
Check if the path is an absolute path considering the possibility that we are running locally on Windows. This
288-
case needs special handling as the definition of is_absolute() differs.
372+
Check if the path is an absolute path.
289373
"""
290374
if isinstance(self._session, OMCSessionLocal) and platform.system() == 'Windows':
291375
return pathlib.PureWindowsPath(self.as_posix()).is_absolute()
292376
return super().is_absolute()
293377

294-
def read_text(self, encoding=None, errors=None, newline=None) -> str:
378+
def read_text(self) -> str:
295379
"""
296380
Read the content of the file represented by this path as text.
297-
298-
The additional arguments `encoding`, `errors` and `newline` are only defined for compatibility with Path()
299-
definition.
300381
"""
301382
return self._session.sendExpression(expr=f'readFile("{self.as_posix()}")')
302383

303-
def write_text(self, data: str, encoding=None, errors=None, newline=None):
384+
def write_text(self, data: str):
304385
"""
305386
Write text data to the file represented by this path.
306-
307-
The additional arguments `encoding`, `errors`, and `newline` are only defined for compatibility with Path()
308-
definitions.
309387
"""
310388
if not isinstance(data, str):
311389
raise TypeError(f"data must be str, not {data.__class__.__name__}")
@@ -315,11 +393,13 @@ def write_text(self, data: str, encoding=None, errors=None, newline=None):
315393

316394
return len(data)
317395

318-
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
396+
def mkdir(self, parents: bool = True, exist_ok: bool = False):
319397
"""
320-
Create a directory at the path represented by this OMCPath object.
398+
Create a directory at the path represented by this class.
321399
322-
The additional arguments `mode`, and `parents` are only defined for compatibility with Path() definitions.
400+
The argument parents with default value True exists to ensure compatibility with the fallback solution for
401+
Python < 3.12. In this case, pathlib.Path is used directly and this option ensures, that missing parent
402+
directories are also created.
323403
"""
324404
if self.is_dir() and not exist_ok:
325405
raise FileExistsError(f"Directory {self.as_posix()} already exists!")
@@ -328,7 +408,7 @@ def mkdir(self, mode=0o777, parents=False, exist_ok=False):
328408

329409
def cwd(self):
330410
"""
331-
Returns the current working directory as an OMCPath object.
411+
Returns the current working directory as an OMPathBase object.
332412
"""
333413
cwd_str = self._session.sendExpression(expr='cd()')
334414
return OMCPath(cwd_str, session=self._session)
@@ -381,19 +461,6 @@ def _omc_resolve(self, pathstr: str) -> str:
381461

382462
return pathstr_resolved
383463

384-
def absolute(self):
385-
"""
386-
Resolve the path to an absolute path. This is done by calling resolve() as it is the best we can do
387-
using OMC functions.
388-
"""
389-
return self.resolve(strict=True)
390-
391-
def exists(self, follow_symlinks=True) -> bool:
392-
"""
393-
Semi replacement for pathlib.Path.exists().
394-
"""
395-
return self.is_file() or self.is_dir()
396-
397464
def size(self) -> int:
398465
"""
399466
Get the size of the file in bytes - this is an extra function and the best we can do using OMC.
@@ -408,47 +475,47 @@ def size(self) -> int:
408475
raise OMCSessionException(f"Error reading file size for path {self.as_posix()}!")
409476

410477

411-
if sys.version_info < (3, 12):
478+
class OMPathCompatibility(pathlib.Path):
479+
"""
480+
Compatibility class for OMPathBase in Python < 3.12. This allows to run all code which uses OMPathBase (mainly
481+
ModelicaSystem) on these Python versions. There are remaining limitation as only local execution is possible.
482+
"""
412483

413-
class OMCPathCompatibility(pathlib.Path):
484+
# modified copy of pathlib.Path.__new__() definition
485+
def __new__(cls, *args, **kwargs):
486+
logger.warning("Python < 3.12 - using a version of class OMCPath "
487+
"based on pathlib.Path for local usage only.")
488+
489+
if cls is OMPathCompatibility:
490+
cls = OMPathCompatibilityWindows if os.name == 'nt' else OMPathCompatibilityPosix
491+
self = cls._from_parts(args)
492+
if not self._flavour.is_supported:
493+
raise NotImplementedError(f"cannot instantiate {cls.__name__} on your system")
494+
return self
495+
496+
def size(self) -> int:
414497
"""
415-
Compatibility class for OMCPath in Python < 3.12. This allows to run all code which uses OMCPath (mainly
416-
ModelicaSystem) on these Python versions. There is one remaining limitation: only OMCProcessLocal will work as
417-
OMCPathCompatibility is based on the standard pathlib.Path implementation.
498+
Needed compatibility function to have the same interface as OMCPathReal
418499
"""
500+
return self.stat().st_size
419501

420-
# modified copy of pathlib.Path.__new__() definition
421-
def __new__(cls, *args, **kwargs):
422-
logger.warning("Python < 3.12 - using a version of class OMCPath "
423-
"based on pathlib.Path for local usage only.")
424502

425-
if cls is OMCPathCompatibility:
426-
cls = OMCPathCompatibilityWindows if os.name == 'nt' else OMCPathCompatibilityPosix
427-
self = cls._from_parts(args)
428-
if not self._flavour.is_supported:
429-
raise NotImplementedError(f"cannot instantiate {cls.__name__} on your system")
430-
return self
431-
432-
def size(self) -> int:
433-
"""
434-
Needed compatibility function to have the same interface as OMCPathReal
435-
"""
436-
return self.stat().st_size
503+
class OMPathCompatibilityPosix(pathlib.PosixPath, OMPathCompatibility):
504+
"""
505+
Compatibility class for OMCPath on Posix systems (Python < 3.12)
506+
"""
437507

438-
class OMCPathCompatibilityPosix(pathlib.PosixPath, OMCPathCompatibility):
439-
"""
440-
Compatibility class for OMCPath on Posix systems (Python < 3.12)
441-
"""
442508

443-
class OMCPathCompatibilityWindows(pathlib.WindowsPath, OMCPathCompatibility):
444-
"""
445-
Compatibility class for OMCPath on Windows systems (Python < 3.12)
446-
"""
509+
class OMPathCompatibilityWindows(pathlib.WindowsPath, OMPathCompatibility):
510+
"""
511+
Compatibility class for OMCPath on Windows systems (Python < 3.12)
512+
"""
447513

448-
OMCPath = OMCPathCompatibility
449514

515+
if sys.version_info < (3, 12):
516+
OMCPath = OMPathCompatibility
450517
else:
451-
OMCPath = OMCPathReal
518+
OMCPath = _OMCPath
452519

453520

454521
class ModelExecutionException(Exception):
@@ -570,13 +637,13 @@ def escape_str(value: str) -> str:
570637
"""
571638
return OMCSession.escape_str(value=value)
572639

573-
def omcpath(self, *path) -> OMCPath:
640+
def omcpath(self, *path) -> OMPathABC:
574641
"""
575642
Create an OMCPath object based on the given path segments and the current OMC process definition.
576643
"""
577644
return self.omc_process.omcpath(*path)
578645

579-
def omcpath_tempdir(self, tempdir_base: Optional[OMCPath] = None) -> OMCPath:
646+
def omcpath_tempdir(self, tempdir_base: Optional[OMPathABC] = None) -> OMPathABC:
580647
"""
581648
Get a temporary directory using OMC. It is our own implementation as non-local usage relies on OMC to run all
582649
filesystem related access.
@@ -796,7 +863,7 @@ def get_version(self) -> str:
796863
"""
797864
return self.sendExpression("getVersion()", parsed=True)
798865

799-
def set_workdir(self, workdir: OMCPath) -> None:
866+
def set_workdir(self, workdir: OMPathABC) -> None:
800867
"""
801868
Set the workdir for this session.
802869
"""
@@ -810,7 +877,7 @@ def model_execution_prefix(self, cwd: Optional[OMCPath] = None) -> list[str]:
810877

811878
return []
812879

813-
def omcpath(self, *path) -> OMCPath:
880+
def omcpath(self, *path) -> OMPathABC:
814881
"""
815882
Create an OMCPath object based on the given path segments and the current OMCSession* class.
816883
"""
@@ -823,7 +890,7 @@ def omcpath(self, *path) -> OMCPath:
823890
raise OMCSessionException("OMCPath is supported for Python < 3.12 only if OMCSessionLocal is used!")
824891
return OMCPath(*path, session=self)
825892

826-
def omcpath_tempdir(self, tempdir_base: Optional[OMCPath] = None) -> OMCPath:
893+
def omcpath_tempdir(self, tempdir_base: Optional[OMPathABC] = None) -> OMPathABC:
827894
"""
828895
Get a temporary directory using OMC. It is our own implementation as non-local usage relies on OMC to run all
829896
filesystem related access.
@@ -840,10 +907,10 @@ def omcpath_tempdir(self, tempdir_base: Optional[OMCPath] = None) -> OMCPath:
840907
return self._tempdir(tempdir_base=tempdir_base)
841908

842909
@staticmethod
843-
def _tempdir(tempdir_base: OMCPath) -> OMCPath:
910+
def _tempdir(tempdir_base: OMPathABC) -> OMPathABC:
844911
names = [str(uuid.uuid4()) for _ in range(100)]
845912

846-
tempdir: Optional[OMCPath] = None
913+
tempdir: Optional[OMPathABC] = None
847914
for name in names:
848915
# create a unique temporary directory name
849916
tempdir = tempdir_base / name
@@ -1243,15 +1310,15 @@ def get_docker_container_id(self) -> str:
12431310

12441311
return self._docker_container_id
12451312

1246-
def model_execution_prefix(self, cwd: Optional[OMCPath] = None) -> list[str]:
1313+
def model_execution_prefix(self, cwd: Optional[OMPathABC] = None) -> list[str]:
12471314
"""
12481315
Helper function which returns a command prefix needed for docker and WSL. It defaults to an empty list.
12491316
"""
12501317
docker_cmd = [
12511318
"docker", "exec",
12521319
"--user", str(self._getuid()),
1253-
]
1254-
if isinstance(cwd, OMCPath):
1320+
]
1321+
if isinstance(cwd, OMPathABC):
12551322
docker_cmd += ["--workdir", cwd.as_posix()]
12561323
docker_cmd += self._docker_extra_args
12571324
if isinstance(self._docker_container_id, str):
@@ -1520,7 +1587,7 @@ def __init__(
15201587
# connect to the running omc instance using ZMQ
15211588
self._omc_port = self._omc_port_get()
15221589

1523-
def model_execution_prefix(self, cwd: Optional[OMCPath] = None) -> list[str]:
1590+
def model_execution_prefix(self, cwd: Optional[OMPathABC] = None) -> list[str]:
15241591
"""
15251592
Helper function which returns a command prefix needed for docker and WSL. It defaults to an empty list.
15261593
"""
@@ -1530,7 +1597,7 @@ def model_execution_prefix(self, cwd: Optional[OMCPath] = None) -> list[str]:
15301597
wsl_cmd += ['--distribution', self._wsl_distribution]
15311598
if isinstance(self._wsl_user, str):
15321599
wsl_cmd += ['--user', self._wsl_user]
1533-
if isinstance(cwd, OMCPath):
1600+
if isinstance(cwd, OMPathABC):
15341601
wsl_cmd += ['--cd', cwd.as_posix()]
15351602
wsl_cmd += ['--']
15361603

0 commit comments

Comments
 (0)