Merge lp:~zyga/lava-dashboard-tool/4.1 into lp:lava-dashboard-tool/linaro-11.05

Proposed by Zygmunt Krynicki
Status: Merged
Merged at revision: 132
Proposed branch: lp:~zyga/lava-dashboard-tool/4.1
Merge into: lp:lava-dashboard-tool/linaro-11.05
Diff against target: 285 lines (+182/-10)
4 files modified
launch_control_tool/commands/dashboard.py (+158/-7)
launch_control_tool/dispatcher.py (+8/-3)
launch_control_tool/interface.py (+14/-0)
setup.py (+2/-0)
To merge this branch: bzr merge lp:~zyga/lava-dashboard-tool/4.1
Reviewer Review Type Date Requested Status
Zygmunt Krynicki Pending
Review via email: mp+57118@code.launchpad.net

Description of the change

New sub-commands for querying and listing data views.

To post a comment you must log in.
lp:~zyga/lava-dashboard-tool/4.1 updated
140. By Zygmunt Krynicki

Display the value of DASHBOARD_URL in --help of certain commands

141. By Zygmunt Krynicki

Improvements to pull

The UI is now more informative and better oriented. It can use a new feature in
0.4 server series to calculate the amount of data that needs to be synchronized
in each stream. The output is also more consistent and easier to follow.

