Show More
@@ -0,0 +1,75 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | """Tests for CommandChainDispatcher.""" | |||
|
3 | ||||
|
4 | from __future__ import absolute_import | |||
|
5 | ||||
|
6 | #----------------------------------------------------------------------------- | |||
|
7 | # Imports | |||
|
8 | #----------------------------------------------------------------------------- | |||
|
9 | ||||
|
10 | import nose.tools as nt | |||
|
11 | from IPython.core.error import TryNext | |||
|
12 | from IPython.core.hooks import CommandChainDispatcher | |||
|
13 | ||||
|
14 | #----------------------------------------------------------------------------- | |||
|
15 | # Local utilities | |||
|
16 | #----------------------------------------------------------------------------- | |||
|
17 | ||||
|
18 | # Define two classes, one which succeeds and one which raises TryNext. Each | |||
|
19 | # sets the attribute `called` to True when it is called. | |||
|
20 | class Okay(object): | |||
|
21 | def __init__(self, message): | |||
|
22 | self.message = message | |||
|
23 | self.called = False | |||
|
24 | def __call__(self): | |||
|
25 | self.called = True | |||
|
26 | return self.message | |||
|
27 | ||||
|
28 | class Fail(object): | |||
|
29 | def __init__(self, message): | |||
|
30 | self.message = message | |||
|
31 | self.called = False | |||
|
32 | def __call__(self): | |||
|
33 | self.called = True | |||
|
34 | raise TryNext(self.message) | |||
|
35 | ||||
|
36 | #----------------------------------------------------------------------------- | |||
|
37 | # Test functions | |||
|
38 | #----------------------------------------------------------------------------- | |||
|
39 | ||||
|
40 | def test_command_chain_dispatcher_ff(): | |||
|
41 | """Test two failing hooks""" | |||
|
42 | fail1 = Fail(u'fail1') | |||
|
43 | fail2 = Fail(u'fail2') | |||
|
44 | dp = CommandChainDispatcher([(0, fail1), | |||
|
45 | (10, fail2)]) | |||
|
46 | ||||
|
47 | try: | |||
|
48 | dp() | |||
|
49 | except TryNext as e: | |||
|
50 | nt.assert_equal(str(e), u'fail2') | |||
|
51 | else: | |||
|
52 | assert False, "Expected exception was not raised." | |||
|
53 | ||||
|
54 | nt.assert_true(fail1.called) | |||
|
55 | nt.assert_true(fail2.called) | |||
|
56 | ||||
|
57 | def test_command_chain_dispatcher_fofo(): | |||
|
58 | """Test a mixture of failing and succeeding hooks.""" | |||
|
59 | fail1 = Fail(u'fail1') | |||
|
60 | fail2 = Fail(u'fail2') | |||
|
61 | okay1 = Okay(u'okay1') | |||
|
62 | okay2 = Okay(u'okay2') | |||
|
63 | ||||
|
64 | dp = CommandChainDispatcher([(0, fail1), | |||
|
65 | # (5, okay1), # add this later | |||
|
66 | (10, fail2), | |||
|
67 | (15, okay2)]) | |||
|
68 | dp.add(okay1, 5) | |||
|
69 | ||||
|
70 | nt.assert_equal(dp(), u'okay1') | |||
|
71 | ||||
|
72 | nt.assert_true(fail1.called) | |||
|
73 | nt.assert_true(okay1.called) | |||
|
74 | nt.assert_false(fail2.called) | |||
|
75 | nt.assert_false(okay2.called) |
@@ -35,22 +35,9 b' class TryNext(IPythonCoreError):' | |||||
35 | """Try next hook exception. |
|
35 | """Try next hook exception. | |
36 |
|
36 | |||
37 | Raise this in your hook function to indicate that the next hook handler |
|
37 | Raise this in your hook function to indicate that the next hook handler | |
38 |
should be used to handle the operation. |
|
38 | should be used to handle the operation. | |
39 | constructor those arguments will be used by the next hook instead of the |
|
|||
40 | original ones. |
|
|||
41 |
|
||||
42 | A _msg argument will not be passed on, so it can be used as a displayable |
|
|||
43 | error message. |
|
|||
44 | """ |
|
39 | """ | |
45 |
|
40 | |||
46 | def __init__(self, _msg="", *args, **kwargs): |
|
|||
47 | self.args = args |
|
|||
48 | self.kwargs = kwargs |
|
|||
49 | self.msg = _msg |
|
|||
50 |
|
||||
51 | def __str__(self): |
|
|||
52 | return str(self.msg) |
|
|||
53 |
|
||||
54 | class UsageError(IPythonCoreError): |
|
41 | class UsageError(IPythonCoreError): | |
55 | """Error in magic function arguments, etc. |
|
42 | """Error in magic function arguments, etc. | |
56 |
|
43 |
@@ -131,17 +131,15 b' class CommandChainDispatcher:' | |||||
131 | This will call all funcs in chain with the same args as were given to |
|
131 | This will call all funcs in chain with the same args as were given to | |
132 | this function, and return the result of first func that didn't raise |
|
132 | this function, and return the result of first func that didn't raise | |
133 | TryNext""" |
|
133 | TryNext""" | |
134 |
|
134 | last_exc = TryNext() | ||
135 | for prio,cmd in self.chain: |
|
135 | for prio,cmd in self.chain: | |
136 | #print "prio",prio,"cmd",cmd #dbg |
|
136 | #print "prio",prio,"cmd",cmd #dbg | |
137 | try: |
|
137 | try: | |
138 | return cmd(*args, **kw) |
|
138 | return cmd(*args, **kw) | |
139 |
except TryNext |
|
139 | except TryNext as exc: | |
140 | if exc.args or exc.kwargs: |
|
140 | last_exc = exc | |
141 | args = exc.args |
|
|||
142 | kw = exc.kwargs |
|
|||
143 | # if no function will accept it, raise TryNext up to the caller |
|
141 | # if no function will accept it, raise TryNext up to the caller | |
144 | raise TryNext(*args, **kw) |
|
142 | raise last_exc | |
145 |
|
143 | |||
146 | def __str__(self): |
|
144 | def __str__(self): | |
147 | return str(self.chain) |
|
145 | return str(self.chain) |
@@ -15,9 +15,8 b' def win32_clipboard_get():' | |||||
15 | try: |
|
15 | try: | |
16 | import win32clipboard |
|
16 | import win32clipboard | |
17 | except ImportError: |
|
17 | except ImportError: | |
18 |
|
|
18 | raise TryNext("Getting text from the clipboard requires the pywin32 " | |
19 | "extensions: http://sourceforge.net/projects/pywin32/") |
|
19 | "extensions: http://sourceforge.net/projects/pywin32/") | |
20 | raise TryNext(_msg=message) |
|
|||
21 | win32clipboard.OpenClipboard() |
|
20 | win32clipboard.OpenClipboard() | |
22 | text = win32clipboard.GetClipboardData(win32clipboard.CF_TEXT) |
|
21 | text = win32clipboard.GetClipboardData(win32clipboard.CF_TEXT) | |
23 | # FIXME: convert \r\n to \n? |
|
22 | # FIXME: convert \r\n to \n? | |
@@ -44,9 +43,8 b' def tkinter_clipboard_get():' | |||||
44 | try: |
|
43 | try: | |
45 | import Tkinter |
|
44 | import Tkinter | |
46 | except ImportError: |
|
45 | except ImportError: | |
47 |
|
|
46 | raise TryNext("Getting text from the clipboard on this platform " | |
48 | "requires Tkinter.") |
|
47 | "requires Tkinter.") | |
49 | raise TryNext(_msg=message) |
|
|||
50 | root = Tkinter.Tk() |
|
48 | root = Tkinter.Tk() | |
51 | root.withdraw() |
|
49 | root.withdraw() | |
52 | text = root.clipboard_get() |
|
50 | text = root.clipboard_get() |
@@ -25,3 +25,11 b' Other new features' | |||||
25 | :meth:`~IPython.core.interactiveshell.InteractiveShell.run_cell` to |
|
25 | :meth:`~IPython.core.interactiveshell.InteractiveShell.run_cell` to | |
26 | :meth:`~IPython.core.interactiveshell.InteractiveShell.run_ast_nodes` |
|
26 | :meth:`~IPython.core.interactiveshell.InteractiveShell.run_ast_nodes` | |
27 | is now configurable. |
|
27 | is now configurable. | |
|
28 | ||||
|
29 | Backwards incompatible changes | |||
|
30 | ------------------------------ | |||
|
31 | ||||
|
32 | * The exception :exc:`IPython.core.error.TryNext` previously accepted | |||
|
33 | arguments and keyword arguments to be passed to the next implementation | |||
|
34 | of the hook. This feature was removed as it made error message propagation | |||
|
35 | difficult and violated the principle of loose coupling. |
General Comments 0
You need to be logged in to leave comments.
Login now