Skip to content

Overhauled CLI.#963

Merged
emeryberger merged 7 commits into
masterfrom
overhaul_cli
Dec 18, 2025
Merged

Overhauled CLI.#963
emeryberger merged 7 commits into
masterfrom
overhaul_cli

Conversation

@emeryberger
Copy link
Copy Markdown
Member

CLI Overhaul: Verb-based Commands and YAML Configuration

This PR introduces a major refactoring of Scalene's command-line interface to be more intuitive and maintainable.

New Command Structure

Scalene now uses a verb-based CLI with two main commands:

scalene run [options] program.py    # Profile a program
scalene view [options] [file.json]  # View results

scalene run - Profiling

Profiles a Python program and saves results to JSON (default: scalene-profile.json).

scalene run prog.py                    # profile, save to scalene-profile.json
scalene run -o results.json prog.py    # custom output file
scalene run --cpu-only prog.py         # CPU profiling only (faster)
scalene run -c config.yaml prog.py     # load options from YAML config
scalene run prog.py --- --arg          # pass arguments to program
  • Basic options: -o/--outfile, --cpu-only, -c/--config, --help-advanced

  • Advanced options (via --help-advanced): --profile-all, --profile-only, --profile-exclude, --gpu, --memory, --stacks,
    --profile-interval, --use-virtual-time, --cpu-percent-threshold, --cpu-sampling-rate, --malloc-threshold,
    --memory-leak-detector, --on, --off

scalene view - Viewing Results

Displays an existing profile in browser, terminal, or saves as HTML.

  scalene view                       # open scalene-profile.json in browser
  scalene view --cli                 # view in terminal
  scalene view --html                # save to scalene-profile.html
  scalene view -r --cli              # reduced view (only lines with activity)
  scalene view myprofile.json        # view specific profile

YAML Configuration Files

New support for storing options in a YAML config file:

  # scalene.yaml
  outfile: my-profile.json
  cpu-only: true
  profile-only: "mypackage,utils"
  profile-exclude: "tests,venv"
  cpu-percent-threshold: 5

Usage: scalene run -c scalene.yaml prog.py

Command-line arguments override config file settings.

Key Changes

  • Separation of concerns: Profiling (run) and viewing (view) are now distinct operations
  • JSON as default output: run saves JSON profiles; view handles display/conversion
  • Two-tier help: Basic options shown by default; --help-advanced shows advanced options
  • Cleaner option naming: --cpu-only (was --cpu), -o shorthand for --outfile
  • Background profiling docs: Instructions for --on/--off moved to advanced help

Files Changed

  • scalene/scalene_parseargs.py - Main CLI implementation
  • scalene/scalene_profiler.py - Updated subprocess cmdline generation
  • scalene/scalene_arguments.py - Updated defaults
  • pyproject.toml - Added pyyaml>=6.0 dependency
  • README.md, index.rst, scalene/scalene-usage.txt - Documentation updates
  • tests/test_coverup_*.py, tests/test_nested_package_relative_import.py - Test updates

Migration Guide

Old CLI New CLI
scalene prog.py scalene run prog.py
scalene --cli prog.py scalene run prog.py && scalene view --cli
scalene --cpu prog.py scalene run --cpu-only prog.py
scalene --html --outfile out.html prog.py scalene run prog.py && scalene view --html
scalene --help scalene run --help or scalene run --help-advanced

Comment thread scalene/scalene_parseargs.py Fixed
"""Handle the 'view' subcommand to view an existing profile."""
import json
import subprocess
import scalene.scalene_config

Check notice

Code scanning / CodeQL

Module is imported with 'import' and 'import from' Note

Module 'scalene_config' is imported with both 'import' and 'import from'.
Module 'scalene.scalene_config' is imported with both 'import' and 'import from'.

Copilot Autofix

AI 5 months ago

To fix the problem, we should remove the redundant import scalene.scalene_config statement inside the _handle_view_command method. Instead, we should import SCALENE_PORT alongside the other named imports from scalene.scalene_config at the top of the file, since this variable is the only member being referenced from the module after line 692. Then, within the code, change any access of scalene.scalene_config.SCALENE_PORT to just SCALENE_PORT. This preserves functionality and increases clarity, while resolving the potential for confusion and the warning generated by CodeQL.

Specifically:

  • Edit the top-level import (from scalene.scalene_config import scalene_date, scalene_version) to also import SCALENE_PORT.
  • Remove line 692: import scalene.scalene_config
  • Update line 737: change scalene.scalene_config.SCALENE_PORT to just SCALENE_PORT.

