Skip to content

Commit 51301b6

Browse files
authored
Minor fixes and improvements (#15)
* Make error handling and output pipe changes * Test changes in previous commit * Correct documentation * Update changelog and version * - Fix range - Improve start and stop of MAD-NG * Remove closing modifications * Fix issue of pipe closing prematurely (after 10 seconds). Now wait for pipe to be ready to be read indefinitely --------- Co-authored-by: jgray-19 <[email protected]>
1 parent 5da2860 commit 51301b6

File tree

5 files changed

+35
-29
lines changed

5 files changed

+35
-29
lines changed

src/pymadng/madp_object.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import platform
44
from pathlib import Path
5-
from typing import Any, TYPE_CHECKING, TextIO # To make stuff look nicer
5+
from typing import Any, TYPE_CHECKING, TextIO # To make stuff look nicer
66

77
import numpy as np # For arrays (Works well with multiprocessing and mmap)
88

@@ -31,8 +31,9 @@
3131

3232
bin_path = Path(__file__).parent.resolve() / "bin"
3333

34+
3435
# --------------------- Overload recv_ref functions ---------------------- #
35-
# Override the type of reference created by python
36+
# Override the type of reference created by python
3637
# (so madp_pymad can be run independently, these objects create pythonic objects)
3738
def recv_ref(self: mad_process) -> high_level_mad_ref:
3839
return high_level_mad_ref(self.varname, self)
@@ -200,25 +201,25 @@ def send(self, data: str | int | float | np.ndarray | bool | list) -> MAD:
200201
"""
201202
self.__process.send(data)
202203
return self
203-
204+
204205
def protected_send(self, string: str) -> MAD:
205-
"""Send a string to MAD-NG, but if any of the command errors, python will be notified.
206+
"""Send a string to MAD-NG, but if any of the command errors, python will be notified.
206207
Then, once python receives the fact that the command errored, it will raise an error.
207208
208-
For example, if you send ``mad.send("a = 1/'a'")``, MAD-NG will error, and python will just continue.
209-
But if you send ``mad.protected_send("a = 1/'a'").recv()``, python will raise an error.
209+
For example, if you send ``mad.send("a = 1/'a'")``, MAD-NG will error, and python will just continue.
210+
But if you send ``mad.protected_send("a = 1/'a'").recv()``, python will raise an error.
210211
Note, if the ``recv`` is not called, the error will not be raised.
211-
212+
212213
Args:
213214
string (str): The string to send to MAD-NG. This must be a string to be evaluated in MAD-NG. Not a string to be sent to MAD-NG.
214215
215216
Returns:
216-
self (the instance of the mad object)
217+
self (the instance of the mad object)
217218
"""
218219
assert isinstance(string, str), "The input to protected_send must be a string"
219220
self.__process.protected_send(string)
220221
return self
221-
222+
222223
def psend(self, string: str) -> MAD:
223224
"""See :meth:`protected_send`"""
224225
return self.protected_send(string)
@@ -441,7 +442,7 @@ def __dir__(self) -> Iterable[str]:
441442
pyObjs.extend(dir(self.recv_vars("_G")))
442443
return pyObjs
443444

444-
def globals(self) -> list[str]:
445+
def globals(self) -> list[str]:
445446
"""Retreive the list of names of variables in the environment of MAD
446447
447448
Returns:
@@ -466,7 +467,6 @@ def __enter__(self):
466467
return self
467468

468469
def __exit__(self, exc_type, exc_value, tb):
469-
# Close the process, as the with statement is finished
470470
self.__process.close()
471471

472472
# ---------------------------------------------------------------------------------------------------#

src/pymadng/madp_pymad.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import sys
99
from pathlib import Path
1010
from typing import Any, TYPE_CHECKING, TextIO
11+
from contextlib import suppress
1112

1213
import numpy as np
1314

@@ -16,6 +17,7 @@
1617

1718
__all__ = ["mad_process"]
1819

20+
# TODO: look at cpymad for the suppression of the error messages at exit - copy? (jgray 2024)
1921

2022
def is_private(varname):
2123
assert isinstance(varname, str), "Variable name to receive must be a string"
@@ -68,7 +70,8 @@ def __init__(
6870
stderr = sys.stderr.fileno()
6971

7072
# Create a chunk of code to start the process
71-
startupChunk = f"MAD.pymad '{py_name}' {{_dbg = {str(bool(debug)).lower()}}} :__ini({mad_write})"
73+
lua_debug_flag = "true" if debug else "false"
74+
startupChunk = f"MAD.pymad '{py_name}' {{_dbg = {lua_debug_flag}}} :__ini({mad_write})"
7275
original_sigint_handler = signal.getsignal(signal.SIGINT)
7376

7477
def delete_process(sig, frame):
@@ -110,15 +113,19 @@ def delete_process(sig, frame):
110113
self.send("io.stdout:setvbuf('line')")
111114

112115
# Check if MAD started successfully
113-
self.send(f"{self.py_name}:send(1)")
116+
self.send(f"{self.py_name}:send('started')")
114117
startup_status_checker = select.select(
115118
[self.mad_read_stream], [], [], 10
116119
) # May not work on windows
117120

118121
# Check if MAD started successfully using select
119-
if not startup_status_checker[0] or self.recv() != 1: # Need to check number?
122+
mad_rtrn = self.recv()
123+
if not startup_status_checker[0] or mad_rtrn != 'started':
120124
self.close()
121-
raise OSError(f"Unsuccessful starting of {mad_path} process")
125+
if mad_rtrn == 'started':
126+
raise OSError(f"Could not establish communication with {mad_path} process")
127+
else:
128+
raise OSError(f"Could not start {mad_path} process, received: {mad_rtrn}")
122129

123130
# Set the error handler to be on by default
124131
if raise_on_madng_error:
@@ -230,7 +237,7 @@ def close(self) -> None:
230237
"""Close the pipes and wait for the process to finish"""
231238
if self.process.poll() is None: # If process is still running
232239
self.send(f"{self.py_name}:__fin()") # Tell the mad side to finish
233-
open_pipe = select.select([self.mad_read_stream], [], [], 10)
240+
open_pipe = select.select([self.mad_read_stream], [], [])
234241
if open_pipe[0]:
235242
# Wait for the mad side to finish (variable name in case of errors that need to be caught elsewhere)
236243
close_msg = self.recv("closing")
@@ -241,10 +248,8 @@ def close(self) -> None:
241248
self.process.terminate() # Terminate the process on the python side
242249

243250
# Close the debug file if it exists
244-
try:
251+
with suppress(AttributeError):
245252
self.stdout_file.close()
246-
except AttributeError:
247-
pass
248253

249254
# Close the pipes
250255
if not self.mad_read_stream.closed:

tests/comm_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ class TestRngs(unittest.TestCase):
223223
def test_recv(self):
224224
with MAD() as mad:
225225
mad.send("""
226-
irng = 3..11..2
226+
irng = MAD.range(3, 11, 2)
227227
rng = MAD.nrange(3.5, 21.4, 12)
228228
lrng = MAD.nlogrange(1, 20, 20)
229229
py:send(irng)

tests/inputs/example.log

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
***pymad.recv: binary data 4 bytes
22
***pymad.recv: [io.stdout:setvbuf('line')] 25 bytes
33
***pymad.recv: binary data 4 bytes
4-
***pymad.recv: [py:send(1)] 10 bytes
5-
***pymad.send: [int_] 4 bytes
4+
***pymad.recv: [py:send('started')] 18 bytes
5+
***pymad.send: [str_] 4 bytes
66
***pymad.send: binary data 4 bytes
7+
***pymad.send: [started] 7 bytes
78
***pymad.recv: binary data 4 bytes
89
***pymad.recv: [element = MAD.element
910
sequence = MAD.sequence
@@ -26,7 +27,7 @@ match = MAD.match
2627
***pymad.recv: [py:__err(true):send(MAD['env']['version']):__err(false)] 55 bytes
2728
***pymad.send: [str_] 4 bytes
2829
***pymad.send: binary data 4 bytes
29-
***pymad.send: [1.0.0] 5 bytes
30+
***pymad.send: [1.1.0] 5 bytes
3031
***pymad.recv: binary data 4 bytes
3132
***pymad.recv: [
3233
function __mklast__ (a, b, ...)

tests/obj_test.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -373,13 +373,13 @@ def generalDataFrame(self, headers, DataFrame):
373373
boolean = true,
374374
list = {1, 2, 3, 4, 5},
375375
table = {1, 2, ["key"] = "value"},
376-
range = 1..11,
376+
range = MAD.range(1, 11),
377377
}
378-
+ {"a", 1.1, 1, 1 + 2i, true , {1, 2 }, {1 , 2 , ["3" ] = 3 }, 1..11,}
379-
+ {"b", 2.2, 2, 2 + 3i, false, {3, 4 }, {4 , 5 , ["6" ] = 6 }, 2..12,}
380-
+ {"c", 3.3, 3, 3 + 4i, true , {5, 6 }, {7 , 8 , ["9" ] = 9 }, 3..13,}
381-
+ {"d", 4.4, 4, 4 + 5i, false, {7, 8 }, {10, 11, ["12"] = 12}, 4..14,}
382-
+ {"e", 5.5, 5, 5 + 6i, true , {9, 10}, {13, 14, ["15"] = 15}, 5..15,}
378+
+ {"a", 1.1, 1, 1 + 2i, true , {1, 2 }, {1 , 2 , ["3" ] = 3 }, MAD.range(1, 11),}
379+
+ {"b", 2.2, 2, 2 + 3i, false, {3, 4 }, {4 , 5 , ["6" ] = 6 }, MAD.range(2, 12),}
380+
+ {"c", 3.3, 3, 3 + 4i, true , {5, 6 }, {7 , 8 , ["9" ] = 9 }, MAD.range(3, 13),}
381+
+ {"d", 4.4, 4, 4 + 5i, false, {7, 8 }, {10, 11, ["12"] = 12}, MAD.range(4, 14),}
382+
+ {"e", 5.5, 5, 5 + 6i, true , {9, 10}, {13, 14, ["15"] = 15}, MAD.range(5, 15),}
383383
384384
test:addcol("generator", \\ri, m -> m:getcol("number")[ri] + 1i * m:getcol("number")[ri])
385385
test:write("test")

0 commit comments

Comments
 (0)