Show More
@@ -0,0 +1,64 b'' | |||
|
1 | """ | |
|
2 | Inputhook for running the original asyncio event loop while we're waiting for | |
|
3 | input. | |
|
4 | ||
|
5 | By default, in IPython, we run the prompt with a different asyncio event loop, | |
|
6 | because otherwise we risk that people are freezing the prompt by scheduling bad | |
|
7 | coroutines. E.g., a coroutine that does a while/true and never yield back | |
|
8 | control to the loop. We can't cancel that. | |
|
9 | ||
|
10 | However, sometimes we want the asyncio loop to keep running while waiting for | |
|
11 | a prompt. | |
|
12 | ||
|
13 | The following example will print the numbers from 1 to 10 above the prompt, | |
|
14 | while we are waiting for input. (This works also because we use | |
|
15 | prompt_toolkit`s `patch_stdout`):: | |
|
16 | ||
|
17 | In [1]: import asyncio | |
|
18 | ||
|
19 | In [2]: %gui asyncio | |
|
20 | ||
|
21 | In [3]: async def f(): | |
|
22 | ...: for i in range(10): | |
|
23 | ...: await asyncio.sleep(1) | |
|
24 | ...: print(i) | |
|
25 | ||
|
26 | ||
|
27 | In [4]: asyncio.ensure_future(f()) | |
|
28 | ||
|
29 | """ | |
|
30 | import asyncio | |
|
31 | from prompt_toolkit import __version__ as ptk_version | |
|
32 | ||
|
33 | PTK3 = ptk_version.startswith('3.') | |
|
34 | ||
|
35 | ||
|
36 | # Keep reference to the original asyncio loop, because getting the event loop | |
|
37 | # within the input hook would return the other loop. | |
|
38 | loop = asyncio.get_event_loop() | |
|
39 | ||
|
40 | ||
|
41 | def inputhook(context): | |
|
42 | """ | |
|
43 | Inputhook for asyncio event loop integration. | |
|
44 | """ | |
|
45 | # For prompt_toolkit 3.0, this input hook literally doesn't do anything. | |
|
46 | # The event loop integration here is implemented in `interactiveshell.py` | |
|
47 | # by running the prompt itself in the current asyncio loop. The main reason | |
|
48 | # for this is that nesting asyncio event loops is unreliable. | |
|
49 | if PTK3: | |
|
50 | return | |
|
51 | ||
|
52 | # For prompt_toolkit 2.0, we can run the current asyncio event loop, | |
|
53 | # because prompt_toolkit 2.0 uses a different event loop internally. | |
|
54 | ||
|
55 | def stop(): | |
|
56 | loop.stop() | |
|
57 | ||
|
58 | fileno = context.fileno() | |
|
59 | loop.add_reader(fileno, stop) | |
|
60 | try: | |
|
61 | loop.run_forever() | |
|
62 | finally: | |
|
63 | loop.remove_reader(fileno) | |
|
64 |
@@ -0,0 +1,60 b'' | |||
|
1 | ||
|
2 | .. _shell_mimerenderer: | |
|
3 | ||
|
4 | ||
|
5 | Mime Renderer Extensions | |
|
6 | ======================== | |
|
7 | ||
|
8 | Like it's cousins, Jupyter Notebooks and JupyterLab, Terminal IPython can be | |
|
9 | thought to render a number of mimetypes in the shell. This can be used to either | |
|
10 | display inline images if your terminal emulator supports it; or open some | |
|
11 | display results with external file viewers. | |
|
12 | ||
|
13 | Registering new mimetype handlers can so far only be done my extensions and | |
|
14 | requires 4 steps: | |
|
15 | ||
|
16 | - Define a callable that takes 2 parameters:``data`` and ``metadata``; return | |
|
17 | value of the callable is so far ignored. This callable is responsible for | |
|
18 | "displaying" the given mimetype. Which can be sending the right escape | |
|
19 | sequences and bytes to the current terminal; or open an external program. - | |
|
20 | - Appending the right mimetype to ``ipython.display_formatter.active_types`` | |
|
21 | for IPython to know it should not ignore those mimetypes. | |
|
22 | - Enabling the given mimetype: ``ipython.display_formatter.formatters[mime].enabled = True`` | |
|
23 | - Registering above callable with mimetype handler: | |
|
24 | ``ipython.mime_renderers[mime] = handler`` | |
|
25 | ||
|
26 | ||
|
27 | Here is a complete IPython extension to display images inline and convert math | |
|
28 | to png, before displaying it inline for iterm2 on macOS :: | |
|
29 | ||
|
30 | ||
|
31 | from base64 import encodebytes | |
|
32 | from IPython.lib.latextools import latex_to_png | |
|
33 | ||
|
34 | ||
|
35 | def mathcat(data, meta): | |
|
36 | png = latex_to_png(f'$${data}$$'.replace('\displaystyle', '').replace('$$$', '$$')) | |
|
37 | imcat(png, meta) | |
|
38 | ||
|
39 | IMAGE_CODE = '\033]1337;File=name=name;inline=true;:{}\a' | |
|
40 | ||
|
41 | def imcat(image_data, metadata): | |
|
42 | try: | |
|
43 | print(IMAGE_CODE.format(encodebytes(image_data).decode())) | |
|
44 | # bug workaround | |
|
45 | except: | |
|
46 | print(IMAGE_CODE.format(image_data)) | |
|
47 | ||
|
48 | def register_mimerenderer(ipython, mime, handler): | |
|
49 | ipython.display_formatter.active_types.append(mime) | |
|
50 | ipython.display_formatter.formatters[mime].enabled = True | |
|
51 | ipython.mime_renderers[mime] = handler | |
|
52 | ||
|
53 | def load_ipython_extension(ipython): | |
|
54 | register_mimerenderer(ipython, 'image/png', imcat) | |
|
55 | register_mimerenderer(ipython, 'image/jpeg', imcat) | |
|
56 | register_mimerenderer(ipython, 'text/latex', mathcat) | |
|
57 | ||
|
58 | This example only work for iterm2 on macOS and skip error handling for brevity. | |
|
59 | One could also invoke an external viewer with ``subprocess.run()`` and a | |
|
60 | temporary file, which is left as an exercise. |
@@ -29,6 +29,8 b' from IPython.core.release import author_email' | |||
|
29 | 29 | from IPython.utils.sysinfo import sys_info |
|
30 | 30 | from IPython.utils.py3compat import input |
|
31 | 31 | |
|
32 | from IPython.core.release import __version__ as version | |
|
33 | ||
|
32 | 34 | #----------------------------------------------------------------------------- |
|
33 | 35 | # Code |
|
34 | 36 | #----------------------------------------------------------------------------- |
@@ -68,7 +70,7 b' To ensure accurate tracking of this issue, please file a report about it at:' | |||
|
68 | 70 | """ |
|
69 | 71 | |
|
70 | 72 | _lite_message_template = """ |
|
71 | If you suspect this is an IPython bug, please report it at: | |
|
73 | If you suspect this is an IPython {version} bug, please report it at: | |
|
72 | 74 | https://github.com/ipython/ipython/issues |
|
73 | 75 | or send an email to the mailing list at {email} |
|
74 | 76 | |
@@ -222,5 +224,5 b' def crash_handler_lite(etype, evalue, tb):' | |||
|
222 | 224 | else: |
|
223 | 225 | # we are not in a shell, show generic config |
|
224 | 226 | config = "c." |
|
225 | print(_lite_message_template.format(email=author_email, config=config), file=sys.stderr) | |
|
227 | print(_lite_message_template.format(email=author_email, config=config, version=version), file=sys.stderr) | |
|
226 | 228 |
@@ -153,7 +153,7 b' class DisplayHook(Configurable):' | |||
|
153 | 153 | # This can be set to True by the write_output_prompt method in a subclass |
|
154 | 154 | prompt_end_newline = False |
|
155 | 155 | |
|
156 | def write_format_data(self, format_dict, md_dict=None): | |
|
156 | def write_format_data(self, format_dict, md_dict=None) -> None: | |
|
157 | 157 | """Write the format data dict to the frontend. |
|
158 | 158 | |
|
159 | 159 | This default version of this method simply writes the plain text |
@@ -19,7 +19,7 b' spec.' | |||
|
19 | 19 | import sys |
|
20 | 20 | |
|
21 | 21 | from traitlets.config.configurable import Configurable |
|
22 | from traitlets import List | |
|
22 | from traitlets import List, Dict | |
|
23 | 23 | |
|
24 | 24 | # This used to be defined here - it is imported for backwards compatibility |
|
25 | 25 | from .display import publish_display_data |
@@ -28,6 +28,7 b' from .display import publish_display_data' | |||
|
28 | 28 | # Main payload class |
|
29 | 29 | #----------------------------------------------------------------------------- |
|
30 | 30 | |
|
31 | ||
|
31 | 32 | class DisplayPublisher(Configurable): |
|
32 | 33 | """A traited class that publishes display data to frontends. |
|
33 | 34 | |
@@ -35,6 +36,10 b' class DisplayPublisher(Configurable):' | |||
|
35 | 36 | be accessed there. |
|
36 | 37 | """ |
|
37 | 38 | |
|
39 | def __init__(self, shell=None, *args, **kwargs): | |
|
40 | self.shell = shell | |
|
41 | super().__init__(*args, **kwargs) | |
|
42 | ||
|
38 | 43 | def _validate_data(self, data, metadata=None): |
|
39 | 44 | """Validate the display data. |
|
40 | 45 | |
@@ -53,7 +58,7 b' class DisplayPublisher(Configurable):' | |||
|
53 | 58 | raise TypeError('metadata must be a dict, got: %r' % data) |
|
54 | 59 | |
|
55 | 60 | # use * to indicate transient, update are keyword-only |
|
56 | def publish(self, data, metadata=None, source=None, *, transient=None, update=False, **kwargs): | |
|
61 | def publish(self, data, metadata=None, source=None, *, transient=None, update=False, **kwargs) -> None: | |
|
57 | 62 | """Publish data and metadata to all frontends. |
|
58 | 63 | |
|
59 | 64 | See the ``display_data`` message in the messaging documentation for |
@@ -98,7 +103,15 b' class DisplayPublisher(Configurable):' | |||
|
98 | 103 | rather than creating a new output. |
|
99 | 104 | """ |
|
100 | 105 | |
|
101 | # The default is to simply write the plain text data using sys.stdout. | |
|
106 | handlers = {} | |
|
107 | if self.shell is not None: | |
|
108 | handlers = getattr(self.shell, 'mime_renderers', {}) | |
|
109 | ||
|
110 | for mime, handler in handlers.items(): | |
|
111 | if mime in data: | |
|
112 | handler(data[mime], metadata.get(mime, None)) | |
|
113 | return | |
|
114 | ||
|
102 | 115 | if 'text/plain' in data: |
|
103 | 116 | print(data['text/plain']) |
|
104 | 117 |
@@ -857,7 +857,7 b' class InteractiveShell(SingletonConfigurable):' | |||
|
857 | 857 | self.configurables.append(self.display_formatter) |
|
858 | 858 | |
|
859 | 859 | def init_display_pub(self): |
|
860 | self.display_pub = self.display_pub_class(parent=self) | |
|
860 | self.display_pub = self.display_pub_class(parent=self, shell=self) | |
|
861 | 861 | self.configurables.append(self.display_pub) |
|
862 | 862 | |
|
863 | 863 | def init_data_pub(self): |
@@ -853,6 +853,8 b' python-profiler package from non-free.""")' | |||
|
853 | 853 | sys.argv = save_argv |
|
854 | 854 | if restore_main: |
|
855 | 855 | sys.modules['__main__'] = restore_main |
|
856 | if '__mp_main__' in sys.modules: | |
|
857 | sys.modules['__mp_main__'] = restore_main | |
|
856 | 858 | else: |
|
857 | 859 | # Remove from sys.modules the reference to main_mod we'd |
|
858 | 860 | # added. Otherwise it will trap references to objects |
@@ -76,7 +76,7 b" info_fields = ['type_name', 'base_class', 'string_form', 'namespace'," | |||
|
76 | 76 | 'call_def', 'call_docstring', |
|
77 | 77 | # These won't be printed but will be used to determine how to |
|
78 | 78 | # format the object |
|
79 |
'ismagic', 'isalias', 'isclass', ' |
|
|
79 | 'ismagic', 'isalias', 'isclass', 'found', 'name' | |
|
80 | 80 | ] |
|
81 | 81 | |
|
82 | 82 | |
@@ -200,26 +200,39 b' def is_simple_callable(obj):' | |||
|
200 | 200 | return (inspect.isfunction(obj) or inspect.ismethod(obj) or \ |
|
201 | 201 | isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type)) |
|
202 | 202 | |
|
203 | ||
|
203 | @undoc | |
|
204 | 204 | def getargspec(obj): |
|
205 | 205 | """Wrapper around :func:`inspect.getfullargspec` on Python 3, and |
|
206 | 206 | :func:inspect.getargspec` on Python 2. |
|
207 | 207 | |
|
208 | 208 | In addition to functions and methods, this can also handle objects with a |
|
209 | 209 | ``__call__`` attribute. |
|
210 | ||
|
211 | DEPRECATED: Deprecated since 7.10. Do not use, will be removed. | |
|
210 | 212 | """ |
|
213 | ||
|
214 | warnings.warn('`getargspec` function is deprecated as of IPython 7.10' | |
|
215 | 'and will be removed in future versions.', DeprecationWarning, stacklevel=2) | |
|
216 | ||
|
211 | 217 | if safe_hasattr(obj, '__call__') and not is_simple_callable(obj): |
|
212 | 218 | obj = obj.__call__ |
|
213 | 219 | |
|
214 | 220 | return inspect.getfullargspec(obj) |
|
215 | 221 | |
|
216 | ||
|
222 | @undoc | |
|
217 | 223 | def format_argspec(argspec): |
|
218 | 224 | """Format argspect, convenience wrapper around inspect's. |
|
219 | 225 | |
|
220 | 226 | This takes a dict instead of ordered arguments and calls |
|
221 | 227 | inspect.format_argspec with the arguments in the necessary order. |
|
228 | ||
|
229 | DEPRECATED: Do not use; will be removed in future versions. | |
|
222 | 230 | """ |
|
231 | ||
|
232 | warnings.warn('`format_argspec` function is deprecated as of IPython 7.10' | |
|
233 | 'and will be removed in future versions.', DeprecationWarning, stacklevel=2) | |
|
234 | ||
|
235 | ||
|
223 | 236 | return inspect.formatargspec(argspec['args'], argspec['varargs'], |
|
224 | 237 | argspec['varkw'], argspec['defaults']) |
|
225 | 238 | |
@@ -916,33 +929,6 b' class Inspector(Colorable):' | |||
|
916 | 929 | if call_ds: |
|
917 | 930 | out['call_docstring'] = call_ds |
|
918 | 931 | |
|
919 | # Compute the object's argspec as a callable. The key is to decide | |
|
920 | # whether to pull it from the object itself, from its __init__ or | |
|
921 | # from its __call__ method. | |
|
922 | ||
|
923 | if inspect.isclass(obj): | |
|
924 | # Old-style classes need not have an __init__ | |
|
925 | callable_obj = getattr(obj, "__init__", None) | |
|
926 | elif callable(obj): | |
|
927 | callable_obj = obj | |
|
928 | else: | |
|
929 | callable_obj = None | |
|
930 | ||
|
931 | if callable_obj is not None: | |
|
932 | try: | |
|
933 | argspec = getargspec(callable_obj) | |
|
934 | except Exception: | |
|
935 | # For extensions/builtins we can't retrieve the argspec | |
|
936 | pass | |
|
937 | else: | |
|
938 | # named tuples' _asdict() method returns an OrderedDict, but we | |
|
939 | # we want a normal | |
|
940 | out['argspec'] = argspec_dict = dict(argspec._asdict()) | |
|
941 | # We called this varkw before argspec became a named tuple. | |
|
942 | # With getfullargspec it's also called varkw. | |
|
943 | if 'varkw' not in argspec_dict: | |
|
944 | argspec_dict['varkw'] = argspec_dict.pop('keywords') | |
|
945 | ||
|
946 | 932 | return object_info(**out) |
|
947 | 933 | |
|
948 | 934 | @staticmethod |
@@ -15,9 +15,11 b' rid of that dependency, we could move it there.' | |||
|
15 | 15 | |
|
16 | 16 | |
|
17 | 17 | import os |
|
18 | import io | |
|
18 | 19 | import re |
|
19 | 20 | import sys |
|
20 | 21 | import tempfile |
|
22 | import subprocess | |
|
21 | 23 | |
|
22 | 24 | from io import UnsupportedOperation |
|
23 | 25 | |
@@ -208,9 +210,13 b' def pager_page(strng, start=0, screen_lines=0, pager_cmd=None):' | |||
|
208 | 210 | else: |
|
209 | 211 | try: |
|
210 | 212 | retval = None |
|
211 | # if I use popen4, things hang. No idea why. | |
|
212 |
|
|
|
213 | pager = os.popen(pager_cmd, 'w') | |
|
213 | # Emulate os.popen, but redirect stderr | |
|
214 | proc = subprocess.Popen(pager_cmd, | |
|
215 | shell=True, | |
|
216 | stdin=subprocess.PIPE, | |
|
217 | stderr=subprocess.DEVNULL | |
|
218 | ) | |
|
219 | pager = os._wrap_close(io.TextIOWrapper(proc.stdin), proc) | |
|
214 | 220 | try: |
|
215 | 221 | pager_encoding = pager.encoding or sys.stdout.encoding |
|
216 | 222 | pager.write(strng) |
@@ -20,7 +20,7 b" name = 'ipython'" | |||
|
20 | 20 | # release. 'dev' as a _version_extra string means this is a development |
|
21 | 21 | # version |
|
22 | 22 | _version_major = 7 |
|
23 |
_version_minor = 1 |
|
|
23 | _version_minor = 11 | |
|
24 | 24 | _version_patch = 0 |
|
25 | 25 | _version_extra = '.dev' |
|
26 | 26 | # _version_extra = 'b1' |
@@ -7,7 +7,6 b" with better-isolated tests that don't rely on the global instance in iptest." | |||
|
7 | 7 | """ |
|
8 | 8 | from IPython.core.splitinput import LineInfo |
|
9 | 9 | from IPython.core.prefilter import AutocallChecker |
|
10 | from IPython.utils import py3compat | |
|
11 | 10 | |
|
12 | 11 | def doctest_autocall(): |
|
13 | 12 | """ |
@@ -49,11 +49,11 b' def test_handlers():' | |||
|
49 | 49 | # For many of the below, we're also checking that leading whitespace |
|
50 | 50 | # turns off the esc char, which it should unless there is a continuation |
|
51 | 51 | # line. |
|
52 | run([(i,py3compat.u_format(o)) for i,o in \ | |
|
52 | run( | |
|
53 | 53 | [('"no change"', '"no change"'), # normal |
|
54 | 54 | (u"lsmagic", "get_ipython().run_line_magic('lsmagic', '')"), # magic |
|
55 | 55 | #("a = b # PYTHON-MODE", '_i'), # emacs -- avoids _in cache |
|
56 |
] |
|
|
56 | ]) | |
|
57 | 57 | |
|
58 | 58 | # Objects which are instances of IPyAutocall are *always* autocalled |
|
59 | 59 | autocallable = Autocallable() |
@@ -539,6 +539,35 b' def test_run_tb():' | |||
|
539 | 539 | del ip.user_ns['bar'] |
|
540 | 540 | del ip.user_ns['foo'] |
|
541 | 541 | |
|
542 | ||
|
543 | def test_multiprocessing_run(): | |
|
544 | """Set we can run mutiprocesgin without messing up up main namespace | |
|
545 | ||
|
546 | Note that import `nose.tools as nt` mdify the value s | |
|
547 | sys.module['__mp_main__'] so wee need to temporarily set it to None to test | |
|
548 | the issue. | |
|
549 | """ | |
|
550 | with TemporaryDirectory() as td: | |
|
551 | mpm = sys.modules.get('__mp_main__') | |
|
552 | assert mpm is not None | |
|
553 | sys.modules['__mp_main__'] = None | |
|
554 | try: | |
|
555 | path = pjoin(td, 'test.py') | |
|
556 | with open(path, 'w') as f: | |
|
557 | f.write("import multiprocessing\nprint('hoy')") | |
|
558 | with capture_output() as io: | |
|
559 | _ip.run_line_magic('run', path) | |
|
560 | _ip.run_cell("i_m_undefined") | |
|
561 | out = io.stdout | |
|
562 | nt.assert_in("hoy", out) | |
|
563 | nt.assert_not_in("AttributeError", out) | |
|
564 | nt.assert_in("NameError", out) | |
|
565 | nt.assert_equal(out.count("---->"), 1) | |
|
566 | except: | |
|
567 | raise | |
|
568 | finally: | |
|
569 | sys.modules['__mp_main__'] = mpm | |
|
570 | ||
|
542 | 571 | @dec.knownfailureif(sys.platform == 'win32', "writes to io.stdout aren't captured on Windows") |
|
543 | 572 | def test_script_tb(): |
|
544 | 573 | """Test traceback offset in `ipython script.py`""" |
@@ -296,6 +296,25 b' except Exception:' | |||
|
296 | 296 | tt.AssertPrints("ValueError", suppress=False): |
|
297 | 297 | ip.run_cell(self.SUPPRESS_CHAINING_CODE) |
|
298 | 298 | |
|
299 | def test_plain_direct_cause_error(self): | |
|
300 | with tt.AssertPrints(["KeyError", "NameError", "direct cause"]): | |
|
301 | ip.run_cell("%xmode Plain") | |
|
302 | ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE) | |
|
303 | ip.run_cell("%xmode Verbose") | |
|
304 | ||
|
305 | def test_plain_exception_during_handling_error(self): | |
|
306 | with tt.AssertPrints(["KeyError", "NameError", "During handling"]): | |
|
307 | ip.run_cell("%xmode Plain") | |
|
308 | ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE) | |
|
309 | ip.run_cell("%xmode Verbose") | |
|
310 | ||
|
311 | def test_plain_suppress_exception_chaining(self): | |
|
312 | with tt.AssertNotPrints("ZeroDivisionError"), \ | |
|
313 | tt.AssertPrints("ValueError", suppress=False): | |
|
314 | ip.run_cell("%xmode Plain") | |
|
315 | ip.run_cell(self.SUPPRESS_CHAINING_CODE) | |
|
316 | ip.run_cell("%xmode Verbose") | |
|
317 | ||
|
299 | 318 | |
|
300 | 319 | class RecursionTest(unittest.TestCase): |
|
301 | 320 | DEFINITIONS = """ |
@@ -530,6 +530,30 b' class TBTools(colorable.Colorable):' | |||
|
530 | 530 | |
|
531 | 531 | ostream = property(_get_ostream, _set_ostream) |
|
532 | 532 | |
|
533 | def get_parts_of_chained_exception(self, evalue): | |
|
534 | def get_chained_exception(exception_value): | |
|
535 | cause = getattr(exception_value, '__cause__', None) | |
|
536 | if cause: | |
|
537 | return cause | |
|
538 | if getattr(exception_value, '__suppress_context__', False): | |
|
539 | return None | |
|
540 | return getattr(exception_value, '__context__', None) | |
|
541 | ||
|
542 | chained_evalue = get_chained_exception(evalue) | |
|
543 | ||
|
544 | if chained_evalue: | |
|
545 | return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__ | |
|
546 | ||
|
547 | def prepare_chained_exception_message(self, cause): | |
|
548 | direct_cause = "\nThe above exception was the direct cause of the following exception:\n" | |
|
549 | exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n" | |
|
550 | ||
|
551 | if cause: | |
|
552 | message = [[direct_cause]] | |
|
553 | else: | |
|
554 | message = [[exception_during_handling]] | |
|
555 | return message | |
|
556 | ||
|
533 | 557 | def set_colors(self, *args, **kw): |
|
534 | 558 | """Shorthand access to the color table scheme selector method.""" |
|
535 | 559 | |
@@ -603,7 +627,7 b' class ListTB(TBTools):' | |||
|
603 | 627 | self.ostream.write(self.text(etype, value, elist)) |
|
604 | 628 | self.ostream.write('\n') |
|
605 | 629 | |
|
606 |
def structured_traceback(self, etype, value, e |
|
|
630 | def structured_traceback(self, etype, evalue, etb=None, tb_offset=None, | |
|
607 | 631 | context=5): |
|
608 | 632 | """Return a color formatted string with the traceback info. |
|
609 | 633 | |
@@ -612,15 +636,16 b' class ListTB(TBTools):' | |||
|
612 | 636 | etype : exception type |
|
613 | 637 | Type of the exception raised. |
|
614 | 638 | |
|
615 | value : object | |
|
639 | evalue : object | |
|
616 | 640 | Data stored in the exception |
|
617 | 641 | |
|
618 |
e |
|
|
619 | List of frames, see class docstring for details. | |
|
642 | etb : object | |
|
643 | If list: List of frames, see class docstring for details. | |
|
644 | If Traceback: Traceback of the exception. | |
|
620 | 645 | |
|
621 | 646 | tb_offset : int, optional |
|
622 | 647 | Number of frames in the traceback to skip. If not given, the |
|
623 | instance value is used (set in constructor). | |
|
648 | instance evalue is used (set in constructor). | |
|
624 | 649 | |
|
625 | 650 | context : int, optional |
|
626 | 651 | Number of lines of context information to print. |
@@ -629,6 +654,19 b' class ListTB(TBTools):' | |||
|
629 | 654 | ------- |
|
630 | 655 | String with formatted exception. |
|
631 | 656 | """ |
|
657 | # This is a workaround to get chained_exc_ids in recursive calls | |
|
658 | # etb should not be a tuple if structured_traceback is not recursive | |
|
659 | if isinstance(etb, tuple): | |
|
660 | etb, chained_exc_ids = etb | |
|
661 | else: | |
|
662 | chained_exc_ids = set() | |
|
663 | ||
|
664 | if isinstance(etb, list): | |
|
665 | elist = etb | |
|
666 | elif etb is not None: | |
|
667 | elist = self._extract_tb(etb) | |
|
668 | else: | |
|
669 | elist = [] | |
|
632 | 670 | tb_offset = self.tb_offset if tb_offset is None else tb_offset |
|
633 | 671 | Colors = self.Colors |
|
634 | 672 | out_list = [] |
@@ -641,9 +679,25 b' class ListTB(TBTools):' | |||
|
641 | 679 | (Colors.normalEm, Colors.Normal) + '\n') |
|
642 | 680 | out_list.extend(self._format_list(elist)) |
|
643 | 681 | # The exception info should be a single entry in the list. |
|
644 | lines = ''.join(self._format_exception_only(etype, value)) | |
|
682 | lines = ''.join(self._format_exception_only(etype, evalue)) | |
|
645 | 683 | out_list.append(lines) |
|
646 | 684 | |
|
685 | exception = self.get_parts_of_chained_exception(evalue) | |
|
686 | ||
|
687 | if exception and not id(exception[1]) in chained_exc_ids: | |
|
688 | chained_exception_message = self.prepare_chained_exception_message( | |
|
689 | evalue.__cause__)[0] | |
|
690 | etype, evalue, etb = exception | |
|
691 | # Trace exception to avoid infinite 'cause' loop | |
|
692 | chained_exc_ids.add(id(exception[1])) | |
|
693 | chained_exceptions_tb_offset = 0 | |
|
694 | out_list = ( | |
|
695 | self.structured_traceback( | |
|
696 | etype, evalue, (etb, chained_exc_ids), | |
|
697 | chained_exceptions_tb_offset, context) | |
|
698 | + chained_exception_message | |
|
699 | + out_list) | |
|
700 | ||
|
647 | 701 | return out_list |
|
648 | 702 | |
|
649 | 703 | def _format_list(self, extracted_list): |
@@ -763,7 +817,7 b' class ListTB(TBTools):' | |||
|
763 | 817 | etype : exception type |
|
764 | 818 | value : exception value |
|
765 | 819 | """ |
|
766 |
return ListTB.structured_traceback(self, etype, value |
|
|
820 | return ListTB.structured_traceback(self, etype, value) | |
|
767 | 821 | |
|
768 | 822 | def show_exception_only(self, etype, evalue): |
|
769 | 823 | """Only print the exception type and message, without a traceback. |
@@ -1013,16 +1067,6 b' class VerboseTB(TBTools):' | |||
|
1013 | 1067 | _format_traceback_lines(lnum, index, lines, Colors, lvals, |
|
1014 | 1068 | _line_format))) |
|
1015 | 1069 | |
|
1016 | def prepare_chained_exception_message(self, cause): | |
|
1017 | direct_cause = "\nThe above exception was the direct cause of the following exception:\n" | |
|
1018 | exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n" | |
|
1019 | ||
|
1020 | if cause: | |
|
1021 | message = [[direct_cause]] | |
|
1022 | else: | |
|
1023 | message = [[exception_during_handling]] | |
|
1024 | return message | |
|
1025 | ||
|
1026 | 1070 | def prepare_header(self, etype, long_version=False): |
|
1027 | 1071 | colors = self.Colors # just a shorthand + quicker name lookup |
|
1028 | 1072 | colorsnormal = colors.Normal # used a lot |
@@ -1117,20 +1161,6 b' class VerboseTB(TBTools):' | |||
|
1117 | 1161 | info('\nUnfortunately, your original traceback can not be constructed.\n') |
|
1118 | 1162 | return None |
|
1119 | 1163 | |
|
1120 | def get_parts_of_chained_exception(self, evalue): | |
|
1121 | def get_chained_exception(exception_value): | |
|
1122 | cause = getattr(exception_value, '__cause__', None) | |
|
1123 | if cause: | |
|
1124 | return cause | |
|
1125 | if getattr(exception_value, '__suppress_context__', False): | |
|
1126 | return None | |
|
1127 | return getattr(exception_value, '__context__', None) | |
|
1128 | ||
|
1129 | chained_evalue = get_chained_exception(evalue) | |
|
1130 | ||
|
1131 | if chained_evalue: | |
|
1132 | return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__ | |
|
1133 | ||
|
1134 | 1164 | def structured_traceback(self, etype, evalue, etb, tb_offset=None, |
|
1135 | 1165 | number_of_lines_of_context=5): |
|
1136 | 1166 | """Return a nice text document describing the traceback.""" |
@@ -1294,9 +1324,8 b' class FormattedTB(VerboseTB, ListTB):' | |||
|
1294 | 1324 | # out-of-date source code. |
|
1295 | 1325 | self.check_cache() |
|
1296 | 1326 | # Now we can extract and format the exception |
|
1297 | elist = self._extract_tb(tb) | |
|
1298 | 1327 | return ListTB.structured_traceback( |
|
1299 |
self, etype, value, |
|
|
1328 | self, etype, value, tb, tb_offset, number_of_lines_of_context | |
|
1300 | 1329 | ) |
|
1301 | 1330 | |
|
1302 | 1331 | def stb2text(self, stb): |
@@ -32,15 +32,15 b' def win32_clipboard_get():' | |||
|
32 | 32 | win32clipboard.CloseClipboard() |
|
33 | 33 | return text |
|
34 | 34 | |
|
35 | def osx_clipboard_get(): | |
|
35 | def osx_clipboard_get() -> str: | |
|
36 | 36 | """ Get the clipboard's text on OS X. |
|
37 | 37 | """ |
|
38 | 38 | p = subprocess.Popen(['pbpaste', '-Prefer', 'ascii'], |
|
39 | 39 | stdout=subprocess.PIPE) |
|
40 |
|
|
|
40 | bytes_, stderr = p.communicate() | |
|
41 | 41 | # Text comes in with old Mac \r line endings. Change them to \n. |
|
42 |
|
|
|
43 | text = py3compat.cast_unicode(text, py3compat.DEFAULT_ENCODING) | |
|
42 | bytes_ = bytes_.replace(b'\r', b'\n') | |
|
43 | text = py3compat.decode(bytes_) | |
|
44 | 44 | return text |
|
45 | 45 | |
|
46 | 46 | def tkinter_clipboard_get(): |
@@ -97,9 +97,6 b" __all__ = ['pretty', 'pprint', 'PrettyPrinter', 'RepresentationPrinter'," | |||
|
97 | 97 | |
|
98 | 98 | |
|
99 | 99 | MAX_SEQ_LENGTH = 1000 |
|
100 | # The language spec says that dicts preserve order from 3.7, but CPython | |
|
101 | # does so from 3.6, so it seems likely that people will expect that. | |
|
102 | DICT_IS_ORDERED = True | |
|
103 | 100 | _re_pattern_type = type(re.compile('')) |
|
104 | 101 | |
|
105 | 102 | def _safe_getattr(obj, attr, default=None): |
@@ -606,11 +603,6 b' def _dict_pprinter_factory(start, end):' | |||
|
606 | 603 | step = len(start) |
|
607 | 604 | p.begin_group(step, start) |
|
608 | 605 | keys = obj.keys() |
|
609 | # if dict isn't large enough to be truncated, sort keys before displaying | |
|
610 | # From Python 3.7, dicts preserve order by definition, so we don't sort. | |
|
611 | if not DICT_IS_ORDERED \ | |
|
612 | and not (p.max_seq_length and len(obj) >= p.max_seq_length): | |
|
613 | keys = _sorted_for_pprint(keys) | |
|
614 | 606 | for idx, key in p._enumerate(keys): |
|
615 | 607 | if idx: |
|
616 | 608 | p.text(',') |
@@ -12,7 +12,7 b' from IPython.utils.path import (' | |||
|
12 | 12 | ensure_dir_exists, fs_encoding) |
|
13 | 13 | from IPython.utils import py3compat |
|
14 | 14 | |
|
15 | def get_ipython_dir(): | |
|
15 | def get_ipython_dir() -> str: | |
|
16 | 16 | """Get the IPython directory for this platform and user. |
|
17 | 17 | |
|
18 | 18 | This uses the logic in `get_home_dir` to find the home directory |
@@ -28,10 +28,9 b' def get_ipython_dir():' | |||
|
28 | 28 | home_dir = get_home_dir() |
|
29 | 29 | xdg_dir = get_xdg_dir() |
|
30 | 30 | |
|
31 | # import pdb; pdb.set_trace() # dbg | |
|
32 | 31 | if 'IPYTHON_DIR' in env: |
|
33 | warn('The environment variable IPYTHON_DIR is deprecated. ' | |
|
34 | 'Please use IPYTHONDIR instead.') | |
|
32 | warn('The environment variable IPYTHON_DIR is deprecated since IPython 3.0. ' | |
|
33 | 'Please use IPYTHONDIR instead.', DeprecationWarning) | |
|
35 | 34 | ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None)) |
|
36 | 35 | if ipdir is None: |
|
37 | 36 | # not set explicitly, use ~/.ipython |
@@ -67,11 +66,11 b' def get_ipython_dir():' | |||
|
67 | 66 | warn("IPython parent '{0}' is not a writable location," |
|
68 | 67 | " using a temp directory.".format(parent)) |
|
69 | 68 | ipdir = tempfile.mkdtemp() |
|
69 | assert isinstance(ipdir, str), "all path manipulation should be str(unicode), but are not." | |
|
70 | return ipdir | |
|
70 | 71 | |
|
71 | return py3compat.cast_unicode(ipdir, fs_encoding) | |
|
72 | 72 | |
|
73 | ||
|
74 | def get_ipython_cache_dir(): | |
|
73 | def get_ipython_cache_dir() -> str: | |
|
75 | 74 | """Get the cache directory it is created if it does not exist.""" |
|
76 | 75 | xdgdir = get_xdg_cache_dir() |
|
77 | 76 | if xdgdir is None: |
@@ -82,13 +81,14 b' def get_ipython_cache_dir():' | |||
|
82 | 81 | elif not _writable_dir(xdgdir): |
|
83 | 82 | return get_ipython_dir() |
|
84 | 83 | |
|
85 | return py3compat.cast_unicode(ipdir, fs_encoding) | |
|
84 | return ipdir | |
|
86 | 85 | |
|
87 | 86 | |
|
88 | def get_ipython_package_dir(): | |
|
87 | def get_ipython_package_dir() -> str: | |
|
89 | 88 | """Get the base directory where IPython itself is installed.""" |
|
90 | 89 | ipdir = os.path.dirname(IPython.__file__) |
|
91 | return py3compat.cast_unicode(ipdir, fs_encoding) | |
|
90 | assert isinstance(ipdir, str) | |
|
91 | return ipdir | |
|
92 | 92 | |
|
93 | 93 | |
|
94 | 94 | def get_ipython_module_path(module_str): |
@@ -17,6 +17,9 b' from prompt_toolkit.shortcuts.prompt import PromptSession' | |||
|
17 | 17 | from prompt_toolkit.enums import EditingMode |
|
18 | 18 | from prompt_toolkit.formatted_text import PygmentsTokens |
|
19 | 19 | |
|
20 | from prompt_toolkit import __version__ as ptk_version | |
|
21 | PTK3 = ptk_version.startswith('3.') | |
|
22 | ||
|
20 | 23 | |
|
21 | 24 | class TerminalPdb(Pdb): |
|
22 | 25 | """Standalone IPython debugger.""" |
@@ -49,20 +52,23 b' class TerminalPdb(Pdb):' | |||
|
49 | 52 | & ~cursor_in_leading_ws |
|
50 | 53 | ))(display_completions_like_readline) |
|
51 | 54 | |
|
52 | self.pt_app = PromptSession( | |
|
53 |
|
|
|
54 |
|
|
|
55 |
|
|
|
56 |
|
|
|
57 |
|
|
|
58 |
|
|
|
59 |
|
|
|
60 |
|
|
|
61 |
|
|
|
62 | inputhook=self.shell.inputhook, | |
|
63 | color_depth=self.shell.color_depth, | |
|
55 | options = dict( | |
|
56 | message=(lambda: PygmentsTokens(get_prompt_tokens())), | |
|
57 | editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()), | |
|
58 | key_bindings=kb, | |
|
59 | history=self.shell.debugger_history, | |
|
60 | completer=self._ptcomp, | |
|
61 | enable_history_search=True, | |
|
62 | mouse_support=self.shell.mouse_support, | |
|
63 | complete_style=self.shell.pt_complete_style, | |
|
64 | style=self.shell.style, | |
|
65 | color_depth=self.shell.color_depth, | |
|
64 | 66 | ) |
|
65 | 67 | |
|
68 | if not PTK3: | |
|
69 | options['inputhook'] = self.shell.inputhook | |
|
70 | self.pt_app = PromptSession(**options) | |
|
71 | ||
|
66 | 72 | def cmdloop(self, intro=None): |
|
67 | 73 | """Repeatedly issue a prompt, accept input, parse an initial prefix |
|
68 | 74 | off the received input, and dispatch to action methods, passing them |
@@ -1,5 +1,6 b'' | |||
|
1 | 1 | """IPython terminal interface using prompt_toolkit""" |
|
2 | 2 | |
|
3 | import asyncio | |
|
3 | 4 | import os |
|
4 | 5 | import sys |
|
5 | 6 | import warnings |
@@ -25,6 +26,7 b' from prompt_toolkit.patch_stdout import patch_stdout' | |||
|
25 | 26 | from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text |
|
26 | 27 | from prompt_toolkit.styles import DynamicStyle, merge_styles |
|
27 | 28 | from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict |
|
29 | from prompt_toolkit import __version__ as ptk_version | |
|
28 | 30 | |
|
29 | 31 | from pygments.styles import get_style_by_name |
|
30 | 32 | from pygments.style import Style |
@@ -38,6 +40,7 b' from .ptutils import IPythonPTCompleter, IPythonPTLexer' | |||
|
38 | 40 | from .shortcuts import create_ipython_shortcuts |
|
39 | 41 | |
|
40 | 42 | DISPLAY_BANNER_DEPRECATED = object() |
|
43 | PTK3 = ptk_version.startswith('3.') | |
|
41 | 44 | |
|
42 | 45 | |
|
43 | 46 | class _NoStyle(Style): pass |
@@ -87,7 +90,17 b' else:' | |||
|
87 | 90 | |
|
88 | 91 | _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty) |
|
89 | 92 | |
|
93 | def black_reformat_handler(text_before_cursor): | |
|
94 | import black | |
|
95 | formatted_text = black.format_str(text_before_cursor, mode=black.FileMode()) | |
|
96 | if not text_before_cursor.endswith('\n') and formatted_text.endswith('\n'): | |
|
97 | formatted_text = formatted_text[:-1] | |
|
98 | return formatted_text | |
|
99 | ||
|
100 | ||
|
90 | 101 | class TerminalInteractiveShell(InteractiveShell): |
|
102 | mime_renderers = Dict().tag(config=True) | |
|
103 | ||
|
91 | 104 | space_for_menu = Integer(6, help='Number of line at the bottom of the screen ' |
|
92 | 105 | 'to reserve for the completion menu' |
|
93 | 106 | ).tag(config=True) |
@@ -120,6 +133,11 b' class TerminalInteractiveShell(InteractiveShell):' | |||
|
120 | 133 | help="Shortcut style to use at the prompt. 'vi' or 'emacs'.", |
|
121 | 134 | ).tag(config=True) |
|
122 | 135 | |
|
136 | autoformatter = Unicode(None, | |
|
137 | help="Autoformatter to reformat Terminal code. Can be `'black'` or `None`", | |
|
138 | allow_none=True | |
|
139 | ).tag(config=True) | |
|
140 | ||
|
123 | 141 | mouse_support = Bool(False, |
|
124 | 142 | help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)" |
|
125 | 143 | ).tag(config=True) |
@@ -150,6 +168,16 b' class TerminalInteractiveShell(InteractiveShell):' | |||
|
150 | 168 | if self.pt_app: |
|
151 | 169 | self.pt_app.editing_mode = u_mode |
|
152 | 170 | |
|
171 | @observe('autoformatter') | |
|
172 | def _autoformatter_changed(self, change): | |
|
173 | formatter = change.new | |
|
174 | if formatter is None: | |
|
175 | self.reformat_handler = lambda x:x | |
|
176 | elif formatter == 'black': | |
|
177 | self.reformat_handler = black_reformat_handler | |
|
178 | else: | |
|
179 | raise ValueError | |
|
180 | ||
|
153 | 181 | @observe('highlighting_style') |
|
154 | 182 | @observe('colors') |
|
155 | 183 | def _highlighting_style_changed(self, change): |
@@ -282,6 +310,7 b' class TerminalInteractiveShell(InteractiveShell):' | |||
|
282 | 310 | |
|
283 | 311 | editing_mode = getattr(EditingMode, self.editing_mode.upper()) |
|
284 | 312 | |
|
313 | self.pt_loop = asyncio.new_event_loop() | |
|
285 | 314 | self.pt_app = PromptSession( |
|
286 | 315 | editing_mode=editing_mode, |
|
287 | 316 | key_bindings=key_bindings, |
@@ -390,7 +419,7 b' class TerminalInteractiveShell(InteractiveShell):' | |||
|
390 | 419 | # work around this. |
|
391 | 420 | get_message = get_message() |
|
392 | 421 | |
|
393 |
|
|
|
422 | options = { | |
|
394 | 423 | 'complete_in_thread': False, |
|
395 | 424 | 'lexer':IPythonPTLexer(), |
|
396 | 425 | 'reserve_space_for_menu':self.space_for_menu, |
@@ -407,8 +436,11 b' class TerminalInteractiveShell(InteractiveShell):' | |||
|
407 | 436 | processor=HighlightMatchingBracketProcessor(chars='[](){}'), |
|
408 | 437 | filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() & |
|
409 | 438 | Condition(lambda: self.highlight_matching_brackets))], |
|
410 | 'inputhook': self.inputhook, | |
|
411 | 439 | } |
|
440 | if not PTK3: | |
|
441 | options['inputhook'] = self.inputhook | |
|
442 | ||
|
443 | return options | |
|
412 | 444 | |
|
413 | 445 | def prompt_for_code(self): |
|
414 | 446 | if self.rl_next_input: |
@@ -417,11 +449,28 b' class TerminalInteractiveShell(InteractiveShell):' | |||
|
417 | 449 | else: |
|
418 | 450 | default = '' |
|
419 | 451 | |
|
420 | with patch_stdout(raw=True): | |
|
421 | text = self.pt_app.prompt( | |
|
422 | default=default, | |
|
423 | # pre_run=self.pre_prompt,# reset_current_buffer=True, | |
|
424 | **self._extra_prompt_options()) | |
|
452 | # In order to make sure that asyncio code written in the | |
|
453 | # interactive shell doesn't interfere with the prompt, we run the | |
|
454 | # prompt in a different event loop. | |
|
455 | # If we don't do this, people could spawn coroutine with a | |
|
456 | # while/true inside which will freeze the prompt. | |
|
457 | ||
|
458 | try: | |
|
459 | old_loop = asyncio.get_event_loop() | |
|
460 | except RuntimeError: | |
|
461 | # This happens when the user used `asyncio.run()`. | |
|
462 | old_loop = None | |
|
463 | ||
|
464 | asyncio.set_event_loop(self.pt_loop) | |
|
465 | try: | |
|
466 | with patch_stdout(raw=True): | |
|
467 | text = self.pt_app.prompt( | |
|
468 | default=default, | |
|
469 | **self._extra_prompt_options()) | |
|
470 | finally: | |
|
471 | # Restore the original event loop. | |
|
472 | asyncio.set_event_loop(old_loop) | |
|
473 | ||
|
425 | 474 | return text |
|
426 | 475 | |
|
427 | 476 | def enable_win_unicode_console(self): |
@@ -528,12 +577,33 b' class TerminalInteractiveShell(InteractiveShell):' | |||
|
528 | 577 | |
|
529 | 578 | active_eventloop = None |
|
530 | 579 | def enable_gui(self, gui=None): |
|
531 | if gui: | |
|
580 | if gui and (gui != 'inline') : | |
|
532 | 581 | self.active_eventloop, self._inputhook =\ |
|
533 | 582 | get_inputhook_name_and_func(gui) |
|
534 | 583 | else: |
|
535 | 584 | self.active_eventloop = self._inputhook = None |
|
536 | 585 | |
|
586 | # For prompt_toolkit 3.0. We have to create an asyncio event loop with | |
|
587 | # this inputhook. | |
|
588 | if PTK3: | |
|
589 | import asyncio | |
|
590 | from prompt_toolkit.eventloop import new_eventloop_with_inputhook | |
|
591 | ||
|
592 | if gui == 'asyncio': | |
|
593 | # When we integrate the asyncio event loop, run the UI in the | |
|
594 | # same event loop as the rest of the code. don't use an actual | |
|
595 | # input hook. (Asyncio is not made for nesting event loops.) | |
|
596 | self.pt_loop = asyncio.get_event_loop() | |
|
597 | ||
|
598 | elif self._inputhook: | |
|
599 | # If an inputhook was set, create a new asyncio event loop with | |
|
600 | # this inputhook for the prompt. | |
|
601 | self.pt_loop = new_eventloop_with_inputhook(self._inputhook) | |
|
602 | else: | |
|
603 | # When there's no inputhook, run the prompt in a separate | |
|
604 | # asyncio event loop. | |
|
605 | self.pt_loop = asyncio.new_event_loop() | |
|
606 | ||
|
537 | 607 | # Run !system commands directly, not through pipes, so terminal programs |
|
538 | 608 | # work correctly. |
|
539 | 609 | system = InteractiveShell.system_raw |
@@ -89,3 +89,14 b' class RichPromptDisplayHook(DisplayHook):' | |||
|
89 | 89 | ) |
|
90 | 90 | else: |
|
91 | 91 | sys.stdout.write(prompt_txt) |
|
92 | ||
|
93 | def write_format_data(self, format_dict, md_dict=None) -> None: | |
|
94 | if self.shell.mime_renderers: | |
|
95 | ||
|
96 | for mime, handler in self.shell.mime_renderers.items(): | |
|
97 | if mime in format_dict: | |
|
98 | handler(format_dict[mime], None) | |
|
99 | return | |
|
100 | ||
|
101 | super().write_format_data(format_dict, md_dict) | |
|
102 |
@@ -13,6 +13,7 b' backends = [' | |||
|
13 | 13 | 'wx', |
|
14 | 14 | 'pyglet', 'glut', |
|
15 | 15 | 'osx', |
|
16 | 'asyncio' | |
|
16 | 17 | ] |
|
17 | 18 | |
|
18 | 19 | registered = {} |
@@ -44,6 +44,15 b' def create_ipython_shortcuts(shell):' | |||
|
44 | 44 | & insert_mode |
|
45 | 45 | ))(return_handler) |
|
46 | 46 | |
|
47 | def reformat_and_execute(event): | |
|
48 | reformat_text_before_cursor(event.current_buffer, event.current_buffer.document, shell) | |
|
49 | event.current_buffer.validate_and_handle() | |
|
50 | ||
|
51 | kb.add('escape', 'enter', filter=(has_focus(DEFAULT_BUFFER) | |
|
52 | & ~has_selection | |
|
53 | & insert_mode | |
|
54 | ))(reformat_and_execute) | |
|
55 | ||
|
47 | 56 | kb.add('c-\\')(force_exit) |
|
48 | 57 | |
|
49 | 58 | kb.add('c-p', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)) |
@@ -86,7 +95,17 b' def create_ipython_shortcuts(shell):' | |||
|
86 | 95 | return kb |
|
87 | 96 | |
|
88 | 97 | |
|
98 | def reformat_text_before_cursor(buffer, document, shell): | |
|
99 | text = buffer.delete_before_cursor(len(document.text[:document.cursor_position])) | |
|
100 | try: | |
|
101 | formatted_text = shell.reformat_handler(text) | |
|
102 | buffer.insert_text(formatted_text) | |
|
103 | except Exception as e: | |
|
104 | buffer.insert_text(text) | |
|
105 | ||
|
106 | ||
|
89 | 107 | def newline_or_execute_outer(shell): |
|
108 | ||
|
90 | 109 | def newline_or_execute(event): |
|
91 | 110 | """When the user presses return, insert a newline or execute the code.""" |
|
92 | 111 | b = event.current_buffer |
@@ -107,7 +126,12 b' def newline_or_execute_outer(shell):' | |||
|
107 | 126 | else: |
|
108 | 127 | check_text = d.text[:d.cursor_position] |
|
109 | 128 | status, indent = shell.check_complete(check_text) |
|
110 | ||
|
129 | ||
|
130 | # if all we have after the cursor is whitespace: reformat current text | |
|
131 | # before cursor | |
|
132 | after_cursor = d.text[d.cursor_position:] | |
|
133 | if not after_cursor.strip(): | |
|
134 | reformat_text_before_cursor(b, d, shell) | |
|
111 | 135 | if not (d.on_last_line or |
|
112 | 136 | d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end() |
|
113 | 137 | ): |
@@ -118,6 +142,7 b' def newline_or_execute_outer(shell):' | |||
|
118 | 142 | return |
|
119 | 143 | |
|
120 | 144 | if (status != 'incomplete') and b.accept_handler: |
|
145 | reformat_text_before_cursor(b, d, shell) | |
|
121 | 146 | b.validate_and_handle() |
|
122 | 147 | else: |
|
123 | 148 | if shell.autoindent: |
@@ -169,7 +169,7 b' class HomeDirError(Exception):' | |||
|
169 | 169 | pass |
|
170 | 170 | |
|
171 | 171 | |
|
172 | def get_home_dir(require_writable=False): | |
|
172 | def get_home_dir(require_writable=False) -> str: | |
|
173 | 173 | """Return the 'home' directory, as a unicode string. |
|
174 | 174 | |
|
175 | 175 | Uses os.path.expanduser('~'), and checks for writability. |
@@ -197,21 +197,18 b' def get_home_dir(require_writable=False):' | |||
|
197 | 197 | if not _writable_dir(homedir) and os.name == 'nt': |
|
198 | 198 | # expanduser failed, use the registry to get the 'My Documents' folder. |
|
199 | 199 | try: |
|
200 | try: | |
|
201 | import winreg as wreg # Py 3 | |
|
202 | except ImportError: | |
|
203 | import _winreg as wreg # Py 2 | |
|
204 | key = wreg.OpenKey( | |
|
200 | import winreg as wreg | |
|
201 | with wreg.OpenKey( | |
|
205 | 202 | wreg.HKEY_CURRENT_USER, |
|
206 | 203 | r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" |
|
207 | ) | |
|
208 | homedir = wreg.QueryValueEx(key,'Personal')[0] | |
|
209 | key.Close() | |
|
204 | ) as key: | |
|
205 | homedir = wreg.QueryValueEx(key,'Personal')[0] | |
|
210 | 206 | except: |
|
211 | 207 | pass |
|
212 | 208 | |
|
213 | 209 | if (not require_writable) or _writable_dir(homedir): |
|
214 | return py3compat.cast_unicode(homedir, fs_encoding) | |
|
210 | assert isinstance(homedir, str), "Homedir shoudl be unicode not bytes" | |
|
211 | return homedir | |
|
215 | 212 | else: |
|
216 | 213 | raise HomeDirError('%s is not a writable dir, ' |
|
217 | 214 | 'set $HOME environment variable to override' % homedir) |
@@ -254,31 +251,31 b' def get_xdg_cache_dir():' | |||
|
254 | 251 | |
|
255 | 252 | @undoc |
|
256 | 253 | def get_ipython_dir(): |
|
257 | warn("get_ipython_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2) | |
|
254 | warn("get_ipython_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2) | |
|
258 | 255 | from IPython.paths import get_ipython_dir |
|
259 | 256 | return get_ipython_dir() |
|
260 | 257 | |
|
261 | 258 | @undoc |
|
262 | 259 | def get_ipython_cache_dir(): |
|
263 | warn("get_ipython_cache_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2) | |
|
260 | warn("get_ipython_cache_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2) | |
|
264 | 261 | from IPython.paths import get_ipython_cache_dir |
|
265 | 262 | return get_ipython_cache_dir() |
|
266 | 263 | |
|
267 | 264 | @undoc |
|
268 | 265 | def get_ipython_package_dir(): |
|
269 | warn("get_ipython_package_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2) | |
|
266 | warn("get_ipython_package_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2) | |
|
270 | 267 | from IPython.paths import get_ipython_package_dir |
|
271 | 268 | return get_ipython_package_dir() |
|
272 | 269 | |
|
273 | 270 | @undoc |
|
274 | 271 | def get_ipython_module_path(module_str): |
|
275 | warn("get_ipython_module_path has moved to the IPython.paths module since IPython 4.0.", stacklevel=2) | |
|
272 | warn("get_ipython_module_path has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2) | |
|
276 | 273 | from IPython.paths import get_ipython_module_path |
|
277 | 274 | return get_ipython_module_path(module_str) |
|
278 | 275 | |
|
279 | 276 | @undoc |
|
280 | 277 | def locate_profile(profile='default'): |
|
281 | warn("locate_profile has moved to the IPython.paths module since IPython 4.0.", stacklevel=2) | |
|
278 | warn("locate_profile has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2) | |
|
282 | 279 | from IPython.paths import locate_profile |
|
283 | 280 | return locate_profile(profile=profile) |
|
284 | 281 |
@@ -171,8 +171,12 b' def test_get_home_dir_8():' | |||
|
171 | 171 | env.pop(key, None) |
|
172 | 172 | |
|
173 | 173 | class key: |
|
174 | def __enter__(self): | |
|
175 | pass | |
|
174 | 176 | def Close(self): |
|
175 | 177 | pass |
|
178 | def __exit__(*args, **kwargs): | |
|
179 | pass | |
|
176 | 180 | |
|
177 | 181 | with patch.object(wreg, 'OpenKey', return_value=key()), \ |
|
178 | 182 | patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]): |
@@ -117,3 +117,15 b' version::' | |||
|
117 | 117 | ... |
|
118 | 118 | install_requires=install_req |
|
119 | 119 | ) |
|
120 | ||
|
121 | Alternatives to IPython | |
|
122 | ======================= | |
|
123 | ||
|
124 | IPython may not be to your taste; if that's the case there might be similar | |
|
125 | project that you might want to use: | |
|
126 | ||
|
127 | - the classic Python REPL. | |
|
128 | - `bpython <https://bpython-interpreter.org/>`_ | |
|
129 | - `mypython <https://www.asmeurer.com/mypython/>`_ | |
|
130 | - `ptpython and ptipython <https://pypi.org/project/ptpython/>` | |
|
131 | - `xonsh <https://xon.sh/>` |
@@ -29,6 +29,7 b' Extending and integrating with IPython' | |||
|
29 | 29 | extensions/index |
|
30 | 30 | integrating |
|
31 | 31 | custommagics |
|
32 | shell_mimerenderer | |
|
32 | 33 | inputtransforms |
|
33 | 34 | callbacks |
|
34 | 35 | eventloops |
@@ -1,13 +1,14 b'' | |||
|
1 | 1 | .. _execution_semantics: |
|
2 | 2 | |
|
3 |
Execution |
|
|
4 |
======================================== |
|
|
3 | Execution of cells in the IPython kernel | |
|
4 | ======================================== | |
|
5 | 5 | |
|
6 | The execution of user code consists of the following phases: | |
|
6 | When IPython kernel receives `execute_request <https://jupyter-client.readthedocs.io/en/latest/messaging.html#execute>`_ | |
|
7 | with user code, it processes the message in the following phases: | |
|
7 | 8 | |
|
8 | 9 | 1. Fire the ``pre_execute`` event. |
|
9 | 10 | 2. Fire the ``pre_run_cell`` event unless silent is ``True``. |
|
10 |
3. Execute the ``code`` |
|
|
11 | 3. Execute ``run_cell`` method to preprocess ``code``, compile and run it, see below for details. | |
|
11 | 12 | 4. If execution succeeds, expressions in ``user_expressions`` are computed. |
|
12 | 13 | This ensures that any error in the expressions don't affect the main code execution. |
|
13 | 14 | 5. Fire the ``post_execute`` event. |
@@ -18,9 +19,15 b' The execution of user code consists of the following phases:' | |||
|
18 | 19 | :doc:`/config/callbacks` |
|
19 | 20 | |
|
20 | 21 | |
|
21 | To understand how the ``code`` field is executed, one must know that Python | |
|
22 | code can be compiled in one of three modes (controlled by the ``mode`` argument | |
|
23 | to the :func:`compile` builtin): | |
|
22 | Running user ``code`` | |
|
23 | ===================== | |
|
24 | ||
|
25 | First, the ``code`` cell is transformed to expand ``%magic`` and ``!system`` | |
|
26 | commands by ``IPython.core.inputtransformer2``. Then expanded cell is compiled | |
|
27 | using standard Python :func:`compile` function and executed. | |
|
28 | ||
|
29 | Python :func:`compile` function provides ``mode`` argument to select one | |
|
30 | of three ways of compiling code: | |
|
24 | 31 | |
|
25 | 32 | *single* |
|
26 | 33 | Valid for a single interactive statement (though the source can contain |
@@ -50,16 +57,16 b" execution in 'single' mode, and then:" | |||
|
50 | 57 | |
|
51 | 58 | - If there is more than one block: |
|
52 | 59 | |
|
53 |
* if the last |
|
|
60 | * if the last block is a single line long, run all but the last in 'exec' mode | |
|
54 | 61 | and the very last one in 'single' mode. This makes it easy to type simple |
|
55 | 62 | expressions at the end to see computed values. |
|
56 | 63 | |
|
57 |
* if the last |
|
|
64 | * if the last block is no more than two lines long, run all but the last in | |
|
58 | 65 | 'exec' mode and the very last one in 'single' mode. This makes it easy to |
|
59 | 66 | type simple expressions at the end to see computed values. - otherwise |
|
60 | 67 | (last one is also multiline), run all in 'exec' mode |
|
61 | 68 | |
|
62 |
* otherwise (last |
|
|
69 | * otherwise (last block is also multiline), run all in 'exec' mode as a single | |
|
63 | 70 | unit. |
|
64 | 71 | |
|
65 | 72 |
@@ -11,7 +11,7 b' This document describes in-flight development work.' | |||
|
11 | 11 | `docs/source/whatsnew/pr` folder |
|
12 | 12 | |
|
13 | 13 | |
|
14 |
Released .... ...., 201 |
|
|
14 | Released .... ...., 2019 | |
|
15 | 15 | |
|
16 | 16 | |
|
17 | 17 | Need to be updated: |
@@ -22,8 +22,6 b' Need to be updated:' | |||
|
22 | 22 | |
|
23 | 23 | pr/* |
|
24 | 24 | |
|
25 | ||
|
26 | ||
|
27 | 25 | .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT. |
|
28 | 26 | |
|
29 | 27 |
@@ -1,6 +1,48 b'' | |||
|
1 | 1 | Issues closed in the 7.x development cycle |
|
2 | 2 | ========================================== |
|
3 | 3 | |
|
4 | Issues closed in 7.10.1 | |
|
5 | ----------------------- | |
|
6 | ||
|
7 | GitHub stats for 2019/11/27 - 2019/12/01 (tag: 7.10.0) | |
|
8 | ||
|
9 | These lists are automatically generated, and may be incomplete or contain duplicates. | |
|
10 | ||
|
11 | We closed 5 issues and merged 7 pull requests. | |
|
12 | The full list can be seen `on GitHub <https://github.com/ipython/ipython/issues?q=milestone%3A7.10.1>`__ | |
|
13 | ||
|
14 | The following 2 authors contributed 14 commits. | |
|
15 | ||
|
16 | * Jonathan Slenders | |
|
17 | * Matthias Bussonnier | |
|
18 | ||
|
19 | Issues closed in 7.10 | |
|
20 | --------------------- | |
|
21 | ||
|
22 | GitHub stats for 2019/10/25 - 2019/11/27 (tag: 7.9.0) | |
|
23 | ||
|
24 | These lists are automatically generated, and may be incomplete or contain duplicates. | |
|
25 | ||
|
26 | We closed 4 issues and merged 22 pull requests. | |
|
27 | The full list can be seen `on GitHub <https://github.com/ipython/ipython/issues?q=milestone%3A7.10>`__ | |
|
28 | ||
|
29 | The following 15 authors contributed 101 commits. | |
|
30 | ||
|
31 | * anatoly techtonik | |
|
32 | * Ben Lewis | |
|
33 | * Benjamin Ragan-Kelley | |
|
34 | * Gerrit Buss | |
|
35 | * grey275 | |
|
36 | * Gökcen Eraslan | |
|
37 | * Jonathan Slenders | |
|
38 | * Joris Van den Bossche | |
|
39 | * kousik | |
|
40 | * Matthias Bussonnier | |
|
41 | * Nicholas Bollweg | |
|
42 | * Paul McCarthy | |
|
43 | * Srinivas Reddy Thatiparthy | |
|
44 | * Timo Kaufmann | |
|
45 | * Tony Fast | |
|
4 | 46 | |
|
5 | 47 | Issues closed in 7.9 |
|
6 | 48 | -------------------- |
@@ -2,8 +2,156 b'' | |||
|
2 | 2 | 7.x Series |
|
3 | 3 | ============ |
|
4 | 4 | |
|
5 | .. _version 7101: | |
|
6 | ||
|
7 | IPython 7.10.1 | |
|
8 | ============== | |
|
9 | ||
|
10 | IPython 7.10.1 fix a couple of incompatibilities with Prompt toolkit 3 (please | |
|
11 | update Prompt toolkit to 3.0.2 at least), and fixes some interaction with | |
|
12 | headless IPython. | |
|
13 | ||
|
14 | .. _version 7100: | |
|
15 | ||
|
16 | IPython 7.10.0 | |
|
17 | ============== | |
|
18 | ||
|
19 | IPython 7.10 is the first double digit minor release in the last decade, and | |
|
20 | first since the release of IPython 1.0, previous double digit minor release was | |
|
21 | in August 2009. | |
|
22 | ||
|
23 | We've been trying to give you regular release on the last Friday of every month | |
|
24 | for a guaranty of rapid access to bug fixes and new features. | |
|
25 | ||
|
26 | Unlike the previous first few releases that have seen only a couple of code | |
|
27 | changes, 7.10 bring a number of changes, new features and bugfixes. | |
|
28 | ||
|
29 | Stop Support for Python 3.5 – Adopt NEP 29 | |
|
30 | ------------------------------------------ | |
|
31 | ||
|
32 | IPython has decided to follow the informational `NEP 29 | |
|
33 | <https://numpy.org/neps/nep-0029-deprecation_policy.html>`_ which layout a clear | |
|
34 | policy as to which version of (C)Python and NumPy are supported. | |
|
35 | ||
|
36 | We thus dropped support for Python 3.5, and cleaned up a number of code path | |
|
37 | that were Python-version dependant. If you are on 3.5 or earlier pip should | |
|
38 | automatically give you the latest compatible version of IPython so you do not | |
|
39 | need to pin to a given version. | |
|
40 | ||
|
41 | Support for Prompt Toolkit 3.0 | |
|
42 | ------------------------------ | |
|
43 | ||
|
44 | Prompt Toolkit 3.0 was release a week before IPython 7.10 and introduces a few | |
|
45 | breaking changes. We believe IPython 7.10 should be compatible with both Prompt | |
|
46 | Toolkit 2.x and 3.x, though it has not been extensively tested with 3.x so | |
|
47 | please report any issues. | |
|
48 | ||
|
49 | ||
|
50 | Prompt Rendering Performance improvements | |
|
51 | ----------------------------------------- | |
|
52 | ||
|
53 | Pull Request :ghpull:`11933` introduced an optimisation in the prompt rendering | |
|
54 | logic that should decrease the resource usage of IPython when using the | |
|
55 | _default_ configuration but could potentially introduce a regression of | |
|
56 | functionalities if you are using a custom prompt. | |
|
57 | ||
|
58 | We know assume if you haven't changed the default keybindings that the prompt | |
|
59 | **will not change** during the duration of your input – which is for example | |
|
60 | not true when using vi insert mode that switches between `[ins]` and `[nor]` | |
|
61 | for the current mode. | |
|
62 | ||
|
63 | If you are experiencing any issue let us know. | |
|
64 | ||
|
65 | Code autoformatting | |
|
66 | ------------------- | |
|
67 | ||
|
68 | The IPython terminal can now auto format your code just before entering a new | |
|
69 | line or executing a command. To do so use the | |
|
70 | ``--TerminalInteractiveShell.autoformatter`` option and set it to ``'black'``; | |
|
71 | if black is installed IPython will use black to format your code when possible. | |
|
72 | ||
|
73 | IPython cannot always properly format your code; in particular it will | |
|
74 | auto formatting with *black* will only work if: | |
|
75 | ||
|
76 | - Your code does not contains magics or special python syntax. | |
|
77 | ||
|
78 | - There is no code after your cursor. | |
|
79 | ||
|
80 | The Black API is also still in motion; so this may not work with all versions of | |
|
81 | black. | |
|
82 | ||
|
83 | It should be possible to register custom formatter, though the API is till in | |
|
84 | flux. | |
|
85 | ||
|
86 | Arbitrary Mimetypes Handing in Terminal (Aka inline images in terminal) | |
|
87 | ----------------------------------------------------------------------- | |
|
88 | ||
|
89 | When using IPython terminal it is now possible to register function to handle | |
|
90 | arbitrary mimetypes. While rendering non-text based representation was possible in | |
|
91 | many jupyter frontend; it was not possible in terminal IPython, as usually | |
|
92 | terminal are limited to displaying text. As many terminal these days provide | |
|
93 | escape sequences to display non-text; bringing this loved feature to IPython CLI | |
|
94 | made a lot of sens. This functionality will not only allow inline images; but | |
|
95 | allow opening of external program; for example ``mplayer`` to "display" sound | |
|
96 | files. | |
|
97 | ||
|
98 | So far only the hooks necessary for this are in place, but no default mime | |
|
99 | renderers added; so inline images will only be available via extensions. We will | |
|
100 | progressively enable these features by default in the next few releases, and | |
|
101 | contribution is welcomed. | |
|
102 | ||
|
103 | We welcome any feedback on the API. See :ref:`shell_mimerenderer` for more | |
|
104 | informations. | |
|
105 | ||
|
106 | This is originally based on work form in :ghpull:`10610` from @stephanh42 | |
|
107 | started over two years ago, and still a lot need to be done. | |
|
108 | ||
|
109 | MISC | |
|
110 | ---- | |
|
111 | ||
|
112 | - Completions can define their own ordering :ghpull:`11855` | |
|
113 | - Enable Plotting in the same cell than the one that import matplotlib | |
|
114 | :ghpull:`11916` | |
|
115 | - Allow to store and restore multiple variables at once :ghpull:`11930` | |
|
116 | ||
|
117 | You can see `all pull-requests <https://github.com/ipython/ipython/pulls?q=is%3Apr+milestone%3A7.10+is%3Aclosed>`_ for this release. | |
|
118 | ||
|
119 | API Changes | |
|
120 | ----------- | |
|
121 | ||
|
122 | Change of API and exposed objects automatically detected using `frappuccino <https://pypi.org/project/frappuccino/>`_ (still in beta): | |
|
123 | ||
|
124 | The following items are new in IPython 7.10:: | |
|
125 | ||
|
126 | + IPython.terminal.shortcuts.reformat_text_before_cursor(buffer, document, shell) | |
|
127 | + IPython.terminal.interactiveshell.PTK3 | |
|
128 | + IPython.terminal.interactiveshell.black_reformat_handler(text_before_cursor) | |
|
129 | + IPython.terminal.prompts.RichPromptDisplayHook.write_format_data(self, format_dict, md_dict='None') | |
|
130 | ||
|
131 | The following items have been removed in 7.10:: | |
|
132 | ||
|
133 | - IPython.lib.pretty.DICT_IS_ORDERED | |
|
134 | ||
|
135 | The following signatures differ between versions:: | |
|
136 | ||
|
137 | - IPython.extensions.storemagic.restore_aliases(ip) | |
|
138 | + IPython.extensions.storemagic.restore_aliases(ip, alias='None') | |
|
139 | ||
|
140 | Special Thanks | |
|
141 | -------------- | |
|
142 | ||
|
143 | - @stephanh42 who started the work on inline images in terminal 2 years ago | |
|
144 | - @augustogoulart who spent a lot of time triaging issues and responding to | |
|
145 | users. | |
|
146 | - @con-f-use who is my (@Carreau) first sponsor on GitHub, as a reminder if you | |
|
147 | like IPython, Jupyter and many other library of the SciPy stack you can | |
|
148 | donate to numfocus.org non profit | |
|
149 | ||
|
5 | 150 | .. _version 790: |
|
6 | 151 | |
|
152 | IPython 7.9.0 | |
|
153 | ============= | |
|
154 | ||
|
7 | 155 | IPython 7.9 is a small release with a couple of improvement and bug fixes. |
|
8 | 156 | |
|
9 | 157 | - Xterm terminal title should be restored on exit :ghpull:`11910` |
@@ -13,12 +161,12 b' IPython 7.9 is a small release with a couple of improvement and bug fixes.' | |||
|
13 | 161 | find all objects needing reload. This should avoid large objects traversal |
|
14 | 162 | like pandas dataframes. :ghpull:`11876` |
|
15 | 163 | - Get ready for Python 4. :ghpull:`11874` |
|
16 |
- `%env` Magic no |
|
|
164 | - `%env` Magic now has heuristic to hide potentially sensitive values :ghpull:`11896` | |
|
17 | 165 | |
|
18 | 166 | This is a small release despite a number of Pull Request Pending that need to |
|
19 | 167 | be reviewed/worked on. Many of the core developers have been busy outside of |
|
20 | 168 | IPython/Jupyter and we thanks all contributor for their patience; we'll work on |
|
21 |
these as soon as we have time. |
|
|
169 | these as soon as we have time. | |
|
22 | 170 | |
|
23 | 171 | |
|
24 | 172 | .. _version780: |
@@ -190,7 +190,7 b' install_requires = [' | |||
|
190 | 190 | 'decorator', |
|
191 | 191 | 'pickleshare', |
|
192 | 192 | 'traitlets>=4.2', |
|
193 |
'prompt_toolkit>=2.0.0,< |
|
|
193 | 'prompt_toolkit>=2.0.0,<3.1.0,!=3.0.0,!=3.0.1', | |
|
194 | 194 | 'pygments', |
|
195 | 195 | 'backcall', |
|
196 | 196 | ] |
@@ -1,5 +1,5 b'' | |||
|
1 | 1 | # Simple tool to help for release |
|
2 |
# when releasing with bash, simple |
|
|
2 | # when releasing with bash, simple source it to get asked questions. | |
|
3 | 3 | |
|
4 | 4 | # misc check before starting |
|
5 | 5 | |
@@ -9,14 +9,6 b" python -c 'import sphinx'" | |||
|
9 | 9 | python -c 'import sphinx_rtd_theme' |
|
10 | 10 | python -c 'import nose' |
|
11 | 11 | |
|
12 | echo -n 'PREV_RELEASE (X.y.z):' | |
|
13 | read PREV_RELEASE | |
|
14 | echo -n 'MILESTONE (X.y):' | |
|
15 | read MILESTONE | |
|
16 | echo -n 'VERSION (X.y.z):' | |
|
17 | read VERSION | |
|
18 | echo -n 'branch (master|X.y):' | |
|
19 | read branch | |
|
20 | 12 | |
|
21 | 13 | BLACK=$(tput setaf 1) |
|
22 | 14 | RED=$(tput setaf 1) |
@@ -28,30 +20,66 b' CYAN=$(tput setaf 6)' | |||
|
28 | 20 | WHITE=$(tput setaf 7) |
|
29 | 21 | NOR=$(tput sgr0) |
|
30 | 22 | |
|
31 | echo | |
|
32 | echo $BLUE"Updating what's new with informations from docs/source/whatsnew/pr"$NOR | |
|
33 | python tools/update_whatsnew.py | |
|
34 | 23 | |
|
35 | echo | |
|
36 | echo $BLUE"please move the contents of "docs/source/whatsnew/development.rst" to version-X.rst"$NOR | |
|
37 | echo $GREEN"Press enter to continue"$NOR | |
|
38 | read | |
|
24 | echo -n "PREV_RELEASE (X.y.z) [$PREV_RELEASE]: " | |
|
25 | read input | |
|
26 | PREV_RELEASE=${input:-$PREV_RELEASE} | |
|
27 | echo -n "MILESTONE (X.y) [$MILESTONE]: " | |
|
28 | read input | |
|
29 | MILESTONE=${input:-$MILESTONE} | |
|
30 | echo -n "VERSION (X.y.z) [$VERSION]:" | |
|
31 | read input | |
|
32 | VERSION=${input:-$VERSION} | |
|
33 | echo -n "BRANCH (master|X.y) [$BRANCH]:" | |
|
34 | read input | |
|
35 | BRANCH=${input:-$BRANCH} | |
|
36 | ||
|
37 | ask_section(){ | |
|
38 | echo | |
|
39 | echo $BLUE"$1"$NOR | |
|
40 | echo -n $GREEN"Press Enter to continue, S to skip: "$GREEN | |
|
41 | read -n1 value | |
|
42 | echo | |
|
43 | if [ -z $value ] || [ $value = 'y' ] ; then | |
|
44 | return 0 | |
|
45 | fi | |
|
46 | return 1 | |
|
47 | } | |
|
48 | ||
|
39 | 49 | |
|
40 | echo | |
|
41 | echo $BLUE"here are all the authors that contributed to this release:"$NOR | |
|
42 | git log --format="%aN <%aE>" $PREV_RELEASE... | sort -u -f | |
|
43 | 50 | |
|
44 | 51 | echo |
|
45 | echo $BLUE"If you see any duplicates cancel (Ctrl-C), then edit .mailmap."$GREEN"Press enter to continue:"$NOR | |
|
46 | read | |
|
52 | if ask_section "Updating what's new with informations from docs/source/whatsnew/pr" | |
|
53 | then | |
|
54 | python tools/update_whatsnew.py | |
|
47 | 55 | |
|
48 | echo $BLUE"generating stats"$NOR | |
|
49 | python tools/github_stats.py --milestone $MILESTONE > stats.rst | |
|
56 | echo | |
|
57 | echo $BLUE"please move the contents of "docs/source/whatsnew/development.rst" to version-X.rst"$NOR | |
|
58 | echo $GREEN"Press enter to continue"$NOR | |
|
59 | read | |
|
60 | fi | |
|
50 | 61 | |
|
51 | echo $BLUE"stats.rst files generated."$NOR | |
|
52 | echo $GREEN"Please merge it with the right file (github-stats-X.rst) and commit."$NOR | |
|
53 | echo $GREEN"press enter to continue."$NOR | |
|
54 | read | |
|
62 | if ask_section "Gen Stats, and authors" | |
|
63 | then | |
|
64 | ||
|
65 | echo | |
|
66 | echo $BLUE"here are all the authors that contributed to this release:"$NOR | |
|
67 | git log --format="%aN <%aE>" $PREV_RELEASE... | sort -u -f | |
|
68 | ||
|
69 | echo | |
|
70 | echo $BLUE"If you see any duplicates cancel (Ctrl-C), then edit .mailmap." | |
|
71 | echo $GREEN"Press enter to continue:"$NOR | |
|
72 | read | |
|
73 | ||
|
74 | echo $BLUE"generating stats"$NOR | |
|
75 | python tools/github_stats.py --milestone $MILESTONE > stats.rst | |
|
76 | ||
|
77 | echo $BLUE"stats.rst files generated."$NOR | |
|
78 | echo $GREEN"Please merge it with the right file (github-stats-X.rst) and commit."$NOR | |
|
79 | echo $GREEN"press enter to continue."$NOR | |
|
80 | read | |
|
81 | ||
|
82 | fi | |
|
55 | 83 | |
|
56 | 84 | echo "Cleaning repository" |
|
57 | 85 | git clean -xfdi |
@@ -61,30 +89,81 b' echo $GREEN"please update version number in ${RED}IPython/core/release.py${NOR} ' | |||
|
61 | 89 | echo $GREEN"Press enter to continue"$NOR |
|
62 | 90 | read |
|
63 | 91 | |
|
64 | echo | |
|
65 | echo "Attempting to build the docs.." | |
|
66 | make html -C docs | |
|
92 | if ask_section "Build the documentation ?" | |
|
93 | then | |
|
94 | make html -C docs | |
|
95 | echo | |
|
96 | echo $GREEN"Check the docs, press enter to continue"$NOR | |
|
97 | read | |
|
67 | 98 | |
|
68 | echo | |
|
69 | echo $GREEN"Check the docs, press enter to continue"$NOR | |
|
70 | read | |
|
99 | fi | |
|
71 | 100 | |
|
72 | 101 | echo |
|
73 | 102 | echo $BLUE"Attempting to build package..."$NOR |
|
74 | 103 | |
|
75 | 104 | tools/build_release |
|
76 | ||
|
77 | echo | |
|
78 | echo "Let\'s commit : git commit -am \"release $VERSION\" -S" | |
|
79 | echo $GREEN"Press enter to continue"$NOR | |
|
80 | read | |
|
81 |
|
|
|
82 | ||
|
83 | echo | |
|
84 | echo "git push origin \$BRANCH ?" | |
|
85 | echo "Press enter to continue" | |
|
86 | read | |
|
87 | git push origin $BRANCH | |
|
88 | # git tag -am "release $VERSION" "$VERSION" -s | |
|
89 | # git push origin $VERSION | |
|
90 | ||
|
105 | rm dist/* | |
|
106 | ||
|
107 | if ask_section "Should we commit, tag, push... etc ? " | |
|
108 | then | |
|
109 | echo | |
|
110 | echo "Let's commit : git commit -am \"release $VERSION\" -S" | |
|
111 | echo $GREEN"Press enter to commit"$NOR | |
|
112 | read | |
|
113 | git commit -am "release $VERSION" -S | |
|
114 | ||
|
115 | echo | |
|
116 | echo $BLUE"git push origin \$BRANCH ($BRANCH)?"$NOR | |
|
117 | echo $GREEN"Make sure you can push"$NOR | |
|
118 | echo $GREEN"Press enter to continue"$NOR | |
|
119 | read | |
|
120 | git push origin $BRANCH | |
|
121 | ||
|
122 | echo | |
|
123 | echo "Let's tag : git tag -am \"release $VERSION\" \"$VERSION\" -s" | |
|
124 | echo $GREEN"Press enter to tag commit"$NOR | |
|
125 | read | |
|
126 | git tag -am "release $VERSION" "$VERSION" -s | |
|
127 | ||
|
128 | echo | |
|
129 | echo $BLUE"And push the tag: git push origin \$VERSION ?"$NOR | |
|
130 | echo $GREEN"Press enter to continue"$NOR | |
|
131 | read | |
|
132 | git push origin $VERSION | |
|
133 | ||
|
134 | ||
|
135 | echo $GREEN"please update version number and back to .dev in ${RED}IPython/core/release.py" | |
|
136 | echo ${BLUE}"Do not commit yet – we'll do it later."$NOR | |
|
137 | ||
|
138 | echo $GREEN"Press enter to continue"$NOR | |
|
139 | read | |
|
140 | ||
|
141 | echo | |
|
142 | echo "Let's commit : git commit -am \"back to dev\" -S" | |
|
143 | echo $GREEN"Press enter to commit"$NOR | |
|
144 | read | |
|
145 | git commit -am "back to dev" -S | |
|
146 | ||
|
147 | echo | |
|
148 | echo $BLUE"let's : git checkout $VERSION"$NOR | |
|
149 | echo $GREEN"Press enter to continue"$NOR | |
|
150 | read | |
|
151 | git checkout $VERSION | |
|
152 | fi | |
|
153 | ||
|
154 | if ask_section "Should we build and release ?" | |
|
155 | then | |
|
156 | ||
|
157 | echo | |
|
158 | echo $BLUE"Attempting to build package..."$NOR | |
|
159 | ||
|
160 | tools/build_release | |
|
161 | ||
|
162 | echo '$ shasum -a 256 dist/*' | |
|
163 | shasum -a 256 dist/* | |
|
164 | ||
|
165 | if ask_section "upload packages ?" | |
|
166 | then | |
|
167 | tools/build_release upload | |
|
168 | fi | |
|
169 | fi |
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now