Merge lp:~cboylan/boots/boots_cleanup into lp:boots

Proposed by Clark Boylan
Status: Merged
Merged at revision: not available
Proposed branch: lp:~cboylan/boots/boots_cleanup
Merge into: lp:boots
Diff against target: 528 lines (+195/-19)
11 files modified
boots.py (+7/-1)
boots/api/api.py (+11/-0)
boots/app/client_config.py (+7/-5)
boots/app/info.py (+2/-0)
boots/lib/console.py (+2/-0)
boots/lib/ui/generic.py (+8/-0)
boots/lib/ui/plain.py (+40/-3)
setup.py (+3/-1)
tests/boots/api/api_tests.py (+14/-1)
tests/boots/boots_unit_test.py (+51/-2)
tests/boots/tests.py (+50/-6)
To merge this branch: bzr merge lp:~cboylan/boots/boots_cleanup
Reviewer Review Type Date Requested Status
David Rosenbaum Approve
Review via email: mp+19461@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Clark Boylan (cboylan) wrote :

I felt the need for some mostly thoughtless work so I started to write much needed docstrings and apply some of the suggestions made by pylint.

Revision history for this message
David Rosenbaum (davidjrosenbaum) wrote :

Looks good; I'll go ahead and merge this.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'boots.py'
2--- boots.py 2010-02-14 22:25:48 +0000
3+++ boots.py 2010-02-17 03:55:20 +0000
4@@ -28,15 +28,21 @@
5 # Contributor(s):
6 #
7 # ##### END LICENSE BLOCK #####
8+"""This module is the entry point to boots. It builds a configuration and
9+starts a boots console with that configuration."""
10
11 from boots.app import client_config
12 from boots.lib.console import Console
13
14 def main():
15+ """Entrance point to the boots executable.
16+
17+ Loads a boots configuration and starts the boots console with that
18+ configuration."""
19 configuration = client_config.ClientConfig()
20 configuration.load()
21 console = Console(configuration)
22 console.main()
23
24-if __name__=='__main__':
25+if __name__ == "__main__":
26 main()
27
28=== modified file 'boots/api/api.py'
29--- boots/api/api.py 2010-02-15 22:15:08 +0000
30+++ boots/api/api.py 2010-02-17 03:55:20 +0000
31@@ -27,6 +27,12 @@
32 # Contributor(s):
33 #
34 # ##### END LICENSE BLOCK #####
35+"""This module provides a high level API to SQL DB servers. It sits above
36+the Drizzle dbapi providing boots with a simple and easy to use interface to
37+drizzle.
38+
39+This API can be implemented for any dbapi implementation allowing boots to
40+speak to a variety of DB servers."""
41
42 from drizzle import db
43
44@@ -49,6 +55,8 @@
45 self._connection = None
46
47 def connect(self):
48+ """Connect to the database server whose details were provided through
49+ the __init__ method."""
50 self._connection = db.connect(
51 host = self.hostname,
52 port = self.port,
53@@ -57,11 +65,14 @@
54 database = self.auth.get('database', ""))
55
56 def disconnect(self):
57+ """Disconnect from the connected database server."""
58 self._connection.close()
59 self._connection = None
60
61 @property
62 def is_connected(self):
63+ """Property that is True when the Server is connected and False
64+ otherwise."""
65 return self._connection is not None
66
67 def execute(self, query):
68
69=== modified file 'boots/app/client_config.py'
70--- boots/app/client_config.py 2010-02-07 09:29:31 +0000
71+++ boots/app/client_config.py 2010-02-17 03:55:20 +0000
72@@ -27,6 +27,9 @@
73 # Contributor(s):
74 #
75 # ##### END LICENSE BLOCK #####
76+"""This module provides a class that builds a boots client configuration. The
77+configuration is built using a set of defaults, an rc file, and command line
78+options/arguments."""
79
80 import optparse
81 import os
82@@ -40,7 +43,6 @@
83
84 This method performs no IO and after initializaion you must call load
85 to populate a config."""
86-
87 # Specify default values for a client configuration.
88 self._defaults = {"command": None,
89 "database": None,
90@@ -64,7 +66,8 @@
91 self.conf_file = conf_file_loc
92
93 # Build cli option parser.
94- version_string = "{name} version {version}".format(name = info.NAME, version = info.VERSION)
95+ version_string = "{name} version {version}".format(name = info.NAME,
96+ version = info.VERSION)
97 self._cli_parser = optparse.OptionParser(version = version_string);
98 self._cli_parser.add_option("-c", "--command",
99 action = "store",
100@@ -132,9 +135,11 @@
101 help = "Specify max history file length")
102
103 def __getitem__(self, key):
104+ """Allows one to use the [] operator to get config items."""
105 return self._dict[key]
106
107 def __setitem__(self, key, value):
108+ """Allows one to use the [] to set config items."""
109 self._dict[key] = value
110
111 @property
112@@ -147,7 +152,6 @@
113
114 Options specified in rc file overwrite defaults and cli options
115 overwrite rc options."""
116-
117 from_file = self.get_file_conf(self.conf_file)
118 from_cli = self.get_cli_conf()
119 self._dict.update(from_file)
120@@ -155,7 +159,6 @@
121
122 def get_file_conf(self, filepath):
123 """Read a configuration from the specified file. Return a dict."""
124-
125 file_dict = {}
126 try:
127 execfile(filepath, {}, file_dict)
128@@ -169,7 +172,6 @@
129
130 def get_cli_conf(self):
131 """Read a configuration from the cli. Return a dict."""
132-
133 (options, args) = self._cli_parser.parse_args()
134 tmp_options = vars(options)
135 new_options = {}
136
137=== modified file 'boots/app/info.py'
138--- boots/app/info.py 2009-12-24 02:43:12 +0000
139+++ boots/app/info.py 2010-02-17 03:55:20 +0000
140@@ -27,6 +27,8 @@
141 # Contributor(s):
142 #
143 # ##### END LICENSE BLOCK #####
144+"""Module that contains information about the boots client. For example
145+the name of the client and its version."""
146
147 import os
148
149
150=== modified file 'boots/lib/console.py'
151--- boots/lib/console.py 2010-02-11 00:11:38 +0000
152+++ boots/lib/console.py 2010-02-17 03:55:20 +0000
153@@ -27,6 +27,8 @@
154 # Contributor(s):
155 #
156 # ##### END LICENSE BLOCK #####
157+"""This module provides the boots Console class. This class is the core
158+driver of any boots client."""
159
160 from boots.api import api
161 from boots.api.nodes import node
162
163=== modified file 'boots/lib/ui/generic.py'
164--- boots/lib/ui/generic.py 2010-02-07 09:29:31 +0000
165+++ boots/lib/ui/generic.py 2010-02-17 03:55:20 +0000
166@@ -27,12 +27,17 @@
167 # Contributor(s):
168 #
169 # ##### END LICENSE BLOCK #####
170+"""This module provides a simple generic UI for boots. The UI is composed of
171+two pieces, each classes, the StreamDriver and StreamPresenter."""
172
173 import sys
174 import StringIO
175
176 class StreamDriver(object):
177+ """StreamDrivers receive stream inputs to boots, stdin for example."""
178 def __init__(self, console, stream, lingo):
179+ """Initialize a StreamDriver giving it a link back to its parent
180+ console, the stream to receive input on, and the lingo being used."""
181 self.console = console
182 self.stream = stream
183 self.lingo = lingo
184@@ -55,7 +60,10 @@
185 pass
186
187 class StreamPresenter(object):
188+ """StreamPresenters write out boots results to streams, stdout for
189+ example."""
190 def __init__(self, stream):
191+ """Initialize a StreamPresenter giving it the stream to write to."""
192 self.stream = stream
193
194 def present(self, row):
195
196=== modified file 'boots/lib/ui/plain.py'
197--- boots/lib/ui/plain.py 2010-02-14 21:13:24 +0000
198+++ boots/lib/ui/plain.py 2010-02-17 03:55:20 +0000
199@@ -27,6 +27,8 @@
200 # Contributor(s):
201 #
202 # ##### END LICENSE BLOCK #####
203+"""This module provides a 'plain' UI for boots. The UI is plain because it
204+uses stdin and stdout to do its job."""
205
206 import readline
207 import sys
208@@ -36,7 +38,14 @@
209 from boots.lib.ui.generic import StdinDriver, StdoutPresenter
210
211 class PlainUI(StdinDriver, StdoutPresenter):
212+ """Class that provides a 'plain' UI for boots. Input is taken on stdin,
213+ primary and secondary prompts are provided depending on context, and
214+ results are printed to stdout. These results are printed as tables when
215+ they are Server packets and as string interpretations otherwise."""
216 def __init__(self, console, prompt1, prompt2, hist_length, hist_file, lingo):
217+ """Initialize a UI giving it its parent console, primary prompt,
218+ secondary prompt, maximum number of history lines, history file, and
219+ lingo to be used."""
220 self.metacommands = MetaCommands({
221 '\use': self.switchlingo})
222 self.console = console
223@@ -52,20 +61,26 @@
224 self.read_history()
225
226 def read_history(self):
227+ """Read existing history from the history file."""
228 try:
229 readline.read_history_file(self.hist_file)
230 except IOError:
231 pass
232
233 def save_history(self):
234+ """Save history from this session back to the history file."""
235 # FIXME: commands with newlines are not properly being preserved
236 readline.write_history_file(self.hist_file)
237
238 def unload(self):
239+ """'unload' a PlainUI saving its history."""
240 self.save_history()
241
242 def get_input(self):
243- """Gets user input from stdin via a prompt."""
244+ """Gets user input from stdin via a prompt.
245+
246+ On incomplete inputs print the secondary prompt instead of the
247+ primary print."""
248 while True:
249 try:
250 line = raw_input(self.prompt1)
251@@ -87,14 +102,34 @@
252 except EOFError:
253 return
254
255- def set_prompt(self, prompt):
256- self.prompt = prompt
257+ def set_prompt(self, prompt1 = None, prompt2 = None):
258+ """Change the prompts during execution of a PlainUI."""
259+ if prompt1 is None and prompt2 is None:
260+ return
261+ elif prompt2 is None:
262+ self.prompt1 = prompt1
263+ elif prompt1 is None:
264+ self.prompt2 = prompt2
265+ else:
266+ self.prompt1 = prompt1
267+ self.prompt2 = prompt2
268
269 def present(self, result):
270+ """Print the result provided as an argument.
271+
272+ If the result is a packet buffer the results until the last packet is
273+ received. At that point print all of the buffered result rows in table
274+ format. Non packet results are presented in their string representation
275+ as received."""
276 def padded(fields, widths):
277+ """Utility function used to convert rows from tuples to table rows.
278+ Results are returned as strings."""
279 return "| {0} |\n".format(" | ".join(map(str.ljust, fields, widths)))
280
281 def show_NULL(value):
282+ """There is a 'bug' in the dbapi that does not convert NULL objects
283+ to the string 'NULL'. This utility function performs that
284+ conversion."""
285 return value if value is not None else "NULL"
286
287 if type(result) is dict and "__server_execute_sql_query" in result:
288@@ -123,4 +158,6 @@
289 #interface: Register the lingo used in main(), and register lingos
290 #switched to here.
291 def switchlingo(self, lingo):
292+ """Switch the lingo used by the PlainUI during execution to the
293+ lingo provided as an argument."""
294 self.lingo = lingo
295
296=== modified file 'setup.py'
297--- setup.py 2010-02-14 22:32:23 +0000
298+++ setup.py 2010-02-17 03:55:20 +0000
299@@ -28,6 +28,7 @@
300 # Contributor(s):
301 #
302 # ##### END LICENSE BLOCK #####
303+"""This is the setup script for boots. It uses the python distutils."""
304
305 from distutils.core import setup
306
307@@ -41,7 +42,8 @@
308 boots_app_packages = ["boots.app"]
309 boots_lib_packages = ["boots.lib", "boots.lib.ui", "boots.lib.ui.components",
310 "boots.lib.lingos", "boots.lib.lingos.lisp"]
311-boots_packages = ["boots"] + boots_api_packages + boots_app_packages + boots_lib_packages
312+boots_packages = ["boots"] + boots_api_packages +\
313+ boots_app_packages + boots_lib_packages
314
315 setup(name="boots",
316 version=boots_version,
317
318=== modified file 'tests/boots/api/api_tests.py'
319--- tests/boots/api/api_tests.py 2010-02-15 22:15:08 +0000
320+++ tests/boots/api/api_tests.py 2010-02-17 03:55:20 +0000
321@@ -1,10 +1,17 @@
322+"""This module contains unit tests for the boots' api module."""
323 import unittest
324 from drizzle import errors
325 from boots.api import api
326 import boots_unit_test
327
328 class TestServerConnections(boots_unit_test.BootsQueryTester):
329+ """Class that provides unit tests that test the api.Server class' ability
330+ to connect to servers as well as provide proper errors when connections
331+ should fail."""
332+ #FIXME should use the configuration provided.
333 def setUp(self):
334+ #We want to derive from the BootsQueryTester but not use the default
335+ #setUp method due to the cost incurred by it.
336 pass
337
338 def test_valid_connect_fqdn(self):
339@@ -44,13 +51,15 @@
340 self.assert_(not server.is_connected)
341
342 def test_invalid_disconnect(self):
343- # Todo this should probably be set up to assert an exception.
344 server = api.Server("kororaa.cs.pdx.edu", 9306, {})
345 self.assertRaises(errors.InterfaceError, server.connect)
346 self.assertRaises(AttributeError, server.disconnect)
347 self.assert_(not server.is_connected)
348
349 class TestServerExecute(boots_unit_test.BootsQueryTester):
350+ """Class that provides unit tests that tests api.Server class' ability
351+ to execute SQL statements on a server."""
352+ #FIXME needs to test more than just SELECT statements.
353 def setUp(self):
354 boots_unit_test.BootsQueryTester.setUp(self)
355 self.server = api.Server(self.config["host"], self.config["port"], {"database": self.config["database"]})
356@@ -60,6 +69,10 @@
357 self.server.disconnect()
358
359 def _concat_packet_results(self, iter):
360+ """Results come from the Server object in 'packets'. These packets need
361+ their rows extracted so that they can be compared properly. This method
362+ takes an iterator of packets and does this extraction for you. Results
363+ are returned in a single list."""
364 result = []
365 for packet in iter:
366 result.extend(packet["result"])
367
368=== modified file 'tests/boots/boots_unit_test.py'
369--- tests/boots/boots_unit_test.py 2010-02-15 22:15:08 +0000
370+++ tests/boots/boots_unit_test.py 2010-02-17 03:55:20 +0000
371@@ -1,18 +1,67 @@
372+# Boots Client
373+# www.launchpad.net/boots
374+# www.launchpad.net/drizzle
375+# tests/boots/boots_unit_test.py
376+#
377+# ##### BEGIN LICENSE BLOCK #####
378+# Version: MPL 1.1
379+#
380+# The contents of this file are subject to the Mozilla Public License
381+# Version 1.1 (the "License"); you may not use this file except in
382+# compliance with the License. You may obtain a copy of the License at
383+# http://www.mozilla.org/MPL/
384+#
385+# Software distributed under the License is distributed on an "AS IS"
386+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
387+# License for the specific language governing rights and limitations
388+# under the License.
389+#
390+# The Original Code is Boots Client code.
391+#
392+# The Initial Developer of the Original Code is Clark Boylan, Ken
393+# Brotherton, Max Goodman, Victoria Lewis, David Rosenbaum, and Andreas
394+# Turriff. Portions created by Clark Boylan, Ken Brotherton, Max Goodman,
395+# Victoria Lewis, David Rosenbaum, and Andreas Turriff are Copyright (C)
396+# 2009. All Rights Reserved.
397+#
398+# Contributor(s):
399+#
400+# ##### END LICENSE BLOCK #####
401+"""This module contains the base test classes to be used for boots unit
402+tests. BootsBaseTester contains the test config. BootsQueryTester includes
403+the config and its setUp method will load a fresh test DB for testing."""
404+
405 import unittest
406 import os.path
407 from subprocess import Popen, PIPE
408
409 class BootsBaseTester(unittest.TestCase):
410+ """Simple unit test test case class that includes a boots testing config.
411+
412+ All boots unit test classes should inherit from this class or one of its
413+ children."""
414 config = {"host": "localhost",
415 "port": 9306,
416 "database": "boots_fec_test"}
417
418 class BootsQueryTester(BootsBaseTester):
419+ """Unit test class that includes a setUp method that can be used to load
420+ a fresh copy of the test database before each unit test.
421+
422+ Do not use this class unless you need a fresh database loaded into the
423+ server before each test. The time cost of doing this is quite high and
424+ should be avoided whenever possible. Additionally the current drizzle
425+ client should be in your path when you run tests using this class."""
426 def setUp(self):
427+ """Default setUp method that loads a fresh copy of the test DB into
428+ the server before each test.
429+
430+ This allows any classes deriving from this one to easily get clean test
431+ data for its tests."""
432 # The drizzle client should be in your path allowing the DB to be
433 # created if necessary.
434- cmd = "drizzle -h {host} -p {port} -B < "\
435- .format(**self.config) + os.path.dirname(os.path.realpath(__file__)) + "/fec.sql"
436+ cmd = "drizzle -h {host} -p {port} -B < ".format(**self.config) +\
437+ os.path.dirname(os.path.realpath(__file__)) + "/fec.sql"
438 p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, close_fds=True)
439 (cin, cout) = (p.stdin, p.stdout)
440 cin.close()
441
442=== modified file 'tests/boots/tests.py'
443--- tests/boots/tests.py 2010-02-15 22:15:08 +0000
444+++ tests/boots/tests.py 2010-02-17 03:55:20 +0000
445@@ -1,11 +1,46 @@
446+#!/usr/bin/env python
447+# Boots Client
448+# www.launchpad.net/boots
449+# www.launchpad.net/drizzle
450+# tests/boots/tests.py
451+#
452+# ##### BEGIN LICENSE BLOCK #####
453+# Version: MPL 1.1
454+#
455+# The contents of this file are subject to the Mozilla Public License
456+# Version 1.1 (the "License"); you may not use this file except in
457+# compliance with the License. You may obtain a copy of the License at
458+# http://www.mozilla.org/MPL/
459+#
460+# Software distributed under the License is distributed on an "AS IS"
461+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
462+# License for the specific language governing rights and limitations
463+# under the License.
464+#
465+# The Original Code is Boots Client code.
466+#
467+# The Initial Developer of the Original Code is Clark Boylan, Ken
468+# Brotherton, Max Goodman, Victoria Lewis, David Rosenbaum, and Andreas
469+# Turriff. Portions created by Clark Boylan, Ken Brotherton, Max Goodman,
470+# Victoria Lewis, David Rosenbaum, and Andreas Turriff are Copyright (C)
471+# 2009. All Rights Reserved.
472+#
473+# Contributor(s):
474+#
475+# ##### END LICENSE BLOCK #####
476+"""This module contains the boots test driver. The test driver runs all tests by
477+default or any specified on the command line."""
478+
479 import unittest
480 from optparse import OptionParser
481 import sys
482 import os
483 import os.path
484 from boots_unit_test import BootsBaseTester
485+
486 # Path mangling to import boots files properly.
487-new_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
488+new_path = os.path.dirname(os.path.dirname(os.path.dirname(
489+ os.path.realpath(__file__))))
490 sys.path.insert(0, new_path)
491 from api import api_tests
492 from api.nodes import node_tests
493@@ -14,21 +49,30 @@
494 from lib.lingos import lingo_tests
495 from lib.ui import ui_tests
496
497-if __name__ == "__main__":
498+def boots_test_driver():
499+ """Function that reads test options from the command line and runs the
500+ appropriate tests."""
501 parser = OptionParser()
502- parser.add_option("-H", "--host", dest="host", default=BootsBaseTester.config["host"],
503+ parser.add_option("-H", "--host", dest="host",
504+ default=BootsBaseTester.config["host"],
505 help="Host running the Drizzle server on which the test\
506 DB should be run.")
507- parser.add_option("-p", "--port", type="int", dest="port", default=BootsBaseTester.config["port"],
508+ parser.add_option("-p", "--port", type="int", dest="port",
509+ default=BootsBaseTester.config["port"],
510 help="Port the Drizzle server is running on.")
511- parser.add_option("-D", "--database", dest="database", default=BootsBaseTester.config["database"],
512+ parser.add_option("-D", "--database", dest="database",
513+ default=BootsBaseTester.config["database"],
514 help="Database to use for testing on the Drizzle server.")
515 (options, args) = parser.parse_args()
516 BootsBaseTester.config.update(vars(options))
517 if not args:
518- modules = [api_tests, node_tests, app_tests, console_tests, lingo_tests, ui_tests]
519+ modules = [api_tests, node_tests, app_tests,
520+ console_tests, lingo_tests, ui_tests]
521 else:
522 modules = map(globals().__getitem__, args)
523 for module in modules:
524 suite = unittest.TestLoader().loadTestsFromModule(module)
525 unittest.TextTestRunner(verbosity=2).run(suite)
526+
527+if __name__ == "__main__":
528+ boots_test_driver()

Subscribers

People subscribed via source and target branches

to status/vote changes: