Repository: xorq-labs/xorq
Pull request #748: feat(cli): add default value to expr_name
Author: login: dlovell
Created: 2025-03-28T20:01:57Z | Merged: 2025-03-29T14:24:53Z



Diff:
diff --git a/python/xorq/cli.py b/python/xorq/cli.py
index 0e3a5a91f..a46286e1d 100644
--- a/python/xorq/cli.py
+++ b/python/xorq/cli.py
@@ -8,14 +8,14 @@
 from xorq.vendor.ibis import Expr
 
 
-def build_command(script_path, expression, builds_dir="builds"):
+def build_command(script_path, expr_name, builds_dir="builds"):
     """
     Generate artifacts from an expression in a given Python script
 
     Parameters
     ----------
     script_path : Path to the Python script
-    expression : The name of the expression to build
+    expr_name : The name of the expression to build
     builds_dir : Directory where artifacts will be generated
 
     Returns
@@ -23,39 +23,35 @@ def build_command(script_path, expression, builds_dir="builds"):
 
     """
 
-    if len(expression) != 1:
-        print("Expected one, and only one expression", file=sys.stderr)
-        sys.exit(1)
-
-    (expression,) = expression
-
     if not os.path.exists(script_path):
         print(f"Error: Script not found at {script_path}", file=sys.stderr)
         sys.exit(1)
 
-    print(f"Building {expression} from {script_path}")
+    print(f"Building {expr_name} from {script_path}")
 
     build_manager = BuildManager(builds_dir)
 
     vars_module = import_from_path(script_path)
 
-    if not hasattr(vars_module, expression):
-        print(f"Expression {expression} not found", file=sys.stderr)
+    if not hasattr(vars_module, expr_name):
+        print(f"Expression {expr_name} not found", file=sys.stderr)
         sys.exit(1)
 
-    expr = getattr(vars_module, expression)
+    expr = getattr(vars_module, expr_name)
 
     if not isinstance(expr, Expr):
         print(
-            f"The object {expression} must be an instance of {Expr.__module__}.{Expr.__name__}",
+            f"The object {expr_name} must be an instance of {Expr.__module__}.{Expr.__name__}",
             file=sys.stderr,
         )
         sys.exit(1)
 
     expr_hash = build_manager.compile_expr(expr)
     print(
-        f"Written '{expression}' to {build_manager.artifact_store.get_path(expr_hash)}"
+        f"Written '{expr_name}' to {build_manager.artifact_store.get_path(expr_hash)}",
+        file=sys.stderr,
     )
+    print(build_manager.artifact_store.get_path(expr_hash))
 
 
 def run_command(expr_path, output_path=None, output_format="parquet"):
@@ -98,8 +94,7 @@ def run_command(expr_path, output_path=None, output_format="parquet"):
         sys.exit(1)
 
 
-def main():
-    """Main entry point for the xorq CLI."""
+def parse_args(override=None):
     parser = argparse.ArgumentParser(description="xorq - build and run expressions")
 
     subparsers = parser.add_subparsers(dest="command", help="Command to execute")
@@ -111,8 +106,8 @@ def main():
     build_parser.add_argument("script_path", help="Path to the Python script")
     build_parser.add_argument(
         "-e",
-        "--expressions",
-        nargs="?",
+        "--expr-name",
+        default="expr",
         help="Name of the expression variable in the Python script",
     )
     build_parser.add_argument(
@@ -135,12 +130,17 @@ def main():
         help="Output format (default: parquet)",
     )
 
-    args = parser.parse_args()
+    args = parser.parse_args(override)
+    return args
+
+
+def main():
+    """Main entry point for the xorq CLI."""
+    args = parse_args()
 
     match args.command:
         case "build":
-            expressions = [args.expressions] if args.expressions else []
-            build_command(args.script_path, expressions, args.builds_dir)
+            build_command(args.script_path, args.expr_name, args.builds_dir)
         case "run":
             run_command(args.build_path, args.output_path, args.format)
         case _:
diff --git a/python/xorq/tests/test_cli.py b/python/xorq/tests/test_cli.py
index 775debb21..163b01985 100644
--- a/python/xorq/tests/test_cli.py
+++ b/python/xorq/tests/test_cli.py
@@ -93,7 +93,7 @@ def test_run_command_default(monkeypatch, tmp_path, capsys):
     target_dir = tmp_path / "build"
     script_path = Path(__file__).absolute().parent / "fixtures" / "pipeline.py"
 
-    build_command(str(script_path), ["expr"], builds_dir=str(target_dir))
+    build_command(str(script_path), "expr", builds_dir=str(target_dir))
     capture = capsys.readouterr()
 
     if match := re.search(f"{target_dir}/([0-9a-f]+)", str(capture.out)):
