diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 17678bb0..96fbfaf6 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -12,7 +12,7 @@ import numpy as np from OMPython.model_execution import ( - ModelExecutionCmd, + ModelExecutionConfig, ModelExecutionException, ) from OMPython.om_session_omc import ( @@ -176,7 +176,7 @@ class ModelicaSystemDoE(ModelicaDoEOMC): """ -class ModelicaSystemCmd(ModelExecutionCmd): +class ModelicaSystemCmd(ModelExecutionConfig): """ Compatibility class; in the new version it is renamed as ModelExecutionCmd. """ diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index c5511923..ecc033f1 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -282,12 +282,14 @@ def omcpath_tempdir(self, tempdir_base: Optional[OMPathABC] = None) -> OMPathABC def execute(self, command: str): return self.omc_process.execute(command=command) - def sendExpression(self, command: str, parsed: bool = True) -> Any: + def sendExpression(self, command: str, parsed: bool = True) -> Any: # pylint: disable=W0237 """ Send an expression to the OMC server and return the result. - The complete error handling of the OMC result is done within this method using '"getMessagesStringInternal()'. - Caller should only check for OMSessionException. + The complete error handling of the OMC result is done within this method using 'getMessagesStringInternal()'. + Caller should only check for OMCSessionException. + + Compatibility: 'command' was renamed to 'expr' """ return self.omc_process.sendExpression(expr=command, parsed=parsed) diff --git a/OMPython/__init__.py b/OMPython/__init__.py index 282923a7..1ea0ed8a 100644 --- a/OMPython/__init__.py +++ b/OMPython/__init__.py @@ -6,14 +6,14 @@ ``` import OMPython omc = OMPython.OMCSessionLocal() -omc.sendExpression("command") +omc.sendExpression("getVersion()") ``` """ from OMPython.model_execution import ( - ModelExecutionCmd, - ModelExecutionData, + ModelExecutionConfig, + ModelExecutionRun, ModelExecutionException, ) from OMPython.om_session_abc import ( @@ -58,15 +58,16 @@ ModelicaDoERunner, ) +# the imports below are compatibility functionality (OMPython v4.0.0) from OMPython.ModelicaSystem import ( ModelicaSystem, - ModelicaSystemDoE, ModelicaSystemCmd, + ModelicaSystemDoE, ) from OMPython.OMCSession import ( OMCSessionCmd, - OMCSessionZMQ, OMCSessionException, + OMCSessionZMQ, OMCProcessLocal, OMCProcessPort, @@ -80,8 +81,8 @@ 'LinearizationResult', - 'ModelExecutionCmd', - 'ModelExecutionData', + 'ModelExecutionConfig', + 'ModelExecutionRun', 'ModelExecutionException', 'ModelicaDoEABC', diff --git a/OMPython/model_execution.py b/OMPython/model_execution.py index ebd4c011..87fc6bdf 100644 --- a/OMPython/model_execution.py +++ b/OMPython/model_execution.py @@ -27,14 +27,13 @@ class ModelExecutionException(Exception): @dataclasses.dataclass -class ModelExecutionData: +class ModelExecutionRun: """ - Data class to store the command line data for running a model executable in the OMC environment. + Data class to store the command line data for running a model executable. This definition is independent of the OMC + environment as only the executable is needed. - All data should be defined for the environment, where OMC is running (local, docker or WSL) - - To use this as a definition of an OMC simulation run, it has to be processed within - OMCProcess*.self_update(). This defines the attribute cmd_model_executable. + All data should be defined for the environment, where the executable was defined / is located. This is especially + important if OMPython and the executable are defined in different environments (docker or WSL). """ # cmd_path is the expected working directory cmd_path: str @@ -105,11 +104,12 @@ def run(self) -> int: return returncode -class ModelExecutionCmd: +class ModelExecutionConfig: """ - All information about a compiled model executable. This should include data about all structured parameters, i.e. - parameters which need a recompilation of the model. All non-structured parameters can be easily changed without - the need for recompilation. + This class collects all information about a compiled model executable. This includes data about all structured + parameters, i.e. parameters which need a recompilation of the model. All non-structured parameters can be easily + changed without the need for recompilation. The final result is an instance of class ModelExecutionRun - a + definition to run one simulation based on the compiled model executable. """ def __init__( @@ -261,7 +261,7 @@ def get_cmd_args(self) -> list[str]: return cmdl - def definition(self) -> ModelExecutionData: + def definition(self) -> ModelExecutionRun: """ Define all needed data to run the model executable. The data is stored in an OMCSessionRunData object. """ @@ -301,7 +301,7 @@ def definition(self) -> ModelExecutionData: if self._cmd_local: cmd_cwd_local = cmd_path.as_posix() - omc_run_data = ModelExecutionData( + omc_run_data = ModelExecutionRun( cmd_path=cmd_path.as_posix(), cmd_model_name=self._model_name, cmd_args=self.get_cmd_args(), diff --git a/OMPython/modelica_doe_abc.py b/OMPython/modelica_doe_abc.py index e3ab8403..0ab3add9 100644 --- a/OMPython/modelica_doe_abc.py +++ b/OMPython/modelica_doe_abc.py @@ -13,7 +13,7 @@ from typing import Any, cast, Optional, Tuple from OMPython.model_execution import ( - ModelExecutionData, + ModelExecutionRun, ) from OMPython.om_session_abc import ( OMPathABC, @@ -138,7 +138,7 @@ def __init__( self._parameters = {} self._doe_def: Optional[dict[str, dict[str, Any]]] = None - self._doe_cmd: Optional[dict[str, ModelExecutionData]] = None + self._doe_cmd: Optional[dict[str, ModelExecutionRun]] = None def get_session(self) -> OMSessionABC: """ @@ -255,7 +255,7 @@ def get_doe_definition(self) -> Optional[dict[str, dict[str, Any]]]: """ return self._doe_def - def get_doe_command(self) -> Optional[dict[str, ModelExecutionData]]: + def get_doe_command(self) -> Optional[dict[str, ModelExecutionRun]]: """ Get the definitions of simulations commands to run for this DoE. """ diff --git a/OMPython/modelica_system_abc.py b/OMPython/modelica_system_abc.py index fcc31deb..0f04e4df 100644 --- a/OMPython/modelica_system_abc.py +++ b/OMPython/modelica_system_abc.py @@ -17,7 +17,7 @@ import numpy as np from OMPython.model_execution import ( - ModelExecutionCmd, + ModelExecutionConfig, ) from OMPython.om_session_abc import ( OMPathABC, @@ -189,7 +189,7 @@ def check_model_executable(self): Check if the model executable is working """ # check if the executable exists ... - om_cmd = ModelExecutionCmd( + om_cmd = ModelExecutionConfig( runpath=self.getWorkDirectory(), cmd_local=self._session.model_execution_local, cmd_windows=self._session.model_execution_windows, @@ -579,7 +579,7 @@ def _parse_om_version(version: str) -> tuple[int, int, int]: def _process_override_data( self, - om_cmd: ModelExecutionCmd, + om_cmd: ModelExecutionConfig, override_file: OMPathABC, override_var: dict[str, str], override_sim: dict[str, str], @@ -619,7 +619,7 @@ def simulate_cmd( result_file: OMPathABC, simflags: Optional[str] = None, simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None, - ) -> ModelExecutionCmd: + ) -> ModelExecutionConfig: """ This method prepares the simulates model according to the simulation options. It returns an instance of ModelicaSystemCmd which can be used to run the simulation. @@ -641,7 +641,7 @@ def simulate_cmd( An instance if ModelicaSystemCmd to run the requested simulation. """ - om_cmd = ModelExecutionCmd( + om_cmd = ModelExecutionConfig( runpath=self.getWorkDirectory(), cmd_local=self._session.model_execution_local, cmd_windows=self._session.model_execution_windows, @@ -1026,7 +1026,7 @@ def setInputs( self._inputs[key] = [(float(self._simulate_options["startTime"]), float(val)), (float(self._simulate_options["stopTime"]), float(val))] elif isinstance(val_evaluated, list): - if not all([isinstance(item, tuple) for item in val_evaluated]): + if not all(isinstance(item, tuple) for item in val_evaluated): raise ModelicaSystemError("Value for setInput() must be in tuple format; " f"got {repr(val_evaluated)}") if val_evaluated != sorted(val_evaluated, key=lambda x: x[0]): @@ -1134,7 +1134,7 @@ def linearize( "use ModelicaSystemOMC() to build the model first" ) - om_cmd = ModelExecutionCmd( + om_cmd = ModelExecutionConfig( runpath=self.getWorkDirectory(), cmd_local=self._session.model_execution_local, cmd_windows=self._session.model_execution_windows, diff --git a/OMPython/om_session_abc.py b/OMPython/om_session_abc.py index 70e897d7..fdfa5491 100644 --- a/OMPython/om_session_abc.py +++ b/OMPython/om_session_abc.py @@ -97,13 +97,13 @@ def with_segments(self, *pathsegments) -> OMPathABC: return type(self)(*pathsegments, session=self._session) @abc.abstractmethod - def is_file(self) -> bool: + def is_file(self, *, follow_symlinks=True) -> bool: """ Check if the path is a regular file. """ @abc.abstractmethod - def is_dir(self) -> bool: + def is_dir(self, *, follow_symlinks: bool = True) -> bool: """ Check if the path is a directory. """ @@ -115,19 +115,19 @@ def is_absolute(self) -> bool: """ @abc.abstractmethod - def read_text(self) -> str: + def read_text(self, encoding=None, errors=None, newline=None) -> str: """ Read the content of the file represented by this path as text. """ @abc.abstractmethod - def write_text(self, data: str) -> int: + def write_text(self, data: str, encoding=None, errors=None, newline=None) -> int: """ Write text data to the file represented by this path. """ @abc.abstractmethod - def mkdir(self, parents: bool = True, exist_ok: bool = False) -> None: + def mkdir(self, mode=0o777, parents: bool = False, exist_ok: bool = False) -> None: """ Create a directory at the path represented by this class. @@ -137,7 +137,7 @@ def mkdir(self, parents: bool = True, exist_ok: bool = False) -> None: """ @abc.abstractmethod - def cwd(self) -> OMPathABC: + def cwd(self) -> OMPathABC: # pylint: disable=W0221 # is @classmethod in the original; see pathlib.PathBase """ Returns the current working directory as an OMPathABC object. """ diff --git a/OMPython/om_session_omc.py b/OMPython/om_session_omc.py index 6626cd17..4fafc3a1 100644 --- a/OMPython/om_session_omc.py +++ b/OMPython/om_session_omc.py @@ -52,19 +52,23 @@ class _OMCPath(OMPathABC): OMCSession* classes. """ - def is_file(self) -> bool: + def is_file(self, *, follow_symlinks=True) -> bool: """ Check if the path is a regular file. """ + del follow_symlinks + retval = self.get_session().sendExpression(expr=f'regularFileExists("{self.as_posix()}")') if not isinstance(retval, bool): raise OMSessionException(f"Invalid return value for is_file(): {retval} - expect bool") return retval - def is_dir(self) -> bool: + def is_dir(self, *, follow_symlinks: bool = True) -> bool: """ Check if the path is a directory. """ + del follow_symlinks + retval = self.get_session().sendExpression(expr=f'directoryExists("{self.as_posix()}")') if not isinstance(retval, bool): raise OMSessionException(f"Invalid return value for is_dir(): {retval} - expect bool") @@ -78,19 +82,23 @@ def is_absolute(self) -> bool: return pathlib.PureWindowsPath(self.as_posix()).is_absolute() return pathlib.PurePosixPath(self.as_posix()).is_absolute() - def read_text(self) -> str: + def read_text(self, encoding=None, errors=None, newline=None) -> str: """ Read the content of the file represented by this path as text. """ + del encoding, errors, newline + retval = self.get_session().sendExpression(expr=f'readFile("{self.as_posix()}")') if not isinstance(retval, str): raise OMSessionException(f"Invalid return value for read_text(): {retval} - expect str") return retval - def write_text(self, data: str) -> int: + def write_text(self, data: str, encoding=None, errors=None, newline=None) -> int: """ Write text data to the file represented by this path. """ + del encoding, errors, newline + if not isinstance(data, str): raise TypeError(f"data must be str, not {data.__class__.__name__}") @@ -99,7 +107,7 @@ def write_text(self, data: str) -> int: return len(data) - def mkdir(self, parents: bool = True, exist_ok: bool = False) -> None: + def mkdir(self, mode=0o777, parents: bool = False, exist_ok: bool = False) -> None: """ Create a directory at the path represented by this class. @@ -107,13 +115,15 @@ def mkdir(self, parents: bool = True, exist_ok: bool = False) -> None: Python < 3.12. In this case, pathlib.Path is used directly and this option ensures, that missing parent directories are also created. """ + del mode + if self.is_dir() and not exist_ok: raise FileExistsError(f"Directory {self.as_posix()} already exists!") if not self._session.sendExpression(expr=f'mkdir("{self.as_posix()}")'): raise OMSessionException(f"Error on directory creation for {self.as_posix()}!") - def cwd(self) -> OMPathABC: + def cwd(self) -> OMPathABC: # pylint: disable=W0221 # is @classmethod in the original; see pathlib.PathBase """ Returns the current working directory as an OMPathABC object. """ diff --git a/OMPython/om_session_runner.py b/OMPython/om_session_runner.py index fc8e5ac8..b81c3ae1 100644 --- a/OMPython/om_session_runner.py +++ b/OMPython/om_session_runner.py @@ -49,16 +49,20 @@ class _OMPathRunnerLocal(OMPathRunnerABC): conversion via pathlib.Path(.as_posix()). """ - def is_file(self) -> bool: + def is_file(self, *, follow_symlinks=True) -> bool: """ Check if the path is a regular file. """ + del follow_symlinks + return self._path().is_file() - def is_dir(self) -> bool: + def is_dir(self, *, follow_symlinks: bool = True) -> bool: """ Check if the path is a directory. """ + del follow_symlinks + return self._path().is_dir() def is_absolute(self) -> bool: @@ -67,22 +71,26 @@ def is_absolute(self) -> bool: """ return self._path().is_absolute() - def read_text(self) -> str: + def read_text(self, encoding=None, errors=None, newline=None) -> str: """ Read the content of the file represented by this path as text. """ + del encoding, errors, newline + return self._path().read_text(encoding='utf-8') - def write_text(self, data: str): + def write_text(self, data: str, encoding=None, errors=None, newline=None): """ Write text data to the file represented by this path. """ + del encoding, errors, newline + if not isinstance(data, str): raise TypeError(f"data must be str, not {data.__class__.__name__}") return self._path().write_text(data=data, encoding='utf-8') - def mkdir(self, parents: bool = True, exist_ok: bool = False) -> None: + def mkdir(self, mode=0o777, parents: bool = False, exist_ok: bool = False) -> None: """ Create a directory at the path represented by this class. @@ -90,9 +98,11 @@ def mkdir(self, parents: bool = True, exist_ok: bool = False) -> None: Python < 3.12. In this case, pathlib.Path is used directly and this option ensures, that missing parent directories are also created. """ + del mode + self._path().mkdir(parents=parents, exist_ok=exist_ok) - def cwd(self) -> OMPathABC: + def cwd(self) -> OMPathABC: # pylint: disable=W0221 # is @classmethod in the original; see pathlib.PathBase """ Returns the current working directory as an OMPathABC object. """ @@ -132,10 +142,12 @@ class _OMPathRunnerBash(OMPathRunnerABC): conversion via pathlib.Path(.as_posix()). """ - def is_file(self) -> bool: + def is_file(self, *, follow_symlinks=True) -> bool: """ Check if the path is a regular file. """ + del follow_symlinks + cmdl = self.get_session().get_cmd_prefix() cmdl += ['bash', '-c', f'test -f "{self.as_posix()}"'] @@ -145,7 +157,7 @@ def is_file(self) -> bool: except subprocess.CalledProcessError: return False - def is_dir(self) -> bool: + def is_dir(self, *, follow_symlinks: bool = True) -> bool: """ Check if the path is a directory. """ @@ -172,10 +184,12 @@ def is_absolute(self) -> bool: except subprocess.CalledProcessError: return False - def read_text(self) -> str: + def read_text(self, encoding=None, errors=None, newline=None) -> str: """ Read the content of the file represented by this path as text. """ + del encoding, errors, newline + cmdl = self.get_session().get_cmd_prefix() cmdl += ['bash', '-c', f'cat "{self.as_posix()}"'] @@ -184,10 +198,12 @@ def read_text(self) -> str: return result.stdout.decode('utf-8') raise FileNotFoundError(f"Cannot read file: {self.as_posix()}") - def write_text(self, data: str) -> int: + def write_text(self, data: str, encoding=None, errors=None, newline=None) -> int: """ Write text data to the file represented by this path. """ + del encoding, errors, newline + if not isinstance(data, str): raise TypeError(f"data must be str, not {data.__class__.__name__}") @@ -202,7 +218,7 @@ def write_text(self, data: str) -> int: except subprocess.CalledProcessError as exc: raise IOError(f"Error writing data to file {self.as_posix()}!") from exc - def mkdir(self, parents: bool = True, exist_ok: bool = False) -> None: + def mkdir(self, mode=0o777, parents: bool = False, exist_ok: bool = False) -> None: """ Create a directory at the path represented by this class. @@ -210,6 +226,7 @@ def mkdir(self, parents: bool = True, exist_ok: bool = False) -> None: Python < 3.12. In this case, pathlib.Path is used directly and this option ensures, that missing parent directories are also created. """ + del mode if self.is_file(): raise OSError(f"The given path {self.as_posix()} exists and is a file!") @@ -226,7 +243,7 @@ def mkdir(self, parents: bool = True, exist_ok: bool = False) -> None: except subprocess.CalledProcessError as exc: raise OMSessionException(f"Error on directory creation for {self.as_posix()}!") from exc - def cwd(self) -> OMPathABC: + def cwd(self) -> OMPathABC: # pylint: disable=W0221 # is @classmethod in the original; see pathlib.PathBase """ Returns the current working directory as an OMPathABC object. """ diff --git a/README.md b/README.md index a9cf3bdc..56730349 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ OMPython is a Python interface that uses ZeroMQ to communicate with OpenModelica ## Dependencies -- Python 3.x supported -- PyZMQ is required + - Python >= 3.10 supported with complete functionality for Python >= 3.12 + - Additional packages: numpy, psutil, pyparsing and pyzmq ## Installation @@ -49,8 +49,8 @@ help(OMPython) ``` ```python -from OMPython import OMCSessionLocal -omc = OMCSessionLocal() +import OMPython +omc = OMPython.OMCSessionLocal() omc.sendExpression("getVersion()") ``` diff --git a/tests/test_ModelExecutionCmd.py b/tests/test_ModelExecutionCmd.py index db5aadeb..71e96fc1 100644 --- a/tests/test_ModelExecutionCmd.py +++ b/tests/test_ModelExecutionCmd.py @@ -24,7 +24,7 @@ def mscmd_firstorder(model_firstorder): model_name="M", ) - mscmd = OMPython.ModelExecutionCmd( + mscmd = OMPython.ModelExecutionConfig( runpath=mod.getWorkDirectory(), cmd_local=mod.get_session().model_execution_local, cmd_windows=mod.get_session().model_execution_windows,