Skip to content

Shared run function for tracking algorithms#174

Draft
surbhigoel77 wants to merge 11 commits into
mainfrom
run_shared
Draft

Shared run function for tracking algorithms#174
surbhigoel77 wants to merge 11 commits into
mainfrom
run_shared

Conversation

@surbhigoel77
Copy link
Copy Markdown
Member

@surbhigoel77 surbhigoel77 commented Apr 30, 2026

The PR aims to build a shared function to run the tracking algorithms. At present, each of the algorithm bindings (tempestextremes.py, tracker.py, tstorms.py) have their own run functions. These functions are part of the python wrappers that take commands (built using make functions) from c++ based algorithms and pass them to OS using subprocess.run to run them. Thus is how we access the external processes.

The commands contain path to the binaries of the algorithm related functions. which can be run subprocess.run

Update:

  • The code allows 3 verbosity levels (print no o/p, print few lines print, print output real-time)
  • The code has checks for valid input command set like compatible verbosity value and input_str value.

@surbhigoel77 surbhigoel77 changed the title Shared run fucntion for tracking algorithms Shared run function for tracking algorithms Apr 30, 2026
@surbhigoel77 surbhigoel77 marked this pull request as draft April 30, 2026 14:52
@surbhigoel77 surbhigoel77 self-assigned this Apr 30, 2026
@surbhigoel77
Copy link
Copy Markdown
Member Author

@sjavis I will add a unit test for the run function to the same PR

Copy link
Copy Markdown
Collaborator

@sjavis sjavis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good. It is clear there were a few complications in putting it into a single function that I hadn't considered.

I have given some suggestions below. Mostly these are about condensing and simplifying the code.

stdout_lines.append(line) # also collect it

stdout = "".join(stdout_lines) # ← combine lines into string
stderr, _ = process.communicate()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The order of the return values from process.communicate() is flipped here. stderr should be the second output.

Comment on lines +311 to +313
if not command_list:
msg = "command_list cannot be empty"
raise ValueError(msg)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check should probably be with the initial checks at the start of the function. I don't think it should only be applied for verbosity==0.

Comment on lines +358 to +365
print(f"{command_name} completed successfully.")
print(
f"First 12 lines of output:\n"
f"{''.join(stdout.splitlines(True)[:12])}"
f"\n...\n\n"
f"Last 12 lines of output:\n"
f"{''.join(stdout.splitlines(True)[-12:])}"
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the printing of the output is the only difference between verbosity 0 and 1. So I think the better layout for this function to minimise repetition would be:

if verbosity == 2:
    # The full verbosity code since it is different
else:
    # Run the subprocess and get the outputs
    if verbosity == 1:
        # Print the output

self._set_metadata()
# ── Level 1: Summary — first and last 12 lines ──
elif verbosity == 1:
print(f"Executing {command_name}...")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also be for verbosity == 2. Perhaps move it up to after the initial checks as long as verbosity isn't 0.

Comment on lines 305 to +310

# Store the global metadata, including the parameters in a json format
self._global_metadata = {
"tctrack_version": importlib.metadata.version("tctrack"),
"tctrack_tracker": type(self).__name__,
"tctrack_parameters": json.dumps(parameters),
}
try:
# ── Level 0: Silent — no output ──
if verbosity == 0:
stdin_file = open(input_file, "r") if input_file is not None else None # noqa: SIM115
try:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since stdin_file is defined in all cases (as stdin for verbosity=2) I think it can be moved above the outer try statement. Then only one finally statement is needed and it removes the additional try blocks

Comment on lines +449 to +456
class TestRunTrackerSubprocess:
"""Test suite for run_tracker_subprocess."""

@pytest.fixture
def tracker(self):
"""Create a tracker instance for testing."""
detect_params = TEDetectParameters(in_data=["dummy.nc"])
return TETracker(detect_params)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest removing this class and just use use self.ExampleTracker([]) instead of using the tracker fixture.

Comment on lines +486 to +501
with tempfile.NamedTemporaryFile(
mode="w", suffix=".txt", delete=False
) as f:
f.write("test\n")
temp_file = f.name

try:
with pytest.raises(ValueError):
tracker.run_tracker_subprocess(
command_name="TestCommand",
command_list=["cat"],
input_file=temp_file,
verbosity=5,
)
finally:
Path(temp_file).unlink()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test can be simplified because it doesn't need the temporary file.

Suggested change
with tempfile.NamedTemporaryFile(
mode="w", suffix=".txt", delete=False
) as f:
f.write("test\n")
temp_file = f.name
try:
with pytest.raises(ValueError):
tracker.run_tracker_subprocess(
command_name="TestCommand",
command_list=["cat"],
input_file=temp_file,
verbosity=5,
)
finally:
Path(temp_file).unlink()
with pytest.raises(ValueError):
tracker.run_tracker_subprocess(
command_name="TestCommand",
command_list=["cat"],
verbosity=5,
)

detect_params = TEDetectParameters(in_data=["dummy.nc"])
return TETracker(detect_params)

def test_run_tracker_subprocess_returns_dict(self, tracker):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to parametrise this to check the output is correct for different verbosity levels. Make sure to also update the value being passed to run_tracker_subprocess.

Suggested change
def test_run_tracker_subprocess_returns_dict(self, tracker):
@pytest.mark.parametrize("verbosity", [0, 1, 2])
def test_run_tracker_subprocess_returns_dict(self, verbosity):


finally:
Path(temp_file).unlink()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest adding a test that the stderr is captured successfully. Eg:

Suggested change
@pytest.mark.parametrize("verbosity", [0, 1, 2])
def test_run_tracker_subprocess_returns_stderr(self, verbosity):
"""Test that function returns correct stderr."""
result = self.ExampleTracker([]).run_tracker_subprocess(
command_name="TestCommand",
command_list=["sh", "-c", 'echo "test error" >&2'],
verbosity=verbosity,
)
assert isinstance(result, dict), "Should return a dict"
assert "stderr" in result, "Dict should have stderr key"
assert "test error" in result["stderr"], "stderr should contain input"

@@ -0,0 +1,93 @@
"""Test script for verbosity=0 (silent mode)"""
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we discussed, this can be moved into the tests folder. Perhaps merge #199 into this branch and then move it there.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, will add test_run.py to #199 and then merge, just to keep it clean. It was added to this commit by mistake.

@sjavis sjavis linked an issue May 29, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Shared run function

2 participants