Skip to content

Commit 83b1200

Browse files
authored
Merge branch 'master' into pylint_fix
2 parents b651052 + c53a937 commit 83b1200

3 files changed

Lines changed: 102 additions & 76 deletions

File tree

.github/workflows/Test.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,15 @@ jobs:
1414
timeout-minutes: 30
1515
strategy:
1616
matrix:
17-
python-version: ['3.10', '3.12', '3.13']
17+
# test for:
18+
# * oldest supported version
19+
# * latest available Python version
20+
python-version: ['3.10', '3.14']
21+
# * Linux using ubuntu-latest
22+
# * Windows using windows-latest
1823
os: ['ubuntu-latest', 'windows-latest']
24+
# * OM stable - latest stable version
25+
# * OM nightly - latest nightly build
1926
omc-version: ['stable', 'nightly']
2027

2128
steps:

OMPython/OMCSession.py

Lines changed: 91 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -482,8 +482,6 @@ class OMCSessionRunData:
482482
cmd_model_executable: Optional[str] = None
483483
# additional library search path; this is mainly needed if OMCProcessLocal is run on Windows
484484
cmd_library_path: Optional[str] = None
485-
# command timeout
486-
cmd_timeout: Optional[float] = 10.0
487485

488486
# working directory to be used on the *local* system
489487
cmd_cwd_local: Optional[str] = None
@@ -558,13 +556,12 @@ def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunD
558556
"""
559557
return self.omc_process.omc_run_data_update(omc_run_data=omc_run_data)
560558

561-
@staticmethod
562-
def run_model_executable(cmd_run_data: OMCSessionRunData) -> int:
559+
def run_model_executable(self, cmd_run_data: OMCSessionRunData) -> int:
563560
"""
564561
Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to
565562
keep instances of over classes around.
566563
"""
567-
return OMCSession.run_model_executable(cmd_run_data=cmd_run_data)
564+
return self.omc_process.run_model_executable(cmd_run_data=cmd_run_data)
568565

569566
def execute(self, command: str):
570567
return self.omc_process.execute(command=command)
@@ -661,12 +658,12 @@ def __init__(
661658
self._omc_zmq: Optional[zmq.Socket[bytes]] = None
662659

663660
# setup log file - this file must be closed in the destructor
664-
logfile = self._temp_dir / (self._omc_filebase + ".log")
661+
self._omc_logfile = self._temp_dir / (self._omc_filebase + ".log")
665662
self._omc_loghandle: Optional[io.TextIOWrapper] = None
666663
try:
667-
self._omc_loghandle = open(file=logfile, mode="w+", encoding="utf-8")
664+
self._omc_loghandle = open(file=self._omc_logfile, mode="w+", encoding="utf-8")
668665
except OSError as ex:
669-
raise OMCSessionException(f"Cannot open log file {logfile}.") from ex
666+
raise OMCSessionException(f"Cannot open log file {self._omc_logfile}.") from ex
670667

671668
# variables to store compiled re expressions use in self.sendExpression()
672669
self._re_log_entries: Optional[re.Pattern[str]] = None
@@ -679,6 +676,9 @@ def __post_init__(self) -> None:
679676
"""
680677
Create the connection to the OMC server using ZeroMQ.
681678
"""
679+
# set_timeout() is used to define the value of _timeout as it includes additional checks
680+
self.set_timeout(timeout=self._timeout)
681+
682682
port = self.get_port()
683683
if not isinstance(port, str):
684684
raise OMCSessionException(f"Invalid content for port: {port}")
@@ -721,6 +721,44 @@ def __del__(self):
721721
finally:
722722
self._omc_process = None
723723

724+
def _timeout_loop(
725+
self,
726+
timeout: Optional[float] = None,
727+
timestep: float = 0.1,
728+
):
729+
"""
730+
Helper (using yield) for while loops to check OMC startup / response. The loop is executed as long as True is
731+
returned, i.e. the first False will stop the while loop.
732+
"""
733+
734+
if timeout is None:
735+
timeout = self._timeout
736+
if timeout <= 0:
737+
raise OMCSessionException(f"Invalid timeout: {timeout}")
738+
739+
timer = 0.0
740+
yield True
741+
while True:
742+
timer += timestep
743+
if timer > timeout:
744+
break
745+
time.sleep(timestep)
746+
yield True
747+
yield False
748+
749+
def set_timeout(self, timeout: Optional[float] = None) -> float:
750+
"""
751+
Set the timeout to be used for OMC communication (OMCSession).
752+
753+
The defined value is set and the current value is returned. If None is provided as argument, nothing is changed.
754+
"""
755+
retval = self._timeout
756+
if timeout is not None:
757+
if timeout <= 0.0:
758+
raise OMCSessionException(f"Invalid timeout value: {timeout}!")
759+
self._timeout = timeout
760+
return retval
761+
724762
@staticmethod
725763
def escape_str(value: str) -> str:
726764
"""
@@ -772,11 +810,9 @@ def omcpath_tempdir(self, tempdir_base: Optional[OMCPath] = None) -> OMCPath:
772810

