Skip to content

Commit 7f2a02b

Browse files
committed
(D004) define OMSessionRunner
[(_)OMPathRunnerLocal] improve definition * fix return values * additional cleanups [__init__] define OMPathRunnerLocal for public interface [_OMPathRunnerBash] define class [__init__] define OMPathRunnerBash for public interface [OMSessionRunner] update code such that it can be used by OMPathRunnerLocal and OMPathRunner Bash
1 parent 7f52092 commit 7f2a02b

File tree

2 files changed

+213
-18
lines changed

2 files changed

+213
-18
lines changed

OMPython/OMCSession.py

Lines changed: 205 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import sys
2121
import tempfile
2222
import time
23-
from typing import Any, Optional, Tuple
23+
from typing import Any, Optional, Tuple, Type
2424
import uuid
2525
import warnings
2626

@@ -291,6 +291,8 @@ class OMPathCompatibilityWindows(pathlib.WindowsPath, OMPathCompatibility):
291291
OMCPath = OMPathCompatibility
292292
OMPathRunnerABC = OMPathCompatibility
293293
OMPathRunnerLocal = OMPathCompatibility
294+
OMPathRunnerBash = OMPathCompatibility
295+
294296
else:
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

628802
class 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
"""

OMPython/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
OMCSessionException,
4242
OMCSessionLocal,
4343
OMCSessionPort,
44+
45+
OMPathRunnerBash,
46+
OMPathRunnerLocal,
47+
4448
OMCSessionWSL,
4549
OMCSessionZMQ,
4650
)
@@ -77,6 +81,10 @@
7781
'OMCSessionException',
7882
'OMCSessionPort',
7983
'OMCSessionLocal',
84+
85+
'OMPathRunnerBash',
86+
'OMPathRunnerLocal',
87+
8088
'OMCSessionWSL',
8189
'OMCSessionZMQ',
8290
]

0 commit comments

Comments
 (0)