Show More
@@ -0,0 +1,139 b'' | |||||
|
1 | """Infrastructure for registering and firing callbacks on application events. | |||
|
2 | ||||
|
3 | Unlike :mod:`IPython.core.hooks`, which lets end users set single functions to | |||
|
4 | be called at specific times, or a collection of alternative methods to try, | |||
|
5 | callbacks are designed to be used by extension authors. A number of callbacks | |||
|
6 | can be registered for the same event without needing to be aware of one another. | |||
|
7 | ||||
|
8 | The functions defined in this module are no-ops indicating the names of available | |||
|
9 | events and the arguments which will be passed to them. | |||
|
10 | ||||
|
11 | .. note:: | |||
|
12 | ||||
|
13 | This API is experimental in IPython 2.0, and may be revised in future versions. | |||
|
14 | """ | |||
|
15 | from __future__ import print_function | |||
|
16 | ||||
|
17 | class EventManager(object): | |||
|
18 | """Manage a collection of events and a sequence of callbacks for each. | |||
|
19 | ||||
|
20 | This is attached to :class:`~IPython.core.interactiveshell.InteractiveShell` | |||
|
21 | instances as an ``events`` attribute. | |||
|
22 | ||||
|
23 | .. note:: | |||
|
24 | ||||
|
25 | This API is experimental in IPython 2.0, and may be revised in future versions. | |||
|
26 | """ | |||
|
27 | def __init__(self, shell, available_events): | |||
|
28 | """Initialise the :class:`CallbackManager`. | |||
|
29 | ||||
|
30 | Parameters | |||
|
31 | ---------- | |||
|
32 | shell | |||
|
33 | The :class:`~IPython.core.interactiveshell.InteractiveShell` instance | |||
|
34 | available_callbacks | |||
|
35 | An iterable of names for callback events. | |||
|
36 | """ | |||
|
37 | self.shell = shell | |||
|
38 | self.callbacks = {n:[] for n in available_events} | |||
|
39 | ||||
|
40 | def register(self, event, function): | |||
|
41 | """Register a new event callback | |||
|
42 | ||||
|
43 | Parameters | |||
|
44 | ---------- | |||
|
45 | event : str | |||
|
46 | The event for which to register this callback. | |||
|
47 | function : callable | |||
|
48 | A function to be called on the given event. It should take the same | |||
|
49 | parameters as the appropriate callback prototype. | |||
|
50 | ||||
|
51 | Raises | |||
|
52 | ------ | |||
|
53 | TypeError | |||
|
54 | If ``function`` is not callable. | |||
|
55 | KeyError | |||
|
56 | If ``event`` is not one of the known events. | |||
|
57 | """ | |||
|
58 | if not callable(function): | |||
|
59 | raise TypeError('Need a callable, got %r' % function) | |||
|
60 | self.callbacks[event].append(function) | |||
|
61 | ||||
|
62 | def unregister(self, event, function): | |||
|
63 | """Remove a callback from the given event.""" | |||
|
64 | self.callbacks[event].remove(function) | |||
|
65 | ||||
|
66 | def reset(self, event): | |||
|
67 | """Clear all callbacks for the given event.""" | |||
|
68 | self.callbacks[event] = [] | |||
|
69 | ||||
|
70 | def reset_all(self): | |||
|
71 | """Clear all callbacks for all events.""" | |||
|
72 | self.callbacks = {n:[] for n in self.callbacks} | |||
|
73 | ||||
|
74 | def trigger(self, event, *args, **kwargs): | |||
|
75 | """Call callbacks for ``event``. | |||
|
76 | ||||
|
77 | Any additional arguments are passed to all callbacks registered for this | |||
|
78 | event. Exceptions raised by callbacks are caught, and a message printed. | |||
|
79 | """ | |||
|
80 | for func in self.callbacks[event]: | |||
|
81 | try: | |||
|
82 | func(*args, **kwargs) | |||
|
83 | except Exception: | |||
|
84 | print("Error in callback {} (for {}):".format(func, event)) | |||
|
85 | self.shell.showtraceback() | |||
|
86 | ||||
|
87 | # event_name -> prototype mapping | |||
|
88 | available_events = {} | |||
|
89 | ||||
|
90 | def _define_event(callback_proto): | |||
|
91 | available_events[callback_proto.__name__] = callback_proto | |||
|
92 | return callback_proto | |||
|
93 | ||||
|
94 | # ------------------------------------------------------------------------------ | |||
|
95 | # Callback prototypes | |||
|
96 | # | |||
|
97 | # No-op functions which describe the names of available events and the | |||
|
98 | # signatures of callbacks for those events. | |||
|
99 | # ------------------------------------------------------------------------------ | |||
|
100 | ||||
|
101 | @_define_event | |||
|
102 | def pre_execute(): | |||
|
103 | """Fires before code is executed in response to user/frontend action. | |||
|
104 | ||||
|
105 | This includes comm and widget messages and silent execution, as well as user | |||
|
106 | code cells.""" | |||
|
107 | pass | |||
|
108 | ||||
|
109 | @_define_event | |||
|
110 | def pre_run_cell(): | |||
|
111 | """Fires before user-entered code runs.""" | |||
|
112 | pass | |||
|
113 | ||||
|
114 | @_define_event | |||
|
115 | def post_execute(): | |||
|
116 | """Fires after code is executed in response to user/frontend action. | |||
|
117 | ||||
|
118 | This includes comm and widget messages and silent execution, as well as user | |||
|
119 | code cells.""" | |||
|
120 | pass | |||
|
121 | ||||
|
122 | @_define_event | |||
|
123 | def post_run_cell(): | |||
|
124 | """Fires after user-entered code runs.""" | |||
|
125 | pass | |||
|
126 | ||||
|
127 | @_define_event | |||
|
128 | def shell_initialized(ip): | |||
|
129 | """Fires after initialisation of :class:`~IPython.core.interactiveshell.InteractiveShell`. | |||
|
130 | ||||
|
131 | This is before extensions and startup scripts are loaded, so it can only be | |||
|
132 | set by subclassing. | |||
|
133 | ||||
|
134 | Parameters | |||
|
135 | ---------- | |||
|
136 | ip : :class:`~IPython.core.interactiveshell.InteractiveShell` | |||
|
137 | The newly initialised shell. | |||
|
138 | """ | |||
|
139 | pass |
@@ -0,0 +1,46 b'' | |||||
|
1 | import unittest | |||
|
2 | try: # Python 3.3 + | |||
|
3 | from unittest.mock import Mock | |||
|
4 | except ImportError: | |||
|
5 | from mock import Mock | |||
|
6 | ||||
|
7 | from IPython.core import events | |||
|
8 | import IPython.testing.tools as tt | |||
|
9 | ||||
|
10 | def ping_received(): | |||
|
11 | pass | |||
|
12 | ||||
|
13 | class CallbackTests(unittest.TestCase): | |||
|
14 | def setUp(self): | |||
|
15 | self.em = events.EventManager(get_ipython(), {'ping_received': ping_received}) | |||
|
16 | ||||
|
17 | def test_register_unregister(self): | |||
|
18 | cb = Mock() | |||
|
19 | ||||
|
20 | self.em.register('ping_received', cb) | |||
|
21 | self.em.trigger('ping_received') | |||
|
22 | self.assertEqual(cb.call_count, 1) | |||
|
23 | ||||
|
24 | self.em.unregister('ping_received', cb) | |||
|
25 | self.em.trigger('ping_received') | |||
|
26 | self.assertEqual(cb.call_count, 1) | |||
|
27 | ||||
|
28 | def test_reset(self): | |||
|
29 | cb = Mock() | |||
|
30 | self.em.register('ping_received', cb) | |||
|
31 | self.em.reset('ping_received') | |||
|
32 | self.em.trigger('ping_received') | |||
|
33 | assert not cb.called | |||
|
34 | ||||
|
35 | def test_reset_all(self): | |||
|
36 | cb = Mock() | |||
|
37 | self.em.register('ping_received', cb) | |||
|
38 | self.em.reset_all() | |||
|
39 | self.em.trigger('ping_received') | |||
|
40 | assert not cb.called | |||
|
41 | ||||
|
42 | def test_cb_error(self): | |||
|
43 | cb = Mock(side_effect=ValueError) | |||
|
44 | self.em.register('ping_received', cb) | |||
|
45 | with tt.AssertPrints("Error in callback"): | |||
|
46 | self.em.trigger('ping_received') No newline at end of file |
@@ -0,0 +1,42 b'' | |||||
|
1 | ===================== | |||
|
2 | Registering callbacks | |||
|
3 | ===================== | |||
|
4 | ||||
|
5 | Extension code can register callbacks functions which will be called on specific | |||
|
6 | events within the IPython code. You can see the current list of available | |||
|
7 | callbacks, and the parameters that will be passed with each, in the callback | |||
|
8 | prototype functions defined in :mod:`IPython.core.callbacks`. | |||
|
9 | ||||
|
10 | To register callbacks, use :meth:`IPython.core.events.EventManager.register`. | |||
|
11 | For example:: | |||
|
12 | ||||
|
13 | class VarWatcher(object): | |||
|
14 | def __init__(self, ip): | |||
|
15 | self.shell = ip | |||
|
16 | self.last_x = None | |||
|
17 | ||||
|
18 | def pre_execute(self): | |||
|
19 | self.last_x = self.shell.user_ns.get('x', None) | |||
|
20 | ||||
|
21 | def post_execute(self): | |||
|
22 | if self.shell.user_ns.get('x', None) != self.last_x: | |||
|
23 | print("x changed!") | |||
|
24 | ||||
|
25 | def load_ipython_extension(ip): | |||
|
26 | vw = VarWatcher(ip) | |||
|
27 | ip.events.register('pre_execute', vw.pre_execute) | |||
|
28 | ip.events.register('post_execute', vw.post_execute) | |||
|
29 | ||||
|
30 | .. note:: | |||
|
31 | ||||
|
32 | This API is experimental in IPython 2.0, and may be revised in future versions. | |||
|
33 | ||||
|
34 | .. seealso:: | |||
|
35 | ||||
|
36 | Module :mod:`IPython.core.hooks` | |||
|
37 | The older 'hooks' system allows end users to customise some parts of | |||
|
38 | IPython's behaviour. | |||
|
39 | ||||
|
40 | :doc:`inputtransforms` | |||
|
41 | By registering input transformers that don't change code, you can monitor | |||
|
42 | what is being executed. |
@@ -0,0 +1,1 b'' | |||||
|
1 | * A new callback system has been introduced. For details, see :doc:`/config/callbacks`. |
@@ -7,7 +7,7 b' before_install:' | |||||
7 | # workaround for https://github.com/travis-ci/travis-cookbooks/issues/155 |
|
7 | # workaround for https://github.com/travis-ci/travis-cookbooks/issues/155 | |
8 | - sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm |
|
8 | - sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm | |
9 | - easy_install -q pyzmq |
|
9 | - easy_install -q pyzmq | |
10 | - pip install jinja2 sphinx pygments tornado requests |
|
10 | - pip install jinja2 sphinx pygments tornado requests mock | |
11 | # Pierre Carrier's PPA for PhantomJS and CasperJS |
|
11 | # Pierre Carrier's PPA for PhantomJS and CasperJS | |
12 | - sudo add-apt-repository -y ppa:pcarrier/ppa |
|
12 | - sudo add-apt-repository -y ppa:pcarrier/ppa | |
13 | - sudo apt-get update |
|
13 | - sudo apt-get update |
@@ -49,6 +49,11 b" __all__ = ['editor', 'fix_error_editor', 'synchronize_with_editor'," | |||||
49 | 'show_in_pager','pre_prompt_hook', |
|
49 | 'show_in_pager','pre_prompt_hook', | |
50 | 'pre_run_code_hook', 'clipboard_get'] |
|
50 | 'pre_run_code_hook', 'clipboard_get'] | |
51 |
|
51 | |||
|
52 | deprecated = {'pre_run_code_hook': "a callback for the 'pre_execute' or 'pre_run_cell' event", | |||
|
53 | 'late_startup_hook': "a callback for the 'shell_initialized' event", | |||
|
54 | 'shutdown_hook': "the atexit module", | |||
|
55 | } | |||
|
56 | ||||
52 | def editor(self, filename, linenum=None, wait=True): |
|
57 | def editor(self, filename, linenum=None, wait=True): | |
53 | """Open the default editor at the given filename and linenumber. |
|
58 | """Open the default editor at the given filename and linenumber. | |
54 |
|
59 |
@@ -41,6 +41,7 b' from IPython.core import ultratb' | |||||
41 | from IPython.core.alias import AliasManager, AliasError |
|
41 | from IPython.core.alias import AliasManager, AliasError | |
42 | from IPython.core.autocall import ExitAutocall |
|
42 | from IPython.core.autocall import ExitAutocall | |
43 | from IPython.core.builtin_trap import BuiltinTrap |
|
43 | from IPython.core.builtin_trap import BuiltinTrap | |
|
44 | from IPython.core.events import EventManager, available_events | |||
44 | from IPython.core.compilerop import CachingCompiler, check_linecache_ipython |
|
45 | from IPython.core.compilerop import CachingCompiler, check_linecache_ipython | |
45 | from IPython.core.display_trap import DisplayTrap |
|
46 | from IPython.core.display_trap import DisplayTrap | |
46 | from IPython.core.displayhook import DisplayHook |
|
47 | from IPython.core.displayhook import DisplayHook | |
@@ -467,6 +468,7 b' class InteractiveShell(SingletonConfigurable):' | |||||
467 |
|
468 | |||
468 | self.init_syntax_highlighting() |
|
469 | self.init_syntax_highlighting() | |
469 | self.init_hooks() |
|
470 | self.init_hooks() | |
|
471 | self.init_events() | |||
470 | self.init_pushd_popd_magic() |
|
472 | self.init_pushd_popd_magic() | |
471 | # self.init_traceback_handlers use to be here, but we moved it below |
|
473 | # self.init_traceback_handlers use to be here, but we moved it below | |
472 | # because it and init_io have to come after init_readline. |
|
474 | # because it and init_io have to come after init_readline. | |
@@ -510,6 +512,7 b' class InteractiveShell(SingletonConfigurable):' | |||||
510 | self.init_payload() |
|
512 | self.init_payload() | |
511 | self.init_comms() |
|
513 | self.init_comms() | |
512 | self.hooks.late_startup_hook() |
|
514 | self.hooks.late_startup_hook() | |
|
515 | self.events.trigger('shell_initialized', self) | |||
513 | atexit.register(self.atexit_operations) |
|
516 | atexit.register(self.atexit_operations) | |
514 |
|
517 | |||
515 | def get_ipython(self): |
|
518 | def get_ipython(self): | |
@@ -785,9 +788,10 b' class InteractiveShell(SingletonConfigurable):' | |||||
785 | for hook_name in hooks.__all__: |
|
788 | for hook_name in hooks.__all__: | |
786 | # default hooks have priority 100, i.e. low; user hooks should have |
|
789 | # default hooks have priority 100, i.e. low; user hooks should have | |
787 | # 0-100 priority |
|
790 | # 0-100 priority | |
788 | self.set_hook(hook_name,getattr(hooks,hook_name), 100) |
|
791 | self.set_hook(hook_name,getattr(hooks,hook_name), 100, _warn_deprecated=False) | |
789 |
|
792 | |||
790 |
def set_hook(self,name,hook, priority |
|
793 | def set_hook(self,name,hook, priority=50, str_key=None, re_key=None, | |
|
794 | _warn_deprecated=True): | |||
791 | """set_hook(name,hook) -> sets an internal IPython hook. |
|
795 | """set_hook(name,hook) -> sets an internal IPython hook. | |
792 |
|
796 | |||
793 | IPython exposes some of its internal API as user-modifiable hooks. By |
|
797 | IPython exposes some of its internal API as user-modifiable hooks. By | |
@@ -816,6 +820,11 b' class InteractiveShell(SingletonConfigurable):' | |||||
816 | if name not in IPython.core.hooks.__all__: |
|
820 | if name not in IPython.core.hooks.__all__: | |
817 | print("Warning! Hook '%s' is not one of %s" % \ |
|
821 | print("Warning! Hook '%s' is not one of %s" % \ | |
818 | (name, IPython.core.hooks.__all__ )) |
|
822 | (name, IPython.core.hooks.__all__ )) | |
|
823 | ||||
|
824 | if _warn_deprecated and (name in IPython.core.hooks.deprecated): | |||
|
825 | alternative = IPython.core.hooks.deprecated[name] | |||
|
826 | warn("Hook {} is deprecated. Use {} instead.".format(name, alternative)) | |||
|
827 | ||||
819 | if not dp: |
|
828 | if not dp: | |
820 | dp = IPython.core.hooks.CommandChainDispatcher() |
|
829 | dp = IPython.core.hooks.CommandChainDispatcher() | |
821 |
|
830 | |||
@@ -827,12 +836,21 b' class InteractiveShell(SingletonConfigurable):' | |||||
827 |
|
836 | |||
828 | setattr(self.hooks,name, dp) |
|
837 | setattr(self.hooks,name, dp) | |
829 |
|
838 | |||
|
839 | #------------------------------------------------------------------------- | |||
|
840 | # Things related to events | |||
|
841 | #------------------------------------------------------------------------- | |||
|
842 | ||||
|
843 | def init_events(self): | |||
|
844 | self.events = EventManager(self, available_events) | |||
|
845 | ||||
830 | def register_post_execute(self, func): |
|
846 | def register_post_execute(self, func): | |
831 | """Register a function for calling after code execution. |
|
847 | """DEPRECATED: Use ip.events.register('post_run_cell', func) | |
|
848 | ||||
|
849 | Register a function for calling after code execution. | |||
832 | """ |
|
850 | """ | |
833 | if not callable(func): |
|
851 | warn("ip.register_post_execute is deprecated, use " | |
834 | raise ValueError('argument %s must be callable' % func) |
|
852 | "ip.events.register('post_run_cell', func) instead.") | |
835 | self._post_execute[func] = True |
|
853 | self.events.register('post_run_cell', func) | |
836 |
|
854 | |||
837 | #------------------------------------------------------------------------- |
|
855 | #------------------------------------------------------------------------- | |
838 | # Things related to the "main" module |
|
856 | # Things related to the "main" module | |
@@ -2649,6 +2667,10 b' class InteractiveShell(SingletonConfigurable):' | |||||
2649 | if silent: |
|
2667 | if silent: | |
2650 | store_history = False |
|
2668 | store_history = False | |
2651 |
|
2669 | |||
|
2670 | self.events.trigger('pre_execute') | |||
|
2671 | if not silent: | |||
|
2672 | self.events.trigger('pre_run_cell') | |||
|
2673 | ||||
2652 | # If any of our input transformation (input_transformer_manager or |
|
2674 | # If any of our input transformation (input_transformer_manager or | |
2653 | # prefilter_manager) raises an exception, we store it in this variable |
|
2675 | # prefilter_manager) raises an exception, we store it in this variable | |
2654 | # so that we can display the error after logging the input and storing |
|
2676 | # so that we can display the error after logging the input and storing | |
@@ -2717,28 +2739,10 b' class InteractiveShell(SingletonConfigurable):' | |||||
2717 | interactivity = "none" if silent else self.ast_node_interactivity |
|
2739 | interactivity = "none" if silent else self.ast_node_interactivity | |
2718 | self.run_ast_nodes(code_ast.body, cell_name, |
|
2740 | self.run_ast_nodes(code_ast.body, cell_name, | |
2719 | interactivity=interactivity, compiler=compiler) |
|
2741 | interactivity=interactivity, compiler=compiler) | |
2720 |
|
2742 | |||
2721 | # Execute any registered post-execution functions. |
|
2743 | self.events.trigger('post_execute') | |
2722 |
|
|
2744 | if not silent: | |
2723 | post_exec = [] if silent else iteritems(self._post_execute) |
|
2745 | self.events.trigger('post_run_cell') | |
2724 |
|
||||
2725 | for func, status in post_exec: |
|
|||
2726 | if self.disable_failing_post_execute and not status: |
|
|||
2727 | continue |
|
|||
2728 | try: |
|
|||
2729 | func() |
|
|||
2730 | except KeyboardInterrupt: |
|
|||
2731 | print("\nKeyboardInterrupt", file=io.stderr) |
|
|||
2732 | except Exception: |
|
|||
2733 | # register as failing: |
|
|||
2734 | self._post_execute[func] = False |
|
|||
2735 | self.showtraceback() |
|
|||
2736 | print('\n'.join([ |
|
|||
2737 | "post-execution function %r produced an error." % func, |
|
|||
2738 | "If this problem persists, you can disable failing post-exec functions with:", |
|
|||
2739 | "", |
|
|||
2740 | " get_ipython().disable_failing_post_execute = True" |
|
|||
2741 | ]), file=io.stderr) |
|
|||
2742 |
|
2746 | |||
2743 | if store_history: |
|
2747 | if store_history: | |
2744 | # Write output to the database. Does nothing unless |
|
2748 | # Write output to the database. Does nothing unless |
@@ -353,7 +353,7 b' def configure_inline_support(shell, backend):' | |||||
353 |
|
353 | |||
354 | if backend == backends['inline']: |
|
354 | if backend == backends['inline']: | |
355 | from IPython.kernel.zmq.pylab.backend_inline import flush_figures |
|
355 | from IPython.kernel.zmq.pylab.backend_inline import flush_figures | |
356 |
shell.register |
|
356 | shell.events.register('post_execute', flush_figures) | |
357 |
|
357 | |||
358 | # Save rcParams that will be overwrittern |
|
358 | # Save rcParams that will be overwrittern | |
359 | shell._saved_rcParams = dict() |
|
359 | shell._saved_rcParams = dict() | |
@@ -363,8 +363,10 b' def configure_inline_support(shell, backend):' | |||||
363 | pyplot.rcParams.update(cfg.rc) |
|
363 | pyplot.rcParams.update(cfg.rc) | |
364 | else: |
|
364 | else: | |
365 | from IPython.kernel.zmq.pylab.backend_inline import flush_figures |
|
365 | from IPython.kernel.zmq.pylab.backend_inline import flush_figures | |
366 | if flush_figures in shell._post_execute: |
|
366 | try: | |
367 |
shell. |
|
367 | shell.events.unregister('post_execute', flush_figures) | |
|
368 | except ValueError: | |||
|
369 | pass | |||
368 | if hasattr(shell, '_saved_rcParams'): |
|
370 | if hasattr(shell, '_saved_rcParams'): | |
369 | pyplot.rcParams.update(shell._saved_rcParams) |
|
371 | pyplot.rcParams.update(shell._saved_rcParams) | |
370 | del shell._saved_rcParams |
|
372 | del shell._saved_rcParams |
@@ -27,6 +27,10 b' import shutil' | |||||
27 | import sys |
|
27 | import sys | |
28 | import tempfile |
|
28 | import tempfile | |
29 | import unittest |
|
29 | import unittest | |
|
30 | try: | |||
|
31 | from unittest import mock | |||
|
32 | except ImportError: | |||
|
33 | import mock | |||
30 | from os.path import join |
|
34 | from os.path import join | |
31 |
|
35 | |||
32 | # third-party |
|
36 | # third-party | |
@@ -277,21 +281,32 b' class InteractiveShellTestCase(unittest.TestCase):' | |||||
277 | # ZeroDivisionError |
|
281 | # ZeroDivisionError | |
278 | self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}") |
|
282 | self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}") | |
279 |
|
283 | |||
280 |
def test_silent_ |
|
284 | def test_silent_postexec(self): | |
281 |
"""run_cell(silent=True) doesn't invoke p |
|
285 | """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks""" | |
282 | d = dict(called=False) |
|
286 | pre_explicit = mock.Mock() | |
283 | def set_called(): |
|
287 | pre_always = mock.Mock() | |
284 | d['called'] = True |
|
288 | post_explicit = mock.Mock() | |
|
289 | post_always = mock.Mock() | |||
285 |
|
290 | |||
286 | ip.register_post_execute(set_called) |
|
291 | ip.events.register('pre_run_cell', pre_explicit) | |
287 | ip.run_cell("1", silent=True) |
|
292 | ip.events.register('pre_execute', pre_always) | |
288 | self.assertFalse(d['called']) |
|
293 | ip.events.register('post_run_cell', post_explicit) | |
289 | # double-check that non-silent exec did what we expected |
|
294 | ip.events.register('post_execute', post_always) | |
290 | # silent to avoid |
|
295 | ||
291 | ip.run_cell("1") |
|
296 | try: | |
292 | self.assertTrue(d['called']) |
|
297 | ip.run_cell("1", silent=True) | |
293 | # remove post-exec |
|
298 | assert pre_always.called | |
294 | ip._post_execute.pop(set_called) |
|
299 | assert not pre_explicit.called | |
|
300 | assert post_always.called | |||
|
301 | assert not post_explicit.called | |||
|
302 | # double-check that non-silent exec did what we expected | |||
|
303 | # silent to avoid | |||
|
304 | ip.run_cell("1") | |||
|
305 | assert pre_explicit.called | |||
|
306 | assert post_explicit.called | |||
|
307 | finally: | |||
|
308 | # remove post-exec | |||
|
309 | ip.events.reset_all() | |||
295 |
|
310 | |||
296 | def test_silent_noadvance(self): |
|
311 | def test_silent_noadvance(self): | |
297 | """run_cell(silent=True) doesn't advance execution_count""" |
|
312 | """run_cell(silent=True) doesn't advance execution_count""" |
@@ -392,7 +392,6 b' def superreload(module, reload=reload, old_objects={}):' | |||||
392 | # IPython connectivity |
|
392 | # IPython connectivity | |
393 | #------------------------------------------------------------------------------ |
|
393 | #------------------------------------------------------------------------------ | |
394 |
|
394 | |||
395 | from IPython.core.hooks import TryNext |
|
|||
396 | from IPython.core.magic import Magics, magics_class, line_magic |
|
395 | from IPython.core.magic import Magics, magics_class, line_magic | |
397 |
|
396 | |||
398 | @magics_class |
|
397 | @magics_class | |
@@ -491,17 +490,16 b' class AutoreloadMagics(Magics):' | |||||
491 | # Inject module to user namespace |
|
490 | # Inject module to user namespace | |
492 | self.shell.push({top_name: top_module}) |
|
491 | self.shell.push({top_name: top_module}) | |
493 |
|
492 | |||
494 |
def pre_run_c |
|
493 | def pre_run_cell(self): | |
495 |
if |
|
494 | if self._reloader.enabled: | |
496 |
|
|
495 | try: | |
497 | try: |
|
496 | self._reloader.check() | |
498 | self._reloader.check() |
|
497 | except: | |
499 | except: |
|
498 | pass | |
500 | pass |
|
|||
501 |
|
499 | |||
502 |
|
500 | |||
503 | def load_ipython_extension(ip): |
|
501 | def load_ipython_extension(ip): | |
504 | """Load the extension in IPython.""" |
|
502 | """Load the extension in IPython.""" | |
505 | auto_reload = AutoreloadMagics(ip) |
|
503 | auto_reload = AutoreloadMagics(ip) | |
506 | ip.register_magics(auto_reload) |
|
504 | ip.register_magics(auto_reload) | |
507 |
ip. |
|
505 | ip.events.register('pre_run_cell', auto_reload.pre_run_cell) |
@@ -23,7 +23,7 b' import nose.tools as nt' | |||||
23 | import IPython.testing.tools as tt |
|
23 | import IPython.testing.tools as tt | |
24 |
|
24 | |||
25 | from IPython.extensions.autoreload import AutoreloadMagics |
|
25 | from IPython.extensions.autoreload import AutoreloadMagics | |
26 |
from IPython.core. |
|
26 | from IPython.core.events import EventManager, pre_run_cell | |
27 | from IPython.utils.py3compat import PY3 |
|
27 | from IPython.utils.py3compat import PY3 | |
28 |
|
28 | |||
29 | if PY3: |
|
29 | if PY3: | |
@@ -41,15 +41,14 b' class FakeShell(object):' | |||||
41 |
|
41 | |||
42 | def __init__(self): |
|
42 | def __init__(self): | |
43 | self.ns = {} |
|
43 | self.ns = {} | |
|
44 | self.events = EventManager(self, {'pre_run_cell', pre_run_cell}) | |||
44 | self.auto_magics = AutoreloadMagics(shell=self) |
|
45 | self.auto_magics = AutoreloadMagics(shell=self) | |
|
46 | self.events.register('pre_run_cell', self.auto_magics.pre_run_cell) | |||
45 |
|
47 | |||
46 | register_magics = set_hook = noop |
|
48 | register_magics = set_hook = noop | |
47 |
|
49 | |||
48 | def run_code(self, code): |
|
50 | def run_code(self, code): | |
49 | try: |
|
51 | self.events.trigger('pre_run_cell') | |
50 | self.auto_magics.pre_run_code_hook(self) |
|
|||
51 | except TryNext: |
|
|||
52 | pass |
|
|||
53 | exec(code, self.ns) |
|
52 | exec(code, self.ns) | |
54 |
|
53 | |||
55 | def push(self, items): |
|
54 | def push(self, items): |
@@ -134,7 +134,9 b' class Comm(LoggingConfigurable):' | |||||
134 | """Handle a comm_msg message""" |
|
134 | """Handle a comm_msg message""" | |
135 | self.log.debug("handle_msg[%s](%s)", self.comm_id, msg) |
|
135 | self.log.debug("handle_msg[%s](%s)", self.comm_id, msg) | |
136 | if self._msg_callback: |
|
136 | if self._msg_callback: | |
|
137 | self.shell.events.trigger('pre_execute') | |||
137 | self._msg_callback(msg) |
|
138 | self._msg_callback(msg) | |
|
139 | self.shell.events.trigger('post_execute') | |||
138 |
|
140 | |||
139 |
|
141 | |||
140 | __all__ = ['Comm'] |
|
142 | __all__ = ['Comm'] |
@@ -1,7 +1,7 b'' | |||||
1 | """Function signature objects for callables |
|
1 | """Function signature objects for callables. | |
2 |
|
2 | |||
3 | Back port of Python 3.3's function signature tools from the inspect module, |
|
3 | Back port of Python 3.3's function signature tools from the inspect module, | |
4 |
modified to be compatible with Python 2. |
|
4 | modified to be compatible with Python 2.7 and 3.2+. | |
5 | """ |
|
5 | """ | |
6 |
|
6 | |||
7 | #----------------------------------------------------------------------------- |
|
7 | #----------------------------------------------------------------------------- | |
@@ -346,20 +346,20 b' class Parameter(object):' | |||||
346 |
|
346 | |||
347 |
|
347 | |||
348 | class BoundArguments(object): |
|
348 | class BoundArguments(object): | |
349 | '''Result of `Signature.bind` call. Holds the mapping of arguments |
|
349 | '''Result of :meth:`Signature.bind` call. Holds the mapping of arguments | |
350 | to the function's parameters. |
|
350 | to the function's parameters. | |
351 |
|
351 | |||
352 | Has the following public attributes: |
|
352 | Has the following public attributes: | |
353 |
|
353 | |||
354 |
|
|
354 | arguments : :class:`collections.OrderedDict` | |
355 |
|
|
355 | An ordered mutable mapping of parameters' names to arguments' values. | |
356 |
|
|
356 | Does not contain arguments' default values. | |
357 |
|
|
357 | signature : :class:`Signature` | |
358 |
|
|
358 | The Signature object that created this instance. | |
359 |
|
|
359 | args : tuple | |
360 |
|
|
360 | Tuple of positional arguments values. | |
361 |
|
|
361 | kwargs : dict | |
362 |
|
|
362 | Dict of keyword arguments values. | |
363 | ''' |
|
363 | ''' | |
364 |
|
364 | |||
365 | def __init__(self, signature, arguments): |
|
365 | def __init__(self, signature, arguments): | |
@@ -447,22 +447,16 b' class Signature(object):' | |||||
447 | It stores a Parameter object for each parameter accepted by the |
|
447 | It stores a Parameter object for each parameter accepted by the | |
448 | function, as well as information specific to the function itself. |
|
448 | function, as well as information specific to the function itself. | |
449 |
|
449 | |||
450 |
A Signature object has the following public attributes |
|
450 | A Signature object has the following public attributes: | |
451 |
|
451 | |||
452 |
|
|
452 | parameters : :class:`collections.OrderedDict` | |
453 |
|
|
453 | An ordered mapping of parameters' names to the corresponding | |
454 |
|
|
454 | Parameter objects (keyword-only arguments are in the same order | |
455 |
|
|
455 | as listed in `code.co_varnames`). | |
456 |
|
|
456 | return_annotation | |
457 |
|
|
457 | The annotation for the return type of the function if specified. | |
458 |
|
|
458 | If the function has no annotation for its return type, this | |
459 |
|
|
459 | attribute is not set. | |
460 | * bind(*args, **kwargs) -> BoundArguments |
|
|||
461 | Creates a mapping from positional and keyword arguments to |
|
|||
462 | parameters. |
|
|||
463 | * bind_partial(*args, **kwargs) -> BoundArguments |
|
|||
464 | Creates a partial mapping from positional and keyword arguments |
|
|||
465 | to parameters (simulating 'functools.partial' behavior.) |
|
|||
466 | ''' |
|
460 | ''' | |
467 |
|
461 | |||
468 | __slots__ = ('_return_annotation', '_parameters') |
|
462 | __slots__ = ('_return_annotation', '_parameters') | |
@@ -775,16 +769,16 b' class Signature(object):' | |||||
775 | return self._bound_arguments_cls(self, arguments) |
|
769 | return self._bound_arguments_cls(self, arguments) | |
776 |
|
770 | |||
777 | def bind(self, *args, **kwargs): |
|
771 | def bind(self, *args, **kwargs): | |
778 | '''Get a BoundArguments object, that maps the passed `args` |
|
772 | '''Get a :class:`BoundArguments` object, that maps the passed `args` | |
779 | and `kwargs` to the function's signature. Raises `TypeError` |
|
773 | and `kwargs` to the function's signature. Raises :exc:`TypeError` | |
780 | if the passed arguments can not be bound. |
|
774 | if the passed arguments can not be bound. | |
781 | ''' |
|
775 | ''' | |
782 | return self._bind(args, kwargs) |
|
776 | return self._bind(args, kwargs) | |
783 |
|
777 | |||
784 | def bind_partial(self, *args, **kwargs): |
|
778 | def bind_partial(self, *args, **kwargs): | |
785 | '''Get a BoundArguments object, that partially maps the |
|
779 | '''Get a :class:`BoundArguments` object, that partially maps the | |
786 | passed `args` and `kwargs` to the function's signature. |
|
780 | passed `args` and `kwargs` to the function's signature. | |
787 | Raises `TypeError` if the passed arguments can not be bound. |
|
781 | Raises :exc:`TypeError` if the passed arguments can not be bound. | |
788 | ''' |
|
782 | ''' | |
789 | return self._bind(args, kwargs, partial=True) |
|
783 | return self._bind(args, kwargs, partial=True) | |
790 |
|
784 |
@@ -28,3 +28,4 b' Extending and integrating with IPython' | |||||
28 | extensions/index |
|
28 | extensions/index | |
29 | integrating |
|
29 | integrating | |
30 | inputtransforms |
|
30 | inputtransforms | |
|
31 | callbacks |
@@ -273,10 +273,14 b' extras_require = dict(' | |||||
273 | notebook = ['tornado>=3.1', 'pyzmq>=2.1.11', 'jinja2'], |
|
273 | notebook = ['tornado>=3.1', 'pyzmq>=2.1.11', 'jinja2'], | |
274 | nbconvert = ['pygments', 'jinja2', 'Sphinx>=0.3'] |
|
274 | nbconvert = ['pygments', 'jinja2', 'Sphinx>=0.3'] | |
275 | ) |
|
275 | ) | |
|
276 | if sys.version_info < (3, 3): | |||
|
277 | extras_require['test'].append('mock') | |||
|
278 | ||||
276 | everything = set() |
|
279 | everything = set() | |
277 | for deps in extras_require.values(): |
|
280 | for deps in extras_require.values(): | |
278 | everything.update(deps) |
|
281 | everything.update(deps) | |
279 | extras_require['all'] = everything |
|
282 | extras_require['all'] = everything | |
|
283 | ||||
280 | install_requires = [] |
|
284 | install_requires = [] | |
281 | if sys.platform == 'darwin': |
|
285 | if sys.platform == 'darwin': | |
282 | if any(arg.startswith('bdist') for arg in sys.argv) or not setupext.check_for_readline(): |
|
286 | if any(arg.startswith('bdist') for arg in sys.argv) or not setupext.check_for_readline(): |
@@ -639,10 +639,11 b' def get_bdist_wheel():' | |||||
639 | if found: |
|
639 | if found: | |
640 | lis.pop(idx) |
|
640 | lis.pop(idx) | |
641 |
|
641 | |||
642 | for pkg in ("gnureadline", "pyreadline"): |
|
642 | for pkg in ("gnureadline", "pyreadline", "mock"): | |
643 | _remove_startswith(requires, pkg) |
|
643 | _remove_startswith(requires, pkg) | |
644 | requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'") |
|
644 | requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'") | |
645 | requires.append("pyreadline (>=2.0); sys.platform == 'win32' and platform.python_implementation == 'CPython'") |
|
645 | requires.append("pyreadline (>=2.0); sys.platform == 'win32' and platform.python_implementation == 'CPython'") | |
|
646 | requires.append("mock; extra == 'test' and python_version < '3.3'") | |||
646 | for r in requires: |
|
647 | for r in requires: | |
647 | pkg_info['Requires-Dist'] = r |
|
648 | pkg_info['Requires-Dist'] = r | |
648 | write_pkg_info(metadata_path, pkg_info) |
|
649 | write_pkg_info(metadata_path, pkg_info) |
General Comments 0
You need to be logged in to leave comments.
Login now