Another feature is ability to pull from specific bundle stream (or list of
bundle streams). This fixes LP: #752775

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'launch_control_tool/commands/dashboard.py'
2--- launch_control_tool/commands/dashboard.py 2011-04-07 07:57:50 +0000
3+++ launch_control_tool/commands/dashboard.py 2011-04-11 08:34:24 +0000
4@@ -22,16 +22,17 @@
5 launch_control.dashboard_app.xml_rpc package.
6 """
7
8+import argparse
9+import contextlib
10 import errno
11 import os
12+import re
13 import socket
14 import sys
15+import urllib
16 import urlparse
17-import urllib
18 import xmlrpclib
19
20-import argparse
21-
22 from launch_control_tool.interface import Command
23
24
25@@ -326,9 +327,10 @@
26 help="Show XML-RPC data")
27 return group
28
29- def invoke(self):
30+ @contextlib.contextmanager
31+ def safety_net(self):
32 try:
33- return self.invoke_remote()
34+ yield
35 except socket.error as ex:
36 print >>sys.stderr, "Unable to connect to server at %s" % (
37 self.args.dashboard_url,)
38@@ -353,7 +355,10 @@
39 print >>sys.stderr, ("This command requires at least server version "
40 "%s, actual server version is %s" %
41 (ex.required_version, ex.server_version))
42- return -1
43+
44+ def invoke(self):
45+ with self.safety_net():
46+ return self.invoke_remote()
47
48 def handle_xmlrpc_fault(self, faultCode, faultString):
49 if faultCode == 500:
50@@ -366,6 +371,61 @@
51 raise NotImplementedError()
52
53
54+class ExperimentalNoticeAction(argparse.Action):
55+ """
56+ Argparse action that implements the --experimental-notice
57+ """
58+
59+ message = """
60+ Some lc-tool sub-commands are marked as EXPERIMENTAL. Those commands are
61+ not guaranteed to work identically, or have identical interface between
62+ subsequent lc-tool releases.
63+
64+ We do that to make it possible to provide good user interface and
65+ server-side API when working on new features. Once a feature is stabilized
66+ the UI will be frozen and all subsequent changes will retain backwards
67+ compatibility.
68+ """
69+ message = message.lstrip()
70+ message = re.sub(re.compile("[ \t]+", re.M), " ", message)
71+ message = re.sub(re.compile("^ ", re.M), "", message)
72+
73+ def __init__(self,
74+ option_strings, dest, default=None, required=False,
75+ help=None):
76+ super(ExperimentalNoticeAction, self).__init__(
77+ option_strings=option_strings, dest=dest, default=default, nargs=0,
78+ help=help)
79+
80+ def __call__(self, parser, namespace, values, option_string=None):
81+ parser.exit(message=self.message)
82+
83+
84+class ExperimentalCommandMixIn(object):
85+ """
86+ Experimental command.
87+
88+ Prints a warning message on each call to invoke()
89+ """
90+
91+ def invoke(self):
92+ self.print_experimental_notice()
93+ return super(ExperimentalCommandMixIn, self).invoke()
94+
95+ @classmethod
96+ def register_arguments(cls, parser):
97+ retval = super(ExperimentalCommandMixIn, cls).register_arguments(parser)
98+ parser.register("action", "experimental_notice", ExperimentalNoticeAction)
99+ parser.add_argument("--experimental-notice",
100+ action="experimental_notice",
101+ default=argparse.SUPPRESS,
102+ help="Explain the nature of experimental commands")
103+ return retval
104+
105+ def print_experimental_notice(self):
106+ print "EXPERIMENTAL - SUBJECT TO CHANGE (See --experimental-notice for more info)"
107+
108+
109 class server_version(XMLRPCCommand):
110 """
111 Display dashboard server version
112@@ -632,7 +692,7 @@
113 self.server.put(content, content_filename, stream_pathname)
114
115
116-class pull(XMLRPCCommand):
117+class pull(ExperimentalCommandMixIn, XMLRPCCommand):
118 """
119 Pull bundles and bundle streams from one REMOTE DASHBOARD to DASHBOARD
120 """
121@@ -666,3 +726,94 @@
122 print "Pulling bundle %s" % content_sha1
123 data = self.remote_server.get(content_sha1)
124 self.server.put(data["content"], data["content_filename"], stream["pathname"])
125+
126+
127+class data_views(ExperimentalCommandMixIn, XMLRPCCommand):
128+ """
129+ Show data views defined on the server
130+ """
131+ renderer = DataSetRenderer(
132+ column_map = {
133+ 'name': 'Name',
134+ 'summary': 'Summary',
135+ },
136+ order = ('name', 'summary'),
137+ empty = "There are no data views defined yet",
138+ caption = "Data Views",
139+ separator = " | ")
140+
141+ def invoke_remote(self):
142+ self._check_server_version(self.server, "0.4.0.dev")
143+ self.renderer.render(self.server.data_views())
144+ print
145+ print "Tip: to invoke a data view try `lc-tool query-data-view`"
146+
147+
148+class query_data_view(ExperimentalCommandMixIn, XMLRPCCommand):
149+ """
150+ Invoke a specified data view
151+ """
152+ @classmethod
153+ def register_arguments(cls, parser):
154+ super(query_data_view, cls).register_arguments(parser)
155+ parser.add_argument("QUERY", metavar="QUERY", nargs="...",
156+ help="Data view name and any optional and required arguments")
157+
158+ def _probe_data_views(self):
159+ """
160+ Probe the server for information about data views
161+ """
162+ with self.safety_net():
163+ self._check_server_version(self.server, "0.4.0.dev")
164+ return self.server.data_views()
165+
166+ def reparse_arguments(self, parser, raw_args):
167+ self.data_views = self._probe_data_views()
168+ if self.data_views is None:
169+ return
170+ # Here we hack a little, the last actuin is the QUERY action added
171+ # in register_arguments above. By removing it we make the output
172+ # of lc-tool query-data-view NAME --help more consistent.
173+ del parser._actions[-1]
174+ subparsers = parser.add_subparsers(
175+ title="Data views available on the server")
176+ for data_view in self.data_views:
177+ data_view_parser = subparsers.add_parser(
178+ data_view["name"],
179+ help=data_view["summary"],
180+ epilog=data_view["documentation"])
181+ data_view_parser.set_defaults(data_view=data_view)
182+ group = data_view_parser.add_argument_group("Data view parameters")
183+ for argument in data_view["arguments"]:
184+ group.add_argument(
185+ "--{name}".format(name=argument["name"]),
186+ help=argument["help"],
187+ type=str,
188+ default=argument["default"])
189+ self.args = self.parser.parse_args(raw_args)
190+
191+ def invoke_remote(self):
192+ if self.data_views is None:
193+ return -1
194+ self._check_server_version(self.server, "0.4.0.dev")
195+ # Build a collection of arguments for data view
196+ data_view_args = {}
197+ for argument in self.args.data_view["arguments"]:
198+ arg_name = argument["name"]
199+ if arg_name in self.args:
200+ data_view_args[arg_name] = getattr(self.args, arg_name)
201+ # Invoke the data view
202+ response = self.server.query_data_view(self.args.data_view["name"], data_view_args)
203+ # Create a pretty-printer
204+ renderer = DataSetRenderer(
205+ separator=" | ",
206+ caption=self.args.data_view["summary"],
207+ order=[item["name"] for item in response["columns"]])
208+ # Post-process the data so that it fits the printer
209+ data_for_renderer = [
210+ dict(zip(
211+ [column["name"] for column in response["columns"]],
212+ row))
213+ for row in response["rows"]]
214+ # Print the data
215+ renderer.render(data_for_renderer)
216
217=== modified file 'launch_control_tool/dispatcher.py'
218--- launch_control_tool/dispatcher.py 2011-04-06 17:52:07 +0000
219+++ launch_control_tool/dispatcher.py 2011-04-11 08:34:24 +0000
220@@ -36,7 +36,7 @@
221 """,
222 epilog="""
223 Please report all bugs using the Launchpad bug tracker:
224- http://bugs.launchpad.net/launch-control/+filebug
225+ http://bugs.launchpad.net/launch-control-tool/+filebug
226 """,
227 add_help=False)
228 self.subparsers = self.parser.add_subparsers(
229@@ -48,11 +48,16 @@
230 help=command_cls.get_help(),
231 epilog=command_cls.get_epilog())
232 sub_parser.set_defaults(command_cls=command_cls)
233+ sub_parser.set_defaults(sub_parser=sub_parser)
234 command_cls.register_arguments(sub_parser)
235
236- def dispatch(self, args=None):
237- args = self.parser.parse_args(args)
238+ def dispatch(self, raw_args=None):
239+ args = self.parser.parse_args(raw_args)
240 command = args.command_cls(self.parser, args)
241+ try:
242+ command.reparse_arguments(args.sub_parser, raw_args)
243+ except NotImplementedError:
244+ pass
245 return command.invoke()
246
247
248
249=== modified file 'launch_control_tool/interface.py'
250--- launch_control_tool/interface.py 2011-04-07 07:49:45 +0000
251+++ launch_control_tool/interface.py 2011-04-11 08:34:24 +0000
252@@ -47,6 +47,20 @@
253 """
254 raise NotImplementedError()
255
256+ def reparse_arguments(self, parser, raw_args):
257+ """
258+ Re-parse raw arguments into normal argments
259+
260+ Parser is the same as in register_arguments (a sub-parser)
261+ The true, topmost parser is in self.parser.
262+
263+ This method is only needed for specific commands
264+ that need to peek at the arguments before being
265+ able to truly redefine the parser and reparse the
266+ raw arguments again.
267+ """
268+ raise NotImplementedError()
269+
270 @classmethod
271 def get_name(cls):
272 """
273
274=== modified file 'setup.py'
275--- setup.py 2011-04-06 14:30:23 +0000
276+++ setup.py 2011-04-11 08:34:24 +0000
277@@ -48,6 +48,8 @@
278 restore = launch_control_tool.commands.dashboard:restore
279 pull = launch_control_tool.commands.dashboard:pull
280 streams = launch_control_tool.commands.dashboard:streams
281+ data_views = launch_control_tool.commands.dashboard:data_views
282+ query_data_view = launch_control_tool.commands.dashboard:query_data_view
283 version = launch_control_tool.commands.misc:version
284 """,
285 classifiers=[

Subscribers

People subscribed via source and target branches

to all changes: