Merge lp:~chromakode/boots/postgresql into lp:boots
- postgresql
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~chromakode/boots/postgresql |
Merge into: | lp:boots |
Diff against target: |
319 lines (+98/-32) 8 files modified
boots/api/constructors.py (+1/-1) boots/api/errors.py (+4/-1) boots/api/server.py (+59/-7) boots/lib/console.py (+3/-3) boots/lib/ui/plain.py (+16/-6) tests/boots/api/server_tests.py (+12/-11) tests/boots/boots_unit_test.py (+1/-1) tests/boots/tests.py (+2/-2) |
To merge this branch: | bzr merge lp:~chromakode/boots/postgresql |
Related bugs: | |
Related blueprints: |
PostgreSQL Support
(High)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Boots Developers | Pending | ||
Review via email: mp+22358@code.launchpad.net |
Commit message
Description of the change
Initial PostgreSQL support added. Table output is wonky, because psycopg2 does not calculate display_size for columns, so we'd need to calculate the maximum sizes ourselves.
We can merge this code now, but it won't be very useful until DSN support lands, or the user is able to specify the server type some other way.
In a future patch, we should probably update gen_table() in plain.py to calculate the display size if the value we got is None.
- 163. By Max Goodhart
-
Add slow manual display_size calculation in output ascii table generation, in case display_size is not provided by the database driver.
- 164. By Max Goodhart
-
Stylistic improvement to query code for PostgreSQL server_version.
Unmerged revisions
- 164. By Max Goodhart
-
Stylistic improvement to query code for PostgreSQL server_version.
- 163. By Max Goodhart
-
Add slow manual display_size calculation in output ascii table generation, in case display_size is not provided by the database driver.
- 162. By Max Goodhart
-
Initial internal PostgreSQL support, with the required tweaks and fixes.
- 161. By Max Goodhart
-
Refactor boots.api.api into boots.api.server.
Preview Diff
1 | === modified file 'boots/api/constructors.py' | |||
2 | --- boots/api/constructors.py 2010-03-24 07:33:04 +0000 | |||
3 | +++ boots/api/constructors.py 2010-03-29 08:42:30 +0000 | |||
4 | @@ -23,7 +23,7 @@ | |||
5 | 23 | 23 | ||
6 | 24 | import re | 24 | import re |
7 | 25 | 25 | ||
9 | 26 | from boots.api.api import Rows, ResultInfo | 26 | from boots.api.server import Rows, ResultInfo |
10 | 27 | from boots.api.errors import BootsError | 27 | from boots.api.errors import BootsError |
11 | 28 | from boots.api.nodes.node import NodeGraph, SyncNode, IteratorNode | 28 | from boots.api.nodes.node import NodeGraph, SyncNode, IteratorNode |
12 | 29 | from boots.api import _, n_ | 29 | from boots.api import _, n_ |
13 | 30 | 30 | ||
14 | === modified file 'boots/api/errors.py' | |||
15 | --- boots/api/errors.py 2010-03-28 08:27:19 +0000 | |||
16 | +++ boots/api/errors.py 2010-03-29 08:42:30 +0000 | |||
17 | @@ -85,7 +85,10 @@ | |||
18 | 85 | self.code = code | 85 | self.code = code |
19 | 86 | 86 | ||
20 | 87 | def __str__(self): | 87 | def __str__(self): |
22 | 88 | return "{msg} (#{code})".format(msg=self.msg, code=self.code) | 88 | # Add the error code to the end of the first line of the error message. |
23 | 89 | lines = self.msg.split("\n", 1) | ||
24 | 90 | lines[0] ="{msg} (#{code})".format(msg=lines[0], code=self.code) | ||
25 | 91 | return "\n".join(lines) | ||
26 | 89 | 92 | ||
27 | 90 | class ConnectionError(ServerError, CausedException): | 93 | class ConnectionError(ServerError, CausedException): |
28 | 91 | """Errors related to server connections.""" | 94 | """Errors related to server connections.""" |
29 | 92 | 95 | ||
30 | === renamed file 'boots/api/api.py' => 'boots/api/server.py' | |||
31 | --- boots/api/api.py 2010-03-27 10:24:03 +0000 | |||
32 | +++ boots/api/server.py 2010-03-29 08:42:30 +0000 | |||
33 | @@ -39,11 +39,12 @@ | |||
34 | 39 | ColumnDescription = namedtuple("ColumnDescription", | 39 | ColumnDescription = namedtuple("ColumnDescription", |
35 | 40 | ("name", "type_code", "display_size", | 40 | ("name", "type_code", "display_size", |
36 | 41 | "internal_size", "precision", "scale", | 41 | "internal_size", "precision", "scale", |
38 | 42 | "null_ok")) | 42 | "null_ok", "index")) |
39 | 43 | 43 | ||
40 | 44 | class RowDescription: | 44 | class RowDescription: |
41 | 45 | def __init__(self, description): | 45 | def __init__(self, description): |
43 | 46 | self.description = [ColumnDescription(*column) for column in description] | 46 | self.description = [ColumnDescription(*column, index=index) |
44 | 47 | for index, column in enumerate(description)] | ||
45 | 47 | self.column_index = dict(zip((column[0] for column in description), | 48 | self.column_index = dict(zip((column[0] for column in description), |
46 | 48 | range(len(description)))) | 49 | range(len(description)))) |
47 | 49 | 50 | ||
48 | @@ -265,11 +266,6 @@ | |||
49 | 265 | Server.__init__(self, MySQLdb, hostname, port, database, auth) | 266 | Server.__init__(self, MySQLdb, hostname, port, database, auth) |
50 | 266 | 267 | ||
51 | 267 | def _connect(self): | 268 | def _connect(self): |
52 | 268 | """Establishes database connection. | ||
53 | 269 | |||
54 | 270 | Connect to the database server whose details were provided through | ||
55 | 271 | the __init__ method.""" | ||
56 | 272 | |||
57 | 273 | converter = self.db.converters.conversions.copy() | 269 | converter = self.db.converters.conversions.copy() |
58 | 274 | for x in range(0,256): | 270 | for x in range(0,256): |
59 | 275 | converter.pop(x, None) | 271 | converter.pop(x, None) |
60 | @@ -304,6 +300,62 @@ | |||
61 | 304 | else: | 300 | else: |
62 | 305 | _server_classes.append(MySQLdbServer) | 301 | _server_classes.append(MySQLdbServer) |
63 | 306 | 302 | ||
64 | 303 | try: | ||
65 | 304 | import psycopg2 | ||
66 | 305 | import psycopg2.extensions | ||
67 | 306 | |||
68 | 307 | # Disable psycopg2 type conversions | ||
69 | 308 | raw = psycopg2.extensions.new_type(tuple(psycopg2.extensions.string_types.keys()), | ||
70 | 309 | "raw", | ||
71 | 310 | lambda value, cur:value if value is not None else None) | ||
72 | 311 | psycopg2.extensions.register_type(raw) | ||
73 | 312 | psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) | ||
74 | 313 | |||
75 | 314 | class PsycopgServer(Server): | ||
76 | 315 | def __init__(self, hostname, port, database, auth): | ||
77 | 316 | Server.__init__(self, psycopg2, hostname, port, database, auth) | ||
78 | 317 | |||
79 | 318 | def _connect(self): | ||
80 | 319 | connection = self.db.connect( | ||
81 | 320 | host = self.hostname, | ||
82 | 321 | port = self.port, | ||
83 | 322 | database = self.database, | ||
84 | 323 | user = self.auth.get("username") or "", | ||
85 | 324 | password = self.auth.get("password") or "") | ||
86 | 325 | connection.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) | ||
87 | 326 | return connection | ||
88 | 327 | |||
89 | 328 | def _wrap_exception(self, e): | ||
90 | 329 | if isinstance(e, psycopg2.Error): | ||
91 | 330 | msg = e.pgerror | ||
92 | 331 | if msg is not None and msg.startswith("ERROR:"): | ||
93 | 332 | # Remove the ERROR: title. | ||
94 | 333 | msg = msg.replace("ERROR:", "", 1).strip() | ||
95 | 334 | |||
96 | 335 | if e.pgcode: | ||
97 | 336 | return errors.CodedDatabaseError(e.pgcode, msg, e) | ||
98 | 337 | else: | ||
99 | 338 | return errors.DatabaseError(msg, e) | ||
100 | 339 | else: | ||
101 | 340 | return e | ||
102 | 341 | |||
103 | 342 | @property | ||
104 | 343 | def server_version(self): | ||
105 | 344 | self._check_connected() | ||
106 | 345 | if hasattr(self._connection, "server_version"): | ||
107 | 346 | version = self._connection.server_version | ||
108 | 347 | return "{major}.{minor}.{revision}".format(major=version/10000, | ||
109 | 348 | minor=version/100%100, | ||
110 | 349 | revision=version%10000) | ||
111 | 350 | else: | ||
112 | 351 | # Versions of psycopg2 before 2.0.12 don't have the server_version property. | ||
113 | 352 | return self._execute("show server_version;").fetchone()[0] | ||
114 | 353 | |||
115 | 354 | except ImportError: | ||
116 | 355 | PsycopgServer = None | ||
117 | 356 | else: | ||
118 | 357 | _server_classes.append(PsycopgServer) | ||
119 | 358 | |||
120 | 307 | def DrizzleServer(hostname, port, database, auth): | 359 | def DrizzleServer(hostname, port, database, auth): |
121 | 308 | if LibDrizzleServer is not None: | 360 | if LibDrizzleServer is not None: |
122 | 309 | return LibDrizzleServer(hostname, port, database, auth) | 361 | return LibDrizzleServer(hostname, port, database, auth) |
123 | 310 | 362 | ||
124 | === modified file 'boots/lib/console.py' | |||
125 | --- boots/lib/console.py 2010-03-28 23:55:27 +0000 | |||
126 | +++ boots/lib/console.py 2010-03-29 08:42:30 +0000 | |||
127 | @@ -28,7 +28,7 @@ | |||
128 | 28 | import os.path | 28 | import os.path |
129 | 29 | import signal | 29 | import signal |
130 | 30 | 30 | ||
132 | 31 | from boots.api import api | 31 | from boots.api.server import DrizzleServer |
133 | 32 | from boots.api.errors import BootsError, BootsWarning, CausedException, ConnectionError | 32 | from boots.api.errors import BootsError, BootsWarning, CausedException, ConnectionError |
134 | 33 | from boots.api.nodes.node import NodeGraph, SyncNode, IteratorNode, LinkStackNode | 33 | from boots.api.nodes.node import NodeGraph, SyncNode, IteratorNode, LinkStackNode |
135 | 34 | from boots.lib.ui.plain import PlainUI | 34 | from boots.lib.ui.plain import PlainUI |
136 | @@ -201,8 +201,8 @@ | |||
137 | 201 | return result_data | 201 | return result_data |
138 | 202 | 202 | ||
139 | 203 | def connect(self, host, port, database=None, username=None, password=None): | 203 | def connect(self, host, port, database=None, username=None, password=None): |
142 | 204 | server = api.DrizzleServer(host, port, database, | 204 | server = DrizzleServer(host, port, database, |
143 | 205 | {"username": username, "password": password}) | 205 | {"username": username, "password": password}) |
144 | 206 | server.connect() | 206 | server.connect() |
145 | 207 | self.servers.append(server) | 207 | self.servers.append(server) |
146 | 208 | return True | 208 | return True |
147 | 209 | 209 | ||
148 | === modified file 'boots/lib/ui/plain.py' | |||
149 | --- boots/lib/ui/plain.py 2010-03-28 23:57:18 +0000 | |||
150 | +++ boots/lib/ui/plain.py 2010-03-29 08:42:30 +0000 | |||
151 | @@ -30,9 +30,9 @@ | |||
152 | 30 | import readline | 30 | import readline |
153 | 31 | import subprocess | 31 | import subprocess |
154 | 32 | import threading | 32 | import threading |
156 | 33 | from itertools import izip | 33 | from itertools import imap |
157 | 34 | 34 | ||
159 | 35 | from boots.api.api import Rows, ResultInfo, DurationCounter | 35 | from boots.api.server import Rows, ResultInfo, DurationCounter |
160 | 36 | from boots.api.errors import CausedException | 36 | from boots.api.errors import CausedException |
161 | 37 | from boots.api.nodes.node import Status, NodeGraph, SyncNode, QueueNode, filter_none | 37 | from boots.api.nodes.node import Status, NodeGraph, SyncNode, QueueNode, filter_none |
162 | 38 | from boots.lib.ui.components.help import HelpTopic, CommandHelpTopic | 38 | from boots.lib.ui.components.help import HelpTopic, CommandHelpTopic |
163 | @@ -184,13 +184,20 @@ | |||
164 | 184 | conversion.""" | 184 | conversion.""" |
165 | 185 | return value if value is not None else "NULL" | 185 | return value if value is not None else "NULL" |
166 | 186 | 186 | ||
167 | 187 | def display_size(column): | ||
168 | 188 | if column.display_size is not None: | ||
169 | 189 | return column.display_size | ||
170 | 190 | else: | ||
171 | 191 | # Welcome to slowville, population you. | ||
172 | 192 | return max(imap(len, (row[column.index] for row in self.buffer))) | ||
173 | 193 | |||
174 | 187 | def _gen_table(info): | 194 | def _gen_table(info): |
175 | 188 | with DurationCounter() as table_elapsed: | 195 | with DurationCounter() as table_elapsed: |
176 | 189 | if self.buffer: | 196 | if self.buffer: |
177 | 190 | # If a column can contain the value NULL, we leave at least 4 | 197 | # If a column can contain the value NULL, we leave at least 4 |
178 | 191 | # characters, since display_size does not account for NULL. | 198 | # characters, since display_size does not account for NULL. |
179 | 192 | max_widths = map(max, [(len(column.name), | 199 | max_widths = map(max, [(len(column.name), |
181 | 193 | column.display_size, | 200 | display_size(column), |
182 | 194 | 4 if column.null_ok else 0) | 201 | 4 if column.null_ok else 0) |
183 | 195 | for column in info["description"]]) | 202 | for column in info["description"]]) |
184 | 196 | dashes = map(lambda x: "-"*(x+2), max_widths) | 203 | dashes = map(lambda x: "-"*(x+2), max_widths) |
185 | @@ -210,9 +217,12 @@ | |||
186 | 210 | "{count} rows in set", | 217 | "{count} rows in set", |
187 | 211 | info["row_count"]) | 218 | info["row_count"]) |
188 | 212 | else: | 219 | else: |
192 | 213 | set_count = n_("{count} row affected", | 220 | if info["row_count"] != -1: |
193 | 214 | "{count} rows affected", | 221 | set_count = n_("{count} row affected", |
194 | 215 | info["row_count"]) | 222 | "{count} rows affected", |
195 | 223 | info["row_count"]) | ||
196 | 224 | else: | ||
197 | 225 | set_count = _("Operation completed") | ||
198 | 216 | 226 | ||
199 | 217 | nodes_elapsed = info["end_time"] - info["begin_time"] - info["server_elapsed"] | 227 | nodes_elapsed = info["end_time"] - info["begin_time"] - info["server_elapsed"] |
200 | 218 | timings = [_("{0:.2f}s server").format(info["server_elapsed"]), | 228 | timings = [_("{0:.2f}s server").format(info["server_elapsed"]), |
201 | 219 | 229 | ||
202 | === renamed file 'tests/boots/api/api_tests.py' => 'tests/boots/api/server_tests.py' | |||
203 | --- tests/boots/api/api_tests.py 2010-03-07 10:59:51 +0000 | |||
204 | +++ tests/boots/api/server_tests.py 2010-03-29 08:42:30 +0000 | |||
205 | @@ -25,12 +25,13 @@ | |||
206 | 25 | import unittest | 25 | import unittest |
207 | 26 | from itertools import ifilter | 26 | from itertools import ifilter |
208 | 27 | 27 | ||
210 | 28 | from boots.api import errors, api | 28 | from boots.api import errors |
211 | 29 | from boots.api.server import DrizzleServer | ||
212 | 29 | from boots.api.nodes.node import filter_default | 30 | from boots.api.nodes.node import filter_default |
213 | 30 | import boots_unit_test | 31 | import boots_unit_test |
214 | 31 | 32 | ||
215 | 32 | class TestServerConnections(boots_unit_test.BootsQueryTester): | 33 | class TestServerConnections(boots_unit_test.BootsQueryTester): |
217 | 33 | """Class that provides unit tests that test the api.DrizzleServer class' ability | 34 | """Class that provides unit tests that test the DrizzleServer class' ability |
218 | 34 | to connect to servers as well as provide proper errors when connections | 35 | to connect to servers as well as provide proper errors when connections |
219 | 35 | should fail.""" | 36 | should fail.""" |
220 | 36 | #FIXME should use the configuration provided. | 37 | #FIXME should use the configuration provided. |
221 | @@ -40,54 +41,54 @@ | |||
222 | 40 | pass | 41 | pass |
223 | 41 | 42 | ||
224 | 42 | def test_valid_connect_fqdn(self): | 43 | def test_valid_connect_fqdn(self): |
226 | 43 | server = api.DrizzleServer("capstonedd.cs.pdx.edu", 9306, {}) | 44 | server = DrizzleServer("capstonedd.cs.pdx.edu", 9306, {}) |
227 | 44 | server.connect() | 45 | server.connect() |
228 | 45 | self.assert_(server.is_connected) | 46 | self.assert_(server.is_connected) |
229 | 46 | server.disconnect() | 47 | server.disconnect() |
230 | 47 | 48 | ||
231 | 48 | def test_valid_connect_partial(self): | 49 | def test_valid_connect_partial(self): |
233 | 49 | server = api.DrizzleServer("capstonedd", 9306, {}) | 50 | server = DrizzleServer("capstonedd", 9306, {}) |
234 | 50 | server.connect() | 51 | server.connect() |
235 | 51 | self.assert_(server.is_connected) | 52 | self.assert_(server.is_connected) |
236 | 52 | server.disconnect() | 53 | server.disconnect() |
237 | 53 | 54 | ||
238 | 54 | def test_valid_connect_ip(self): | 55 | def test_valid_connect_ip(self): |
240 | 55 | server = api.DrizzleServer("131.252.214.178", 9306, {}) | 56 | server = DrizzleServer("131.252.214.178", 9306, {}) |
241 | 56 | server.connect() | 57 | server.connect() |
242 | 57 | self.assert_(server.is_connected) | 58 | self.assert_(server.is_connected) |
243 | 58 | server.disconnect() | 59 | server.disconnect() |
244 | 59 | 60 | ||
245 | 60 | def test_valid_connect_fqdn_with_db(self): | 61 | def test_valid_connect_fqdn_with_db(self): |
246 | 61 | boots_unit_test.BootsQueryTester.setUp(self) | 62 | boots_unit_test.BootsQueryTester.setUp(self) |
248 | 62 | server = api.DrizzleServer("capstonedd.cs.pdx.edu", 9306, {"database": "fec"}) | 63 | server = DrizzleServer("capstonedd.cs.pdx.edu", 9306, {"database": "fec"}) |
249 | 63 | server.connect() | 64 | server.connect() |
250 | 64 | self.assert_(server.is_connected) | 65 | self.assert_(server.is_connected) |
251 | 65 | server.disconnect() | 66 | server.disconnect() |
252 | 66 | 67 | ||
253 | 67 | def test_invalid_connect(self): | 68 | def test_invalid_connect(self): |
255 | 68 | server = api.DrizzleServer("kororaa.cs.pdx.edu", 9306, {}) | 69 | server = DrizzleServer("kororaa.cs.pdx.edu", 9306, {}) |
256 | 69 | self.assertRaises(errors.ConnectionError, server.connect) | 70 | self.assertRaises(errors.ConnectionError, server.connect) |
257 | 70 | self.assert_(not server.is_connected) | 71 | self.assert_(not server.is_connected) |
258 | 71 | 72 | ||
259 | 72 | def test_valid_disconnect(self): | 73 | def test_valid_disconnect(self): |
261 | 73 | server = api.DrizzleServer("capstonedd.cs.pdx.edu", 9306, {}) | 74 | server = DrizzleServer("capstonedd.cs.pdx.edu", 9306, {}) |
262 | 74 | server.connect() | 75 | server.connect() |
263 | 75 | server.disconnect() | 76 | server.disconnect() |
264 | 76 | self.assert_(not server.is_connected) | 77 | self.assert_(not server.is_connected) |
265 | 77 | 78 | ||
266 | 78 | def test_invalid_disconnect(self): | 79 | def test_invalid_disconnect(self): |
268 | 79 | server = api.DrizzleServer("kororaa.cs.pdx.edu", 9306, {}) | 80 | server = DrizzleServer("kororaa.cs.pdx.edu", 9306, {}) |
269 | 80 | self.assertRaises(errors.ConnectionError, server.connect) | 81 | self.assertRaises(errors.ConnectionError, server.connect) |
270 | 81 | self.assertRaises(errors.ConnectionError, server.disconnect) | 82 | self.assertRaises(errors.ConnectionError, server.disconnect) |
271 | 82 | self.assert_(not server.is_connected) | 83 | self.assert_(not server.is_connected) |
272 | 83 | 84 | ||
273 | 84 | class TestServerExecute(boots_unit_test.BootsQueryTester): | 85 | class TestServerExecute(boots_unit_test.BootsQueryTester): |
275 | 85 | """Class that provides unit tests that tests api.DrizzleServer class' ability | 86 | """Class that provides unit tests that tests DrizzleServer class' ability |
276 | 86 | to execute SQL statements on a server.""" | 87 | to execute SQL statements on a server.""" |
277 | 87 | #FIXME needs to test more than just SELECT statements. | 88 | #FIXME needs to test more than just SELECT statements. |
278 | 88 | def setUp(self): | 89 | def setUp(self): |
279 | 89 | boots_unit_test.BootsQueryTester.setUp(self) | 90 | boots_unit_test.BootsQueryTester.setUp(self) |
281 | 90 | self.server = api.DrizzleServer(self.config["host"], self.config["port"], {"database": self.config["database"]}) | 91 | self.server = DrizzleServer(self.config["host"], self.config["port"], {"database": self.config["database"]}) |
282 | 91 | self.server.connect() | 92 | self.server.connect() |
283 | 92 | 93 | ||
284 | 93 | def tearDown(self): | 94 | def tearDown(self): |
285 | 94 | 95 | ||
286 | === modified file 'tests/boots/boots_unit_test.py' | |||
287 | --- tests/boots/boots_unit_test.py 2010-03-07 10:59:51 +0000 | |||
288 | +++ tests/boots/boots_unit_test.py 2010-03-29 08:42:30 +0000 | |||
289 | @@ -59,7 +59,7 @@ | |||
290 | 59 | fec_sql_path = os.path.join(base_tests_path, "fec.sql") | 59 | fec_sql_path = os.path.join(base_tests_path, "fec.sql") |
291 | 60 | boots_path = os.path.join(base_tests_path, "../../bin/boots") | 60 | boots_path = os.path.join(base_tests_path, "../../bin/boots") |
292 | 61 | 61 | ||
294 | 62 | cmd = "{boots} -H {host} -p {port} -f {file}".format(boots=boots_path, | 62 | cmd = "{boots} -h {host} -p {port} -f {file}".format(boots=boots_path, |
295 | 63 | file=fec_sql_path, | 63 | file=fec_sql_path, |
296 | 64 | **self.config) | 64 | **self.config) |
297 | 65 | p = Popen(cmd, shell=True) | 65 | p = Popen(cmd, shell=True) |
298 | 66 | 66 | ||
299 | === modified file 'tests/boots/tests.py' | |||
300 | --- tests/boots/tests.py 2010-03-07 10:59:51 +0000 | |||
301 | +++ tests/boots/tests.py 2010-03-29 08:42:30 +0000 | |||
302 | @@ -35,7 +35,7 @@ | |||
303 | 35 | # Path mangling to import boots files properly. | 35 | # Path mangling to import boots files properly. |
304 | 36 | new_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../..") | 36 | new_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../..") |
305 | 37 | sys.path.insert(0, new_path) | 37 | sys.path.insert(0, new_path) |
307 | 38 | from api import api_tests | 38 | from api import server_tests |
308 | 39 | from api.nodes import node_tests | 39 | from api.nodes import node_tests |
309 | 40 | from app import app_tests | 40 | from app import app_tests |
310 | 41 | from lib import console_tests | 41 | from lib import console_tests |
311 | @@ -59,7 +59,7 @@ | |||
312 | 59 | (options, args) = parser.parse_args() | 59 | (options, args) = parser.parse_args() |
313 | 60 | BootsBaseTester.config.update(vars(options)) | 60 | BootsBaseTester.config.update(vars(options)) |
314 | 61 | if not args: | 61 | if not args: |
316 | 62 | modules = [api_tests, node_tests, app_tests, | 62 | modules = [server_tests, node_tests, app_tests, |
317 | 63 | console_tests, lingo_tests, ui_tests] | 63 | console_tests, lingo_tests, ui_tests] |
318 | 64 | else: | 64 | else: |
319 | 65 | modules = map(globals().__getitem__, args) | 65 | modules = map(globals().__getitem__, args) |
And there's display_size calculation in gen_table(). Now PostgreSQL connections should be pretty indistinguishable from other connections.