From 27582ca09776ca7d09d081b9859493c6c4a7d1a7 Mon Sep 17 00:00:00 2001 From: Antonio Rojas Date: Mon, 22 Sep 2025 20:06:09 +0200 Subject: [PATCH 01/10] cli: Allow consuming arguments from the command line when running a file --- src/sage/cli/run_file_cmd.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/sage/cli/run_file_cmd.py b/src/sage/cli/run_file_cmd.py index cdf5df50ff5..600256d9ddc 100644 --- a/src/sage/cli/run_file_cmd.py +++ b/src/sage/cli/run_file_cmd.py @@ -23,7 +23,7 @@ def extend_parser(parser: argparse.ArgumentParser): """ parser.add_argument( "file", - nargs="?", + nargs="*", help="execute the given file as sage code", ) @@ -32,13 +32,16 @@ def __init__(self, options: CliOptions): Initialize the command. """ self.options = options + # shift sys.argv for compatibility with the old sage bash script and python command when consuming arguments from the command line + import sys + del sys.argv[0] def run(self) -> int: r""" Execute the given command. """ - input_file = preparse_file_named(self.options.file) if self.options.file.endswith('.sage') else self.options.file - if self.options.file.endswith('.pyx') or self.options.file.endswith('.spyx'): + input_file = preparse_file_named(self.options.file[0]) if self.options.file[0].endswith('.sage') else self.options.file[0] + if self.options.file[0].endswith('.pyx') or self.options.file[0].endswith('.spyx'): s = load_cython(input_file) eval(compile(s, tmp_filename(), 'exec'), sage_globals()) else: From 576cb0510cdc2a48a0a0243e2eb34d18b25dc30a Mon Sep 17 00:00:00 2001 From: Antonio Rojas Date: Tue, 23 Sep 2025 23:34:32 +0200 Subject: [PATCH 02/10] Add tests for run_file_cmd --- src/sage/cli/run_file_cmd_test.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/sage/cli/run_file_cmd_test.py diff --git a/src/sage/cli/run_file_cmd_test.py b/src/sage/cli/run_file_cmd_test.py new file mode 100644 index 00000000000..165d829919d --- /dev/null +++ b/src/sage/cli/run_file_cmd_test.py @@ -0,0 +1,26 @@ +from sage.cli.run_file_cmd import RunFileCmd +from sage.cli.options import CliOptions +from unittest.mock import patch +import sys + +def test_run_file_cmd(capsys, tmp_path): + file = tmp_path / "test.sage" + file.write_text("print(3^33)") + options = CliOptions(file = [str(file)]) + run_file_cmd = RunFileCmd(options) + + result = run_file_cmd.run() + captured = capsys.readouterr() + assert captured.out == "5559060566555523\n" + + +def test_run_file_cmd_with_args(capsys, tmp_path): + with patch.object(sys, 'argv', ["python3", "test.sage", "1", "1"]): + file = tmp_path / "test.sage" + file.write_text("import sys; print(int(sys.argv[1]) + int(sys.argv[2]))") + options = CliOptions(file = [str(file), "1", "1"]) + run_file_cmd = RunFileCmd(options) + + result = run_file_cmd.run() + captured = capsys.readouterr() + assert captured.out == "2\n" From c65be3b4c9a8293883eca0b90ea7d6c208167b25 Mon Sep 17 00:00:00 2001 From: Antonio Rojas Date: Tue, 23 Sep 2025 23:36:16 +0200 Subject: [PATCH 03/10] Update type of file argument --- src/sage/cli/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/cli/options.py b/src/sage/cli/options.py index 70cd2b99eef..dcba466174c 100644 --- a/src/sage/cli/options.py +++ b/src/sage/cli/options.py @@ -23,4 +23,4 @@ class CliOptions: command: str | None = None """The file to execute.""" - file: str | None = None + file: List[str] | None = None From 48ab5fb5b7d71d859ba5cf3566000a2a6b4fc117 Mon Sep 17 00:00:00 2001 From: Antonio Rojas Date: Tue, 23 Sep 2025 23:42:14 +0200 Subject: [PATCH 04/10] Factor out self.options.file[0] --- src/sage/cli/run_file_cmd.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sage/cli/run_file_cmd.py b/src/sage/cli/run_file_cmd.py index 600256d9ddc..edb889e9094 100644 --- a/src/sage/cli/run_file_cmd.py +++ b/src/sage/cli/run_file_cmd.py @@ -40,8 +40,10 @@ def run(self) -> int: r""" Execute the given command. """ - input_file = preparse_file_named(self.options.file[0]) if self.options.file[0].endswith('.sage') else self.options.file[0] - if self.options.file[0].endswith('.pyx') or self.options.file[0].endswith('.spyx'): + input_file = self.options.file[0] + if input_file.endswith('.sage'): + input_file = str(preparse_file_named(input_file)) + if input_file.endswith('.pyx') or input_file.endswith('.spyx'): s = load_cython(input_file) eval(compile(s, tmp_filename(), 'exec'), sage_globals()) else: From 340d6f685c5730c3e56ccb4aa999528c23a1b302 Mon Sep 17 00:00:00 2001 From: Antonio Rojas Date: Tue, 23 Sep 2025 23:48:05 +0200 Subject: [PATCH 05/10] Fix ruff --- src/sage/cli/run_file_cmd_test.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/cli/run_file_cmd_test.py b/src/sage/cli/run_file_cmd_test.py index 165d829919d..56b1960adb8 100644 --- a/src/sage/cli/run_file_cmd_test.py +++ b/src/sage/cli/run_file_cmd_test.py @@ -3,10 +3,11 @@ from unittest.mock import patch import sys + def test_run_file_cmd(capsys, tmp_path): file = tmp_path / "test.sage" file.write_text("print(3^33)") - options = CliOptions(file = [str(file)]) + options = CliOptions(file=[str(file)]) run_file_cmd = RunFileCmd(options) result = run_file_cmd.run() @@ -18,7 +19,7 @@ def test_run_file_cmd_with_args(capsys, tmp_path): with patch.object(sys, 'argv', ["python3", "test.sage", "1", "1"]): file = tmp_path / "test.sage" file.write_text("import sys; print(int(sys.argv[1]) + int(sys.argv[2]))") - options = CliOptions(file = [str(file), "1", "1"]) + options = CliOptions(file=[str(file), "1", "1"]) run_file_cmd = RunFileCmd(options) result = run_file_cmd.run() From 87037918ac6162b55aaa822a9525950abfc53e43 Mon Sep 17 00:00:00 2001 From: Antonio Rojas Date: Mon, 29 Sep 2025 16:25:48 +0200 Subject: [PATCH 06/10] cli: Allow importing modules from the current directory when running a command or a file. Matches python behavior, and fixes another regression with respect to the bash script Fixes #40908 --- src/sage/cli/eval_cmd.py | 4 ++++ src/sage/cli/run_file_cmd.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/sage/cli/eval_cmd.py b/src/sage/cli/eval_cmd.py index a0d80f7909b..9b37b89cdc4 100644 --- a/src/sage/cli/eval_cmd.py +++ b/src/sage/cli/eval_cmd.py @@ -3,6 +3,7 @@ from sage.cli.options import CliOptions from sage.repl.preparse import preparse from sage.all import sage_globals +import os, sys class EvalCmd: @@ -36,6 +37,9 @@ def run(self) -> int: r""" Execute the given command. """ + # Allow importing modules from the current directory, matching python behavior + sys.path.append(os.getcwd()) + code = preparse(self.options.command) eval(compile(code, "", "exec"), sage_globals()) return 0 diff --git a/src/sage/cli/run_file_cmd.py b/src/sage/cli/run_file_cmd.py index edb889e9094..a42a30d1f5c 100644 --- a/src/sage/cli/run_file_cmd.py +++ b/src/sage/cli/run_file_cmd.py @@ -5,6 +5,7 @@ from sage.repl.load import load_cython from sage.misc.temporary_file import tmp_filename from sage.all import sage_globals +import os, sys class RunFileCmd: @@ -40,6 +41,9 @@ def run(self) -> int: r""" Execute the given command. """ + # Allow importing modules from the current directory, matching python behavior + sys.path.append(os.getcwd()) + input_file = self.options.file[0] if input_file.endswith('.sage'): input_file = str(preparse_file_named(input_file)) From c00d1b5ff53dfa04a38e015130e9248d67211d4b Mon Sep 17 00:00:00 2001 From: Antonio Rojas Date: Mon, 29 Sep 2025 16:30:08 +0200 Subject: [PATCH 07/10] Fix ruff --- src/sage/cli/eval_cmd.py | 3 ++- src/sage/cli/run_file_cmd.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sage/cli/eval_cmd.py b/src/sage/cli/eval_cmd.py index 9b37b89cdc4..4a1accf5ed9 100644 --- a/src/sage/cli/eval_cmd.py +++ b/src/sage/cli/eval_cmd.py @@ -3,7 +3,8 @@ from sage.cli.options import CliOptions from sage.repl.preparse import preparse from sage.all import sage_globals -import os, sys +import os +import sys class EvalCmd: diff --git a/src/sage/cli/run_file_cmd.py b/src/sage/cli/run_file_cmd.py index a42a30d1f5c..3229efb6de3 100644 --- a/src/sage/cli/run_file_cmd.py +++ b/src/sage/cli/run_file_cmd.py @@ -5,7 +5,8 @@ from sage.repl.load import load_cython from sage.misc.temporary_file import tmp_filename from sage.all import sage_globals -import os, sys +import os +import sys class RunFileCmd: From 2011bfd3cc792eaf12ed0bae6a2678e18fe64b05 Mon Sep 17 00:00:00 2001 From: Antonio Rojas Date: Mon, 29 Sep 2025 16:32:01 +0200 Subject: [PATCH 08/10] Remove duplicate import --- src/sage/cli/run_file_cmd.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/cli/run_file_cmd.py b/src/sage/cli/run_file_cmd.py index 3229efb6de3..f0edab33503 100644 --- a/src/sage/cli/run_file_cmd.py +++ b/src/sage/cli/run_file_cmd.py @@ -35,7 +35,6 @@ def __init__(self, options: CliOptions): """ self.options = options # shift sys.argv for compatibility with the old sage bash script and python command when consuming arguments from the command line - import sys del sys.argv[0] def run(self) -> int: From 7ae535166f5a6a8ebb15855bb43033fdd655a2b3 Mon Sep 17 00:00:00 2001 From: Antonio Rojas Date: Wed, 24 Sep 2025 08:21:24 +0200 Subject: [PATCH 09/10] Fix casing Co-authored-by: Tobias Diez --- src/sage/cli/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/cli/options.py b/src/sage/cli/options.py index dcba466174c..b460fd9cf45 100644 --- a/src/sage/cli/options.py +++ b/src/sage/cli/options.py @@ -23,4 +23,4 @@ class CliOptions: command: str | None = None """The file to execute.""" - file: List[str] | None = None + file: list[str] | None = None From 4170f19955c9dc0de0206b2c0d52876d437487a9 Mon Sep 17 00:00:00 2001 From: Antonio Rojas Date: Tue, 30 Sep 2025 23:20:51 +0200 Subject: [PATCH 10/10] Move sys.path manipulation to __main__.py --- pyproject.toml | 2 +- src/sage/cli/__main__.py | 4 ++++ src/sage/cli/eval_cmd.py | 5 ----- src/sage/cli/run_file_cmd.py | 6 +----- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1a9c5db0f0d..5626abaf850 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,7 +103,7 @@ content-type = "text/markdown" file = "README.md" [project.scripts] -sage = "sage.cli:main" +sage = "sage.cli.__main__:main" [tool.conda-lock] platforms = ['linux-64', 'linux-aarch64', 'osx-64', 'osx-arm64'] diff --git a/src/sage/cli/__main__.py b/src/sage/cli/__main__.py index ebee1cbe37f..e7e37ba6148 100644 --- a/src/sage/cli/__main__.py +++ b/src/sage/cli/__main__.py @@ -1,5 +1,9 @@ +import os import sys +# Allow importing modules from the current directory, matching python behavior +sys.path.append(os.getcwd()) + from sage.cli import main sys.exit(main()) diff --git a/src/sage/cli/eval_cmd.py b/src/sage/cli/eval_cmd.py index 4a1accf5ed9..a0d80f7909b 100644 --- a/src/sage/cli/eval_cmd.py +++ b/src/sage/cli/eval_cmd.py @@ -3,8 +3,6 @@ from sage.cli.options import CliOptions from sage.repl.preparse import preparse from sage.all import sage_globals -import os -import sys class EvalCmd: @@ -38,9 +36,6 @@ def run(self) -> int: r""" Execute the given command. """ - # Allow importing modules from the current directory, matching python behavior - sys.path.append(os.getcwd()) - code = preparse(self.options.command) eval(compile(code, "", "exec"), sage_globals()) return 0 diff --git a/src/sage/cli/run_file_cmd.py b/src/sage/cli/run_file_cmd.py index f0edab33503..2709b8f1498 100644 --- a/src/sage/cli/run_file_cmd.py +++ b/src/sage/cli/run_file_cmd.py @@ -1,12 +1,11 @@ import argparse +import sys from sage.cli.options import CliOptions from sage.repl.preparse import preparse_file_named from sage.repl.load import load_cython from sage.misc.temporary_file import tmp_filename from sage.all import sage_globals -import os -import sys class RunFileCmd: @@ -41,9 +40,6 @@ def run(self) -> int: r""" Execute the given command. """ - # Allow importing modules from the current directory, matching python behavior - sys.path.append(os.getcwd()) - input_file = self.options.file[0] if input_file.endswith('.sage'): input_file = str(preparse_file_named(input_file))