diff --git a/burr/cli/__main__.py b/burr/cli/__main__.py index 8967602d9..439252537 100644 --- a/burr/cli/__main__.py +++ b/burr/cli/__main__.py @@ -29,7 +29,7 @@ from importlib.resources import files from pathlib import Path from types import ModuleType -from typing import Optional +from typing import List, Optional, Union from burr import system from burr.core.persistence import PersistedStateData @@ -50,7 +50,9 @@ setup_logging(logging.INFO) -def _command(command: str, capture_output: bool, addl_env: dict | None = None) -> str: +def _command( + command: Union[str, List[str]], capture_output: bool, addl_env: dict | None = None +) -> str: """Runs a simple command""" if addl_env is None: addl_env = {} @@ -59,18 +61,18 @@ def _command(command: str, capture_output: bool, addl_env: dict | None = None) - logger.info(f"Running command: {command}") if isinstance(command, str): command = command.split(" ") - if capture_output: - try: - return ( - subprocess.check_output(command, stderr=subprocess.PIPE, shell=False, env=env) - .decode() - .strip() - ) - except subprocess.CalledProcessError as e: - print(e.stdout.decode()) - print(e.stderr.decode()) - raise e - subprocess.run(command, shell=False, check=True, env=env) + if capture_output: + try: + return ( + subprocess.check_output(command, stderr=subprocess.PIPE, shell=False, env=env) + .decode() + .strip() + ) + except subprocess.CalledProcessError as e: + print(e.stdout.decode()) + print(e.stderr.decode()) + raise e + subprocess.run(command, shell=False, check=True, env=env) def _get_git_root() -> str: @@ -168,9 +170,18 @@ def _run_server( # TODO: Implement server running logic here # Example: Start a web server, configure ports, etc. logger.info(f"Starting server on port {port}") - cmd = f"{sys.executable} -m uvicorn burr.tracking.server.run:app --port {port} --host {host}" + cmd = [ + sys.executable, + "-m", + "uvicorn", + "burr.tracking.server.run:app", + "--port", + str(port), + "--host", + host, + ] if dev_mode: - cmd += " --reload" + cmd.append("--reload") if backend == "local" and not no_copy_demo_data: # TODO: fix this so we don't leak burr_path here since that's the value the local backend users diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 000000000..71e297bfa --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,48 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import pytest + +try: + from burr.cli import __main__ as cli_main +except ImportError: + pytest.skip("the CLI needs the [cli] extra", allow_module_level=True) + + +@pytest.fixture +def captured_runs(monkeypatch): + calls = [] + + def fake_run(command, **kwargs): + calls.append(command) + + monkeypatch.setattr(cli_main.subprocess, "run", fake_run) + return calls + + +def test_command_runs_argument_list_unchanged(captured_runs): + cli_main._command(["/path with spaces/python", "-c", "pass"], capture_output=False) + assert captured_runs == [["/path with spaces/python", "-c", "pass"]] + + +def test_run_server_handles_python_path_with_spaces(monkeypatch, captured_runs): + python_path = "/Users/someone/My Projects/venv/bin/python" + monkeypatch.setattr(cli_main.sys, "executable", python_path) + cli_main._run_server( + port=7241, dev_mode=False, no_open=True, no_copy_demo_data=True, host="127.0.0.1" + ) + assert captured_runs[0][0] == python_path