aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSix <unknown>2010-05-01 22:15:49 -0400
committerSix <unknown>2010-05-01 22:15:49 -0400
commit57c0e1b947d050d94013f83d4d677c8535a35350 (patch)
treebdcaac9516b86817affeed58054f58f4ae26eaf6
parent9160d125524c278ca03841296c5c9cf7d1860c11 (diff)
downloaddodai-macsupport-57c0e1b947d050d94013f83d4d677c8535a35350.tar.bz2
dodai-macsupport-57c0e1b947d050d94013f83d4d677c8535a35350.tar.xz
dodai-macsupport-57c0e1b947d050d94013f83d4d677c8535a35350.zip
changed the dodai.config.databases to work with dictionaries. Created a dodai.exceptions.
-rw-r--r--dodai/config/__init__.py9
-rw-r--r--dodai/config/databases/__init__.py445
-rw-r--r--dodai/config/databases/sa.py80
-rw-r--r--dodai/exception.py236
-rw-r--r--dodai/tools/__init__.py16
-rw-r--r--dodai/tools/himo.py5
-rw-r--r--test/test_config/test_databases.py341
-rw-r--r--test/test_config/test_sa.py45
8 files changed, 848 insertions, 329 deletions
diff --git a/dodai/config/__init__.py b/dodai/config/__init__.py
index b976dca..7fce975 100644
--- a/dodai/config/__init__.py
+++ b/dodai/config/__init__.py
@@ -40,8 +40,15 @@ class Config(object):
40 self.logs = ConfigLog() 40 self.logs = ConfigLog()
41 return self.logs 41 return self.logs
42 elif 'databases' == key: 42 elif 'databases' == key:
43 # Wire up the sqlalchemy objects to use in the sa object
44 # which will be used as the default database handler
43 from dodai.config.databases import ConfigDatabases 45 from dodai.config.databases import ConfigDatabases
44 self.databases = ConfigDatabases() 46 from dodai.config.databases.sa import Sa
47 from sqlalchemy.orm import sessionmaker
48 from sqlalchemy import create_engine
49 from dodai.db import Db
50 sa = Sa(create_engine, sessionmaker, Db)
51 self.databases = ConfigDatabases(sa, 'sa')
45 return self.databases 52 return self.databases
46 else: 53 else:
47 raise KeyError(key) 54 raise KeyError(key)
diff --git a/dodai/config/databases/__init__.py b/dodai/config/databases/__init__.py
index da317e8..ba6b9a9 100644
--- a/dodai/config/databases/__init__.py
+++ b/dodai/config/databases/__init__.py
@@ -16,56 +16,35 @@
16# along with Dodai. If not, see <http://www.gnu.org/licenses/>. 16# along with Dodai. If not, see <http://www.gnu.org/licenses/>.
17 17
18 18
19from dodai.exception import DatabaseEmptyOptionException
20from dodai.exception import DatabasePortException
21from dodai.exception import DatabaseHostnameException
22from dodai.exception import DatabaseProtocolException
23from dodai.exception import DatabaseConnectionException
24from dodai.exception import UnknownDatabaseConnectionException
25from dodai.tools import list_to_english
26
19class ConfigDatabases(object): 27class ConfigDatabases(object):
20 """An object that is used for creating database connection objects 28 """An object that is used for creating database connection objects
21 29
22 """ 30 """
23 def __init__(self): 31 def __init__(self, default_handler, default_name):
24 self.sections = {} 32 self.connections = {}
25 self._handlers = {} 33 self._handlers = {}
34 DatabaseConnection.DEFAULT_HANDLER = default_name
35 self.add_handler(default_name, default_handler)
26 36
27 def add(self, sections): 37 def add(self, sections):
28 """Adds a dictionary of section (objects) to this object that could 38 """Adds a dictionary of sections to this object that could
29 be used for creating database connection objects 39 be used for creating database connection objects. The dictionary
30 40 should be formatted as follows:
31 """
32 self._build_default_handler()
33 for name, section in sections.items():
34 self.sections[name] = DatabaseConnectionValidator(
35 name, section, self._handlers)
36 41
37 def load(self, section_name): 42 sections[name] = {option: value, option: value, ....}
38 """Returns a database connection object of the given section_name.
39 Throws Exceptions for any type of configuration errors or missing
40 configuration data
41 43
42 """ 44 The 'name' is the section name. It is an identifier that is used
43 if section_name in self.sections: 45 to load the database:
44 return self.sections[section_name].load()
45 else:
46 raise UnknownDatabaseConnectionException(section_name)
47 46
48 def _build_default_handler(self): 47 The options and values can have the following:
49 # Creates the default sqlalchemy handler
50 if 'sa' not in self._handlers:
51 from dodai.config.databases.sa import Sa
52 from sqlalchemy.orm import sessionmaker
53 from sqlalchemy import create_engine
54 from dodai.db import Db
55 sa = Sa(create_engine, sessionmaker, Db)
56 self._handlers['sa'] = sa
57
58 def add_handler(self, name, handler):
59 """Adds the given handler and name to this objects handlers
60
61 """
62 self._handlers[name] = obj
63
64
65class DatabaseConnectionValidator(object):
66 """Database validation object that is used to validate a section
67 object which is basically a python object that can have the following
68 properties:
69 48
70 protocol: The database engine (postgresql, mysql, sqlite, 49 protocol: The database engine (postgresql, mysql, sqlite,
71 oracle, mssql etc..) 50 oracle, mssql etc..)
@@ -97,276 +76,210 @@ class DatabaseConnectionValidator(object):
97 handler: The handler used to build the orm object. Dodai 76 handler: The handler used to build the orm object. Dodai
98 only works with sqlalchemy so the default 'sa' 77 only works with sqlalchemy so the default 'sa'
99 is used. 78 is used.
100 Not all of the above options are required.
101 79
102 """ 80 """
103 OPTIONS_REQUIRED = { 81 for name, section in sections.items():
104 'server':['protocol', 'hostname', 'port', 'username', 'password', 82 self.connections[name] = DatabaseConnection(section, name,
105 'database'], 83 self._handlers)
106 'file' :['protocol', 'filename'] 84
107 } 85 def load(self, section_name):
108 OPTIONS_EXTRA = ['protocol_extra', 'handler', 'schema'] 86 """Returns a database connection object of the given section_name.
109 SERVER_PROTOCOLS = ['postgresql', 'mysql', 'sqlite', 'mssql', 'oracle'] 87 Throws Exceptions for any type of configuration errors or missing
110 FILE_PROTOCOLS = ['sqlite'] 88 configuration data
111 DEFAULT_HANDLER = 'sa' 89
112 90 """
113 def __init__(self, name, section, handlers): 91 if section_name in self.connections:
92 return self.connections[section_name].load()
93 else:
94 raise UnknownDatabaseConnectionException(section_name)
95
96 def add_handler(self, name, handler):
97 """Adds the given handler and name to this objects handlers
98
99 """
100 self._handlers[name] = handler
101
102class DatabaseConnection(object):
103
104 DEFAULT_HANDLER = None
105
106 def __init__(self, section, name, handlers):
114 self.section = section 107 self.section = section
115 self.name = name 108 self.name = name
116 self.database_type = self._database_type() 109 self.handlers = handlers
117 self._handlers = handlers 110 self.validate = DatabaseConnectionValidator.load(section, name)
118 self.obj = None 111 self.obj = None
119 112
120 def load(self): 113 def load(self):
121 if not self.obj: 114 if not self.obj:
122 self._validate() 115 self.validate()
123 handler = self._get_handler() 116 handler = self._get_handler()
124 self.obj = handler(self.name, self.section) 117 self.obj = handler(self.name, self.section)
125 return self.obj 118 return self.obj
126 119
127 def _get_handler(self): 120 def _get_handler(self):
128 if hasattr(self.section, 'handler'): 121 if self.section.has_key('handler'):
129 name = self.section.handler.lower() 122 name = self.section['handler']
130 if handler in self._handlers.keys(): 123 if name in self.handlers:
131 return self._handlers[name] 124 return self.handlers[name]
132 return self._handlers[self.DEFAULT_HANDLER] 125 return self.handlers[self.DEFAULT_HANDLER]
133 126
134 def _validate(self): 127
135 if self.database_type == 'server': 128class DatabaseConnectionValidator(object):
136 self._validate_protocol() 129
137 self._validate_port() 130 def __init__(self, section, name, validators=[]):
138 self._validate_option('username') 131 self.section = section
139 self._validate_option('password') 132 self.name = name
140 self._validate_option('database') 133 self.validators = validators
141 elif self.database_type == 'file': 134
142 self._validate_protocol() 135 def __call__(self):
143 self._validate_option('filename') 136 return self.validate()
144 else: 137
145 raise DatabaseConnectionException(self.name, self.OPTIONS_REQUIRED) 138 def validate(self):
139 """Validates the connection. Returns true if valid. Throws
140 DodaiDatabaseConnectionConfigurationError on any errors
141
142 """
143 raise NotImplementedError()
146 144
147 def _validate_option(self, name): 145 def _validate_option(self, name):
148 if not self.section[name]: 146 try:
147 value = self.section[name]
148 except KeyError:
149 value = None
150
151 if not value:
149 raise DatabaseEmptyOptionException(self.name, name) 152 raise DatabaseEmptyOptionException(self.name, name)
150 return True 153 return True
151 154
152 def _validate_hostname(self): 155 def _validate_protocol(self):
153 if not self.section.hostname:
154 raise DatabaseHostnameException(self.name, self.section.hostname)
155 return True
156 156
157 def _validate_port(self): 157 self._validate_option('protocol')
158 try:
159 port = int(self.section.port)
160 except ValueError:
161 pass
162 else:
163 if port >=1 and port <=65535:
164 return True
165 raise DatabasePortException(self.name, self.section.port)
166 158
167 def _validate_protocol(self): 159 if self.section['protocol'] not in self.PROTOCOLS:
168 if self.database_type == 'server': 160 raise DatabaseProtocolException(self.name,
169 if self.section.protocol not in self.SERVER_PROTOCOLS: 161 self.section['protocol'], self.DB_TYPE,
170 raise DatabaseProtocolException(self.name, 162 self.PROTOCOLS)
171 self.section.protocol, self.database_type, 163 return True
172 self.SERVER_PROTOCOLS)
173 else:
174 return True
175 elif self.database_type == 'file':
176 if self.section.protocol not in self.FILE_PROTOCOLS:
177 raise DatabaseProtocolException(self.name,
178 self.section.protocol, self.database_type,
179 self.FILE_PROTOCOLS)
180 else:
181 return True
182 else:
183 return False
184 164
185 def _database_type(self): 165 @staticmethod
186 keys = self.section.___options___.keys() 166 def load(section, name):
187 for type_, pool in self.OPTIONS_REQUIRED.items(): 167 """Static method (factory) that loads the correct database
188 out = True 168 validation class.
189 for key in keys:
190 if key not in pool:
191 out = False
192 if out:
193 return type_
194 return False
195 169
170 Attributes:
171 section: Dictionary of key val connection information
172 name: String name of the section
196 173
197class DatabaseEmptyOptionException(Exception): 174 """
198 """Exception raised for an emption option in the config 175 action = None
176 validators = [DatabaseConnectionValidatorServer,
177 DatabaseConnectionValidatorFile]
199 178
200 Attributes: 179 for validator in validators:
201 section_name: The section of the config file that contains 180 obj = validator.load(section, name)
202 the invalid protocol 181 if obj:
203 option_name: The name of the empty option 182 return obj
204 183
205 """ 184 return DatabaseConnectionValidatorUnknown(section, name, validators)
206 MESSAGE = "In the '{section_name}' section, the '{option_name}' "\
207 "was not set. Please set this option."
208 185
209 def __init__(self, section_name, option_name): 186class DatabaseConnectionValidatorServer(DatabaseConnectionValidator):
210 self.section_name = section_name
211 self.option_name = option_name
212 self.msg = self._build_message()
213 187
214 def _build_message(self): 188 DB_TYPE = 'server'
215 return self.MESSAGE.format(section_name=self.section_name, 189 REQUIRED = ['protocol', 'hostname', 'port', 'username', 'password',
216 option_name=self.option_name) 190 'database']
191 PROTOCOLS = ['postgresql', 'mysql', 'mssql', 'oracle']
217 192
193 def _validate_port(self):
194 self._validate_option('port')
218 195
219class DatabasePortException(Exception): 196 try:
220 """Exception raised for invalid database port connection 197 port = int(self.section['port'])
198 except ValueError:
199 port = self.section['port']
200 else:
201 if port >=1 and port <=65535:
202 return True
203 raise DatabasePortException(self.name, port)
221 204
222 Attributes: 205 def validate(self):
223 section_name: The section of the config file that contains 206 """Validates the connection. Returns true if valid. Throws
224 the invalid protocol 207 DodaiDatabaseConnectionConfigurationError on any errors
225 section_port: The port value that was listed in the
226 config file
227 208
228 """ 209 """
229 MESSAGE = "In the '{section_name}' section, the port of "\ 210 self._validate_protocol()
230 "'{section_port}' is invalid. The port must be a "\ 211 self._validate_port()
231 "number between 1 and 65535" 212 self._validate_option('hostname')
213 self._validate_option('username')
214 self._validate_option('password')
215 self._validate_option('database')
216 return True
232 217
233 def __init__(self, section_name, section_port): 218 @classmethod
234 self.section_name = section_name 219 def load(cls, section, name):
235 self.section_port = section_port 220 """Return this validation class if it is possible that the
236 self.msg = self._build_message() 221 given connection information contains enough data to make
222 a database server connection.
237 223
238 def _build_message(self): 224 Attributes:
239 return self.MESSAGE.format(section_name=self.section_name, 225 section: Dictionary of key val connection information
240 section_port=self.section_port) 226 name: String name of the section
241 227
228 """
229 if section.has_key('protocol'):
230 if section['protocol'] in cls.PROTOCOLS:
231 return cls(section, name)
232 keys = section.keys()
233 for key in keys:
234 if key in ['hostname', 'port']:
235 return cls(section, name)
242 236
243class DatabaseHostnameException(Exception):
244 """Exception raised for invalid database hostname
245 237
246 Attributes: 238class DatabaseConnectionValidatorFile(DatabaseConnectionValidator):
247 section_name: The section of the config file that contains
248 the invalid protocol
249 section_hostname: The hostname value that was listed in the
250 config file
251 239
252 """ 240 DB_TYPE = 'file'
253 MESSAGE = "In the '{section_name}' section, the hostname of "\ 241 REQUIRED = ['protocol', 'filename']
254 "'{section_hostname}' is invalid. Please use a valid "\ 242 PROTOCOLS = ['sqlite']
255 "hostname."
256
257 MSG_NON = "In the '{section_name}' section, the hostname was "\
258 "not set. Please set the hostname."
259
260 def __init__(self, section_name, section_hostname):
261 self.section_name = section_name
262 self.section_hostname = section_hostname
263 self.msg = self._build_message()
264
265 def _build_message(self):
266 if self.section_hostname:
267 return self.MESSAGE.format(section_name=self.section_name,
268 section_hostname=self.section_hostname)
269 else:
270 return self.MSG_NON.format(section_name=self.section_name)
271 243
244 def validate(self):
245 """Validates the connection. Returns true if valid. Throws
246 DodaiDatabaseConnectionConfigurationError on any errors
272 247
273class DatabaseProtocolException(Exception): 248 """
274 """Exception raised for invalid database connection protocols 249 self._validate_protocol()
250 self._validate_option('filename')
251 return True
275 252
276 Attributes: 253 @classmethod
277 section_name: The section of the config file that contains 254 def load(cls, section, name):
278 the invalid protocol 255 """Return this validation class if it is possible that the
279 section_protocol: The protocol value that was listed in the 256 given connection information contains enough data to make
280 config file 257 a database file connection.
281 database_type: Usually 'server' or 'file'
282 protocols: List of valid protocols
283 258
284 """ 259 Attributes:
285 MESSAGE = "In the '{section_name}' section, the protocol of "\ 260 section: Dictionary of key val connection information
286 "'{section_protocol}' is invalid. The valid protocols "\ 261 name: String name of the section
287 "for a '{database_type}' connection are: {protocols}"
288
289 def __init__(self, section_name, section_protocol, database_type,
290 protocols):
291 self.section_name = section_name
292 self.section_protocol = section_protocol
293 self.database_type = database_type
294 self.protocols = protocols
295 self.msg = self._build_message()
296
297 def _build_message(self):
298 protocols = ', '.join(self.protocols)
299 return self.MESSAGE.format(section_name=self.section_name,
300 section_protocol=self.section_protocol,
301 database_type=self.database_type,
302 protocols=protocols)
303
304
305class DatabaseConnectionException(Exception):
306 """Exception raised for missing database connection parameters
307
308 Attributes:
309 section_name: The section of the config file that contains
310 the invalid connection information
311 options_required: A dictionary containing the database_type
312 as the key and a list of required options
313 as the value
314 262
315 """ 263 """
316 MESSAGE = "The '{section_name}' section does not contain all of the "\ 264 if section.has_key('protocol'):
317 "correct information needed to make a database connection." 265 if section['protocol'] in cls.PROTOCOLS:
318 MSG_TYPE = "To make a '{database_type}' connection please make sure "\ 266 return cls(section, name)
319 "To have all of the following options: {options}." 267 keys = section.keys()
320 MSG_END = "Please remember that the option names are case sensitive." 268 for key in keys:
321 269 if key in ['filename']:
322 def __init__(self, section_name, options_required): 270 return cls(section, name)
323 self.section_name = section_name
324 self.options_required = options_required
325 self.msg = self._build_message()
326
327 def _build_message(self):
328 out = []
329 out.append(self.MESSAGE.format(section_name=self.section_name))
330 for database_type, options in self.options_required.items():
331 options = list_to_english(options)
332 out.append(self.MSG_TYPE.format(database_type=database_type,
333 options=options))
334 out.append(self.MSG_END)
335 return ' '.join(out)
336
337
338class UnknownDatabaseConnectionException(Exception):
339 """Exception raised for missing database connection parameters
340
341 Attributes:
342 section: The requested section of the config file that can not
343 be found.
344 271
345 """
346 MESSAGE = "Unable to find the '{section}' section to create a "\
347 "database connection."
348 272
349 def __init__(self, section): 273class DatabaseConnectionValidatorUnknown(DatabaseConnectionValidator):
350 self.section = section
351 self.msg = self._build_message()
352 274
353 def _build_message(self): 275 DB_TYPE = 'unkonwn'
354 return self.MESSAGE.format(section=self.section) 276 REQUIRED = []
277 PROTOCOLS = []
355 278
279 def validate(self):
280 """Validates the connection. Returns true if valid. Throws
281 DodaiDatabaseConnectionConfigurationError on any errors
356 282
357def list_to_english(data): 283 """
358 """Takes the input list and creates a string with each option
359 encased in single quotes and seperated by a comma with exception
360 of the last option which is prefixed with the word 'and'
361 284
362 """ 285 raise DatabaseConnectionException(self.name, self.validators)
363 if data:
364 if len(data) > 1:
365 out = []
366 last = "'{0}'".format(data.pop())
367 for row in data:
368 out.append("'{0}'".format(row))
369 out = ', '.join(out)
370 return "{0} and {1}".format(out, last)
371 else:
372 return "'{0}'".format(data.pop())
diff --git a/dodai/config/databases/sa.py b/dodai/config/databases/sa.py
index a5965a9..21999c2 100644
--- a/dodai/config/databases/sa.py
+++ b/dodai/config/databases/sa.py
@@ -17,31 +17,49 @@
17 17
18 18
19class Sa(object): 19class Sa(object):
20 """Callable object that will wire up sqlalchemy engine and
21 session objects
20 22
23 Attributes:
24 create_engine: The sqlalchemy create_engine object
25 session_maker: The sqlalchemy session_maker object
26 result_object: The object that will be populated with
27 all the connection information along with
28 the sqlalchemy objects
29
30 """
21 def __init__(self, create_engine, session_maker, result_object): 31 def __init__(self, create_engine, session_maker, result_object):
22 self._create_engine = create_engine 32 self._create_engine = create_engine
23 self._session_maker = session_maker 33 self._session_maker = session_maker
24 self._result_object = result_object 34 self._result_object = result_object
25 35
26 def __call__(self, name, obj): 36 def __call__(self, section, name):
27 db = self._result_object() 37 db = self._result_object()
28 db.name = name 38 db.name = name
29 db.protocol = obj.protocol 39 db.protocol = section['protocol']
30 if hasattr(obj, 'protocol_extra') and obj.protocol_extra: 40
31 db.protocol_extra = obj.protocol_extra 41 if section.has_key('protocol_extra') and section['protocol_extra']:
32 if hasattr(obj, 'filename') and obj.filename: 42 db.protocol_extra = section['protocol_extra']
33 db.filename = obj.filename 43
34 if hasattr(obj, 'port') and obj.port: 44 if section.has_key('filename') and section['filename']:
35 db.port = int(obj.port) 45 db.filename = section['filename']
36 if hasattr(obj, 'database') and obj.database: 46
37 db.database = obj.database 47 if section.has_key('port') and section['port']:
38 if hasattr(obj, 'schema') and obj.schema: 48 db.port = int(section['port'])
39 db.schema = obj.schema 49
40 if hasattr(obj, 'username') and obj.username: 50 if section.has_key('database') and section['database']:
41 db.username = obj.username 51 db.database = section['database']
42 if hasattr(obj, 'hostname') and obj.hostname: 52
43 db.hostname = obj.hostname 53 if section.has_key('schema') and section['schema']:
44 db.engine = self._build_engine(obj) 54 db.schema = section['schema']
55
56 if section.has_key('username') and section['username']:
57 db.username = section['username']
58
59 if section.has_key('hostname') and section['hostname']:
60 db.hostname = section['hostname']
61
62 db.engine = self._build_engine(section)
45 db.session = self._build_session(db.engine) 63 db.session = self._build_session(db.engine)
46 return db 64 return db
47 65
@@ -49,25 +67,25 @@ class Sa(object):
49 session = self._session_maker(bind=engine) 67 session = self._session_maker(bind=engine)
50 return session() 68 return session()
51 69
52 def _build_connection_string(self, obj): 70 def _build_connection_string(self, section):
53 out = [] 71 out = []
54 out.append('{db.protocol}') 72 out.append('{section[protocol]}')
55 if hasattr(obj, 'protocol_extra') and obj.protocol_extra: 73 if section.has_key('protocol_extra') and section['protocol_extra']:
56 out.append('+{db.protocol_extra}') 74 out.append('+{section[protocol_extra]}')
57 out.append('://') 75 out.append('://')
58 if hasattr(obj, 'filename') and obj.filename: 76 if section.has_key('filename') and section['filename']:
59 out.append('{db.filename}') 77 out.append('{section[filename]}')
60 else: 78 else:
61 out.append('{db.username}:{db.password}@') 79 out.append('{section[username]}:{section[password]}@')
62 out.append('{db.hostname}') 80 out.append('{section[hostname]}')
63 if hasattr(obj, 'port') and obj.port: 81 if section.has_key('port') and section['port']:
64 out.append(':{db.port}') 82 out.append(':{section[port]}')
65 out.append('/{db.database}') 83 out.append('/{section[database]}')
66 out = ''.join(out) 84 out = ''.join(out)
67 out = out.format(db=obj) 85 out = out.format(section=section)
68 return out 86 return out
69 87
70 def _build_engine(self, obj): 88 def _build_engine(self, section):
71 connection_string = self._build_connection_string(obj) 89 connection_string = self._build_connection_string(section)
72 db_obj = self._create_engine(connection_string) 90 db_obj = self._create_engine(connection_string)
73 return db_obj 91 return db_obj
diff --git a/dodai/exception.py b/dodai/exception.py
new file mode 100644
index 0000000..5af10b4
--- /dev/null
+++ b/dodai/exception.py
@@ -0,0 +1,236 @@
1# Copyright (C) 2010 Leonard Thomas
2#
3# This file is part of Dodai.
4#
5# Dodai is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# Dodai is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with Dodai. If not, see <http://www.gnu.org/licenses/>.
17
18from dodai.tools import list_to_english
19
20class DodaiException(Exception):
21
22 def _system_encoding(self):
23 # Returns the character encoding of the operating system
24
25 encoding = sys.getdefaultencoding()
26 filesystem_encoding = sys.getfilesystemencoding()
27 if filesystem_encoding:
28 encoding = filesystem_encoding
29 return encoding
30
31
32class HimoAsciiError(DodaiException):
33 """Exception raised when the Himo object can't convert a character
34 down to it's root character.
35
36 Attributes:
37 char: The character that can't be converted down to ascii
38 """
39 MESSAGE="Unable to convert the '{char}' character to ascii"
40
41 def __init__(self, char):
42 self.char = char
43 self.msg = self._build_message()
44
45 def _build_message(self):
46 encoding = self._system_encoding()
47 try:
48 char = self.char.encode(encoding)
49 except UnicodeEncodeError:
50 char = 'unichr({0})'.format(ord(self.char))
51 return self.MESSAGE.format(char=self.char)
52
53 def __str__(self):
54 return self.msg
55
56class DodaiDatabaseConnectionConfigurationError(DodaiException):
57 pass
58
59
60class DatabaseEmptyOptionException(DodaiDatabaseConnectionConfigurationError):
61 """Exception raised for an empty option in the config
62
63 Attributes:
64 section_name: The section of the config file that contains
65 the invalid protocol
66 option_name: The name of the empty option
67
68 """
69 MESSAGE = "In the '{section_name}' section, the '{option_name}' "\
70 "was not set or is missing. Please set this option."
71
72 def __init__(self, section_name, option_name):
73 self.section_name = section_name
74 self.option_name = option_name
75 self.msg = self._build_message()
76
77 def _build_message(self):
78 return self.MESSAGE.format(section_name=self.section_name,
79 option_name=self.option_name)
80
81 def __str__(self):
82 return self.msg
83
84
85class DatabasePortException(DodaiDatabaseConnectionConfigurationError):
86 """Exception raised for invalid database port connection
87
88 Attributes:
89 section_name: The section of the config file that contains
90 the invalid protocol
91 section_port: The port value that was listed in the
92 config file
93
94 """
95 MESSAGE = "In the '{section_name}' section, the port of "\
96 "'{section_port}' is invalid. The port must be a "\
97 "number between 1 and 65535"
98
99 def __init__(self, section_name, section_port):
100 self.section_name = section_name
101 self.section_port = section_port
102 self.msg = self._build_message()
103
104 def _build_message(self):
105 return self.MESSAGE.format(section_name=self.section_name,
106 section_port=self.section_port)
107
108 def __str__(self):
109 return self.msg
110
111
112class DatabaseHostnameException(DodaiDatabaseConnectionConfigurationError):
113 """Exception raised for invalid database hostname
114
115 Attributes:
116 section_name: The section of the config file that contains
117 the invalid protocol
118 section_hostname: The hostname value that was listed in the
119 config file
120
121 """
122 MESSAGE = "In the '{section_name}' section, the hostname of "\
123 "'{section_hostname}' is invalid. Please use a valid "\
124 "hostname."
125
126 MSG_NON = "In the '{section_name}' section, the hostname was "\
127 "not set. Please set the hostname."
128
129 def __init__(self, section_name, section_hostname):
130 self.section_name = section_name
131 self.section_hostname = section_hostname
132 self.msg = self._build_message()
133
134 def _build_message(self):
135 if self.section_hostname:
136 return self.MESSAGE.format(section_name=self.section_name,
137 section_hostname=self.section_hostname)
138 else:
139 return self.MSG_NON.format(section_name=self.section_name)
140
141 def __str__(self):
142 return self.msg
143
144
145class DatabaseProtocolException(DodaiDatabaseConnectionConfigurationError):
146 """Exception raised for invalid database connection protocols
147
148 Attributes:
149 section_name: The section of the config file that contains
150 the invalid protocol
151 section_protocol: The protocol value that was listed in the
152 config file
153 database_type: Usually 'server' or 'file'
154 protocols: List of valid protocols
155
156 """
157 MESSAGE = "In the '{section_name}' section, the protocol of "\
158 "'{section_protocol}' is invalid. The valid protocols "\
159 "for a '{database_type}' connection are: {protocols}"
160
161 def __init__(self, section_name, section_protocol, database_type,
162 protocols):
163 self.section_name = section_name
164 self.section_protocol = section_protocol
165 self.database_type = database_type
166 self.protocols = protocols
167 self.msg = self._build_message()
168
169 def _build_message(self):
170 protocols = list_to_english(self.protocols)
171 return self.MESSAGE.format(section_name=self.section_name,
172 section_protocol=self.section_protocol,
173 database_type=self.database_type,
174 protocols=protocols)
175
176 def __str__(self):
177 return self.msg
178
179
180class DatabaseConnectionException(DodaiDatabaseConnectionConfigurationError):
181 """Exception raised for missing database connection parameters
182
183 Attributes:
184 section_name: The section of the config file that contains
185 the invalid connection information
186 options_required: A dictionary containing the database_type
187 as the key and a list of required options
188 as the value
189
190 """
191 MESSAGE = "The '{section_name}' section does not contain all of the "\
192 "correct information needed to make a database connection."
193 MSG_TYPE = "To make a '{database_type}' connection please make sure "\
194 "To have all of the following options: {options}."
195 MSG_END = "Please remember that the option names are case sensitive."
196
197 def __init__(self, section_name, validators):
198 self.section_name = section_name
199 self.validators = validators
200 self.msg = self._build_message()
201
202 def _build_message(self):
203 out = []
204 out.append(self.MESSAGE.format(section_name=self.section_name))
205 for validator in self.validators:
206 options = list_to_english(validator.REQUIRED)
207 out.append(self.MSG_TYPE.format(database_type=validator.DB_TYPE,
208 options=options))
209 out.append(self.MSG_END)
210 return ' '.join(out)
211
212 def __str__(self):
213 return self.msg
214
215
216class UnknownDatabaseConnectionException(
217 DodaiDatabaseConnectionConfigurationError):
218 """Exception raised for missing database connection parameters
219
220 Attributes:
221 section: The requested section of the config file that can not
222 be found.
223
224 """
225 MESSAGE = "Unable to find the '{section}' section to create a "\
226 "database connection."
227
228 def __init__(self, section):
229 self.section = section
230 self.msg = self._build_message()
231
232 def _build_message(self):
233 return self.MESSAGE.format(section=self.section)
234
235 def __str__(self):
236 return self.msg
diff --git a/dodai/tools/__init__.py b/dodai/tools/__init__.py
index ab90cfe..b6d93a1 100644
--- a/dodai/tools/__init__.py
+++ b/dodai/tools/__init__.py
@@ -65,7 +65,6 @@ def config_directory_project():
65 path = os.path.dirname(os.path.abspath(sys.argv[0])) 65 path = os.path.dirname(os.path.abspath(sys.argv[0]))
66 return os.path.join(path, 'config') 66 return os.path.join(path, 'config')
67 67
68
69def config_directories(project_name): 68def config_directories(project_name):
70 """Returns a list of possible project directories 69 """Returns a list of possible project directories
71 70
@@ -82,4 +81,19 @@ def config_directories(project_name):
82 dirs.append(dir) 81 dirs.append(dir)
83 return dirs 82 return dirs
84 83
84def list_to_english(data):
85 """Takes the input list and creates a string with each option
86 encased in single quotes and seperated by a comma with exception
87 of the last option which is prefixed with the word 'and'
85 88
89 """
90 if data:
91 if len(data) > 1:
92 out = []
93 last = "{0}".format(data.pop())
94 for row in data:
95 out.append("{0}".format(row))
96 out = ', '.join(out)
97 return "{0} and {1}".format(out, last)
98 else:
99 return "{0}".format(data.pop())
diff --git a/dodai/tools/himo.py b/dodai/tools/himo.py
index 60051a7..aa4da6c 100644
--- a/dodai/tools/himo.py
+++ b/dodai/tools/himo.py
@@ -22,6 +22,7 @@ import unicodedata
22from htmlentitydefs import name2codepoint 22from htmlentitydefs import name2codepoint
23from htmlentitydefs import codepoint2name 23from htmlentitydefs import codepoint2name
24from decimal import Decimal as D 24from decimal import Decimal as D
25from dodai.exception import HimoAsciiError
25 26
26class String2Himo(object): 27class String2Himo(object):
27 """ 28 """
@@ -164,9 +165,7 @@ class Himo(unicode):
164 if num: 165 if num:
165 out.append(unichr(int(num, 16))) 166 out.append(unichr(int(num, 16)))
166 else: 167 else:
167 print char 168 raise HimoAsciiError(char)
168 raise HimoAsciiError("Unable to convert 'u{0}' "\
169 "character to ascii".format(ord(char)))
170 return str(''.join(out)) 169 return str(''.join(out))
171 170
172class HimoAsciiError(Exception): 171class HimoAsciiError(Exception):
diff --git a/test/test_config/test_databases.py b/test/test_config/test_databases.py
new file mode 100644
index 0000000..9fc6f68
--- /dev/null
+++ b/test/test_config/test_databases.py
@@ -0,0 +1,341 @@
1# Copyright (C) 2010 Leonard Thomas
2#
3# This file is part of Dodai.
4#
5# Dodai is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# Dodai is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with Dodai. If not, see <http://www.gnu.org/licenses/>.
17
18
19import sys
20import os
21import unittest
22import ConfigParser
23from dingus import Dingus
24path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..'))
25sys.path.append(path)
26from dodai.config.databases import ConfigDatabases
27from dodai.config.databases import DatabaseConnectionValidator
28from dodai.exception import DatabaseEmptyOptionException
29from dodai.exception import DatabasePortException
30from dodai.exception import DatabaseHostnameException
31from dodai.exception import DatabaseProtocolException
32from dodai.exception import DatabaseConnectionException
33from dodai.exception import UnknownDatabaseConnectionException
34from dodai.tools import list_to_english
35from dodai.tools.himo import String2Himo
36from dodai.config.sections import ConfigSections
37
38
39class TestConfigDatabases(unittest.TestCase):
40
41 SECTIONS = {}
42
43 # valid server connection
44 SECTIONS['db_1'] = {}
45 SECTIONS['db_1']['protocol'] = 'postgresql'
46 SECTIONS['db_1']['hostname'] = '127.0.0.1'
47 SECTIONS['db_1']['username'] = 'test'
48 SECTIONS['db_1']['password'] = 'test'
49 SECTIONS['db_1']['port'] = '12345'
50 SECTIONS['db_1']['database'] = 'testing'
51 SECTIONS['db_1']['schema'] = 'foo'
52
53 # valid file connection
54 SECTIONS['db_2'] = {}
55 SECTIONS['db_2']['protocol'] = 'sqlite'
56 SECTIONS['db_2']['filename'] = '/tmp/test'
57
58 # missing protocol server
59 SECTIONS['db_3'] = {}
60 SECTIONS['db_3']['hostname'] = '127.0.0.1'
61 SECTIONS['db_3']['username'] = 'test'
62 SECTIONS['db_3']['password'] = 'test'
63 SECTIONS['db_3']['port'] = '12345'
64 SECTIONS['db_3']['database'] = 'testing'
65
66 # missing protocol file
67 SECTIONS['db_4'] = {}
68 SECTIONS['db_4']['filename'] = '/tmp/test'
69
70 # empty protocol server
71 SECTIONS['db_5'] = {}
72 SECTIONS['db_5']['protocol'] = ''
73 SECTIONS['db_5']['hostname'] = '127.0.0.1'
74 SECTIONS['db_5']['username'] = 'test'
75 SECTIONS['db_5']['password'] = 'test'
76 SECTIONS['db_5']['port'] = '12345'
77 SECTIONS['db_5']['database'] = 'testing'
78
79 # empty protocol file
80 SECTIONS['db_6'] = {}
81 SECTIONS['db_6']['protocol'] = None
82 SECTIONS['db_6']['filename'] ='/tmp/test'
83
84 # Missing port
85 SECTIONS['db_7'] = {}
86 SECTIONS['db_7']['protocol'] = 'postgresql'
87 SECTIONS['db_7']['protocol_extra'] = 'psycopg2'
88 SECTIONS['db_7']['hostname'] = '127.0.0.1'
89 SECTIONS['db_7']['username'] = 'test'
90 SECTIONS['db_7']['password'] = 'test'
91 SECTIONS['db_7']['database'] = 'testing'
92
93 # Empty port
94 SECTIONS['db_8'] = {}
95 SECTIONS['db_8']['protocol'] ='postgresql'
96 SECTIONS['db_8']['hostname'] ='127.0.0.1'
97 SECTIONS['db_8']['username'] ='test'
98 SECTIONS['db_8']['password'] ='test'
99 SECTIONS['db_8']['port'] =''
100 SECTIONS['db_8']['database'] ='testing'
101
102 # Invalid port because it's a string
103 SECTIONS['db_9'] = {}
104 SECTIONS['db_9']['protocol'] ='postgresql'
105 SECTIONS['db_9']['hostname'] ='127.0.0.1'
106 SECTIONS['db_9']['username'] ='test'
107 SECTIONS['db_9']['password'] ='test'
108 SECTIONS['db_9']['port'] ='blah'
109 SECTIONS['db_9']['database'] ='testing'
110
111 # Invalid port low
112 SECTIONS['db_10'] = {}
113 SECTIONS['db_10']['protocol'] ='postgresql'
114 SECTIONS['db_10']['hostname'] ='127.0.0.1'
115 SECTIONS['db_10']['username'] ='test'
116 SECTIONS['db_10']['password'] ='test'
117 SECTIONS['db_10']['port'] ='0'
118 SECTIONS['db_10']['database'] ='testing'
119
120 # Invalid port high
121 SECTIONS['db_11'] = {}
122 SECTIONS['db_11']['protocol'] ='postgresql'
123 SECTIONS['db_11']['hostname'] ='127.0.0.1'
124 SECTIONS['db_11']['username'] ='test'
125 SECTIONS['db_11']['password'] ='test'
126 SECTIONS['db_11']['port'] ='655555'
127 SECTIONS['db_11']['database'] ='testing'
128
129 # missing hostname
130 SECTIONS['db_12'] = {}
131 SECTIONS['db_12']['protocol'] ='postgresql'
132 SECTIONS['db_12']['username'] ='test'
133 SECTIONS['db_12']['password'] ='test'
134 SECTIONS['db_12']['port'] ='655'
135 SECTIONS['db_12']['database'] ='testing'
136
137 # empty hostname
138 SECTIONS['db_13'] = {}
139 SECTIONS['db_13']['protocol'] ='postgresql'
140 SECTIONS['db_13']['hostname'] =''
141 SECTIONS['db_13']['username'] ='test'
142 SECTIONS['db_13']['password'] ='test'
143 SECTIONS['db_13']['port'] ='655'
144 SECTIONS['db_13']['database'] ='testing'
145
146 # missing username
147 SECTIONS['db_14'] = {}
148 SECTIONS['db_14']['protocol'] = 'postgresql'
149 SECTIONS['db_14']['hostname'] = '127.0.0.1'
150 SECTIONS['db_14']['password'] = 'test'
151 SECTIONS['db_14']['port'] = '12345'
152 SECTIONS['db_14']['database'] = 'testing'
153 SECTIONS['db_14']['schema'] = 'foo'
154
155 # empty username
156 SECTIONS['db_15'] = {}
157 SECTIONS['db_15']['protocol'] = 'postgresql'
158 SECTIONS['db_15']['hostname'] = '127.0.0.1'
159 SECTIONS['db_15']['username'] = ''
160 SECTIONS['db_15']['password'] = 'test'
161 SECTIONS['db_15']['port'] = '12345'
162 SECTIONS['db_15']['database'] = 'testing'
163
164 # missing password
165 SECTIONS['db_16'] = {}
166 SECTIONS['db_16']['protocol'] = 'postgresql'
167 SECTIONS['db_16']['hostname'] = '127.0.0.1'
168 SECTIONS['db_16']['username'] = 'test'
169 SECTIONS['db_16']['port'] = '12345'
170 SECTIONS['db_16']['database'] = 'testing'
171
172 # empty password
173 SECTIONS['db_17'] = {}
174 SECTIONS['db_17']['protocol'] = 'postgresql'
175 SECTIONS['db_17']['hostname'] = '127.0.0.1'
176 SECTIONS['db_17']['username'] = 'test'
177 SECTIONS['db_17']['password'] = ''
178 SECTIONS['db_17']['port'] = '12345'
179 SECTIONS['db_17']['database'] = 'testing'
180
181 # missing database
182 SECTIONS['db_18'] = {}
183 SECTIONS['db_18']['protocol'] = 'postgresql'
184 SECTIONS['db_18']['hostname'] = '127.0.0.1'
185 SECTIONS['db_18']['username'] = 'test'
186 SECTIONS['db_18']['password'] = 'test'
187 SECTIONS['db_18']['port'] = '12345'
188
189 # empty database
190 SECTIONS['db_19'] = {}
191 SECTIONS['db_19']['protocol'] = 'postgresql'
192 SECTIONS['db_19']['hostname'] = '127.0.0.1'
193 SECTIONS['db_19']['username'] = 'test'
194 SECTIONS['db_19']['password'] = 'test'
195 SECTIONS['db_19']['port'] = '12345'
196 SECTIONS['db_19']['database'] = ''
197
198 # missing filename
199 SECTIONS['db_20'] = {}
200 SECTIONS['db_20']['protocol'] = 'sqlite'
201
202 # empty filename
203 SECTIONS['db_21'] = {}
204 SECTIONS['db_21']['protocol'] = 'sqlite'
205 SECTIONS['db_21']['filename'] = ''
206
207 # not a database
208 SECTIONS['db_22'] = {}
209 SECTIONS['db_22']['foo'] = 'bar'
210
211 # valid server test handler
212 SECTIONS['db_23'] = {}
213 SECTIONS['db_23']['handler'] = 'test_handler'
214 SECTIONS['db_23']['protocol'] = 'postgresql'
215 SECTIONS['db_23']['hostname'] = '127.0.0.1'
216 SECTIONS['db_23']['username'] = 'test'
217 SECTIONS['db_23']['password'] = 'test'
218 SECTIONS['db_23']['port'] = '12345'
219 SECTIONS['db_23']['database'] = 'testing'
220
221 # invalid protocol
222 SECTIONS['db_24'] = {}
223 SECTIONS['db_24']['protocol'] = 'foo'
224 SECTIONS['db_24']['hostname'] = '127.0.0.1'
225 SECTIONS['db_24']['username'] = 'test'
226 SECTIONS['db_24']['password'] = 'test'
227 SECTIONS['db_24']['port'] = '12345'
228 SECTIONS['db_24']['database'] = 'testing'
229
230 def setUp(self):
231 sa = Dingus('sa')
232 sa.name = 'test'
233 self.db = ConfigDatabases(sa, 'sa')
234 self.db.add(self.SECTIONS)
235
236 def test_default_handler(self):
237 self.assertTrue('sa' in self.db._handlers)
238 val = self.db._handlers['sa']
239 self.assertTrue('test' == val.name)
240
241 def test_sections(self):
242 self.assertTrue(len(self.db.connections) == len(self.SECTIONS))
243
244 def test_load_server(self):
245 obj = self.db.load('db_1')
246
247 def test_load_file(self):
248 obj = self.db.load('db_2')
249
250 def test_unable_to_load(self):
251 self.failUnlessRaises(UnknownDatabaseConnectionException,
252 self.db.load, 'db_twenty_seven')
253
254 def test_missing_protocol_server(self):
255 self.failUnlessRaises(DatabaseEmptyOptionException,
256 self.db.load, 'db_3')
257
258 def test_missing_protocol_file(self):
259 self.failUnlessRaises(DatabaseEmptyOptionException,
260 self.db.load, 'db_4')
261
262 def test_empty_protocol_server(self):
263 self.failUnlessRaises(DatabaseEmptyOptionException,
264 self.db.load, 'db_5')
265
266 def test_empty_protocol_file(self):
267 self.failUnlessRaises(DatabaseEmptyOptionException,
268 self.db.load, 'db_6')
269
270 def test_missing_port(self):
271 self.failUnlessRaises(DatabaseEmptyOptionException,
272 self.db.load, 'db_7')
273
274 def test_empty_port(self):
275 self.failUnlessRaises(DatabaseEmptyOptionException,
276 self.db.load, 'db_8')
277
278 def test_invalid_port_because_its_a_string(self):
279 self.failUnlessRaises(DatabasePortException,
280 self.db.load, 'db_9')
281
282 def test_invalid_port_because_its_to_low(self):
283 self.failUnlessRaises(DatabasePortException,
284 self.db.load, 'db_10')
285
286 def test_invalid_port_because_its_to_high(self):
287 self.failUnlessRaises(DatabasePortException,
288 self.db.load, 'db_11')
289
290 def test_missing_hostname(self):
291 self.failUnlessRaises(DatabaseEmptyOptionException,
292 self.db.load, 'db_12')
293
294 def test_empty_hostname(self):
295 self.failUnlessRaises(DatabaseEmptyOptionException,
296 self.db.load, 'db_13')
297
298 def test_missing_username(self):
299 self.failUnlessRaises(DatabaseEmptyOptionException,
300 self.db.load, 'db_14')
301
302 def test_empty_username(self):
303 self.failUnlessRaises(DatabaseEmptyOptionException,
304 self.db.load, 'db_15')
305
306 def test_missing_password(self):
307 self.failUnlessRaises(DatabaseEmptyOptionException,
308 self.db.load, 'db_16')
309
310 def test_empty_password(self):
311 self.failUnlessRaises(DatabaseEmptyOptionException,
312 self.db.load, 'db_17')
313
314 def test_missing_database(self):
315 self.failUnlessRaises(DatabaseEmptyOptionException,
316 self.db.load, 'db_18')
317
318 def test_empty_database(self):
319 self.failUnlessRaises(DatabaseEmptyOptionException,
320 self.db.load, 'db_19')
321
322 def test_missing_filename(self):
323 self.failUnlessRaises(DatabaseEmptyOptionException,
324 self.db.load, 'db_20')
325
326 def test_empty_filename(self):
327 self.failUnlessRaises(DatabaseEmptyOptionException,
328 self.db.load, 'db_21')
329
330 def test_not_a_connection(self):
331 self.failUnlessRaises(DatabaseConnectionException,
332 self.db.load, 'db_22')
333
334 def test_non_default_handler(self):
335 test_handler = Dingus('test_handler')
336 self.db.add_handler('test_handler', test_handler)
337 self.db.load('db_23')
338
339 def test_invalid_protocol(self):
340 self.failUnlessRaises(DatabaseProtocolException,
341 self.db.load, 'db_24')
diff --git a/test/test_config/test_sa.py b/test/test_config/test_sa.py
index d6ff940..57459bc 100644
--- a/test/test_config/test_sa.py
+++ b/test/test_config/test_sa.py
@@ -25,6 +25,20 @@ from dodai.config.databases.sa import Sa
25 25
26class TestSa(unittest.TestCase): 26class TestSa(unittest.TestCase):
27 27
28 DB_ONE = {}
29 DB_ONE['protocol'] ='postgresql'
30 DB_ONE['protocol_extra'] ='psycopg2'
31 DB_ONE['hostname'] ='127.0.0.1'
32 DB_ONE['username'] ='test'
33 DB_ONE['password'] ='test'
34 DB_ONE['port'] ='12345'
35 DB_ONE['database'] ='testing'
36 DB_ONE['schema'] ='foo'
37
38 DB_TWO = {}
39 DB_TWO['protocol'] ='sqlite'
40 DB_TWO['filename'] ='/tmp/test'
41
28 def setUp(self): 42 def setUp(self):
29 self.create_engine = Dingus('create_engine') 43 self.create_engine = Dingus('create_engine')
30 self.session_maker = Dingus('session_maker') 44 self.session_maker = Dingus('session_maker')
@@ -32,33 +46,10 @@ class TestSa(unittest.TestCase):
32 self.obj = Sa(self.create_engine, self.session_maker, 46 self.obj = Sa(self.create_engine, self.session_maker,
33 self.result_object) 47 self.result_object)
34 48
35
36 def test_sa_one(self): 49 def test_sa_one(self):
37 name = 'test_db_two' 50 name = 'test_db_one'
38 obj = DbOne() 51 db = self.obj(self.DB_ONE, name)
39 db = self.obj(name, obj)
40
41 52
42 def test_sa_two(self): 53 def test_sa_two(self):
43 name = 'test_db_six' 54 name = 'test_db_two'
44 obj = DbTwo() 55 db = self.obj(self.DB_TWO, name)
45 db = self.obj(name, obj)
46
47
48class DbOne(object):
49
50 def __init__(self):
51 self.protocol='postgresql'
52 self.protocol_extra='psycopg2'
53 self.hostname='127.0.0.1'
54 self.username='test'
55 self.password='test'
56 self.port='12345'
57 self.database='testing'
58 self.schema='foo'
59
60class DbTwo(object):
61
62 def __init__(self):
63 self.protocol='sqlite'
64 self.filename='/tmp/test'