No other changes are needed.


Suggested changeset 1
scalene/scalene_parseargs.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/scalene/scalene_parseargs.py b/scalene/scalene_parseargs.py
--- a/scalene/scalene_parseargs.py
+++ b/scalene/scalene_parseargs.py
@@ -11,7 +11,7 @@
 
 from scalene.find_browser import find_browser
 from scalene.scalene_arguments import ScaleneArguments
-from scalene.scalene_config import scalene_date, scalene_version
+from scalene.scalene_config import scalene_date, scalene_version, SCALENE_PORT
 from scalene.scalene_statistics import Filename
 from scalene.scalene_utility import generate_html
 
@@ -689,7 +689,6 @@
         import json
         import subprocess
 
-        import scalene.scalene_config
 
         profile_file = args.profile_file
         output_file = "scalene-profile.html"
@@ -734,7 +733,7 @@
                         sys.executable,
                         f"{dir}{os.sep}launchbrowser.py",
                         os.path.abspath(output_file),
-                        str(scalene.scalene_config.SCALENE_PORT),
+                        str(SCALENE_PORT),
                     ],
                     stdout=subprocess.DEVNULL,
                     stderr=subprocess.DEVNULL,
EOF
@@ -11,7 +11,7 @@

from scalene.find_browser import find_browser
from scalene.scalene_arguments import ScaleneArguments
from scalene.scalene_config import scalene_date, scalene_version
from scalene.scalene_config import scalene_date, scalene_version, SCALENE_PORT
from scalene.scalene_statistics import Filename
from scalene.scalene_utility import generate_html

@@ -689,7 +689,6 @@
import json
import subprocess

import scalene.scalene_config

profile_file = args.profile_file
output_file = "scalene-profile.html"
@@ -734,7 +733,7 @@
sys.executable,
f"{dir}{os.sep}launchbrowser.py",
os.path.abspath(output_file),
str(scalene.scalene_config.SCALENE_PORT),
str(SCALENE_PORT),
],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +752 to +754
def _parse_args_impl(
defaults: ScaleneArguments,
) -> Tuple[argparse.Namespace, List[str]]:

Check notice

Code scanning / CodeQL

Explicit returns mixed with implicit (fall through) returns Note

Mixing implicit and explicit returns may indicate an error, as implicit returns always return None.
user_site = site.getusersitepackages()
if user_site:
paths.add(os.path.normpath(user_site))
except Exception:

Check notice

Code scanning / CodeQL

Empty except Note

'except' clause does nothing but pass and there is no explanatory comment.

Copilot Autofix

AI 5 months ago

The best practice for fixing this issue is to avoid silently suppressing all exceptions. Instead, catch the exceptions, log an informative warning, and only suppress non-critical (non-fatal) errors. This can be done by calling warnings.warn() (already imported in the file) within the except block and including the exception message/context in the warning. The placement of the warning should make it clear this is related to site-packages discovery. No additional imports or method definitions are needed since warnings is already available.

Lines to change:
In scalene/scalene_profiler.py, lines 1165-1166 should be modified to use warnings.warn to report the exception.


Suggested changeset 1
scalene/scalene_profiler.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/scalene/scalene_profiler.py b/scalene/scalene_profiler.py
--- a/scalene/scalene_profiler.py
+++ b/scalene/scalene_profiler.py
@@ -1162,8 +1162,8 @@
             user_site = site.getusersitepackages()
             if user_site:
                 paths.add(os.path.normpath(user_site))
-        except Exception:
-            pass
+        except Exception as ex:
+            warnings.warn(f"Could not import site.getsitepackages() or user site-packages: {ex}")
 
         # Python prefix paths (covers most installations)
         for prefix in (sys.prefix, sys.base_prefix, sys.exec_prefix):
EOF
@@ -1162,8 +1162,8 @@
user_site = site.getusersitepackages()
if user_site:
paths.add(os.path.normpath(user_site))
except Exception:
pass
except Exception as ex:
warnings.warn(f"Could not import site.getsitepackages() or user site-packages: {ex}")

# Python prefix paths (covers most installations)
for prefix in (sys.prefix, sys.base_prefix, sys.exec_prefix):
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Copy Markdown
Collaborator

@jaltmayerpizzorno jaltmayerpizzorno left a comment

Choose a reason for hiding this comment

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

I think splitting into commands is a good move!

@emeryberger emeryberger merged commit 87d5385 into master Dec 18, 2025
71 of 82 checks passed
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.

3 participants