Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/Test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Test-Publish

on:
push:
branches: ['master']
branches: [ 'master' ]
tags:
- 'v*' # only publish when pushing version tags (e.g., v1.0.0)
pull_request:
Expand All @@ -22,13 +22,13 @@ jobs:
# test for:
# * oldest supported version
# * latest available Python version
python-version: ['3.10', '3.14']
python-version: [ '3.12', '3.14' ]
# * Linux using ubuntu-latest
# * Windows using windows-latest
os: ['ubuntu-latest', 'windows-latest']
os: [ 'ubuntu-latest', 'windows-latest' ]
# * OM stable - latest stable version
# * OM nightly - latest nightly build
omc-version: ['stable', 'nightly']
omc-version: [ 'stable', 'nightly' ]

steps:
- uses: actions/checkout@v7
Expand Down Expand Up @@ -98,8 +98,8 @@ jobs:
needs: test
strategy:
matrix:
python-version: ['3.10']
os: ['ubuntu-latest']
python-version: [ '3.12' ]
os: [ 'ubuntu-latest' ]
if: startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v7
Expand Down
33 changes: 33 additions & 0 deletions OMPython/modelica_system_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,12 +596,20 @@ def _process_override_data(
override_file: OMPathABC,
override_var: dict[str, str],
override_sim: dict[str, str],
variable_filter: Optional[str] = None,
) -> None:
"""
Define the override parameters. As the definition of simulation specific override parameter changes with OM
1.26.0, version specific code is needed. Please keep in mind, that this will fail if OMC is not used to run the
model executable.

Including also override of variable filter settings.
"""

# define variable filter if defined (override any original setting)
if variable_filter is not None:
om_cmd.arg_set(key="variableFilter", val=variable_filter)

if len(override_var) == 0 and len(override_sim) == 0:
return

Expand Down Expand Up @@ -672,6 +680,7 @@ def simulate_cmd(
override_file=result_file.parent / f"{result_file.stem}_override.txt",
override_var=self._override_variables,
override_sim=self._simulate_options_override,
variable_filter=self._variable_filter,
)

if self._inputs: # if model has input quantities
Expand Down Expand Up @@ -937,6 +946,29 @@ def setOptimizationOptions(
datatype="optimization-option",
overridedata=None)

def set_variable_filter(
self,
variable_filter: Optional[str] = None,
escape: bool = False,
) -> None:
"""
This method is used to set variable filters. If escape is True, all regex special characters are escaped.
"""
if variable_filter is None:
self._variable_filter = None
return

if escape:
variable_filter = re.escape(variable_filter)

# Validate filter_val as a regular expression
try:
re.compile(variable_filter)
except re.error as exc:
raise ModelicaSystemError(f"Invalid variable_filter regular expression: {variable_filter!r} ({exc})")

self._variable_filter = variable_filter

@staticmethod
def toInputs(data: dict[str, list[float]]) -> dict[str, list[tuple[float, float]]]:
"""
Expand Down Expand Up @@ -1158,6 +1190,7 @@ def linearize(
override_file=self.getWorkDirectory() / f'{self._model_name}_override_linear.txt',
override_var=self._override_variables,
override_sim=self._linearization_options,
variable_filter=self._variable_filter,
)

if self._inputs:
Expand Down
2 changes: 1 addition & 1 deletion OMPython/modelica_system_omc.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def model(
# set variables
self._model_name = model_name # Model class name
self._libraries = libraries # may be needed if model is derived from other model
self._variable_filter = variable_filter
self.set_variable_filter(variable_filter=variable_filter, escape=True)

if self._libraries:
self._loadLibrary(libraries=self._libraries)
Expand Down
227 changes: 92 additions & 135 deletions OMPython/om_session_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@

import abc
import logging
import os
import pathlib
import platform
import sys
from typing import Any, Optional
import uuid

Expand All @@ -26,151 +24,110 @@ class OMSessionException(Exception):
"""


# due to the compatibility layer to Python < 3.12, the OM(C)Path classes must be hidden behind the following if
# conditions. This is also the reason for OMPathABC, a simple base class to be used in ModelicaSystem* classes.
# Reason: before Python 3.12, pathlib.PurePosixPath can not be derived from; therefore, OMPathABC is not possible
if sys.version_info < (3, 12):
class _OMPathCompatibility(pathlib.Path):
class OMPathABC(pathlib.PurePosixPath, metaclass=abc.ABCMeta):
"""
Implementation of a basic (PurePosix)Path object to be used within OMPython. The derived classes can use OMC as
backend and - thus - work on different configurations like docker or WSL. The connection to OMC is provided via
an instances of classes derived from BaseSession.

PurePosixPath is selected as it covers all but Windows systems (Linux, docker, WSL). However, the code is
written such that possible Windows system are taken into account. Nevertheless, the overall functionality is
limited compared to standard pathlib.Path objects.
"""

def __init__(self, *path, session: OMSessionABC) -> None:
super().__init__(*path)
self._session = session

def get_session(self) -> OMSessionABC:
"""
Get session definition used for this instance of OMPath.
"""
return self._session

def with_segments(self, *pathsegments) -> OMPathABC:
"""
Compatibility class for OMPathABC in Python < 3.12. This allows to run all code which uses OMPathABC (mainly
ModelicaSystem) on these Python versions. There are remaining limitation as only local execution is possible.
Create a new OMCPath object with the given path segments.

The original definition of Path is overridden to ensure the session data is set.
"""
return type(self)(*pathsegments, session=self._session)

# modified copy of pathlib.Path.__new__() definition
def __new__(cls, *args, **kwargs):
logger.warning("Python < 3.12 - using a version of class OMCPath "
"based on pathlib.Path for local usage only.")
@abc.abstractmethod
def is_file(self, *, follow_symlinks=True) -> bool:
"""
Check if the path is a regular file.
"""

