@@ -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
456523else :
457- OMCPath = OMCPathReal
524+ OMCPath = _OMCPath
458525
459526
460527class 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