2020import sys
2121import tempfile
2222import time
23- from typing import Any , Optional , Tuple
23+ from typing import Any , Optional , Tuple , Type
2424import uuid
2525import warnings
2626
@@ -291,6 +291,8 @@ class OMPathCompatibilityWindows(pathlib.WindowsPath, OMPathCompatibility):
291291 OMCPath = OMPathCompatibility
292292 OMPathRunnerABC = OMPathCompatibility
293293 OMPathRunnerLocal = OMPathCompatibility
294+ OMPathRunnerBash = OMPathCompatibility
295+
294296else :
295297 class OMPathABC (pathlib .PurePosixPath , metaclass = abc .ABCMeta ):
296298 """
@@ -544,10 +546,10 @@ def _path(self) -> pathlib.Path:
544546
545547 class _OMPathRunnerLocal (OMPathRunnerABC ):
546548 """
547- Implementation of OMPathBase which does not use the session data at all. Thus, this implementation can run
549+ Implementation of OMPathABC which does not use the session data at all. Thus, this implementation can run
548550 locally without any usage of OMC.
549551
550- This class is based on OMPathBase and, therefore, on pathlib.PurePosixPath. This is working well, but it is not
552+ This class is based on OMPathABC and, therefore, on pathlib.PurePosixPath. This is working well, but it is not
551553 the correct implementation on Windows systems. To get a valid Windows representation of the path, use the
552554 conversion via pathlib.Path(<OMCPathDummy>.as_posix()).
553555 """
@@ -564,7 +566,7 @@ def is_dir(self) -> bool:
564566 """
565567 return self ._path ().is_dir ()
566568
567- def is_absolute (self ):
569+ def is_absolute (self ) -> bool :
568570 """
569571 Check if the path is an absolute path.
570572 """
@@ -580,31 +582,34 @@ def write_text(self, data: str):
580582 """
581583 Write text data to the file represented by this path.
582584 """
585+ if not isinstance (data , str ):
586+ raise TypeError (f"data must be str, not { data .__class__ .__name__ } " )
587+
583588 return self ._path ().write_text (data = data , encoding = 'utf-8' )
584589
585- def mkdir (self , parents : bool = True , exist_ok : bool = False ):
590+ def mkdir (self , parents : bool = True , exist_ok : bool = False ) -> None :
586591 """
587592 Create a directory at the path represented by this class.
588593
589594 The argument parents with default value True exists to ensure compatibility with the fallback solution for
590595 Python < 3.12. In this case, pathlib.Path is used directly and this option ensures, that missing parent
591596 directories are also created.
592597 """
593- return self ._path ().mkdir (parents = parents , exist_ok = exist_ok )
598+ self ._path ().mkdir (parents = parents , exist_ok = exist_ok )
594599
595- def cwd (self ):
600+ def cwd (self ) -> OMPathABC :
596601 """
597- Returns the current working directory as an OMPathBase object.
602+ Returns the current working directory as an OMPathABC object.
598603 """
599- return self ._path ().cwd ()
604+ return type ( self )( self ._path ().cwd (). as_posix (), session = self . _session )
600605
601606 def unlink (self , missing_ok : bool = False ) -> None :
602607 """
603608 Unlink (delete) the file or directory represented by this path.
604609 """
605- return self ._path ().unlink (missing_ok = missing_ok )
610+ self ._path ().unlink (missing_ok = missing_ok )
606611
607- def resolve (self , strict : bool = False ):
612+ def resolve (self , strict : bool = False ) -> OMPathABC :
608613 """
609614 Resolve the path to an absolute path. This is done based on available OMC functions.
610615 """
@@ -621,8 +626,177 @@ def size(self) -> int:
621626 path = self ._path ()
622627 return path .stat ().st_size
623628
629+ class _OMPathRunnerBash (OMPathRunnerABC ):
630+ """
631+ Implementation of OMPathABC which does not use the session data at all. Thus, this implementation can run
632+ locally without any usage of OMC. The special case of this class is the usage of POSIX bash to run all the
633+ commands. Thus, it can be used in WSL or docker.
634+
635+ This class is based on OMPathABC and, therefore, on pathlib.PurePosixPath. This is working well, but it is not
636+ the correct implementation on Windows systems. To get a valid Windows representation of the path, use the
637+ conversion via pathlib.Path(<OMCPathDummy>.as_posix()).
638+ """
639+
640+ def is_file (self ) -> bool :
641+ """
642+ Check if the path is a regular file.
643+ """
644+ cmdl = self .get_session ().get_cmd_prefix ()
645+ cmdl += ['bash' , '-c' , f'test -f "{ self .as_posix ()} "' ]
646+
647+ try :
648+ subprocess .run (cmdl , check = True )
649+ return True
650+ except subprocess .CalledProcessError :
651+ return False
652+
653+ def is_dir (self ) -> bool :
654+ """
655+ Check if the path is a directory.
656+ """
657+ cmdl = self .get_session ().get_cmd_prefix ()
658+ cmdl += ['bash' , '-c' , f'test -d "{ self .as_posix ()} "' ]
659+
660+ try :
661+ subprocess .run (cmdl , check = True )
662+ return True
663+ except subprocess .CalledProcessError :
664+ return False
665+
666+ def is_absolute (self ) -> bool :
667+ """
668+ Check if the path is an absolute path.
669+ """
670+
671+ cmdl = self .get_session ().get_cmd_prefix ()
672+ cmdl += ['bash' , '-c' , f'case "{ self .as_posix ()} " in /*) exit 0;; *) exit 1;; esac' ]
673+
674+ try :
675+ subprocess .check_call (cmdl )
676+ return True
677+ except subprocess .CalledProcessError :
678+ return False
679+
680+ def read_text (self ) -> str :
681+ """
682+ Read the content of the file represented by this path as text.
683+ """
684+ cmdl = self .get_session ().get_cmd_prefix ()
685+ cmdl += ['bash' , '-c' , f'cat "{ self .as_posix ()} "' ]
686+
687+ result = subprocess .run (cmdl , capture_output = True , check = True )
688+ if result .returncode == 0 :
689+ return result .stdout .decode ('utf-8' )
690+ raise FileNotFoundError (f"Cannot read file: { self .as_posix ()} " )
691+
692+ def write_text (self , data : str ) -> int :
693+ """
694+ Write text data to the file represented by this path.
695+ """
696+ if not isinstance (data , str ):
697+ raise TypeError (f"data must be str, not { data .__class__ .__name__ } " )
698+
699+ data_escape = self ._session .escape_str (data )
700+
701+ cmdl = self .get_session ().get_cmd_prefix ()
702+ cmdl += ['bash' , '-c' , f'printf %s "{ data_escape } " > "{ self .as_posix ()} "' ]
703+
704+ try :
705+ subprocess .run (cmdl , check = True )
706+ return len (data )
707+ except subprocess .CalledProcessError as exc :
708+ raise IOError (f"Error writing data to file { self .as_posix ()} !" ) from exc
709+
710+ def mkdir (self , parents : bool = True , exist_ok : bool = False ) -> None :
711+ """
712+ Create a directory at the path represented by this class.
713+
714+ The argument parents with default value True exists to ensure compatibility with the fallback solution for
715+ Python < 3.12. In this case, pathlib.Path is used directly and this option ensures, that missing parent
716+ directories are also created.
717+ """
718+
719+ if self .is_file ():
720+ raise OSError (f"The given path { self .as_posix ()} exists and is a file!" )
721+ if self .is_dir () and not exist_ok :
722+ raise OSError (f"The given path { self .as_posix ()} exists and is a directory!" )
723+ if not parents and not self .parent .is_dir ():
724+ raise FileNotFoundError (f"Parent directory of { self .as_posix ()} does not exists!" )
725+
726+ cmdl = self .get_session ().get_cmd_prefix ()
727+ cmdl += ['bash' , '-c' , f'mkdir -p "{ self .as_posix ()} "' ]
728+
729+ try :
730+ subprocess .run (cmdl , check = True )
731+ except subprocess .CalledProcessError as exc :
732+ raise OMCSessionException (f"Error on directory creation for { self .as_posix ()} !" ) from exc
733+
734+ def cwd (self ) -> OMPathABC :
735+ """
736+ Returns the current working directory as an OMPathABC object.
737+ """
738+ cmdl = self .get_session ().get_cmd_prefix ()
739+ cmdl += ['bash' , '-c' , 'pwd' ]
740+
741+ result = subprocess .run (cmdl , capture_output = True , text = True , check = True )
742+ if result .returncode == 0 :
743+ return type (self )(result .stdout .strip (), session = self ._session )
744+ raise OSError ("Can not get current work directory ..." )
745+
746+ def unlink (self , missing_ok : bool = False ) -> None :
747+ """
748+ Unlink (delete) the file or directory represented by this path.
749+ """
750+
751+ if not self .is_file ():
752+ raise OSError (f"Can not unlink a directory: { self .as_posix ()} !" )
753+
754+ if not self .is_file ():
755+ return
756+
757+ cmdl = self .get_session ().get_cmd_prefix ()
758+ cmdl += ['bash' , '-c' , f'rm "{ self .as_posix ()} "' ]
759+
760+ try :
761+ subprocess .run (cmdl , check = True )
762+ except subprocess .CalledProcessError as exc :
763+ raise OSError (f"Cannot unlink file { self .as_posix ()} : { exc } " ) from exc
764+
765+ def resolve (self , strict : bool = False ) -> OMPathABC :
766+ """
767+ Resolve the path to an absolute path. This is done based on available OMC functions.
768+ """
769+ cmdl = self .get_session ().get_cmd_prefix ()
770+ cmdl += ['bash' , '-c' , f'readlink -f "{ self .as_posix ()} "' ]
771+
772+ result = subprocess .run (cmdl , capture_output = True , text = True , check = True )
773+ if result .returncode == 0 :
774+ return type (self )(result .stdout .strip (), session = self ._session )
775+ raise FileNotFoundError (f"Cannot resolve path: { self .as_posix ()} " )
776+
777+ def size (self ) -> int :
778+ """
779+ Get the size of the file in bytes - implementation baseon on pathlib.Path.
780+ """
781+ if not self .is_file ():
782+ raise OMCSessionException (f"Path { self .as_posix ()} is not a file!" )
783+
784+ cmdl = self .get_session ().get_cmd_prefix ()
785+ cmdl += ['bash' , '-c' , f'stat -c %s "{ self .as_posix ()} "' ]
786+
787+ result = subprocess .run (cmdl , capture_output = True , text = True , check = True )
788+ stdout = result .stdout .strip ()
789+ if result .returncode == 0 :
790+ try :
791+ return int (stdout )
792+ except ValueError as exc :
793+ raise OSError (f"Invalid return value for filesize ({ self .as_posix ()} ): { stdout } " ) from exc
794+ else :
795+ raise OSError (f"Cannot get size for file { self .as_posix ()} " )
796+
624797 OMCPath = _OMCPath
625798 OMPathRunnerLocal = _OMPathRunnerLocal
799+ OMPathRunnerBash = _OMPathRunnerBash
626800
627801
628802class ModelExecutionException (Exception ):
@@ -1870,13 +2044,26 @@ class OMSessionRunner(OMSessionABC):
18702044
18712045 def __init__ (
18722046 self ,
1873- timeout : float = 10.00 ,
1874- version : str = "1.27.0"
2047+ timeout : float = 10.0 ,
2048+ version : str = "1.27.0" ,
2049+ ompath_runner : Type [OMPathRunnerABC ] = OMPathRunnerLocal ,
2050+ cmd_prefix : Optional [list [str ]] = None ,
2051+ model_execution_local : bool = True ,
18752052 ) -> None :
18762053 super ().__init__ (timeout = timeout )
1877- self .model_execution_local = True
18782054 self ._version = version
18792055
2056+ if not issubclass (ompath_runner , OMPathRunnerABC ):
2057+ raise OMCSessionException (f"Invalid OMPathRunner class: { type (ompath_runner )} !" )
2058+ self ._ompath_runner = ompath_runner
2059+
2060+ self .model_execution_local = model_execution_local
2061+ if cmd_prefix is not None :
2062+ self ._cmd_prefix = cmd_prefix
2063+
2064+ # TODO: some checking?!
2065+ # if ompath_runner == Type[OMPathRunnerBash]:
2066+
18802067 def __post_init__ (self ) -> None :
18812068 """
18822069 No connection to an OMC server is created by this class!
@@ -1886,7 +2073,7 @@ def model_execution_prefix(self, cwd: Optional[OMPathABC] = None) -> list[str]:
18862073 """
18872074 Helper function which returns a command prefix.
18882075 """
1889- return []
2076+ return self . get_cmd_prefix ()
18902077
18912078 def get_version (self ) -> str :
18922079 """
@@ -1897,15 +2084,15 @@ def get_version(self) -> str:
18972084
18982085 def set_workdir (self , workdir : OMPathABC ) -> None :
18992086 """
1900- Set the workdir for this session.
2087+ Set the workdir for this session. For OMSessionRunner this is a nop. The workdir must be defined within the
2088+ definition of cmd_prefix.
19012089 """
1902- os .chdir (workdir .as_posix ())
19032090
19042091 def omcpath (self , * path ) -> OMPathABC :
19052092 """
19062093 Create an OMCPath object based on the given path segments and the current OMCSession* class.
19072094 """
1908- return OMPathRunnerLocal (* path , session = self )
2095+ return self . _ompath_runner (* path , session = self )
19092096
19102097 def omcpath_tempdir (self , tempdir_base : Optional [OMPathABC ] = None ) -> OMPathABC :
19112098 """
0 commit comments