aboutsummaryrefslogtreecommitdiff
path: root/dodai/config/databases/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'dodai/config/databases/__init__.py')
-rw-r--r--dodai/config/databases/__init__.py445
1 files changed, 179 insertions, 266 deletions
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())