Merge lp:~frankban/juju-quickstart/improve-input-handling into lp:juju-quickstart
- improve-input-handling
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 39 |
Proposed branch: | lp:~frankban/juju-quickstart/improve-input-handling |
Merge into: | lp:juju-quickstart |
Diff against target: |
337 lines (+110/-41) 6 files modified
cli-app-demo.py (+3/-0) quickstart/cli/base.py (+17/-2) quickstart/cli/views.py (+25/-17) quickstart/tests/cli/helpers.py (+31/-0) quickstart/tests/cli/test_base.py (+20/-1) quickstart/tests/cli/test_views.py (+14/-21) |
To merge this branch: | bzr merge lp:~frankban/juju-quickstart/improve-input-handling |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email: mp+199640@code.launchpad.net |
Commit message
Description of the change
Improve views input handling.
Add a new set_return_
views can call to set the value encapsulated
in the AppExit exception raised when the user
quits the application with ^X.
Tests: `make check`
QA: start the demo app
(`make` and `./cli-
Try exiting with ^X: you should see the
"no environment selected" message.
Restart and exit selecting an environment:
you should see a message indicating that
environment has been selected.
Restart and change the default environment,
then exit: you should see the new env_db
printed and the new default environment.
Francesco Banconi (frankban) wrote : | # |
Madison Scott-Clary (makyo) wrote : | # |
LGTM, thanks!
- 44. By Francesco Banconi
-
Add missing import unicode_literals.
Francesco Banconi (frankban) wrote : | # |
*** Submitted:
Improve views input handling.
Add a new set_return_
views can call to set the value encapsulated
in the AppExit exception raised when the user
quits the application with ^X.
Tests: `make check`
QA: start the demo app
(`make` and `./cli-
Try exiting with ^X: you should see the
"no environment selected" message.
Restart and exit selecting an environment:
you should see a message indicating that
environment has been selected.
Restart and change the default environment,
then exit: you should see the new env_db
printed and the new default environment.
R=matthew.scott
CC=
https:/
Francesco Banconi (frankban) wrote : | # |
Thank you!
Preview Diff
1 | === modified file 'cli-app-demo.py' |
2 | --- cli-app-demo.py 2013-12-18 15:41:31 +0000 |
3 | +++ cli-app-demo.py 2013-12-19 17:41:55 +0000 |
4 | @@ -27,6 +27,8 @@ |
5 | unicode_literals, |
6 | ) |
7 | |
8 | +import pprint |
9 | + |
10 | from quickstart.cli import views |
11 | from quickstart.models import envs |
12 | from quickstart.tests import helpers |
13 | @@ -43,6 +45,7 @@ |
14 | views.env_index, env_type_db, env_db, save_callable) |
15 | if new_env_db != env_db: |
16 | print('saved a new env db') |
17 | + pprint.pprint(new_env_db) |
18 | print('default: {}'.format(new_env_db.get('default'))) |
19 | if env_data is None: |
20 | print('no environment selected') |
21 | |
22 | === modified file 'quickstart/cli/base.py' |
23 | --- quickstart/cli/base.py 2013-12-19 09:12:37 +0000 |
24 | +++ quickstart/cli/base.py 2013-12-19 17:41:55 +0000 |
25 | @@ -40,6 +40,7 @@ |
26 | 'set_contents', 'get_contents', |
27 | 'set_status', 'get_status', |
28 | 'set_message', 'get_message', |
29 | + 'set_return_value_on_exit', |
30 | ], |
31 | ) |
32 | |
33 | @@ -102,7 +103,12 @@ |
34 | - set_message(text): set/change a notification message, which is |
35 | displayed in the footer for a couple of seconds before disappearing; |
36 | - get_message(): return the message currently displayed in the |
37 | - notifications area. |
38 | + notifications area; |
39 | + |
40 | + - set_return_value_on_exit(value): set the value to be encapsulated |
41 | + in the AppExit exception raised when the user quits the application |
42 | + with the exit shortcut. See the quickstart.cli.views module docstring |
43 | + for more information about this functionality. |
44 | """ |
45 | # Set up the application header. |
46 | title = urwid.Text('\npreparing...') |
47 | @@ -143,10 +149,18 @@ |
48 | valign='middle', height=('relative', 90), |
49 | min_width=78, min_height=20) |
50 | # Instantiate the Urwid main loop. |
51 | - loop = _MainLoop(top_widget, palette=ui.PALETTE) |
52 | + loop = _MainLoop( |
53 | + top_widget, palette=ui.PALETTE, |
54 | + unhandled_input=ui.exit_and_return(None)) |
55 | # Add a timeout to the notification message. |
56 | timeout_message = ui.TimeoutText( |
57 | message, 3, loop.set_alarm_in, loop.remove_alarm) |
58 | + |
59 | + # Allow views to set the value returned when the user quits the session. |
60 | + def set_return_value_on_exit(return_value): |
61 | + unhandled_input = ui.exit_and_return(return_value) |
62 | + loop.set_unhandled_input(unhandled_input) |
63 | + |
64 | # Create the App named tuple. If, in the future, we have a view that |
65 | # requires additional capabilities or API access, this is the place to add |
66 | # those. |
67 | @@ -159,5 +173,6 @@ |
68 | get_status=lambda: status.text, |
69 | set_message=lambda msg: timeout_message.set_text(('message', msg)), |
70 | get_message=lambda: timeout_message.text, |
71 | + set_return_value_on_exit=set_return_value_on_exit, |
72 | ) |
73 | return loop, app |
74 | |
75 | === modified file 'quickstart/cli/views.py' |
76 | --- quickstart/cli/views.py 2013-12-19 09:30:08 +0000 |
77 | +++ quickstart/cli/views.py 2013-12-19 17:41:55 +0000 |
78 | @@ -38,7 +38,7 @@ |
79 | |
80 | def myview(app, title): |
81 | app.set_title(title) |
82 | - return 42 |
83 | + app.set_return_value_on_exit(42) |
84 | |
85 | The view above, requiring a title argument, can be started this way: |
86 | |
87 | @@ -52,9 +52,12 @@ |
88 | 2) a view decides it is time to quit (e.g. reacting to an event/input). |
89 | |
90 | In both cases, the show function returns something to the caller: |
91 | - 1) when the user explicitly requests to quit, the value returned by the |
92 | - view itself (the callable passed to show()) is returned. For instance, |
93 | - the show(myview...) call above would return 42; |
94 | + 1) when the user explicitly requests to quit, None is returned by default. |
95 | + However, the view can change this default value by calling |
96 | + app.set_return_value_on_exit(some value). For instance, the |
97 | + show(myview...) call above would return 42. It is safe to call the |
98 | + set_return_value_on_exit API multiple times in order to overwrite the |
99 | + value returned on user exit; |
100 | 2) to force the end of the interactive session, a view can raise a |
101 | quickstart.cli.ui.AppExit exception, passing a return value: if the |
102 | application is exited this way, then show() returns the value |
103 | @@ -71,18 +74,19 @@ |
104 | def exit(): |
105 | raise ui.AppExit(True) |
106 | |
107 | + app.set_return_value_on_exit(False) |
108 | app.set_title('behold the button below') |
109 | button = ui.MenuButton('press to exit', ui.thunk(exit)) |
110 | widgets = urwid.ListBox(urwid.SimpleFocusListWalker([button])) |
111 | app.set_contents(widgets) |
112 | - return False |
113 | |
114 | pressed = views.show(button_view) |
115 | |
116 | In this example the button_view function configures the App to show a button. |
117 | -Clicking that button an AppExit(True) is raised. The view itself instead just |
118 | -returns False. This means that "pressed" will be True if the user exited using |
119 | -the button, or False if the user exited using the global shortcut. |
120 | +Clicking that button an AppExit(True) is raised. The return value on exit |
121 | +instead is set by the view itself to False. This means that "pressed" will be |
122 | +True if the user exited using the button, or False if the user exited using the |
123 | +global shortcut. |
124 | |
125 | As a final note, it is absolutely safe for a view to call, directly or |
126 | indirectly, other views, as long as all the arguments required by the other |
127 | @@ -111,16 +115,14 @@ |
128 | The view is called passing an App named tuple and the provided *args. |
129 | |
130 | Block until the main loop is stopped, either by the user with the exit |
131 | - shortcut or by the view itself with the AppExit exception. In the former |
132 | - case, return what is returned by the view. In the AppExit case, return |
133 | - the value encapsulated in the exception. |
134 | + shortcut or by the view itself. In both cases, an ui.AppExit is raised, and |
135 | + the return value is encapsulated in the exception. |
136 | """ |
137 | loop, app = base.setup_urwid_app() |
138 | - default_return_value = view(app, *args) |
139 | - unhandled_input = ui.exit_and_return(default_return_value) |
140 | - loop.set_unhandled_input(unhandled_input) |
141 | - # Start the Urwid interactive session (main loop). |
142 | try: |
143 | + # Execute the view. |
144 | + view(app, *args) |
145 | + # Start the Urwid interactive session (main loop). |
146 | loop.run() |
147 | except ui.AppExit as err: |
148 | return err.return_value |
149 | @@ -137,6 +139,10 @@ |
150 | - save_callable: a function called to save a new environment database. |
151 | """ |
152 | env_db = copy.deepcopy(env_db) |
153 | + # All the environment views return a tuple (new_env_db, env_data). |
154 | + # Set the env_data to None in the case the user quits the application |
155 | + # without selecting an environment to use. |
156 | + app.set_return_value_on_exit((env_db, None)) |
157 | detail_view = functools.partial( |
158 | env_detail, app, env_type_db, env_db, save_callable) |
159 | # Alphabetically sort the existing environments. |
160 | @@ -185,7 +191,6 @@ |
161 | if status: |
162 | app.set_status(status) |
163 | app.set_contents(contents) |
164 | - return env_db, None |
165 | |
166 | |
167 | def env_detail(app, env_type_db, env_db, save_callable, env_data): |
168 | @@ -201,6 +206,10 @@ |
169 | - env_data: the environment data. |
170 | """ |
171 | env_db = copy.deepcopy(env_db) |
172 | + # All the environment views return a tuple (new_env_db, env_data). |
173 | + # Set the env_data to None in the case the user quits the application |
174 | + # without selecting an environment to use. |
175 | + app.set_return_value_on_exit((env_db, None)) |
176 | index_view = functools.partial( |
177 | env_index, app, env_type_db, env_db, save_callable) |
178 | |
179 | @@ -250,4 +259,3 @@ |
180 | widgets.append(ui.create_controls(*controls)) |
181 | listbox = urwid.ListBox(urwid.SimpleFocusListWalker(widgets)) |
182 | app.set_contents(listbox) |
183 | - return env_db, None |
184 | |
185 | === added file 'quickstart/tests/cli/helpers.py' |
186 | --- quickstart/tests/cli/helpers.py 1970-01-01 00:00:00 +0000 |
187 | +++ quickstart/tests/cli/helpers.py 2013-12-19 17:41:55 +0000 |
188 | @@ -0,0 +1,31 @@ |
189 | +# This file is part of the Juju Quickstart Plugin, which lets users set up a |
190 | +# Juju environment in very few steps (https://launchpad.net/juju-quickstart). |
191 | +# Copyright (C) 2013 Canonical Ltd. |
192 | +# |
193 | +# This program is free software: you can redistribute it and/or modify it under |
194 | +# the terms of the GNU Affero General Public License version 3, as published by |
195 | +# the Free Software Foundation. |
196 | +# |
197 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
198 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
199 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
200 | +# Affero General Public License for more details. |
201 | +# |
202 | +# You should have received a copy of the GNU Affero General Public License |
203 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
204 | + |
205 | +"""Test helpers for the Juju Quickstart CLI infrastructure.""" |
206 | + |
207 | +from __future__ import unicode_literals |
208 | + |
209 | +from quickstart.cli import ui |
210 | + |
211 | + |
212 | +class CliAppTestsMixin(object): |
213 | + """Helper methods to test Quickstart CLI applications.""" |
214 | + |
215 | + def get_on_exit_return_value(self, loop): |
216 | + """Return the value returned by the application when the user quits.""" |
217 | + with self.assertRaises(ui.AppExit) as context_manager: |
218 | + loop.unhandled_input(ui.EXIT_KEY) |
219 | + return context_manager.exception.return_value |
220 | |
221 | === modified file 'quickstart/tests/cli/test_base.py' |
222 | --- quickstart/tests/cli/test_base.py 2013-12-19 09:12:37 +0000 |
223 | +++ quickstart/tests/cli/test_base.py 2013-12-19 17:41:55 +0000 |
224 | @@ -23,6 +23,7 @@ |
225 | import urwid |
226 | |
227 | from quickstart.cli import base |
228 | +from quickstart.tests.cli import helpers as cli_helpers |
229 | |
230 | |
231 | class TestMainLoop(unittest.TestCase): |
232 | @@ -57,7 +58,7 @@ |
233 | self.assertEqual(1, sum(times_called)) |
234 | |
235 | |
236 | -class TestSetupUrwidApp(unittest.TestCase): |
237 | +class TestSetupUrwidApp(cli_helpers.CliAppTestsMixin, unittest.TestCase): |
238 | |
239 | def setUp(self): |
240 | # Set up the base Urwid application. |
241 | @@ -164,3 +165,21 @@ |
242 | message_widget = self.get_message_widget(self.loop) |
243 | message_widget.set_text('42') |
244 | self.assertEqual('42', self.app.get_message()) |
245 | + |
246 | + def test_default_unhandled_input(self): |
247 | + # The default unhandled_input function is configured so that a |
248 | + # quickstart.cli.ui.AppExit exception is raised. The exception's |
249 | + # return value is None by default. |
250 | + return_value = self.get_on_exit_return_value(self.loop) |
251 | + self.assertIsNone(return_value) |
252 | + |
253 | + def test_set_return_value_on_exit(self): |
254 | + # It is possible to change the value returned by the AppExit exception |
255 | + # when the user quits the application using the exit shortcut. |
256 | + self.app.set_return_value_on_exit(42) |
257 | + return_value = self.get_on_exit_return_value(self.loop) |
258 | + self.assertEqual(42, return_value) |
259 | + # The value can be changed multiple times. |
260 | + self.app.set_return_value_on_exit([47, None]) |
261 | + return_value = self.get_on_exit_return_value(self.loop) |
262 | + self.assertEqual([47, None], return_value) |
263 | |
264 | === modified file 'quickstart/tests/cli/test_views.py' |
265 | --- quickstart/tests/cli/test_views.py 2013-12-19 09:30:08 +0000 |
266 | +++ quickstart/tests/cli/test_views.py 2013-12-19 17:41:55 +0000 |
267 | @@ -31,6 +31,7 @@ |
268 | ) |
269 | from quickstart.models import envs |
270 | from quickstart.tests import helpers |
271 | +from quickstart.tests.cli import helpers as cli_helpers |
272 | |
273 | |
274 | class TestShow(unittest.TestCase): |
275 | @@ -70,17 +71,6 @@ |
276 | return_value = views.show(view) |
277 | self.assertEqual('bad wolf', return_value) |
278 | |
279 | - def test_unhandled_input(self): |
280 | - # The unhandled_input callable is properly set up and registered to |
281 | - # the main loop. |
282 | - view = mock.Mock(return_value=42) |
283 | - with self.patch_setup_urwid_app() as (mock_loop, mock_app): |
284 | - views.show(view) |
285 | - unhandled_input = mock_loop.set_unhandled_input.call_args[0][0] |
286 | - with self.assertRaises(ui.AppExit) as context_manager: |
287 | - unhandled_input(ui.EXIT_KEY) |
288 | - self.assertEqual(42, context_manager.exception.return_value) |
289 | - |
290 | def test_view_arguments(self): |
291 | # The view is invoked passing the app and all the optional show |
292 | # function arguments. |
293 | @@ -90,7 +80,7 @@ |
294 | view.assert_called_once_with(mock_app, 'arg1', 'arg2') |
295 | |
296 | |
297 | -class EnvViewTestsMixin(object): |
298 | +class EnvViewTestsMixin(cli_helpers.CliAppTestsMixin): |
299 | """Shared helpers for testing environment views.""" |
300 | |
301 | env_type_db = envs.get_env_type_db() |
302 | @@ -145,12 +135,13 @@ |
303 | |
304 | class TestEnvIndex(EnvViewTestsMixin, unittest.TestCase): |
305 | |
306 | - def test_view_return_value(self): |
307 | - # The view returns a tuple including a copy of the given env_db and |
308 | - # None, the latter meaning no environment has been selected. |
309 | + def test_view_default_return_value_on_exit(self): |
310 | + # The view configures the app so that the return value on user exit is |
311 | + # a tuple including a copy of the given env_db and None, the latter |
312 | + # meaning no environment has been selected. |
313 | env_db = helpers.make_env_db() |
314 | - new_env_db, env_data = views.env_index( |
315 | - self.app, self.env_type_db, env_db, self.save_callable) |
316 | + views.env_index(self.app, self.env_type_db, env_db, self.save_callable) |
317 | + new_env_db, env_data = self.get_on_exit_return_value(self.loop) |
318 | self.assertEqual(env_db, new_env_db) |
319 | self.assertIsNot(env_db, new_env_db) |
320 | self.assertIsNone(env_data) |
321 | @@ -249,10 +240,12 @@ |
322 | self.app, self.env_type_db, self.env_db, self.save_callable, |
323 | self.env_data) |
324 | |
325 | - def test_view_return_value(self): |
326 | - # The view returns a tuple including a copy of the given env_db and |
327 | - # None, the latter meaning no environment has been selected (for now). |
328 | - new_env_db, env_data = self.call_view() |
329 | + def test_view_default_return_value_on_exit(self): |
330 | + # The view configures the app so that the return value on user exit is |
331 | + # a tuple including a copy of the given env_db and None, the latter |
332 | + # meaning no environment has been selected (for now). |
333 | + self.call_view() |
334 | + new_env_db, env_data = self.get_on_exit_return_value(self.loop) |
335 | self.assertEqual(self.env_db, new_env_db) |
336 | self.assertIsNot(self.env_db, new_env_db) |
337 | self.assertIsNone(env_data) |
Reviewers: mp+199640_ code.launchpad. net,
Message:
Please take a look.
Description:
Improve views input handling.
Add a new set_return_ value_on_ exit API that
views can call to set the value encapsulated
in the AppExit exception raised when the user
quits the application with ^X.
Tests: `make check`
QA: start the demo app app-demo. py).
(`make` and `./cli-
Try exiting with ^X: you should see the
"no environment selected" message.
Restart and exit selecting an environment:
you should see a message indicating that
environment has been selected.
Restart and change the default environment,
then exit: you should see the new env_db
printed and the new default environment.
https:/ /code.launchpad .net/~frankban/ juju-quickstart /improve- input-handling/ +merge/ 199640
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/43600047/
Affected files (+110, -41 lines): cli/base. py cli/views. py tests/cli/ helpers. py tests/cli/ test_base. py tests/cli/ test_views. py
A [revision details]
M cli-app-demo.py
M quickstart/
M quickstart/
A quickstart/
M quickstart/
M quickstart/