##// END OF EJS Templates
Merge pull request #5188 from takluyver/callbacks...
Min RK -
r15667:4c8ff1de merge
parent child Browse files
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 = 50, str_key = None, re_key = None):
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 # unless we are silent
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_post_execute(flush_figures)
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._post_execute.pop(flush_figures)
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_nopostexec(self):
284 def test_silent_postexec(self):
281 """run_cell(silent=True) doesn't invoke post-exec funcs"""
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_code_hook(self, ip):
493 def pre_run_cell(self):
495 if not self._reloader.enabled:
494 if self._reloader.enabled:
496 raise TryNext
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.set_hook('pre_run_code_hook', auto_reload.pre_run_code_hook)
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.hooks import TryNext
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.6, 2.7 and 3.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 * arguments : OrderedDict
354 arguments : :class:`collections.OrderedDict`
355 An ordered mutable mapping of parameters' names to arguments' values.
355 An ordered mutable mapping of parameters' names to arguments' values.
356 Does not contain arguments' default values.
356 Does not contain arguments' default values.
357 * signature : Signature
357 signature : :class:`Signature`
358 The Signature object that created this instance.
358 The Signature object that created this instance.
359 * args : tuple
359 args : tuple
360 Tuple of positional arguments values.
360 Tuple of positional arguments values.
361 * kwargs : dict
361 kwargs : dict
362 Dict of keyword arguments values.
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 and methods:
450 A Signature object has the following public attributes:
451
451
452 * parameters : OrderedDict
452 parameters : :class:`collections.OrderedDict`
453 An ordered mapping of parameters' names to the corresponding
453 An ordered mapping of parameters' names to the corresponding
454 Parameter objects (keyword-only arguments are in the same order
454 Parameter objects (keyword-only arguments are in the same order
455 as listed in `code.co_varnames`).
455 as listed in `code.co_varnames`).
456 * return_annotation : object
456 return_annotation
457 The annotation for the return type of the function if specified.
457 The annotation for the return type of the function if specified.
458 If the function has no annotation for its return type, this
458 If the function has no annotation for its return type, this
459 attribute is not set.
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)
@@ -9,6 +9,7 b' envlist = py27, py33'
9 [testenv]
9 [testenv]
10 deps =
10 deps =
11 nose
11 nose
12 mock
12 tornado
13 tornado
13 jinja2
14 jinja2
14 sphinx
15 sphinx
General Comments 0
You need to be logged in to leave comments. Login now