if cls is _OMPathCompatibility:
cls = _OMPathCompatibilityWindows if os.name == 'nt' else _OMPathCompatibilityPosix
self = cls._from_parts(args)
if not self._flavour.is_supported:
raise NotImplementedError(f"cannot instantiate {cls.__name__} on your system")
return self
@abc.abstractmethod
def is_dir(self, *, follow_symlinks: bool = True) -> bool:
"""
Check if the path is a directory.
"""

def size(self) -> int:
"""
Needed compatibility function to have the same interface as OMCPathReal
"""
return self.stat().st_size
@abc.abstractmethod
def is_absolute(self) -> bool:
"""
Check if the path is an absolute path.
"""

class _OMPathCompatibilityPosix(pathlib.PosixPath, _OMPathCompatibility):
@abc.abstractmethod
def read_text(self, encoding=None, errors=None, newline=None) -> str:
"""
Compatibility class for OMCPath on Posix systems (Python < 3.12)
Read the content of the file represented by this path as text.
"""

class _OMPathCompatibilityWindows(pathlib.WindowsPath, _OMPathCompatibility):
@abc.abstractmethod
def write_text(self, data: str, encoding=None, errors=None, newline=None) -> int:
"""
Compatibility class for OMCPath on Windows systems (Python < 3.12)
Write text data to the file represented by this path.
"""

@abc.abstractmethod
def mkdir(self, mode=0o777, parents: bool = False, exist_ok: bool = False) -> None:
"""
Create a directory at the path represented by this class.

OMPathABC = _OMPathCompatibility

else:
class OMPathABC(pathlib.PurePosixPath, metaclass=abc.ABCMeta):
"""
Implementation of a basic (PurePosix)Path object to be used within OMPython. The derived classes can use OMC as
backend and - thus - work on different configurations like docker or WSL. The connection to OMC is provided via
an instances of classes derived from BaseSession.

PurePosixPath is selected as it covers all but Windows systems (Linux, docker, WSL). However, the code is
written such that possible Windows system are taken into account. Nevertheless, the overall functionality is
limited compared to standard pathlib.Path objects.
"""

def __init__(self, *path, session: OMSessionABC) -> None:
super().__init__(*path)
self._session = session

def get_session(self) -> OMSessionABC:
"""
Get session definition used for this instance of OMPath.
"""
return self._session

def with_segments(self, *pathsegments) -> OMPathABC:
"""
Create a new OMCPath object with the given path segments.

The original definition of Path is overridden to ensure the session data is set.
"""
return type(self)(*pathsegments, session=self._session)

@abc.abstractmethod
def is_file(self, *, follow_symlinks=True) -> bool:
"""
Check if the path is a regular file.
"""

@abc.abstractmethod
def is_dir(self, *, follow_symlinks: bool = True) -> bool:
"""
Check if the path is a directory.
"""

@abc.abstractmethod
def is_absolute(self) -> bool:
"""
Check if the path is an absolute path.
"""

@abc.abstractmethod
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, encoding=None, errors=None, newline=None) -> int:
"""
Write text data to the file represented by this path.
"""

@abc.abstractmethod
def mkdir(self, mode=0o777, parents: bool = False, exist_ok: bool = False) -> None:
"""
Create a directory at the path represented by this class.

The argument parents with default value True exists to ensure compatibility with the fallback solution for
Python < 3.12. In this case, pathlib.Path is used directly and this option ensures, that missing parent
directories are also created.
"""

@abc.abstractmethod
def cwd(self) -> OMPathABC: # pylint: disable=W0221 # is @classmethod in the original; see pathlib.PathBase
"""
Returns the current working directory as an OMPathABC object.
"""

@abc.abstractmethod
def unlink(self, missing_ok: bool = False) -> None:
"""
Unlink (delete) the file or directory represented by this path.
"""

@abc.abstractmethod
def resolve(self, strict: bool = False) -> OMPathABC:
"""
Resolve the path to an absolute path.
"""

def absolute(self) -> OMPathABC:
"""
Resolve the path to an absolute path. Just a wrapper for resolve().
"""
return self.resolve()

def exists(self) -> bool:
"""
Semi replacement for pathlib.Path.exists().
"""
return self.is_file() or self.is_dir()

@abc.abstractmethod
def size(self) -> int:
"""
Get the size of the file in bytes - this is an extra function and the best we can do using OMC.
"""
The argument parents with default value True exists to ensure compatibility with the fallback solution for
Python < 3.12. In this case, pathlib.Path is used directly and this option ensures, that missing parent
directories are also created.
"""

@abc.abstractmethod
def cwd(self) -> OMPathABC: # pylint: disable=W0221 # is @classmethod in the original; see pathlib.PathBase
"""
Returns the current working directory as an OMPathABC object.
"""

@abc.abstractmethod
def unlink(self, missing_ok: bool = False) -> None:
"""
Unlink (delete) the file or directory represented by this path.
"""

@abc.abstractmethod
def resolve(self, strict: bool = False) -> OMPathABC:
"""
Resolve the path to an absolute path.
"""

def absolute(self) -> OMPathABC:
"""
Resolve the path to an absolute path. Just a wrapper for resolve().
"""
return self.resolve()

def exists(self) -> bool:
"""
Semi replacement for pathlib.Path.exists().
"""
return self.is_file() or self.is_dir()

@abc.abstractmethod
def size(self) -> int:
"""
Get the size of the file in bytes - this is an extra function and the best we can do using OMC.
"""


class PostInitCaller(type):
Expand Down
Loading
Loading