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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/layup/utilities/data_utilities_for_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,23 @@ def get_test_filepath(filename):

# Returned path: `<base_directory>/tests/data/filename`
return os.path.join(THIS_DIR, "tests/data", filename)


def layup_cli(*args):
"""Build an argv that runs this environment's ``layup``, not ``PATH``'s.

``subprocess.run(["layup", ...])`` resolves the name against ``PATH``, so
the test runs whichever layup comes first on the machine instead of the one
being tested. On a machine with an older layup installed the test fails on
arguments the current code added, and -- worse -- when the older one happens
to accept them, it passes without testing anything (issue #500).

Console scripts are installed next to the interpreter, so resolving from
``sys.executable`` pins the call to this environment while still going
through the real entry point and its verb dispatch.
"""
import sys
from pathlib import Path

exe = Path(sys.executable).parent / "layup"
return [str(exe) if exe.exists() else "layup", *args]
51 changes: 29 additions & 22 deletions src/layup_cmdline/main.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import argparse
import subprocess
import sys
import shutil
import os
from importlib.metadata import distribution

#
# Generic verb dispatcher code
#


def find_layup_verbs():
"""Find available layup commands in the system's PATH."""
layup_verbs = []
for directory in os.environ.get("PATH", "").split(os.pathsep):
if os.path.isdir(directory):
for item in os.listdir(directory):
if item.startswith("layup-") and os.access(os.path.join(directory, item), os.X_OK):
layup_verbs.append(item[len("layup-") :])
return sorted(set(layup_verbs))
"""Return the verbs this installation provides, as a dict mapping verb name
to entry point.

The names come from the installed package's own metadata, so they are the
verbs belonging to this layup, not whichever ones happen to be first on PATH.
"""

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 whole docstring isn't what this code is doing adn is convoluted- it's what the AI thinks it fixed. I think it needs to be rewritten by a human for who is going to edit this code in 6 months or 6 years from now

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Could you please take charge of that, @mschwamb ?

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'd get rid of that function and that would solve the issue. As per my other feedback comment.

verbs = {}
for ep in distribution("layup").entry_points:
if ep.group == "console_scripts" and ep.name.startswith("layup-"):
verbs[ep.name[len("layup-") :]] = ep
return verbs

@mschwamb mschwamb Aug 31, 2026

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.

Do we need this in a separate function? It's a bit of Russian doll to read though. These lines could easily be in find_layup_verbs() and it would say having to dive into a separate function



def main():
Expand Down Expand Up @@ -58,7 +59,7 @@ def main():
action="store_true",
)

parser.add_argument("verb", nargs="?", choices=available_verbs, help="Verb to execute")
parser.add_argument("verb", nargs="?", choices=sorted(available_verbs), help="Verb to execute")
parser.add_argument("args", nargs=argparse.REMAINDER, help="Arguments for the verb")

args = parser.parse_args()
Expand All @@ -75,21 +76,27 @@ def main():
parser.print_help()
sys.exit(1)

# Construct the full command name
utility = f"layup-{args.verb}"

# Ensure the command is available
if not shutil.which(utility):
entry = available_verbs.get(args.verb)
if entry is None:
print(f"Error: '{utility}' is not available.")
sys.exit(1)

# Execute the command with the remaining arguments
# Run the verb in this process. Nothing is resolved by name, so the verb
# that runs is always the one belonging to this installation.
verb_main = entry.load()
argv = sys.argv
sys.argv = [utility, *args.args]
try:
result = subprocess.run([utility] + args.args, check=True)
sys.exit(result.returncode)
except subprocess.CalledProcessError as e:
print(f"Error: Command '{utility}' failed with exit code {e.returncode}.")
sys.exit(e.returncode)
code = verb_main()
except SystemExit as exc: # the verbs exit on their own error paths
code = exc.code
finally:
sys.argv = argv
if code not in (0, None):
print(f"Error: Command '{utility}' failed with exit code {code}.")
sys.exit(code)
sys.exit(0)


if __name__ == "__main__":
Expand Down
4 changes: 2 additions & 2 deletions tests/layup/test_comet.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import numpy as np
from numpy.testing import assert_allclose, assert_equal
from layup.comet import _remove_spc, _assist_integrate, _direction_of_integration, _apply_comet, comet_cli
from layup.utilities.data_utilities_for_tests import get_test_filepath
from layup.utilities.data_utilities_for_tests import get_test_filepath, layup_cli
from layup.utilities.file_io.CSVReader import CSVDataReader
import pandas as pd
import assist
Expand Down Expand Up @@ -214,7 +214,7 @@ def test_comet_output(tmpdir):
# The demo comet fixture is keyed by ObjID; comet's -pid now defaults to
# provID (CLI-consistency), so pass -pid ObjID explicitly.
result = subprocess.run(
["layup", "comet", str(input_file), "-f", "--stem", str(temp_out_file), "-pid", "ObjID"]
layup_cli("comet", str(input_file), "-f", "--stem", str(temp_out_file), "-pid", "ObjID")
)

assert result.returncode == 0
Expand Down
17 changes: 7 additions & 10 deletions tests/layup/test_predict.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
layup_get_residual_vectors,
layup_calculate_rates_and_geometry,
)
from layup.utilities.data_utilities_for_tests import get_test_filepath
from layup.utilities.data_utilities_for_tests import get_test_filepath, layup_cli
from layup.utilities.file_io.CSVReader import CSVDataReader


Expand Down Expand Up @@ -221,8 +221,7 @@ def test_predict_output(tmpdir):
temp_out_file = f"test_output_{input_file.stem}"

result = subprocess.run(
[
"layup",
layup_cli(
"predict",
str(input_file),
"-f",
Expand All @@ -232,7 +231,7 @@ def test_predict_output(tmpdir):
str(tmpdir),
"-s",
start,
]
)
)

assert result.returncode == 0
Expand Down Expand Up @@ -292,8 +291,7 @@ def test_predict_output(tmpdir):
# Testing the output of the sexagesimal conversion separately

result = subprocess.run(
[
"layup",
layup_cli(
"predict",
str(input_file),
"-f",
Expand All @@ -302,7 +300,7 @@ def test_predict_output(tmpdir):
"-s",
start,
"-sg",
]
)
)

assert result.returncode == 0
Expand Down Expand Up @@ -431,8 +429,7 @@ def test_get_onsky_data_output(tmpdir):
temp_out_file = f"test_output_{input_file.stem}"

result = subprocess.run(
[
"layup",
layup_cli(
"predict",
str(input_file),
"-f",
Expand All @@ -443,7 +440,7 @@ def test_get_onsky_data_output(tmpdir):
"-s",
start,
"-osd",
]
)
)
assert result.returncode == 0
result_file = Path(f"{tmpdir}/{temp_out_file}.csv")
Expand Down
Loading