Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Share some options between the CLI and magic. #350

Merged
merged 1 commit into from
Jan 23, 2025
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
37 changes: 29 additions & 8 deletions pyinstrument/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,9 +409,31 @@ def store_and_consume_remaining(
raise inner_exception


class OptionsParseError(Exception):
pass


def compute_render_options(
options: CommandLineOptions, renderer_class: type[renderers.Renderer], output_file: TextIO
options: CommandLineOptions,
renderer_class: type[renderers.Renderer],
unicode_support: bool,
color_support: bool,
) -> dict[str, Any]:
"""
Given a list of `CommandLineOptions`, compute the
rendering options for the given renderer.

Raises an `OptionsParseError` if there is an error parsing the options.

unicode_support:
indicate whether the expected output supports unicode
color_support:
indicate whether the expected output supports color

Both of these will be used to determine the default of outputting unicode
or color, but can be overridden with `options.color` and `option.unicode`.
"""

# parse show/hide options
if options.hide_fnmatch is not None and options.hide_regex is not None:
raise OptionsParseError("You can‘t specify both --hide and --hide-regex")
Expand Down Expand Up @@ -449,8 +471,8 @@ def compute_render_options(
if issubclass(renderer_class, renderers.ConsoleRenderer):
unicode_override = options.unicode is not None
color_override = options.color is not None
unicode: Any = options.unicode if unicode_override else file_supports_unicode(output_file)
color: Any = options.color if color_override else file_supports_color(output_file)
unicode: Any = options.unicode if unicode_override else unicode_support
color: Any = options.color if color_override else color_support

render_options.update({"unicode": unicode, "color": color})

Expand Down Expand Up @@ -481,15 +503,14 @@ def compute_render_options(
return render_options


class OptionsParseError(Exception):
pass


def create_renderer(
renderer_class: type[renderers.Renderer], options: CommandLineOptions, output_file: TextIO
) -> renderers.Renderer:
render_options = compute_render_options(
options, renderer_class=renderer_class, output_file=output_file
options,
renderer_class=renderer_class,
unicode_support=file_supports_unicode(output_file),
color_support=file_supports_color(output_file),
)

try:
Expand Down
82 changes: 75 additions & 7 deletions pyinstrument/magic/magic.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
from IPython.display import IFrame, display

from pyinstrument import Profiler, renderers
from pyinstrument.__main__ import compute_render_options
from pyinstrument.frame import Frame
from pyinstrument.frame_ops import delete_frame_from_tree
from pyinstrument.processors import ProcessorOptions
from pyinstrument.renderers.console import ConsoleRenderer
from pyinstrument.renderers.html import HTMLRenderer

_active_profiler = None

Expand Down Expand Up @@ -76,6 +79,43 @@ def recreate_transformer(self, target_description: str):
)

@magic_arguments()
@argument(
"-p",
"--render-option",
dest="render_options",
action="append",
metavar="RENDER_OPTION",
type=str,
help=(
"options to pass to the renderer, in the format 'flag_name' or 'option_name=option_value'. "
"For example, to set the option 'time', pass '-p time=percent_of_total'. To pass multiple "
"options, use the -p option multiple times. You can set processor options using dot-syntax, "
"like '-p processor_options.filter_threshold=0'. option_value is parsed as a JSON value or "
"a string."
),
)
@argument(
"--show-regex",
dest="show_regex",
action="store",
metavar="REGEX",
help=(
"regex matching the file paths whose frames to always show. "
"Useful if --show doesn't give enough control."
),
)
@argument(
"--show",
dest="show_fnmatch",
action="store",
metavar="EXPR",
help=(
"glob-style pattern matching the file paths whose frames to "
"show, regardless of --hide or --hide-regex. For example, use "
"--show '*/<library>/*' to show frames within a library that "
"would otherwise be hidden."
),
)
@argument(
"--interval",
type=float,
Expand Down Expand Up @@ -110,6 +150,26 @@ def recreate_transformer(self, target_description: str):
nargs="*",
help="When used as a line magic, the code to profile",
)
@argument(
"--hide",
dest="hide_fnmatch",
action="store",
metavar="EXPR",
help=(
"glob-style pattern matching the file paths whose frames to hide. Defaults to "
"hiding non-application code"
),
)
@argument(
"--hide-regex",
dest="hide_regex",
action="store",
metavar="REGEX",
help=(
"regex matching the file paths whose frames to hide. Useful if --hide doesn't give "
"enough control."
),
)
@no_var_expand
@line_cell_magic
def pyinstrument(self, line, cell=None):
Expand All @@ -126,6 +186,12 @@ def pyinstrument(self, line, cell=None):
"""
global _active_profiler
args = parse_argstring(self.pyinstrument, line)

# 2024, always override this for now in IPython,
# we can make an option later if necessary
args.unicode = True
args.color = True

ip = get_ipython()

if not ip:
Expand Down Expand Up @@ -175,10 +241,15 @@ def pyinstrument(self, line, cell=None):
)
return

html_renderer = renderers.HTMLRenderer(
show_all=args.show_all,
timeline=args.timeline,
html_config = compute_render_options(
args, renderer_class=HTMLRenderer, unicode_support=True, color_support=True
)

text_config = compute_render_options(
args, renderer_class=HTMLRenderer, unicode_support=True, color_support=True
)

html_renderer = renderers.HTMLRenderer(show_all=args.show_all, timeline=args.timeline)
html_renderer.preprocessors.append(strip_ipython_frames_processor)
html_str = _active_profiler.output(html_renderer)
as_iframe = IFrame(
Expand All @@ -188,10 +259,7 @@ def pyinstrument(self, line, cell=None):
extras=['style="resize: vertical"', f'srcdoc="{html.escape(html_str)}"'],
)

text_renderer = renderers.ConsoleRenderer(
timeline=args.timeline,
show_all=args.show_all,
)
text_renderer = renderers.ConsoleRenderer(**text_config)
text_renderer.processors.append(strip_ipython_frames_processor)

as_text = _active_profiler.output(text_renderer)
Expand Down
Loading