Skip to content

Commit e2e67c5

Browse files
committed
Share some options between the CLI and magic.
This refactor compute_render_options, to be shared between CLI and magic. Add a few of the CLI options to be available in the magic. The only thing I don't really like is that we redefine the help text options.
1 parent 1ca0962 commit e2e67c5

File tree

2 files changed

+117
-13
lines changed

2 files changed

+117
-13
lines changed

pyinstrument/__main__.py

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import shutil
1010
import sys
1111
import time
12-
from typing import Any, List, TextIO, cast
12+
from typing import Any, List, Optional, TextIO, Tuple, cast
1313

1414
import pyinstrument
1515
from pyinstrument import Profiler, renderers
@@ -412,9 +412,41 @@ def store_and_consume_remaining(
412412
def compute_render_options(
413413
options: CommandLineOptions, renderer_class: type[renderers.Renderer], output_file: TextIO
414414
) -> dict[str, Any]:
415+
"""
416+
Given a list of CommandLineOoptionsCompute the rendering options compute the
417+
rendering options for the given renderer.
418+
419+
Raises if there is an error parsing the options.
420+
421+
"""
422+
423+
unicode_support: bool = file_supports_unicode(output_file)
424+
color_support: bool = file_supports_color(output_file)
425+
426+
error, render_options = _compute_render_options(
427+
options, renderer_class, unicode_support, color_support
428+
)
429+
if error is not None:
430+
raise OptionsParseError(error)
431+
assert render_options is not None
432+
return render_options
433+
434+
435+
def _compute_render_options(
436+
options: CommandLineOptions,
437+
renderer_class: type[renderers.Renderer],
438+
unicode_support: bool,
439+
color_support: bool,
440+
) -> Tuple[Optional[str], Optional[dict[str, Any]]]:
441+
"""
442+
Similar as compute_render_options, but return a tuple (error message, data)
443+
if there is an error; this will let us reuse _compute_render_options in magics.
444+
445+
output_file, has been replaced by unicode_support:bool, color_support:bool
446+
"""
415447
# parse show/hide options
416448
if options.hide_fnmatch is not None and options.hide_regex is not None:
417-
raise OptionsParseError("You can‘t specify both --hide and --hide-regex")
449+
return ("You can‘t specify both --hide and --hide-regex", None)
418450

419451
hide_regex: str | None
420452
show_regex: str | None
@@ -429,7 +461,7 @@ def compute_render_options(
429461
options.show_all,
430462
]
431463
if show_options_used.count(True) > 1:
432-
raise OptionsParseError("You can only specify one of --show, --show-regex and --show-all")
464+
return ("You can only specify one of --show, --show-regex and --show-all", None)
433465

434466
if options.show_fnmatch is not None:
435467
show_regex = fnmatch.translate(options.show_fnmatch)
@@ -449,8 +481,8 @@ def compute_render_options(
449481
if issubclass(renderer_class, renderers.ConsoleRenderer):
450482
unicode_override = options.unicode is not None
451483
color_override = options.color is not None
452-
unicode: Any = options.unicode if unicode_override else file_supports_unicode(output_file)
453-
color: Any = options.color if color_override else file_supports_color(output_file)
484+
unicode: Any = options.unicode if unicode_override else unicode_support
485+
color: Any = options.color if color_override else color_support
454486

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

@@ -478,7 +510,7 @@ def compute_render_options(
478510

479511
keypath.set_value_at_keypath(render_options, key, parsed_value)
480512

481-
return render_options
513+
return None, render_options
482514

483515

484516
class OptionsParseError(Exception):

pyinstrument/magic/magic.py

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414
from IPython.display import IFrame, display
1515

1616
from pyinstrument import Profiler, renderers
17+
from pyinstrument.__main__ import _compute_render_options
1718
from pyinstrument.frame import Frame
1819
from pyinstrument.frame_ops import delete_frame_from_tree
1920
from pyinstrument.processors import ProcessorOptions
21+
from pyinstrument.renderers.console import ConsoleRenderer
22+
from pyinstrument.renderers.html import HTMLRenderer
2023

2124
_active_profiler = None
2225

@@ -76,6 +79,43 @@ def recreate_transformer(self, target_description: str):
7679
)
7780

7881
@magic_arguments()
82+
@argument(
83+
"-p",
84+
"--render-option",
85+
dest="render_options",
86+
action="append",
87+
metavar="RENDER_OPTION",
88+
type=str,
89+
help=(
90+
"options to pass to the renderer, in the format 'flag_name' or 'option_name=option_value'. "
91+
"For example, to set the option 'time', pass '-p time=percent_of_total'. To pass multiple "
92+
"options, use the -p option multiple times. You can set processor options using dot-syntax, "
93+
"like '-p processor_options.filter_threshold=0'. option_value is parsed as a JSON value or "
94+
"a string."
95+
),
96+
)
97+
@argument(
98+
"--show-regex",
99+
dest="show_regex",
100+
action="store",
101+
metavar="REGEX",
102+
help=(
103+
"regex matching the file paths whose frames to always show. "
104+
"Useful if --show doesn't give enough control."
105+
),
106+
)
107+
@argument(
108+
"--show",
109+
dest="show_fnmatch",
110+
action="store",
111+
metavar="EXPR",
112+
help=(
113+
"glob-style pattern matching the file paths whose frames to "
114+
"show, regardless of --hide or --hide-regex. For example, use "
115+
"--show '*/<library>/*' to show frames within a library that "
116+
"would otherwise be hidden."
117+
),
118+
)
79119
@argument(
80120
"--interval",
81121
type=float,
@@ -110,6 +150,26 @@ def recreate_transformer(self, target_description: str):
110150
nargs="*",
111151
help="When used as a line magic, the code to profile",
112152
)
153+
@argument(
154+
"--hide",
155+
dest="hide_fnmatch",
156+
action="store",
157+
metavar="EXPR",
158+
help=(
159+
"glob-style pattern matching the file paths whose frames to hide. Defaults to "
160+
"hiding non-application code"
161+
),
162+
)
163+
@argument(
164+
"--hide-regex",
165+
dest="hide_regex",
166+
action="store",
167+
metavar="REGEX",
168+
help=(
169+
"regex matching the file paths whose frames to hide. Useful if --hide doesn't give "
170+
"enough control."
171+
),
172+
)
113173
@no_var_expand
114174
@line_cell_magic
115175
def pyinstrument(self, line, cell=None):
@@ -126,6 +186,12 @@ def pyinstrument(self, line, cell=None):
126186
"""
127187
global _active_profiler
128188
args = parse_argstring(self.pyinstrument, line)
189+
190+
# 2024, always override this for now in IPython,
191+
# we can make an option later if necessary
192+
args.unicode = True
193+
args.color = True
194+
129195
ip = get_ipython()
130196

131197
if not ip:
@@ -175,10 +241,19 @@ def pyinstrument(self, line, cell=None):
175241
)
176242
return
177243

178-
html_renderer = renderers.HTMLRenderer(
179-
show_all=args.show_all,
180-
timeline=args.timeline,
244+
html_error, html_config = _compute_render_options(
245+
args, renderer_class=HTMLRenderer, unicode_support=True, color_support=True
181246
)
247+
if html_error is not None:
248+
raise ValueError(html_error)
249+
250+
text_error, text_config = _compute_render_options(
251+
args, renderer_class=HTMLRenderer, unicode_support=True, color_support=True
252+
)
253+
if text_error is not None:
254+
raise ValueError(text_error)
255+
256+
html_renderer = renderers.HTMLRenderer(show_all=args.show_all, timeline=args.timeline)
182257
html_renderer.preprocessors.append(strip_ipython_frames_processor)
183258
html_str = _active_profiler.output(html_renderer)
184259
as_iframe = IFrame(
@@ -188,10 +263,7 @@ def pyinstrument(self, line, cell=None):
188263
extras=['style="resize: vertical"', f'srcdoc="{html.escape(html_str)}"'],
189264
)
190265

191-
text_renderer = renderers.ConsoleRenderer(
192-
timeline=args.timeline,
193-
show_all=args.show_all,
194-
)
266+
text_renderer = renderers.ConsoleRenderer(**text_config)
195267
text_renderer.processors.append(strip_ipython_frames_processor)
196268

197269
as_text = _active_profiler.output(text_renderer)

0 commit comments

Comments
 (0)