773811
return tempdir
774812

775-
@staticmethod
776-
def run_model_executable(cmd_run_data: OMCSessionRunData) -> int:
813+
def run_model_executable(self, cmd_run_data: OMCSessionRunData) -> int:
777814
"""
778-
Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to
779-
keep instances of over classes around.
815+
Run the command defined in cmd_run_data.
780816
"""
781817

782818
my_env = os.environ.copy()
@@ -793,7 +829,7 @@ def run_model_executable(cmd_run_data: OMCSessionRunData) -> int:
793829
text=True,
794830
env=my_env,
795831
cwd=cmd_run_data.cmd_cwd_local,
796-
timeout=cmd_run_data.cmd_timeout,
832+
timeout=self._timeout,
797833
check=True,
798834
)
799835
stdout = cmdres.stdout.strip()
@@ -827,34 +863,28 @@ def sendExpression(self, command: str, parsed: bool = True) -> Any:
827863
Caller should only check for OMCSessionException.
828864
"""
829865

830-
# this is needed if the class is not fully initialized or in the process of deletion
831-
if hasattr(self, '_timeout'):
832-
timeout = self._timeout
833-
else:
834-
timeout = 1.0
835-
836866
if self._omc_zmq is None:
837867
raise OMCSessionException("No OMC running. Please create a new instance of OMCSession!")
838868

839869
logger.debug("sendExpression(%r, parsed=%r)", command, parsed)
840870

841-
attempts = 0
842-
while True:
871+
loop = self._timeout_loop(timestep=0.05)
872+
while next(loop):
843873
try:
844874
self._omc_zmq.send_string(str(command), flags=zmq.NOBLOCK)
845875
break
846876
except zmq.error.Again:
847877
pass
848-
attempts += 1
849-
if attempts >= 50:
850-
# in the deletion process, the content is cleared. Thus, any access to a class attribute must be checked
851-
try:
852-
log_content = self.get_log()
853-
except OMCSessionException:
854-
log_content = 'log not available'
855-
raise OMCSessionException(f"No connection with OMC (timeout={timeout}). "
856-
f"Log-file says: \n{log_content}")
857-
time.sleep(timeout / 50.0)
878+
else:
879+
# in the deletion process, the content is cleared. Thus, any access to a class attribute must be checked
880+
try:
881+
log_content = self.get_log()
882+
except OMCSessionException:
883+
log_content = 'log not available'
884+
885+
logger.error(f"OMC did not start. Log-file says:\n{log_content}")
886+
raise OMCSessionException(f"No connection with OMC (timeout={self._timeout}).")
887+
858888
if command == "quit()":
859889
self._omc_zmq.close()
860890
self._omc_zmq = None
@@ -950,7 +980,7 @@ def sendExpression(self, command: str, parsed: bool = True) -> Any:
950980
raise OMCSessionException(f"OMC error occurred for 'sendExpression({command}, {parsed}):\n"
951981
f"{msg_long_str}")
952982

953-
if parsed is False:
983+
if not parsed:
954984
return result
955985

956986
try:
@@ -1099,25 +1129,20 @@ def _omc_port_get(self) -> str:
10991129
port = None
11001130

11011131
# See if the omc server is running
1102-
attempts = 0
1103-
while True:
1132+
loop = self._timeout_loop(timestep=0.1)
1133+
while next(loop):
11041134
omc_portfile_path = self._get_portfile_path()
1105-
11061135
if omc_portfile_path is not None and omc_portfile_path.is_file():
11071136
# Read the port file
11081137
with open(file=omc_portfile_path, mode='r', encoding="utf-8") as f_p:
11091138
port = f_p.readline()
11101139
break
1111-
11121140
if port is not None:
11131141
break
1114-
1115-
attempts += 1
1116-
if attempts == 80.0:
1117-
raise OMCSessionException(f"OMC Server did not start (timeout={self._timeout}). "
1118-
f"Could not open file {omc_portfile_path}. "
1119-
f"Log-file says:\n{self.get_log()}")
1120-
time.sleep(self._timeout / 80.0)
1142+
else:
1143+
logger.error(f"OMC server did not start. Log-file says:\n{self.get_log()}")
1144+
raise OMCSessionException(f"OMC Server did not start (timeout={self._timeout}, "
1145+
f"logfile={repr(self._omc_logfile)}).")
11211146

11221147
logger.info(f"Local OMC Server is up and running at ZMQ port {port} "
11231148
f"pid={self._omc_process.pid if isinstance(self._omc_process, subprocess.Popen) else '?'}")
@@ -1198,8 +1223,8 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]:
11981223
if sys.platform == 'win32':
11991224
raise NotImplementedError("Docker not supported on win32!")
12001225

1201-
docker_process = None
1202-
for _ in range(0, 40):
1226+
loop = self._timeout_loop(timestep=0.2)
1227+
while next(loop):
12031228
docker_top = subprocess.check_output(["docker", "top", docker_cid]).decode().strip()
12041229
docker_process = None
12051230
for line in docker_top.split("\n"):
@@ -1210,10 +1235,11 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]:
12101235
except psutil.NoSuchProcess as ex:
12111236
raise OMCSessionException(f"Could not find PID {docker_top} - "
12121237
"is this a docker instance spawned without --pid=host?") from ex
1213-
12141238
if docker_process is not None:
12151239
break
1216-
time.sleep(self._timeout / 40.0)
1240+
else:
1241+
logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}")
1242+
raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}).")
12171243

12181244
return docker_process
12191245

@@ -1235,8 +1261,8 @@ def _omc_port_get(self) -> str:
12351261
raise OMCSessionException(f"Invalid docker container ID: {self._docker_container_id}")
12361262

12371263
# See if the omc server is running
1238-
attempts = 0
1239-
while True:
1264+
loop = self._timeout_loop(timestep=0.1)
1265+
while next(loop):
12401266
omc_portfile_path = self._get_portfile_path()
12411267
if omc_portfile_path is not None:
12421268
try:
@@ -1247,16 +1273,12 @@ def _omc_port_get(self) -> str:
12471273
port = output.decode().strip()
12481274
except subprocess.CalledProcessError:
12491275
pass
1250-
12511276
if port is not None:
12521277
break
1253-
1254-
attempts += 1
1255-
if attempts == 80.0:
1256-
raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}). "
1257-
f"Could not open port file {omc_portfile_path}. "
1258-
f"Log-file says:\n{self.get_log()}")
1259-
time.sleep(self._timeout / 80.0)
1278+
else:
1279+
logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}")
1280+
raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}, "
1281+
f"logfile={repr(self._omc_logfile)}).")
12601282

12611283
logger.info(f"Docker based OMC Server is up and running at port {port}")
12621284

@@ -1424,25 +1446,24 @@ def _docker_omc_start(self) -> Tuple[subprocess.Popen, DockerPopen, str]:
14241446
raise OMCSessionException(f"Invalid content for docker container ID file path: {docker_cid_file}")
14251447

14261448
docker_cid = None
1427-
for _ in range(0, 40):
1449+
loop = self._timeout_loop(timestep=0.1)
1450+
while next(loop):
14281451
try:
14291452
with open(file=docker_cid_file, mode="r", encoding="utf-8") as fh:
14301453
docker_cid = fh.read().strip()
14311454
except IOError:
14321455
pass
1433-
if docker_cid:
1456+
if docker_cid is not None:
14341457
break
1435-
time.sleep(self._timeout / 40.0)
1436-
1437-
if docker_cid is None:
1458+
else:
14381459
logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}")
14391460
raise OMCSessionException(f"Docker did not start (timeout={self._timeout} might be too short "
14401461
"especially if you did not docker pull the image before this command).")
14411462

14421463
docker_process = self._docker_process_get(docker_cid=docker_cid)
14431464
if docker_process is None:
1444-
raise OMCSessionException(f"Docker top did not contain omc process {self._random_string}. "
1445-
f"Log-file says:\n{self.get_log()}")
1465+
logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}")
1466+
raise OMCSessionException(f"Docker top did not contain omc process {self._random_string}.")
14461467

14471468
return omc_process, docker_process, docker_cid
14481469

@@ -1594,12 +1615,11 @@ def _omc_process_get(self) -> subprocess.Popen:
15941615
return omc_process
15951616

15961617
def _omc_port_get(self) -> str:
1597-
omc_portfile_path: Optional[pathlib.Path] = None
15981618
port = None
15991619

16001620
# See if the omc server is running
1601-
attempts = 0
1602-
while True:
1621+
loop = self._timeout_loop(timestep=0.1)
1622+
while next(loop):
16031623
try:
16041624
omc_portfile_path = self._get_portfile_path()
16051625
if omc_portfile_path is not None:
@@ -1610,16 +1630,12 @@ def _omc_port_get(self) -> str:
16101630
port = output.decode().strip()
16111631
except subprocess.CalledProcessError:
16121632
pass
1613-
16141633
if port is not None:
16151634
break
1616-
1617-
attempts += 1
1618-
if attempts == 80.0:
1619-
raise OMCSessionException(f"WSL based OMC Server did not start (timeout={self._timeout}). "
1620-
f"Could not open port file {omc_portfile_path}. "
1621-
f"Log-file says:\n{self.get_log()}")
1622-
time.sleep(self._timeout / 80.0)
1635+
else:
1636+
logger.error(f"WSL based OMC server did not start. Log-file says:\n{self.get_log()}")
1637+
raise OMCSessionException(f"WSL based OMC Server did not start (timeout={self._timeout}, "
1638+
f"logfile={repr(self._omc_logfile)}).")
16231639

16241640
logger.info(f"WSL based OMC Server is up and running at ZMQ port {port} "
16251641
f"pid={self._omc_process.pid if isinstance(self._omc_process, subprocess.Popen) else '?'}")

OMPython/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
ModelicaSystemError,
1616
)
1717
from OMPython.OMCSession import (
18+
OMCPath,
1819
OMCSession,
1920
OMCSessionCmd,
2021
OMCSessionException,
@@ -35,6 +36,8 @@
3536
'ModelicaSystemDoE',
3637
'ModelicaSystemError',
3738

39+
'OMCPath',
40+
3841
'OMCSession',
3942
'OMCSessionCmd',
4043
'OMCSessionException',

0 commit comments

Comments
 (0)