Skip to content

Commit

Permalink
Merge pull request #350 from Carreau/share-magic
Browse files Browse the repository at this point in the history
Share some options between the CLI and magic.
  • Loading branch information
joerick authored Jan 23, 2025
2 parents 1ca0962 + 266388e commit 6eecceb
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 15 deletions.
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

0 comments on commit 6eecceb

Please sign in to comment.