@@ -118,7 +118,7 @@ def test_run_command(monkeypatch, tmp_path, capsys, output_format):
     target_dir = tmp_path / "build"
     script_path = Path(__file__).absolute().parent / "fixtures" / "pipeline.py"
 
-    build_command(str(script_path), ["expr"], builds_dir=str(target_dir))
+    build_command(str(script_path), "expr", builds_dir=str(target_dir))
     capture = capsys.readouterr()
 
     if match := re.search(f"{target_dir}/([0-9a-f]+)", str(capture.out)):
@@ -146,29 +146,6 @@ def test_run_command(monkeypatch, tmp_path, capsys, output_format):
         raise AssertionError("No expression hash")
 
 
-def test_build_command_not_implemented(monkeypatch, capsys):
-    script_path = Path(__file__).absolute().parent / "fixtures" / "pipeline.py"
-
-    test_args = [
-        "xorq",
-        "build",
-        str(script_path),
-        "--builds-dir",
-        str(Path.cwd()),
-    ]
-    monkeypatch.setattr(sys, "argv", test_args)
-
-    # Run the CLI (with try/except to prevent SystemExit)
-    try:
-        main()
-    except SystemExit:
-        pass
-
-    # Check output
-    captured = capsys.readouterr()
-    assert "Expected one, and only one expression" in captured.err
-
-
 @pytest.mark.parametrize(
     "expression,message",
     [


Reviews:
user: login: mesejo
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-28T20:13:52Z
user: login: dlovell
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-28T20:40:08Z
user: login: mesejo
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-28T20:44:12Z
user: login: mesejo
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-29T14:24:45Z

Review comments:
diff_hunk: @@ -8,54 +8,50 @@
 from xorq.vendor.ibis import Expr
 
 
-def build_command(script_path, expression, builds_dir="builds"):
+def build_command(script_path, expr_name, builds_dir="builds"):
     """
     Generate artifacts from an expression in a given Python script
 
     Parameters
     ----------
     script_path : Path to the Python script
-    expression : The name of the expression to build
+    expr_name : The name of the expression to build
     builds_dir : Directory where artifacts will be generated
 
     Returns
     -------
 
     """
 
-    if len(expression) != 1:
-        print("Expected one, and only one expression", file=sys.stderr)
-        sys.exit(1)
-
-    (expression,) = expression
-
     if not os.path.exists(script_path):
         print(f"Error: Script not found at {script_path}", file=sys.stderr)
         sys.exit(1)
 
-    print(f"Building {expression} from {script_path}")
+    print(f"Building {expr_name} from {script_path}")
 
     build_manager = BuildManager(builds_dir)
 
     vars_module = import_from_path(script_path)
 
-    if not hasattr(vars_module, expression):
-        print(f"Expression {expression} not found", file=sys.stderr)
+    if not hasattr(vars_module, expr_name):
+        print(f"Expression {expr_name} not found", file=sys.stderr)
         sys.exit(1)
 
-    expr = getattr(vars_module, expression)
+    expr = getattr(vars_module, expr_name)
 
     if not isinstance(expr, Expr):
         print(
-            f"The object {expression} must be an instance of {type(expr)}",
+            f"The object {expr_name} must be an instance of {type(expr)}",
             file=sys.stderr,
         )
         sys.exit(1)
 
     expr_hash = build_manager.compile_expr(expr)
     print(
-        f"Written '{expression}' to {build_manager.artifact_store.get_path(expr_hash)}"
+        f"Written '{expr_name}' to {build_manager.artifact_store.get_path(expr_hash)}",
+        file=sys.stderr,
     )
+    print(build_manager.artifact_store.get_path(expr_hash))
path: python/xorq/cli.py
user: login: mesejo
body: Why are we printing this?
created_at: 2025-03-28T20:12:14Z
author_association: COLLABORATOR
diff_hunk: @@ -8,54 +8,50 @@
 from xorq.vendor.ibis import Expr
 
 
-def build_command(script_path, expression, builds_dir="builds"):
+def build_command(script_path, expr_name, builds_dir="builds"):
     """
     Generate artifacts from an expression in a given Python script
 
     Parameters
     ----------
     script_path : Path to the Python script
-    expression : The name of the expression to build
+    expr_name : The name of the expression to build
     builds_dir : Directory where artifacts will be generated
 
     Returns
     -------
 
     """
 
-    if len(expression) != 1:
-        print("Expected one, and only one expression", file=sys.stderr)
-        sys.exit(1)
-
-    (expression,) = expression
-
     if not os.path.exists(script_path):
         print(f"Error: Script not found at {script_path}", file=sys.stderr)
         sys.exit(1)
 
-    print(f"Building {expression} from {script_path}")
+    print(f"Building {expr_name} from {script_path}")
 
     build_manager = BuildManager(builds_dir)
 
     vars_module = import_from_path(script_path)
 
-    if not hasattr(vars_module, expression):
-        print(f"Expression {expression} not found", file=sys.stderr)
+    if not hasattr(vars_module, expr_name):
+        print(f"Expression {expr_name} not found", file=sys.stderr)
         sys.exit(1)
 
-    expr = getattr(vars_module, expression)
+    expr = getattr(vars_module, expr_name)
 
     if not isinstance(expr, Expr):
         print(
-            f"The object {expression} must be an instance of {type(expr)}",
+            f"The object {expr_name} must be an instance of {type(expr)}",
             file=sys.stderr,
         )
         sys.exit(1)
 
     expr_hash = build_manager.compile_expr(expr)
     print(
-        f"Written '{expression}' to {build_manager.artifact_store.get_path(expr_hash)}"
+        f"Written '{expr_name}' to {build_manager.artifact_store.get_path(expr_hash)}",
+        file=sys.stderr,
     )
+    print(build_manager.artifact_store.get_path(expr_hash))
path: python/xorq/cli.py
user: login: dlovell
body: per the commit message
> so scripts can use stdout to compose operations
created_at: 2025-03-28T20:40:08Z
author_association: COLLABORATOR
diff_hunk: @@ -8,54 +8,50 @@
 from xorq.vendor.ibis import Expr
 
 
-def build_command(script_path, expression, builds_dir="builds"):
+def build_command(script_path, expr_name, builds_dir="builds"):
     """
     Generate artifacts from an expression in a given Python script
 
     Parameters
     ----------
     script_path : Path to the Python script
-    expression : The name of the expression to build
+    expr_name : The name of the expression to build
     builds_dir : Directory where artifacts will be generated
 
     Returns
     -------
 
     """
 
-    if len(expression) != 1:
-        print("Expected one, and only one expression", file=sys.stderr)
-        sys.exit(1)
-
-    (expression,) = expression
-
     if not os.path.exists(script_path):
         print(f"Error: Script not found at {script_path}", file=sys.stderr)
         sys.exit(1)
 
-    print(f"Building {expression} from {script_path}")
+    print(f"Building {expr_name} from {script_path}")
 
     build_manager = BuildManager(builds_dir)
 
     vars_module = import_from_path(script_path)
 
-    if not hasattr(vars_module, expression):
-        print(f"Expression {expression} not found", file=sys.stderr)
+    if not hasattr(vars_module, expr_name):
+        print(f"Expression {expr_name} not found", file=sys.stderr)
         sys.exit(1)
 
-    expr = getattr(vars_module, expression)
+    expr = getattr(vars_module, expr_name)
 
     if not isinstance(expr, Expr):
         print(
-            f"The object {expression} must be an instance of {type(expr)}",
+            f"The object {expr_name} must be an instance of {type(expr)}",
             file=sys.stderr,
         )
         sys.exit(1)
 
     expr_hash = build_manager.compile_expr(expr)
     print(
-        f"Written '{expression}' to {build_manager.artifact_store.get_path(expr_hash)}"
+        f"Written '{expr_name}' to {build_manager.artifact_store.get_path(expr_hash)}",
+        file=sys.stderr,
     )
+    print(build_manager.artifact_store.get_path(expr_hash))
path: python/xorq/cli.py
user: login: mesejo
body: But we are also printing the previous line, should we print both? 
created_at: 2025-03-28T20:44:06Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-28T20:02:18Z
body: **ACTION NEEDED**

xorq follows the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) for release automation.

The PR title and description are used as the merge commit message.

Please update your PR title and description to match the specification.

The commitlint output is:

```
npm warn exec The following package was not found and will be installed: commitlint@19.8.0
⧗   input: feat/cli/default-expr-name
✖   subject may not be empty [subject-empty]
✖   type may not be empty [type-empty]

✖   found 2 problems, 0 warnings
ⓘ   Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint
```
author_association: NONE
user: login: codspeed-hq[bot]
created_at: 2025-03-28T20:11:57Z
body: <!-- __CODSPEED_PERFORMANCE_REPORT_COMMENT__ -->
<!-- __CODSPEED_INSTRUMENTATION_PERFORMANCE_REPORT_COMMENT__ -->

## [CodSpeed Performance Report](https://codspeed.io/xorq-labs/xorq/branches/feat%2Fcli%2Fdefault-expr-name)

### Merging #748 will **not alter performance**

<sub>Comparing <code>feat/cli/default-expr-name</code> (fa0bd76) with <code>main</code> (dd23969)</sub>



### Summary

`✅ 3` untouched benchmarks  




author_association: NONE