diff options
-rw-r--r-- | dodai/config/__init__.py | 9 | ||||
-rw-r--r-- | dodai/config/databases/__init__.py | 445 | ||||
-rw-r--r-- | dodai/config/databases/sa.py | 80 | ||||
-rw-r--r-- | dodai/exception.py | 236 | ||||
-rw-r--r-- | dodai/tools/__init__.py | 16 | ||||
-rw-r--r-- | dodai/tools/himo.py | 5 | ||||
-rw-r--r-- | test/test_config/test_databases.py | 341 | ||||
-rw-r--r-- | test/test_config/test_sa.py | 45 |
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 | ||
19 | from dodai.exception import DatabaseEmptyOptionException | ||
20 | from dodai.exception import DatabasePortException | ||
21 | from dodai.exception import DatabaseHostnameException | ||
22 | from dodai.exception import DatabaseProtocolException | ||
23 | from dodai.exception import DatabaseConnectionException | ||
24 | from dodai.exception import UnknownDatabaseConnectionException | ||
25 | from dodai.tools import list_to_english | ||
26 | |||
19 | class ConfigDatabases(object): | 27 | class 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 | |||
65 | class 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 | |||
102 | class 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': | 128 | class 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 | ||
197 | class 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): | 186 | class 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 | ||
219 | class 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 | ||
243 | class DatabaseHostnameException(Exception): | ||
244 | """Exception raised for invalid database hostname | ||
245 | 237 | ||
246 | Attributes: | 238 | class 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 | ||
273 | class 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 | |||
305 | class 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 | |||
338 | class 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): | 273 | class 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 | ||
357 | def 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 | ||
19 | class Sa(object): | 19 | class 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 | |||
18 | from dodai.tools import list_to_english | ||
19 | |||
20 | class 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 | |||
32 | class 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 | |||
56 | class DodaiDatabaseConnectionConfigurationError(DodaiException): | ||
57 | pass | ||
58 | |||
59 | |||
60 | class 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 | |||
85 | class 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 | |||
112 | class 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 | |||
145 | class 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 | |||
180 | class 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 | |||
216 | class 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 | |||
69 | def config_directories(project_name): | 68 | def 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 | ||
84 | def 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 | |||
22 | from htmlentitydefs import name2codepoint | 22 | from htmlentitydefs import name2codepoint |
23 | from htmlentitydefs import codepoint2name | 23 | from htmlentitydefs import codepoint2name |
24 | from decimal import Decimal as D | 24 | from decimal import Decimal as D |
25 | from dodai.exception import HimoAsciiError | ||
25 | 26 | ||
26 | class String2Himo(object): | 27 | class 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 | ||
172 | class HimoAsciiError(Exception): | 171 | class 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 | |||
19 | import sys | ||
20 | import os | ||
21 | import unittest | ||
22 | import ConfigParser | ||
23 | from dingus import Dingus | ||
24 | path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..')) | ||
25 | sys.path.append(path) | ||
26 | from dodai.config.databases import ConfigDatabases | ||
27 | from dodai.config.databases import DatabaseConnectionValidator | ||
28 | from dodai.exception import DatabaseEmptyOptionException | ||
29 | from dodai.exception import DatabasePortException | ||
30 | from dodai.exception import DatabaseHostnameException | ||
31 | from dodai.exception import DatabaseProtocolException | ||
32 | from dodai.exception import DatabaseConnectionException | ||
33 | from dodai.exception import UnknownDatabaseConnectionException | ||
34 | from dodai.tools import list_to_english | ||
35 | from dodai.tools.himo import String2Himo | ||
36 | from dodai.config.sections import ConfigSections | ||
37 | |||
38 | |||
39 | class 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 | ||
26 | class TestSa(unittest.TestCase): | 26 | class 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 | |||
48 | class 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 | |||
60 | class DbTwo(object): | ||
61 | |||
62 | def __init__(self): | ||
63 | self.protocol='sqlite' | ||
64 | self.filename='/tmp/test' | ||