-
Notifications
You must be signed in to change notification settings - Fork 140
Make IPython optional #125
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
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
4a55255
IPython is now optional
Nodd cb13a77
Manage imports in __init__.py
Nodd 6f92dca
Forgot local import
Nodd 53c5c59
Use load_ipython_extension from line_profiler to get a useful message
Nodd d547295
Typo
Nodd dd4c425
Rework IPython plugin layout
Nodd adfc2cb
Update CHANGELOG
Nodd e5f988e
Merge branch 'main' into ipython_optional
Erotemic 5de7781
Load IPython only when needed
Nodd 56e5a74
Add basic ipython test
Nodd 583cf27
Fix deprecation warning
Nodd 007efb7
Check output
Nodd File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| from io import StringIO | ||
|
|
||
| from IPython.core.magic import Magics, magics_class, line_magic | ||
| from IPython.core.page import page | ||
| from IPython.utils.ipstruct import Struct | ||
| from IPython.core.error import UsageError | ||
|
|
||
| from .line_profiler import LineProfiler | ||
|
|
||
|
|
||
| @magics_class | ||
| class LineProfilerMagics(Magics): | ||
| @line_magic | ||
| def lprun(self, parameter_s=""): | ||
| """ Execute a statement under the line-by-line profiler from the | ||
| line_profiler module. | ||
|
|
||
| Usage: | ||
| %lprun -f func1 -f func2 <statement> | ||
|
|
||
| The given statement (which doesn't require quote marks) is run via the | ||
| LineProfiler. Profiling is enabled for the functions specified by the -f | ||
| options. The statistics will be shown side-by-side with the code through the | ||
| pager once the statement has completed. | ||
|
|
||
| Options: | ||
|
|
||
| -f <function>: LineProfiler only profiles functions and methods it is told | ||
| to profile. This option tells the profiler about these functions. Multiple | ||
| -f options may be used. The argument may be any expression that gives | ||
| a Python function or method object. However, one must be careful to avoid | ||
| spaces that may confuse the option parser. | ||
|
|
||
| -m <module>: Get all the functions/methods in a module | ||
|
|
||
| One or more -f or -m options are required to get any useful results. | ||
|
|
||
| -D <filename>: dump the raw statistics out to a pickle file on disk. The | ||
| usual extension for this is ".lprof". These statistics may be viewed later | ||
| by running line_profiler.py as a script. | ||
|
|
||
| -T <filename>: dump the text-formatted statistics with the code side-by-side | ||
| out to a text file. | ||
|
|
||
| -r: return the LineProfiler object after it has completed profiling. | ||
|
|
||
| -s: strip out all entries from the print-out that have zeros. | ||
|
|
||
| -u: specify time unit for the print-out in seconds. | ||
| """ | ||
|
|
||
| # Escape quote markers. | ||
| opts_def = Struct(D=[""], T=[""], f=[], m=[], u=None) | ||
| parameter_s = parameter_s.replace('"', r"\"").replace("'", r"\'") | ||
| opts, arg_str = self.parse_options(parameter_s, "rsf:m:D:T:u:", list_all=True) | ||
| opts.merge(opts_def) | ||
|
|
||
| global_ns = self.shell.user_global_ns | ||
| local_ns = self.shell.user_ns | ||
|
|
||
| # Get the requested functions. | ||
| funcs = [] | ||
| for name in opts.f: | ||
| try: | ||
| funcs.append(eval(name, global_ns, local_ns)) | ||
| except Exception as e: | ||
| raise UsageError( | ||
| f"Could not find module {name}.\n{e.__class__.__name__}: {e}" | ||
| ) | ||
|
|
||
| profile = LineProfiler(*funcs) | ||
|
|
||
| # Get the modules, too | ||
| for modname in opts.m: | ||
| try: | ||
| mod = __import__(modname, fromlist=[""]) | ||
| profile.add_module(mod) | ||
| except Exception as e: | ||
| raise UsageError( | ||
| f"Could not find module {modname}.\n{e.__class__.__name__}: {e}" | ||
| ) | ||
|
|
||
| if opts.u is not None: | ||
| try: | ||
| output_unit = float(opts.u[0]) | ||
| except Exception: | ||
| raise TypeError("Timer unit setting must be a float.") | ||
| else: | ||
| output_unit = None | ||
|
|
||
| # Add the profiler to the builtins for @profile. | ||
| import builtins | ||
|
|
||
| if "profile" in builtins.__dict__: | ||
| had_profile = True | ||
| old_profile = builtins.__dict__["profile"] | ||
| else: | ||
| had_profile = False | ||
| old_profile = None | ||
| builtins.__dict__["profile"] = profile | ||
|
|
||
| try: | ||
| try: | ||
| profile.runctx(arg_str, global_ns, local_ns) | ||
| message = "" | ||
| except SystemExit: | ||
| message = """*** SystemExit exception caught in code being profiled.""" | ||
| except KeyboardInterrupt: | ||
| message = ( | ||
| "*** KeyboardInterrupt exception caught in code being " "profiled." | ||
| ) | ||
| finally: | ||
| if had_profile: | ||
| builtins.__dict__["profile"] = old_profile | ||
|
|
||
| # Trap text output. | ||
| stdout_trap = StringIO() | ||
| profile.print_stats( | ||
| stdout_trap, output_unit=output_unit, stripzeros="s" in opts | ||
| ) | ||
| output = stdout_trap.getvalue() | ||
| output = output.rstrip() | ||
|
|
||
| page(output) | ||
| print(message, end="") | ||
|
|
||
| dump_file = opts.D[0] | ||
| if dump_file: | ||
| profile.dump_stats(dump_file) | ||
| print(f"\n*** Profile stats pickled to file {dump_file!r}. {message}") | ||
|
|
||
| text_file = opts.T[0] | ||
| if text_file: | ||
| pfile = open(text_file, "w") | ||
| pfile.write(output) | ||
| pfile.close() | ||
| print(f"\n*** Profile printout saved to text file {text_file!r}. {message}") | ||
|
|
||
| return_value = None | ||
| if "r" in opts: | ||
| return_value = profile | ||
|
|
||
| return return_value |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,14 +6,8 @@ | |
| import tempfile | ||
| import os | ||
| import sys | ||
| from io import StringIO | ||
| from argparse import ArgumentError, ArgumentParser | ||
|
|
||
| from IPython.core.magic import (Magics, magics_class, line_magic) | ||
| from IPython.core.page import page | ||
| from IPython.utils.ipstruct import Struct | ||
| from IPython.core.error import UsageError | ||
|
|
||
| try: | ||
| from ._line_profiler import LineProfiler as CLineProfiler | ||
| except ImportError as ex: | ||
|
|
@@ -25,6 +19,13 @@ | |
| __version__ = '3.5.0' | ||
|
|
||
|
|
||
| def load_ipython_extension(ip): | ||
| """ API for IPython to recognize this module as an IPython extension. | ||
| """ | ||
| from .ipython_extension import LineProfilerMagics | ||
| ip.register_magics(LineProfilerMagics) | ||
|
|
||
|
|
||
| def is_coroutine(f): | ||
| return False | ||
|
|
||
|
|
@@ -155,6 +156,8 @@ def add_module(self, mod): | |
| return nfuncsadded | ||
|
|
||
|
|
||
| # This could be in the ipython_extension submodule, | ||
| # but it doesn't depend on the IPython module so it's easier to just let it stay here. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, this is just handling the case where we are printing something in a notebook. It doesn't have to do with the plugin directly, so it's fine to stay here. |
||
| def is_ipython_kernel_cell(filename): | ||
| """ Return True if a filename corresponds to a Jupyter Notebook cell | ||
| """ | ||
|
|
@@ -248,142 +251,6 @@ def show_text(stats, unit, output_unit=None, stream=None, stripzeros=False): | |
| output_unit=output_unit, stream=stream, | ||
| stripzeros=stripzeros) | ||
|
|
||
|
|
||
| @magics_class | ||
| class LineProfilerMagics(Magics): | ||
|
|
||
| @line_magic | ||
| def lprun(self, parameter_s=''): | ||
| """ Execute a statement under the line-by-line profiler from the | ||
| line_profiler module. | ||
|
|
||
| Usage: | ||
| %lprun -f func1 -f func2 <statement> | ||
|
|
||
| The given statement (which doesn't require quote marks) is run via the | ||
| LineProfiler. Profiling is enabled for the functions specified by the -f | ||
| options. The statistics will be shown side-by-side with the code through the | ||
| pager once the statement has completed. | ||
|
|
||
| Options: | ||
|
|
||
| -f <function>: LineProfiler only profiles functions and methods it is told | ||
| to profile. This option tells the profiler about these functions. Multiple | ||
| -f options may be used. The argument may be any expression that gives | ||
| a Python function or method object. However, one must be careful to avoid | ||
| spaces that may confuse the option parser. | ||
|
|
||
| -m <module>: Get all the functions/methods in a module | ||
|
|
||
| One or more -f or -m options are required to get any useful results. | ||
|
|
||
| -D <filename>: dump the raw statistics out to a pickle file on disk. The | ||
| usual extension for this is ".lprof". These statistics may be viewed later | ||
| by running line_profiler.py as a script. | ||
|
|
||
| -T <filename>: dump the text-formatted statistics with the code side-by-side | ||
| out to a text file. | ||
|
|
||
| -r: return the LineProfiler object after it has completed profiling. | ||
|
|
||
| -s: strip out all entries from the print-out that have zeros. | ||
|
|
||
| -u: specify time unit for the print-out in seconds. | ||
| """ | ||
|
|
||
| # Escape quote markers. | ||
| opts_def = Struct(D=[''], T=[''], f=[], m=[], u=None) | ||
| parameter_s = parameter_s.replace('"', r'\"').replace("'", r"\'") | ||
| opts, arg_str = self.parse_options(parameter_s, 'rsf:m:D:T:u:', list_all=True) | ||
| opts.merge(opts_def) | ||
|
|
||
| global_ns = self.shell.user_global_ns | ||
| local_ns = self.shell.user_ns | ||
|
|
||
| # Get the requested functions. | ||
| funcs = [] | ||
| for name in opts.f: | ||
| try: | ||
| funcs.append(eval(name, global_ns, local_ns)) | ||
| except Exception as e: | ||
| raise UsageError(f'Could not find module {name}.\n{e.__class__.__name__}: {e}') | ||
|
|
||
| profile = LineProfiler(*funcs) | ||
|
|
||
| # Get the modules, too | ||
| for modname in opts.m: | ||
| try: | ||
| mod = __import__(modname, fromlist=['']) | ||
| profile.add_module(mod) | ||
| except Exception as e: | ||
| raise UsageError(f'Could not find module {modname}.\n{e.__class__.__name__}: {e}') | ||
|
|
||
| if opts.u is not None: | ||
| try: | ||
| output_unit = float(opts.u[0]) | ||
| except Exception: | ||
| raise TypeError('Timer unit setting must be a float.') | ||
| else: | ||
| output_unit = None | ||
|
|
||
| # Add the profiler to the builtins for @profile. | ||
| import builtins | ||
|
|
||
| if 'profile' in builtins.__dict__: | ||
| had_profile = True | ||
| old_profile = builtins.__dict__['profile'] | ||
| else: | ||
| had_profile = False | ||
| old_profile = None | ||
| builtins.__dict__['profile'] = profile | ||
|
|
||
| try: | ||
| try: | ||
| profile.runctx(arg_str, global_ns, local_ns) | ||
| message = '' | ||
| except SystemExit: | ||
| message = """*** SystemExit exception caught in code being profiled.""" | ||
| except KeyboardInterrupt: | ||
| message = ('*** KeyboardInterrupt exception caught in code being ' | ||
| 'profiled.') | ||
| finally: | ||
| if had_profile: | ||
| builtins.__dict__['profile'] = old_profile | ||
|
|
||
| # Trap text output. | ||
| stdout_trap = StringIO() | ||
| profile.print_stats(stdout_trap, output_unit=output_unit, stripzeros='s' in opts) | ||
| output = stdout_trap.getvalue() | ||
| output = output.rstrip() | ||
|
|
||
| page(output) | ||
| print(message, end='') | ||
|
|
||
| dump_file = opts.D[0] | ||
| if dump_file: | ||
| profile.dump_stats(dump_file) | ||
| print(f'\n*** Profile stats pickled to file {dump_file!r}. {message}') | ||
|
|
||
| text_file = opts.T[0] | ||
| if text_file: | ||
| pfile = open(text_file, 'w') | ||
| pfile.write(output) | ||
| pfile.close() | ||
| print(f'\n*** Profile printout saved to text file {text_file!r}. {message}') | ||
|
|
||
| return_value = None | ||
| if 'r' in opts: | ||
| return_value = profile | ||
|
|
||
| return return_value | ||
|
|
||
|
|
||
| def load_ipython_extension(ip): | ||
| """ API for IPython to recognize this module as an IPython extension. | ||
| """ | ||
| ip.register_magics(LineProfilerMagics) | ||
|
|
||
|
|
||
| def load_stats(filename): | ||
| """ Utility function to load a pickled LineStats object from a given | ||
| filename. | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| -r requirements/runtime.txt | ||
| -r requirements/ipython.txt | ||
| -r requirements/build.txt | ||
| -r requirements/tests.txt |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| IPython >=0.13 ; python_version >= '3.7' | ||
| IPython >=0.13, <7.17.0 ; python_version <= '3.6' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +0,0 @@ | ||
| IPython >=0.13 ; python_version >= '3.7' | ||
| IPython >=0.13, <7.17.0 ; python_version <= '3.6' | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can either be imported from this function, or it can be added with the other imports with a sys.modules guard for IPython.
In the second case there would be a circular import but it shouldn't be a problem.