From 22ad14f210b3259fccb9edfc3c0b49722a402f29 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 29 Nov 2018 12:45:07 +0100 Subject: [PATCH 001/458] Don't expand user variables in execution magics Adds magic.no_var_expand decorator to mark a magic as opting out of variable expansion Most useful for magics that take Python code on the line, e.g. %timeit, etc. --- IPython/core/interactiveshell.py | 18 +++++++++++++----- IPython/core/magic.py | 19 +++++++++++++++++++ IPython/core/magics/execution.py | 28 +++++++++++++++++++++++++--- IPython/core/tests/test_magic.py | 13 ++++++++++--- 4 files changed, 67 insertions(+), 11 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index ac46abe3a5a..696bab2954b 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2273,10 +2273,14 @@ def run_line_magic(self, magic_name, line, _stack_depth=1): # Note: this is the distance in the stack to the user's frame. # This will need to be updated if the internal calling logic gets # refactored, or else we'll be expanding the wrong variables. - + # Determine stack_depth depending on where run_line_magic() has been called stack_depth = _stack_depth - magic_arg_s = self.var_expand(line, stack_depth) + if getattr(fn, magic.MAGIC_NO_VAR_EXPAND_ATTR, False): + # magic has opted out of var_expand + magic_arg_s = line + else: + magic_arg_s = self.var_expand(line, stack_depth) # Put magic args in a list so we can call with f(*a) syntax args = [magic_arg_s] kwargs = {} @@ -2284,12 +2288,12 @@ def run_line_magic(self, magic_name, line, _stack_depth=1): if getattr(fn, "needs_local_scope", False): kwargs['local_ns'] = sys._getframe(stack_depth).f_locals with self.builtin_trap: - result = fn(*args,**kwargs) + result = fn(*args, **kwargs) return result def run_cell_magic(self, magic_name, line, cell): """Execute the given cell magic. - + Parameters ---------- magic_name : str @@ -2318,7 +2322,11 @@ def run_cell_magic(self, magic_name, line, cell): # This will need to be updated if the internal calling logic gets # refactored, or else we'll be expanding the wrong variables. stack_depth = 2 - magic_arg_s = self.var_expand(line, stack_depth) + if getattr(fn, magic.MAGIC_NO_VAR_EXPAND_ATTR, False): + # magic has opted out of var_expand + magic_arg_s = line + else: + magic_arg_s = self.var_expand(line, stack_depth) with self.builtin_trap: result = fn(magic_arg_s, cell) return result diff --git a/IPython/core/magic.py b/IPython/core/magic.py index c387d4fb7d5..02af2f8d7b4 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -265,6 +265,25 @@ def mark(func, *a, **kw): return magic_deco +MAGIC_NO_VAR_EXPAND_ATTR = '_ipython_magic_no_var_expand' + + +def no_var_expand(magic_func): + """Mark a magic function as not needing variable expansion + + By default, IPython interprets `{a}` or `$a` in the line passed to magics + as variables that should be interpolated from the interactive namespace + before passing the line to the magic function. + This is not always desirable, e.g. when the magic executes Python code + (%timeit, %time, etc.). + Decorate magics with `@no_var_expand` to opt-out of variable expansion. + + .. versionadded:: 7.2 + """ + setattr(magic_func, MAGIC_NO_VAR_EXPAND_ATTR, True) + return magic_func + + # Create the actual decorators for public use # These three are used to decorate methods in class definitions diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index b651a4248a6..130c5f9a1fd 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -36,7 +36,8 @@ from IPython.core.error import UsageError from IPython.core.macro import Macro from IPython.core.magic import (Magics, magics_class, line_magic, cell_magic, - line_cell_magic, on_off, needs_local_scope) + line_cell_magic, on_off, needs_local_scope, + no_var_expand) from IPython.testing.skipdoctest import skip_doctest from IPython.utils.contexts import preserve_keys from IPython.utils.capture import capture_output @@ -184,6 +185,7 @@ def profile_missing_notice(self, *args, **kwargs): python-profiler package from non-free.""") @skip_doctest + @no_var_expand @line_cell_magic def prun(self, parameter_s='', cell=None): @@ -293,6 +295,11 @@ def prun(self, parameter_s='', cell=None): You can read the complete documentation for the profile module with:: In [1]: import profile; profile.help() + + .. versionchanged:: 7.2 + User variables are no longer expanded, + the magic line is always left unmodified. + """ opts, arg_str = self.parse_options(parameter_s, 'D:l:rs:T:q', list_all=True, posix=False) @@ -422,6 +429,7 @@ def pdb(self, parameter_s=''): You can omit this in cell magic mode. """ ) + @no_var_expand @line_cell_magic def debug(self, line='', cell=None): """Activate the interactive debugger. @@ -442,6 +450,11 @@ def debug(self, line='', cell=None): If you want IPython to automatically do this on every exception, see the %pdb magic for more details. + + .. versionchanged:: 7.2 + When running code, user variables are no longer expanded, + the magic line is always left unmodified. + """ args = magic_arguments.parse_argstring(self.debug, line) @@ -972,6 +985,7 @@ def _run_with_timing(run, nruns): print("Wall time: %10.2f s." % (twall1 - twall0)) @skip_doctest + @no_var_expand @line_cell_magic @needs_local_scope def timeit(self, line='', cell=None, local_ns=None): @@ -1017,6 +1031,9 @@ def timeit(self, line='', cell=None, local_ns=None): -o: return a TimeitResult that can be stored in a variable to inspect the result in more details. + .. versionchanged:: 7.2 + User variables are no longer expanded, + the magic line is always left unmodified. Examples -------- @@ -1161,6 +1178,7 @@ def timeit(self, line='', cell=None, local_ns=None): return timeit_result @skip_doctest + @no_var_expand @needs_local_scope @line_cell_magic def time(self,line='', cell=None, local_ns=None): @@ -1175,12 +1193,16 @@ def time(self,line='', cell=None, local_ns=None): - In line mode you can time a single-line statement (though multiple ones can be chained with using semicolons). - - In cell mode, you can time the cell body (a directly + - In cell mode, you can time the cell body (a directly following statement raises an error). - This function provides very basic timing functionality. Use the timeit + This function provides very basic timing functionality. Use the timeit magic for more control over the measurement. + .. versionchanged:: 7.2 + User variables are no longer expanded, + the magic line is always left unmodified. + Examples -------- :: diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index 6973db0eb51..310751e278b 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -1087,7 +1087,8 @@ def test_logging_magic_quiet_from_config(): lm.logstart(os.path.join(td, "quiet_from_config.log")) finally: _ip.logger.logstop() - + + def test_logging_magic_not_quiet(): _ip.config.LoggingMagics.quiet = False lm = logging.LoggingMagics(shell=_ip) @@ -1098,9 +1099,15 @@ def test_logging_magic_not_quiet(): finally: _ip.logger.logstop() -## + +def test_time_no_var_expand(): + _ip.user_ns['a'] = 5 + _ip.user_ns['b'] = [] + _ip.magic('time b.append("{a}")') + assert _ip.user_ns['b'] == ['{a}'] + + # this is slow, put at the end for local testing. -## def test_timeit_arguments(): "Test valid timeit arguments, should not cause SyntaxError (GH #1269)" if sys.version_info < (3,7): From bece20c534e2e75d0c68bb98100bd2c65ff9f0f3 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 29 Nov 2018 13:14:34 +0100 Subject: [PATCH 002/458] make that 7.3 --- IPython/core/magic.py | 2 +- IPython/core/magics/execution.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/IPython/core/magic.py b/IPython/core/magic.py index 02af2f8d7b4..2b41617bed7 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -278,7 +278,7 @@ def no_var_expand(magic_func): (%timeit, %time, etc.). Decorate magics with `@no_var_expand` to opt-out of variable expansion. - .. versionadded:: 7.2 + .. versionadded:: 7.3 """ setattr(magic_func, MAGIC_NO_VAR_EXPAND_ATTR, True) return magic_func diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index 130c5f9a1fd..aa664a37620 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -296,7 +296,7 @@ def prun(self, parameter_s='', cell=None): In [1]: import profile; profile.help() - .. versionchanged:: 7.2 + .. versionchanged:: 7.3 User variables are no longer expanded, the magic line is always left unmodified. @@ -451,7 +451,7 @@ def debug(self, line='', cell=None): If you want IPython to automatically do this on every exception, see the %pdb magic for more details. - .. versionchanged:: 7.2 + .. versionchanged:: 7.3 When running code, user variables are no longer expanded, the magic line is always left unmodified. @@ -1031,7 +1031,7 @@ def timeit(self, line='', cell=None, local_ns=None): -o: return a TimeitResult that can be stored in a variable to inspect the result in more details. - .. versionchanged:: 7.2 + .. versionchanged:: 7.3 User variables are no longer expanded, the magic line is always left unmodified. @@ -1199,7 +1199,7 @@ def time(self,line='', cell=None, local_ns=None): This function provides very basic timing functionality. Use the timeit magic for more control over the measurement. - .. versionchanged:: 7.2 + .. versionchanged:: 7.3 User variables are no longer expanded, the magic line is always left unmodified. From 885b848b06a4e4deefde3e2c479afb4987da9da0 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 29 Nov 2018 17:14:23 -0800 Subject: [PATCH 003/458] back to development --- IPython/core/release.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/core/release.py b/IPython/core/release.py index 548e69843de..c81c1e49ca5 100644 --- a/IPython/core/release.py +++ b/IPython/core/release.py @@ -20,11 +20,11 @@ # release. 'dev' as a _version_extra string means this is a development # version _version_major = 7 -_version_minor = 2 +_version_minor = 3 _version_patch = 0 _version_extra = '.dev' # _version_extra = 'b1' -_version_extra = '' # Uncomment this for full releases +# _version_extra = '' # Uncomment this for full releases # Construct full version string from these. _ver = [_version_major, _version_minor, _version_patch] From 4d5e7d7c4121f812bdfef9e6a263d9438fb6433f Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 29 Nov 2018 17:17:08 -0800 Subject: [PATCH 004/458] add info to sign releases --- docs/source/coredev/index.rst | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/source/coredev/index.rst b/docs/source/coredev/index.rst index 9d545ebffcf..16f6b60d5ed 100644 --- a/docs/source/coredev/index.rst +++ b/docs/source/coredev/index.rst @@ -215,20 +215,26 @@ We encourage creating a test build of the docs as well. Commit the changes to release.py:: - git commit -am "release $VERSION" + git commit -am "release $VERSION" -S git push origin $BRANCH +(omit the ``-S`` if you are no signing the package) + Create and push the tag:: - git tag -am "release $VERSION" "$VERSION" + git tag -am "release $VERSION" "$VERSION" -S git push origin $VERSION +(omit the ``-S`` if you are no signing the package) + Update release.py back to ``x.y-dev`` or ``x.y-maint``, and re-add the ``development`` entry in ``docs/source/whatsnew/index.rst`` and push:: - git commit -am "back to development" + git commit -am "back to development" -S git push origin $BRANCH +(omit the ``-S`` if you are no signing the package) + Now checkout the tag we just made:: git checkout $VERSION From 34c97ed57304d348084f200bb1ab322a3ac87ee2 Mon Sep 17 00:00:00 2001 From: Laurent Gautier Date: Sat, 1 Dec 2018 12:01:57 -0500 Subject: [PATCH 005/458] Naive fix for deprecation warning. `DeprecationWarning: Importing from numpy.testing.decorators is deprecated, import from numpy.testing instead.` --- IPython/external/decorators/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/external/decorators/__init__.py b/IPython/external/decorators/__init__.py index dd8f52b711a..5b2d80b1f42 100644 --- a/IPython/external/decorators/__init__.py +++ b/IPython/external/decorators/__init__.py @@ -1,5 +1,5 @@ try: - from numpy.testing.decorators import * + from numpy.testing import * from numpy.testing.noseclasses import KnownFailure except ImportError: from ._decorators import * From b0feace4270c144e689d18ee4bc926bc8e2e85a6 Mon Sep 17 00:00:00 2001 From: Laurent Gautier Date: Sat, 1 Dec 2018 12:18:11 -0500 Subject: [PATCH 006/458] Update use of `knownfailureif` --- IPython/testing/iptest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/testing/iptest.py b/IPython/testing/iptest.py index 445e82657f0..5148878b195 100644 --- a/IPython/testing/iptest.py +++ b/IPython/testing/iptest.py @@ -37,7 +37,7 @@ from IPython.utils.py3compat import decode from IPython.utils.importstring import import_item from IPython.testing.plugin.ipdoctest import IPythonDoctest -from IPython.external.decorators import KnownFailure, knownfailureif +from IPython.external.decorators import KnownFailure, dec pjoin = path.join @@ -83,7 +83,7 @@ # ------------------------------------------------------------------------------ def monkeypatch_xunit(): try: - knownfailureif(True)(lambda: None)() + dec.knownfailureif(True)(lambda: None)() except Exception as e: KnownFailureTest = type(e) From a252070e14703c83055859d2dab5fbdf78444c50 Mon Sep 17 00:00:00 2001 From: Laurent Gautier Date: Sat, 1 Dec 2018 12:23:37 -0500 Subject: [PATCH 007/458] Update __init__.py --- IPython/external/decorators/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/IPython/external/decorators/__init__.py b/IPython/external/decorators/__init__.py index 5b2d80b1f42..420be60bdac 100644 --- a/IPython/external/decorators/__init__.py +++ b/IPython/external/decorators/__init__.py @@ -1,5 +1,6 @@ try: from numpy.testing import * + from numpy.testing import dec from numpy.testing.noseclasses import KnownFailure except ImportError: from ._decorators import * From 02430e3a50825a70f3388cfd56813bfd572c889e Mon Sep 17 00:00:00 2001 From: Laurent Gautier Date: Sat, 1 Dec 2018 12:27:45 -0500 Subject: [PATCH 008/458] Update decorators.py --- IPython/testing/decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/testing/decorators.py b/IPython/testing/decorators.py index 4f13ce585ee..876611d5849 100644 --- a/IPython/testing/decorators.py +++ b/IPython/testing/decorators.py @@ -333,7 +333,7 @@ def skip_file_no_x11(name): skipif_not_sympy = skip_without('sympy') -skip_known_failure = knownfailureif(True,'This test is known to fail') +skip_known_failure = dec.knownfailureif(True,'This test is known to fail') # A null 'decorator', useful to make more readable code that needs to pick # between different decorators based on OS or other conditions From 26283d82b387caae3c6bba9799ecfdbdea7181f7 Mon Sep 17 00:00:00 2001 From: "Jan S. (Milania1)" Date: Sat, 1 Dec 2018 21:26:18 +0100 Subject: [PATCH 009/458] Minor fixes in display.Audio documentation --- IPython/lib/display.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/IPython/lib/display.py b/IPython/lib/display.py index 490d76fac6d..73039bdb394 100644 --- a/IPython/lib/display.py +++ b/IPython/lib/display.py @@ -33,9 +33,9 @@ class Audio(DisplayObject): * Bytestring containing raw PCM data or * URL pointing to a file on the web. - If the array option is used the waveform will be normalized. + If the array option is used, the waveform will be normalized. - If a filename or url is used the format support will be browser + If a filename or url is used, the format support will be browser dependent. url : unicode A URL to download the data from. @@ -63,7 +63,7 @@ class Audio(DisplayObject): import numpy as np framerate = 44100 t = np.linspace(0,5,framerate*5) - data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t)) + data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t) Audio(data,rate=framerate) # Can also do stereo or more channels From 7af64d9818907b4eee2deb43c23e07cffdcfb621 Mon Sep 17 00:00:00 2001 From: Laurent Gautier Date: Sat, 1 Dec 2018 17:44:28 -0500 Subject: [PATCH 010/458] Update decorators.py --- IPython/testing/decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/testing/decorators.py b/IPython/testing/decorators.py index 4f13ce585ee..876611d5849 100644 --- a/IPython/testing/decorators.py +++ b/IPython/testing/decorators.py @@ -333,7 +333,7 @@ def skip_file_no_x11(name): skipif_not_sympy = skip_without('sympy') -skip_known_failure = knownfailureif(True,'This test is known to fail') +skip_known_failure = dec.knownfailureif(True,'This test is known to fail') # A null 'decorator', useful to make more readable code that needs to pick # between different decorators based on OS or other conditions From c3c692b88b81366c916d820bd5aca6a853ce241e Mon Sep 17 00:00:00 2001 From: Laurent Gautier Date: Sat, 1 Dec 2018 17:54:41 -0500 Subject: [PATCH 011/458] Update test_run.py --- IPython/core/tests/test_run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/tests/test_run.py b/IPython/core/tests/test_run.py index 2afa5ba7c51..847059c96f7 100644 --- a/IPython/core/tests/test_run.py +++ b/IPython/core/tests/test_run.py @@ -538,7 +538,7 @@ def test_run_tb(): nt.assert_in("RuntimeError", out) nt.assert_equal(out.count("---->"), 3) -@dec.knownfailureif(sys.platform == 'win32', "writes to io.stdout aren't captured on Windows") +@dec.dec.knownfailureif(sys.platform == 'win32', "writes to io.stdout aren't captured on Windows") def test_script_tb(): """Test traceback offset in `ipython script.py`""" with TemporaryDirectory() as td: From e43ab8b6066822d972cbeb4941b648c57ab8f557 Mon Sep 17 00:00:00 2001 From: Laurent Gautier Date: Sat, 1 Dec 2018 17:58:47 -0500 Subject: [PATCH 012/458] Update test_completer.py --- IPython/core/tests/test_completer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index 0a5447d8af7..8ded6d60562 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -16,7 +16,7 @@ from traitlets.config.loader import Config from IPython import get_ipython from IPython.core import completer -from IPython.external.decorators import knownfailureif +from IPython.external.decorators.dec import knownfailureif from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory from IPython.utils.generics import complete_object from IPython.testing import decorators as dec From 23fd224f565b32b1e9aa8d75b26e652afd7d64c5 Mon Sep 17 00:00:00 2001 From: Laurent Gautier Date: Sun, 2 Dec 2018 10:40:58 -0500 Subject: [PATCH 013/458] Update test_completer.py --- IPython/core/tests/test_completer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index 8ded6d60562..5876cc4151c 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -16,7 +16,7 @@ from traitlets.config.loader import Config from IPython import get_ipython from IPython.core import completer -from IPython.external.decorators.dec import knownfailureif +from IPython.external import decorators from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory from IPython.utils.generics import complete_object from IPython.testing import decorators as dec @@ -183,7 +183,7 @@ def test_forward_unicode_completion(): nt.assert_equal(matches[0], 'Ⅴ') @nt.nottest # now we have a completion for \jmath -@dec.knownfailureif(sys.platform == 'win32', 'Fails if there is a C:\\j... path') +@decorators.dec.knownfailureif(sys.platform == 'win32', 'Fails if there is a C:\\j... path') def test_no_ascii_back_completion(): ip = get_ipython() with TemporaryWorkingDirectory(): # Avoid any filename completions @@ -234,7 +234,7 @@ def test_has_open_quotes4(): nt.assert_false(completer.has_open_quotes(s)) -@knownfailureif(sys.platform == 'win32', "abspath completions fail on Windows") +@decorators.dec.knownfailureif(sys.platform == 'win32', "abspath completions fail on Windows") def test_abspath_file_completions(): ip = get_ipython() with TemporaryDirectory() as tmpdir: From 8168be557763074214093bd2b51f668a920931df Mon Sep 17 00:00:00 2001 From: Jake VanderPlas Date: Wed, 5 Dec 2018 21:22:11 -0800 Subject: [PATCH 014/458] ENH: add pip and conda magics --- IPython/core/interactiveshell.py | 3 +- IPython/core/magics/__init__.py | 1 + IPython/core/magics/basic.py | 19 ------ IPython/core/magics/packaging.py | 101 +++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 20 deletions(-) create mode 100644 IPython/core/magics/packaging.py diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index ac46abe3a5a..1d7dceb8f43 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2220,7 +2220,8 @@ def init_magics(self): self.register_magics(m.AutoMagics, m.BasicMagics, m.CodeMagics, m.ConfigMagics, m.DisplayMagics, m.ExecutionMagics, m.ExtensionMagics, m.HistoryMagics, m.LoggingMagics, - m.NamespaceMagics, m.OSMagics, m.PylabMagics, m.ScriptMagics, + m.NamespaceMagics, m.OSMagics, m.PackagingMagics, + m.PylabMagics, m.ScriptMagics, ) if sys.version_info >(3,5): self.register_magics(m.AsyncMagics) diff --git a/IPython/core/magics/__init__.py b/IPython/core/magics/__init__.py index 841f4da2869..a6c5f474c15 100644 --- a/IPython/core/magics/__init__.py +++ b/IPython/core/magics/__init__.py @@ -24,6 +24,7 @@ from .logging import LoggingMagics from .namespace import NamespaceMagics from .osm import OSMagics +from .packaging import PackagingMagics from .pylab import PylabMagics from .script import ScriptMagics diff --git a/IPython/core/magics/basic.py b/IPython/core/magics/basic.py index 225c49a9279..c7bd0064d62 100644 --- a/IPython/core/magics/basic.py +++ b/IPython/core/magics/basic.py @@ -379,25 +379,6 @@ def xmode_switch_err(name): except: xmode_switch_err('user') - - - @line_magic - def pip(self, args=''): - """ - Intercept usage of ``pip`` in IPython and direct user to run command outside of IPython. - """ - print(textwrap.dedent(''' - The following command must be run outside of the IPython shell: - - $ pip {args} - - The Python package manager (pip) can only be used from outside of IPython. - Please reissue the `pip` command in a separate terminal or command prompt. - - See the Python documentation for more information on how to install packages: - - https://docs.python.org/3/installing/'''.format(args=args))) - @line_magic def quickref(self, arg): """ Show a quick reference sheet """ diff --git a/IPython/core/magics/packaging.py b/IPython/core/magics/packaging.py new file mode 100644 index 00000000000..fffb9d7278b --- /dev/null +++ b/IPython/core/magics/packaging.py @@ -0,0 +1,101 @@ +"""Implementation of packaging-related magic functions. +""" +#----------------------------------------------------------------------------- +# Copyright (c) 2018 The IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +import os +import re +import shlex +import sys +from subprocess import Popen, PIPE + +from IPython.core.magic import Magics, magics_class, line_magic + + +def _is_conda_environment(): + """Return True if the current Python executable is in a conda env""" + # TODO: does this need to change on windows? + conda_history = os.path.join(sys.prefix, 'conda-meta', 'history') + return os.path.exists(conda_history) + + +def _get_conda_executable(): + """Find the path to the conda executable""" + # Check if there is a conda executable in the same directory as the Python executable. + # This is the case within conda's root environment. + conda = os.path.join(os.path.dirname(sys.executable), 'conda') + if os.path.isfile(conda): + return conda + + # Otherwise, attempt to extract the executable from conda history. + # This applies in any conda environment. + R = re.compile(r"^#\s*cmd:\s*(?P.*conda)\s[create|install]") + for line in open(os.path.join(sys.prefix, 'conda-meta', 'history')): + match = R.match(line) + if match: + return match.groupdict()['command'] + + # Fallback: assume conda is available on the system path. + return "conda" + + +CONDA_COMMANDS_REQUIRING_PREFIX = { + 'install', 'list', 'remove', 'uninstall', 'update', 'upgrade', +} +CONDA_COMMANDS_REQUIRING_YES = { + 'install', 'remove', 'uninstall', 'update', 'upgrade', +} +CONDA_ENV_FLAGS = {'-p', '--prefix', '-n', '--name'} +CONDA_YES_FLAGS = {'-y', '--y'} + + +@magics_class +class PackagingMagics(Magics): + """Magics related to packaging & installation""" + + @line_magic + def pip(self, line): + """Run the pip package manager within the current kernel. + + Usage: + %pip install [pkgs] + """ + self.shell.system(' '.join([sys.executable, '-m', 'pip', line])) + + @line_magic + def conda(self, line): + """Run the conda package manager within the current kernel. + + Usage: + %conda install [pkgs] + """ + if not _is_conda_environment(): + raise ValueError("The python kernel does not appear to be a conda environment. " + "Please use ``%pip install`` instead.") + + conda = _get_conda_executable() + args = shlex.split(line) + command = args[0] + args = args[1:] + extra_args = [] + + # When the subprocess does not allow us to respond "yes" during the installation, + # we need to insert --yes in the argument list for some commands + stdin_disabled = getattr(self.shell, 'kernel', None) is not None + needs_yes = command in CONDA_COMMANDS_REQUIRING_YES + has_yes = set(args).intersection(CONDA_YES_FLAGS) + if stdin_disabled and needs_yes and not has_yes: + extra_args.append("--yes") + + # Add --prefix to point conda installation to the current environment + needs_prefix = command in CONDA_COMMANDS_REQUIRING_PREFIX + has_prefix = set(args).intersection(CONDA_ENV_FLAGS) + if needs_prefix and not has_prefix: + extra_args.extend(["--prefix", sys.prefix]) + + self.shell.system(' '.join([conda, command] + extra_args + args)) \ No newline at end of file From c214f1c49a56e1a737a8b0e56cd3faf4c083b8e7 Mon Sep 17 00:00:00 2001 From: Philipp A Date: Thu, 6 Dec 2018 10:57:25 +0100 Subject: [PATCH 015/458] Fix signature rendering --- IPython/core/oinspect.py | 4 +++- IPython/core/tests/test_oinspect.py | 33 +++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py index f6dc1f65f7e..4005400c156 100644 --- a/IPython/core/oinspect.py +++ b/IPython/core/oinspect.py @@ -1051,7 +1051,9 @@ def _render_signature(obj_signature, obj_name): # add up name, parameters, braces (2), and commas if len(obj_name) + sum(len(r) + 2 for r in result) > 75: # This doesn’t fit behind “Signature: ” in an inspect window. - rendered = '{}(\n{})'.format(obj_name, ''.join(' {},\n'.format(result))) + rendered = '{}(\n{})'.format(obj_name, ''.join( + ' {},\n'.format(r) for r in result) + ) else: rendered = '{}({})'.format(obj_name, ', '.join(result)) diff --git a/IPython/core/tests/test_oinspect.py b/IPython/core/tests/test_oinspect.py index 06d6d5aaa1f..b3e2b21a78a 100644 --- a/IPython/core/tests/test_oinspect.py +++ b/IPython/core/tests/test_oinspect.py @@ -5,7 +5,7 @@ # Distributed under the terms of the Modified BSD License. -from inspect import Signature, Parameter +from inspect import signature, Signature, Parameter import os import re import sys @@ -22,7 +22,6 @@ from IPython.testing.tools import AssertPrints, AssertNotPrints from IPython.utils.path import compress_user - #----------------------------------------------------------------------------- # Globals and constants #----------------------------------------------------------------------------- @@ -432,3 +431,33 @@ def test_builtin_init(): init_def = info['init_definition'] nt.assert_is_not_none(init_def) + +def test_render_signature_short(): + def short_fun(a: int = 1): pass + sig = oinspect._render_signature( + signature(short_fun), + short_fun.__name__, + ) + nt.assert_equal(sig, 'short_fun(a: int = 1)') + + +def test_render_signature_long(): + from typing import Optional + + def long_function( + a_really_long_parameter: int, + and_another_long_one: bool = False, + let_us_make_sure_this_is_looong: Optional[str] = None, + ) -> bool: pass + + sig = oinspect._render_signature( + signature(long_function), + long_function.__name__, + ) + nt.assert_equal(sig, '''\ +long_function( + a_really_long_parameter: int, + and_another_long_one: bool = False, + let_us_make_sure_this_is_looong: Union[str, NoneType] = None, +) -> bool\ +''') From 442bcbd46bd793d1424a225100e7b9fe5b1a0796 Mon Sep 17 00:00:00 2001 From: Philipp A Date: Thu, 6 Dec 2018 11:06:30 +0100 Subject: [PATCH 016/458] Older versions render a little less pretty --- IPython/core/tests/test_oinspect.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/IPython/core/tests/test_oinspect.py b/IPython/core/tests/test_oinspect.py index b3e2b21a78a..14c7c404ff0 100644 --- a/IPython/core/tests/test_oinspect.py +++ b/IPython/core/tests/test_oinspect.py @@ -22,6 +22,7 @@ from IPython.testing.tools import AssertPrints, AssertNotPrints from IPython.utils.path import compress_user + #----------------------------------------------------------------------------- # Globals and constants #----------------------------------------------------------------------------- @@ -433,12 +434,12 @@ def test_builtin_init(): def test_render_signature_short(): - def short_fun(a: int = 1): pass + def short_fun(a=1): pass sig = oinspect._render_signature( signature(short_fun), short_fun.__name__, ) - nt.assert_equal(sig, 'short_fun(a: int = 1)') + nt.assert_equal(sig, 'short_fun(a=1)') def test_render_signature_long(): @@ -454,10 +455,20 @@ def long_function( signature(long_function), long_function.__name__, ) - nt.assert_equal(sig, '''\ + nt.assert_in(sig, [ + # Python >=3.7 + '''\ long_function( a_really_long_parameter: int, and_another_long_one: bool = False, let_us_make_sure_this_is_looong: Union[str, NoneType] = None, ) -> bool\ -''') +''', # Python <=3.6 + '''\ +long_function( + a_really_long_parameter:int, + and_another_long_one:bool=False, + let_us_make_sure_this_is_looong:Union[str, NoneType]=None, +) -> bool\ +''', + ]) From 594d000f92425e569c602c6eb837c2885dc41e2e Mon Sep 17 00:00:00 2001 From: Jake VanderPlas Date: Thu, 6 Dec 2018 11:09:13 -0800 Subject: [PATCH 017/458] print note about kernel restart after installing packages --- IPython/core/magics/packaging.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/IPython/core/magics/packaging.py b/IPython/core/magics/packaging.py index fffb9d7278b..0f052b45295 100644 --- a/IPython/core/magics/packaging.py +++ b/IPython/core/magics/packaging.py @@ -66,6 +66,7 @@ def pip(self, line): %pip install [pkgs] """ self.shell.system(' '.join([sys.executable, '-m', 'pip', line])) + print("Note: you may need to restart the kernel to use updated packages.") @line_magic def conda(self, line): @@ -98,4 +99,5 @@ def conda(self, line): if needs_prefix and not has_prefix: extra_args.extend(["--prefix", sys.prefix]) - self.shell.system(' '.join([conda, command] + extra_args + args)) \ No newline at end of file + self.shell.system(' '.join([conda, command] + extra_args + args)) + print("\nNote: you may need to restart the kernel to use updated packages.") \ No newline at end of file From c14d2208de7021c76b236eba668e3f40917a49e0 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 10 Dec 2018 14:00:46 -0800 Subject: [PATCH 018/458] Update basic.py --- IPython/core/magics/basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/magics/basic.py b/IPython/core/magics/basic.py index c7bd0064d62..3721cbc4eb0 100644 --- a/IPython/core/magics/basic.py +++ b/IPython/core/magics/basic.py @@ -179,7 +179,7 @@ def alias_magic(self, line=''): @line_magic def lsmagic(self, parameter_s=''): """List currently available magic functions.""" - return MagicsDisplay(self.shell.magics_manager, ignore=[self.pip]) + return MagicsDisplay(self.shell.magics_manager, ignore=[]) def _magic_docs(self, brief=False, rest=False): """Return docstrings from magic functions.""" From 05bfcf38788982320d54550b3aeb0f5853aedb7f Mon Sep 17 00:00:00 2001 From: Jesse Widner Date: Tue, 11 Dec 2018 22:13:39 -0500 Subject: [PATCH 019/458] Adds posix aliases after a %reset. For example, running %reset in IPython, and then trying to run %clear raises a UsageError. --- IPython/core/interactiveshell.py | 7 +++++++ IPython/core/tests/test_interactiveshell.py | 10 ++++++++++ IPython/terminal/interactiveshell.py | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index f98e49c63af..a6c0ebbe706 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -1433,6 +1433,13 @@ def reset(self, new_session=True): self.alias_manager.clear_aliases() self.alias_manager.init_aliases() + # Now define aliases that only make sense on the terminal, because they + # need direct access to the console in a way that we can't emulate in + # GUI or web frontend + if os.name == 'posix': + for cmd in ('clear', 'more', 'less', 'man'): + self.alias_manager.soft_define_alias(cmd, cmd) + # Flush the private list of module references kept for script # execution protection self.clear_main_mod_cache() diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py index 39fa41bd4df..26dbd0d238c 100644 --- a/IPython/core/tests/test_interactiveshell.py +++ b/IPython/core/tests/test_interactiveshell.py @@ -495,6 +495,16 @@ def test_last_execution_result(self): self.assertFalse(ip.last_execution_result.success) self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError) + def test_reset_aliasing(self): + """ Check that standard posix aliases work after %reset. """ + if os.name != 'posix': + return + + ip.reset() + for cmd in ('clear', 'more', 'less', 'man'): + res = ip.run_cell('%' + cmd) + self.assertEqual(res.success, True) + class TestSafeExecfileNonAsciiPath(unittest.TestCase): diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index 6fc89800000..d31cc6ad954 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -447,7 +447,7 @@ def init_alias(self): # need direct access to the console in a way that we can't emulate in # GUI or web frontend if os.name == 'posix': - for cmd in ['clear', 'more', 'less', 'man']: + for cmd in ('clear', 'more', 'less', 'man'): self.alias_manager.soft_define_alias(cmd, cmd) From e4869b6789712f3f49827f60e5d387d68e4997dd Mon Sep 17 00:00:00 2001 From: Tony Fast Date: Wed, 12 Dec 2018 13:23:14 -0500 Subject: [PATCH 020/458] Update shellapp.py Allow the IPython command line to run *.ipynb files . --- IPython/core/shellapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/shellapp.py b/IPython/core/shellapp.py index 4b855c3baf8..bae1869dc6a 100644 --- a/IPython/core/shellapp.py +++ b/IPython/core/shellapp.py @@ -312,7 +312,7 @@ def _exec_file(self, fname, shell_futures=False): # behavior. with preserve_keys(self.shell.user_ns, '__file__'): self.shell.user_ns['__file__'] = fname - if full_filename.endswith('.ipy'): + if full_filename.endswith('.ipy') or full_filename.endswith('.ipynb'): self.shell.safe_execfile_ipy(full_filename, shell_futures=shell_futures) else: From b25bf1c5fb3f1f637661a141857c818143eb4e1d Mon Sep 17 00:00:00 2001 From: Gabriel Potter Date: Sun, 16 Dec 2018 01:38:17 +0100 Subject: [PATCH 021/458] Update dead link --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 994310d8122..f708ca0ecef 100644 --- a/README.rst +++ b/README.rst @@ -87,7 +87,7 @@ manager. For more information see one of our blog posts: - https://blog.jupyter.org/2016/07/08/ipython-5-0-released/ + https://blog.jupyter.org/release-of-ipython-5-0-8ce60b8d2e8e As well as the following Pull-Request for discussion: From 928c5ad78297d57b4e086ca28f7382fce494ad60 Mon Sep 17 00:00:00 2001 From: Steve Nicholson Date: Thu, 27 Dec 2018 06:18:11 -0800 Subject: [PATCH 022/458] Add missing word MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add “which” to incomplete sentence. --- docs/source/overview.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/overview.rst b/docs/source/overview.rst index bf8654e9e65..fa70a2f3beb 100644 --- a/docs/source/overview.rst +++ b/docs/source/overview.rst @@ -208,7 +208,7 @@ of the `Jupyter` project, which includes ``jupyter console``, ``jupyter qtconsole``, and ``jupyter notebook``. As an example, this means that when you start ``jupyter qtconsole``, you're -really starting two processes, a kernel and a Qt-based client can send +really starting two processes, a kernel and a Qt-based client which can send commands to and receive results from that kernel. If there is already a kernel running that you want to connect to, you can pass the ``--existing`` flag which will skip initiating a new kernel and connect to the most recent kernel, From 43246e6dd85ef4647f2c70dfe3a54100aa7227af Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 4 Jan 2019 10:38:43 -0800 Subject: [PATCH 023/458] Apply @needs_local_scope to cell magics. While technically this will not be completely needed (at cell magics cannot be in nested scope), it make the API a tiny bit more consistent between line and cell magics. (Bug report I recieved personally that will be submitted later) --- IPython/core/interactiveshell.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index f98e49c63af..6a907d51c19 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2328,8 +2328,13 @@ def run_cell_magic(self, magic_name, line, cell): magic_arg_s = line else: magic_arg_s = self.var_expand(line, stack_depth) + kwargs = {} + if getattr(fn, "needs_local_scope", False): + kwargs['local_ns'] = sys._getframe(stack_depth).f_locals + with self.builtin_trap: - result = fn(magic_arg_s, cell) + args = (magic_arg_s, cell) + result = fn(*args, **kwargs) return result def find_line_magic(self, magic_name): From d38770bbe722785f00860430228d9476e096a87f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Schoentgen?= Date: Sun, 6 Jan 2019 11:51:28 +0100 Subject: [PATCH 024/458] Fix several DeprecationWarning: invalid escape sequence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mickaël Schoentgen --- IPython/sphinxext/ipython_directive.py | 8 ++++---- IPython/terminal/tests/test_debug_magic.py | 2 +- docs/sphinxext/apigen.py | 6 +++--- setupbase.py | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/IPython/sphinxext/ipython_directive.py b/IPython/sphinxext/ipython_directive.py index f264340f628..1add8f6a6f9 100644 --- a/IPython/sphinxext/ipython_directive.py +++ b/IPython/sphinxext/ipython_directive.py @@ -82,7 +82,7 @@ Sphinx source directory. The default is `html_static_path`. ipython_rgxin: The compiled regular expression to denote the start of IPython input - lines. The default is ``re.compile('In \[(\d+)\]:\s?(.*)\s*')``. You + lines. The default is ``re.compile('In \\[(\\d+)\\]:\\s?(.*)\\s*')``. You shouldn't need to change this. ipython_warning_is_error: [default to True] Fail the build if something unexpected happen, for example if a block raise @@ -90,7 +90,7 @@ what is considered strict, may change between the sphinx directive version. ipython_rgxout: The compiled regular expression to denote the start of IPython output - lines. The default is ``re.compile('Out\[(\d+)\]:\s?(.*)\s*')``. You + lines. The default is ``re.compile('Out\\[(\\d+)\\]:\\s?(.*)\\s*')``. You shouldn't need to change this. ipython_promptin: The string to represent the IPython input prompt in the generated ReST. @@ -1047,9 +1047,9 @@ def setup(app): app.add_config_value('ipython_savefig_dir', 'savefig', 'env') app.add_config_value('ipython_warning_is_error', True, 'env') app.add_config_value('ipython_rgxin', - re.compile('In \[(\d+)\]:\s?(.*)\s*'), 'env') + re.compile(r'In \[(\d+)\]:\s?(.*)\s*'), 'env') app.add_config_value('ipython_rgxout', - re.compile('Out\[(\d+)\]:\s?(.*)\s*'), 'env') + re.compile(r'Out\[(\d+)\]:\s?(.*)\s*'), 'env') app.add_config_value('ipython_promptin', 'In [%d]:', 'env') app.add_config_value('ipython_promptout', 'Out[%d]:', 'env') diff --git a/IPython/terminal/tests/test_debug_magic.py b/IPython/terminal/tests/test_debug_magic.py index 650ba7f9ab9..e5d62dc7e00 100644 --- a/IPython/terminal/tests/test_debug_magic.py +++ b/IPython/terminal/tests/test_debug_magic.py @@ -26,7 +26,7 @@ def test_debug_magic_passes_through_generators(): """ import pexpect import re - in_prompt = re.compile(b'In ?\[\\d+\]:') + in_prompt = re.compile(br'In ?\[\d+\]:') ipdb_prompt = 'ipdb>' env = os.environ.copy() child = pexpect.spawn(sys.executable, ['-m', 'IPython', '--colors=nocolor', '--simple-prompt'], diff --git a/docs/sphinxext/apigen.py b/docs/sphinxext/apigen.py index 0b3c80d0361..c773af639f5 100644 --- a/docs/sphinxext/apigen.py +++ b/docs/sphinxext/apigen.py @@ -105,7 +105,7 @@ def __init__(self, if *package_name* is ``sphinx``, then ``sphinx.util`` will result in ``.util`` being passed for earching by these regexps. If is None, gives default. Default is: - ['\.tests$'] + ['\\.tests$'] module_skip_patterns : None or sequence Sequence of strings giving URIs of modules to be excluded Operates on the module name including preceding URI path, @@ -113,7 +113,7 @@ def __init__(self, ``sphinx.util.console`` results in the string to search of ``.util.console`` If is None, gives default. Default is: - ['\.setup$', '\._'] + ['\\.setup$', '\\._'] names_from__all__ : set, optional Modules listed in here will be scanned by doing ``from mod import *``, rather than finding function and class definitions by scanning the @@ -355,7 +355,7 @@ def discover_modules(self): >>> mods = dw.discover_modules() >>> 'sphinx.util' in mods True - >>> dw.package_skip_patterns.append('\.util$') + >>> dw.package_skip_patterns.append('\\.util$') >>> 'sphinx.util' in dw.discover_modules() False >>> diff --git a/setupbase.py b/setupbase.py index 9460f94278d..7ee9dc06a6f 100644 --- a/setupbase.py +++ b/setupbase.py @@ -270,7 +270,7 @@ def run(self): # Write .cmd wrappers for Windows so 'ipython' etc. work at the # command line cmd_file = os.path.join(self.build_dir, name + '.cmd') - cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format( + cmd = r'@"{python}" "%~dp0\{script}" %*\r\n'.format( python=sys.executable, script=name) log.info("Writing %s wrapper script" % cmd_file) with open(cmd_file, 'w') as f: @@ -358,7 +358,7 @@ class MyBuildPy(build_cmd): def run(self): # loose as `.dev` is suppose to be invalid print("check version number") - loose_pep440re = re.compile('^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$') + loose_pep440re = re.compile(r'^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$') if not loose_pep440re.match(version): raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % version) From 5df756a9097b1f1e99c2251841338c523329b8fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Schoentgen?= Date: Sun, 6 Jan 2019 12:29:06 +0100 Subject: [PATCH 025/458] Fix ResourceWarning: unclosed file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also uniformize usage of the 'with' context manager to prevent resource leaks. Signed-off-by: Mickaël Schoentgen --- IPython/core/crashhandler.py | 13 +++++----- IPython/core/debugger.py | 5 ++-- IPython/core/interactiveshell.py | 5 ++-- IPython/core/magics/code.py | 3 ++- IPython/core/magics/execution.py | 5 ++-- IPython/core/magics/packaging.py | 9 ++++--- IPython/extensions/storemagic.py | 25 +++++++++---------- IPython/extensions/tests/test_autoreload.py | 10 ++------ IPython/testing/plugin/ipdoctest.py | 5 +--- IPython/testing/tools.py | 3 +-- IPython/utils/tests/test_module_paths.py | 3 +-- IPython/utils/tests/test_openpy.py | 4 +-- docs/sphinxext/apigen.py | 20 +++++++-------- examples/IPython Kernel/Rich Output.ipynb | 3 ++- .../IPython Kernel/ipython-get-history.py | 14 ++++++----- 15 files changed, 58 insertions(+), 69 deletions(-) diff --git a/IPython/core/crashhandler.py b/IPython/core/crashhandler.py index f3abc1c6fe0..2117edb5c0b 100644 --- a/IPython/core/crashhandler.py +++ b/IPython/core/crashhandler.py @@ -179,13 +179,14 @@ def __call__(self, etype, evalue, etb): print('Could not create crash report on disk.', file=sys.stderr) return - # Inform user on stderr of what happened - print('\n'+'*'*70+'\n', file=sys.stderr) - print(self.message_template.format(**self.info), file=sys.stderr) + with report: + # Inform user on stderr of what happened + print('\n'+'*'*70+'\n', file=sys.stderr) + print(self.message_template.format(**self.info), file=sys.stderr) + + # Construct report on disk + report.write(self.make_report(traceback)) - # Construct report on disk - report.write(self.make_report(traceback)) - report.close() input("Hit to quit (your terminal may close):") def make_report(self,traceback): diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index 4ece380bf9c..dc4a03ca757 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -206,9 +206,8 @@ def _file_lines(fname): except IOError: return [] else: - out = outfile.readlines() - outfile.close() - return out + with out: + return outfile.readlines() class Pdb(OldPdb): diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index f98e49c63af..df33144e081 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -3469,9 +3469,8 @@ def mktempfile(self, data=None, prefix='ipython_edit_'): self.tempfiles.append(filename) if data: - tmp_file = open(filename,'w') - tmp_file.write(data) - tmp_file.close() + with open(filename, 'w') as tmp_file: + tmp_file.write(data) return filename @undoc diff --git a/IPython/core/magics/code.py b/IPython/core/magics/code.py index 8a718ae4790..41aa37ca7cf 100644 --- a/IPython/core/magics/code.py +++ b/IPython/core/magics/code.py @@ -722,7 +722,8 @@ def edit(self, parameter_s='',last_call=['','']): if is_temp: try: - return open(filename).read() + with open(filename) as f: + return f.read() except IOError as msg: if msg.filename == filename: warn('File not found. Did you forget to save?') diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index aa664a37620..d58fa3c47d2 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -370,9 +370,8 @@ def _run_with_profiler(self, code, opts, namespace): print('\n*** Profile stats marshalled to file',\ repr(dump_file)+'.',sys_exit) if text_file: - pfile = open(text_file,'w') - pfile.write(output) - pfile.close() + with open(text_file, 'w') as pfile: + pfile.write(output) print('\n*** Profile printout saved to text file',\ repr(text_file)+'.',sys_exit) diff --git a/IPython/core/magics/packaging.py b/IPython/core/magics/packaging.py index 0f052b45295..6477c7defc7 100644 --- a/IPython/core/magics/packaging.py +++ b/IPython/core/magics/packaging.py @@ -35,10 +35,11 @@ def _get_conda_executable(): # Otherwise, attempt to extract the executable from conda history. # This applies in any conda environment. R = re.compile(r"^#\s*cmd:\s*(?P.*conda)\s[create|install]") - for line in open(os.path.join(sys.prefix, 'conda-meta', 'history')): - match = R.match(line) - if match: - return match.groupdict()['command'] + with open(os.path.join(sys.prefix, 'conda-meta', 'history')) as f: + for line in f: + match = R.match(line) + if match: + return match.groupdict()['command'] # Fallback: assume conda is available on the system path. return "conda" diff --git a/IPython/extensions/storemagic.py b/IPython/extensions/storemagic.py index 9a203ff5e4a..f2e593120c6 100644 --- a/IPython/extensions/storemagic.py +++ b/IPython/extensions/storemagic.py @@ -172,20 +172,19 @@ def store(self, parameter_s=''): fil = open(fnam, 'a') else: fil = open(fnam, 'w') - obj = ip.ev(args[0]) - print("Writing '%s' (%s) to file '%s'." % (args[0], - obj.__class__.__name__, fnam)) - - - if not isinstance (obj, str): - from pprint import pprint - pprint(obj, fil) - else: - fil.write(obj) - if not obj.endswith('\n'): - fil.write('\n') + with fil: + obj = ip.ev(args[0]) + print("Writing '%s' (%s) to file '%s'." % (args[0], + obj.__class__.__name__, fnam)) + + if not isinstance (obj, str): + from pprint import pprint + pprint(obj, fil) + else: + fil.write(obj) + if not obj.endswith('\n'): + fil.write('\n') - fil.close() return # %store foo diff --git a/IPython/extensions/tests/test_autoreload.py b/IPython/extensions/tests/test_autoreload.py index a942c5ebc15..74e01256bda 100644 --- a/IPython/extensions/tests/test_autoreload.py +++ b/IPython/extensions/tests/test_autoreload.py @@ -109,19 +109,13 @@ def write_file(self, filename, content): time.sleep(1.05) # Write - f = open(filename, 'w') - try: + with open(filename, 'w') as f: f.write(content) - finally: - f.close() def new_module(self, code): mod_name, mod_fn = self.get_module() - f = open(mod_fn, 'w') - try: + with open(mod_fn, 'w') as f: f.write(code) - finally: - f.close() return mod_name, mod_fn #----------------------------------------------------------------------------- diff --git a/IPython/testing/plugin/ipdoctest.py b/IPython/testing/plugin/ipdoctest.py index 70da41662c3..4babb2c5c7d 100644 --- a/IPython/testing/plugin/ipdoctest.py +++ b/IPython/testing/plugin/ipdoctest.py @@ -688,11 +688,8 @@ def loadTestsFromFile(self, filename): else: if self.extension and anyp(filename.endswith, self.extension): name = os.path.basename(filename) - dh = open(filename) - try: + with open(filename) as dh: doc = dh.read() - finally: - dh.close() test = self.parser.get_doctest( doc, globs={'__file__': filename}, name=name, filename=filename, lineno=0) diff --git a/IPython/testing/tools.py b/IPython/testing/tools.py index 59dc15bf5d8..1d16f11900a 100644 --- a/IPython/testing/tools.py +++ b/IPython/testing/tools.py @@ -422,8 +422,7 @@ def mute_warn(): def make_tempfile(name): """ Create an empty, named, temporary file for the duration of the context. """ - f = open(name, 'w') - f.close() + open(name, 'w').close() try: yield finally: diff --git a/IPython/utils/tests/test_module_paths.py b/IPython/utils/tests/test_module_paths.py index 8456ee7f27c..38551cf0e76 100644 --- a/IPython/utils/tests/test_module_paths.py +++ b/IPython/utils/tests/test_module_paths.py @@ -37,8 +37,7 @@ old_syspath = sys.path def make_empty_file(fname): - f = open(fname, 'w') - f.close() + open(fname, 'w').close() def setup(): diff --git a/IPython/utils/tests/test_openpy.py b/IPython/utils/tests/test_openpy.py index 352e993f5cf..5a01ac4bca6 100644 --- a/IPython/utils/tests/test_openpy.py +++ b/IPython/utils/tests/test_openpy.py @@ -8,8 +8,8 @@ nonascii_path = os.path.join(mydir, '../../core/tests/nonascii.py') def test_detect_encoding(): - f = open(nonascii_path, 'rb') - enc, lines = openpy.detect_encoding(f.readline) + with open(nonascii_path, 'rb') as f: + enc, lines = openpy.detect_encoding(f.readline) nt.assert_equal(enc, 'iso-8859-5') def test_read_file(): diff --git a/docs/sphinxext/apigen.py b/docs/sphinxext/apigen.py index 0b3c80d0361..3db1525ce8a 100644 --- a/docs/sphinxext/apigen.py +++ b/docs/sphinxext/apigen.py @@ -392,9 +392,8 @@ def write_modules_api(self, modules,outdir): # write out to file outfile = os.path.join(outdir, m + self.rst_extension) - fileobj = open(outfile, 'wt') - fileobj.write(api_str) - fileobj.close() + with open(outfile, 'wt') as fileobj: + fileobj.write(api_str) written_modules.append(m) self.written_modules = written_modules @@ -445,11 +444,10 @@ def write_index(self, outdir, path='gen.rst', relative_to=None): relpath = outdir.replace(relative_to + os.path.sep, '') else: relpath = outdir - idx = open(path,'wt') - w = idx.write - w('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n') - w('.. autosummary::\n' - ' :toctree: %s\n\n' % relpath) - for mod in self.written_modules: - w(' %s\n' % mod) - idx.close() + with open(path,'wt') as idx: + w = idx.write + w('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n') + w('.. autosummary::\n' + ' :toctree: %s\n\n' % relpath) + for mod in self.written_modules: + w(' %s\n' % mod) diff --git a/examples/IPython Kernel/Rich Output.ipynb b/examples/IPython Kernel/Rich Output.ipynb index 7b0caed3cc9..28c052090a8 100644 --- a/examples/IPython Kernel/Rich Output.ipynb +++ b/examples/IPython Kernel/Rich Output.ipynb @@ -3023,7 +3023,8 @@ "source": [ "from IPython.display import HTML\n", "from base64 import b64encode\n", - "video = open(\"../images/animation.m4v\", \"rb\").read()\n", + "with open(\"../images/animation.m4v\", \"rb\") as f:\n", + " video = f.read()\n", "video_encoded = b64encode(video).decode('ascii')\n", "video_tag = '