diff options
Diffstat (limited to 'lib/dodai/config/databases/__init__.py')
-rw-r--r-- | lib/dodai/config/databases/__init__.py | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/lib/dodai/config/databases/__init__.py b/lib/dodai/config/databases/__init__.py new file mode 100644 index 0000000..c91e77f --- /dev/null +++ b/lib/dodai/config/databases/__init__.py | |||
@@ -0,0 +1,285 @@ | |||
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 | 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 | |||
27 | class ConfigDatabases(object): | ||
28 | """An object that is used for creating database connection objects | ||
29 | |||
30 | """ | ||
31 | def __init__(self, default_handler, default_name): | ||
32 | self.connections = {} | ||
33 | self._handlers = {} | ||
34 | DatabaseConnection.DEFAULT_HANDLER = default_name | ||
35 | self.add_handler(default_name, default_handler) | ||
36 | |||
37 | def add(self, sections): | ||
38 | """Adds a dictionary of sections to this object that could | ||
39 | be used for creating database connection objects. The dictionary | ||
40 | should be formatted as follows: | ||
41 | |||
42 | sections[name] = {option: value, option: value, ....} | ||
43 | |||
44 | The 'name' is the section name. It is an identifier that is used | ||
45 | to load the database: | ||
46 | |||
47 | The options and values can have the following: | ||
48 | |||
49 | protocol: The database engine (postgresql, mysql, sqlite, | ||
50 | oracle, mssql etc..) | ||
51 | |||
52 | hostname: The hostname or ip address of the database server | ||
53 | |||
54 | port: The port that should be used to connect to the | ||
55 | database server should be a number between 1 | ||
56 | and 65535 | ||
57 | |||
58 | username: The username used to connect to the database server | ||
59 | |||
60 | password: The password used to connect to the database server | ||
61 | |||
62 | database: The database to connect to once connected to the | ||
63 | server | ||
64 | |||
65 | schema: The schema that will be used. This option does | ||
66 | not do anything but will exist in the db object | ||
67 | for use | ||
68 | |||
69 | filename: The file path to the database file (sqlite) | ||
70 | |||
71 | protocol_extra: Used to tell the handler which python db engine | ||
72 | to use. For example one might want to tell | ||
73 | the sqlalchemy handler to use the psycopg2 | ||
74 | python object. | ||
75 | |||
76 | handler: The handler used to build the orm object. Dodai | ||
77 | only works with sqlalchemy so the default 'sa' | ||
78 | is used. | ||
79 | |||
80 | """ | ||
81 | for name, section in sections.items(): | ||
82 | self.connections[name] = DatabaseConnection(section, name, | ||
83 | self._handlers) | ||
84 | |||
85 | def load(self, section_name): | ||
86 | """Returns a database connection object of the given section_name. | ||
87 | Throws Exceptions for any type of configuration errors or missing | ||
88 | configuration data | ||
89 | |||
90 | """ | ||
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): | ||
107 | self.section = section | ||
108 | self.name = name | ||
109 | self.handlers = handlers | ||
110 | self.validate = DatabaseConnectionValidator.load(section, name) | ||
111 | self.obj = None | ||
112 | |||
113 | def load(self): | ||
114 | if not self.obj: | ||
115 | self.validate() | ||
116 | handler = self._get_handler() | ||
117 | self.obj = handler(self.section, self.name) | ||
118 | return self.obj | ||
119 | |||
120 | def _get_handler(self): | ||
121 | if self.section.has_key('handler'): | ||
122 | name = self.section['handler'] | ||
123 | if name in self.handlers: | ||
124 | return self.handlers[name] | ||
125 | return self.handlers[self.DEFAULT_HANDLER] | ||
126 | |||
127 | |||
128 | class DatabaseConnectionValidator(object): | ||
129 | |||
130 | def __init__(self, section, name, validators=[]): | ||
131 | self.section = section | ||
132 | self.name = name | ||
133 | self.validators = validators | ||
134 | |||
135 | def __call__(self): | ||
136 | return self.validate() | ||
137 | |||
138 | def validate(self): | ||
139 | """Validates the connection. Returns true if valid. Throws | ||
140 | DodaiDatabaseConnectionConfigurationError on any errors | ||
141 | |||
142 | """ | ||
143 | raise NotImplementedError() | ||
144 | |||
145 | def _validate_option(self, name): | ||
146 | try: | ||
147 | value = self.section[name] | ||
148 | except KeyError: | ||
149 | value = None | ||
150 | |||
151 | if not value: | ||
152 | raise DatabaseEmptyOptionException(self.name, name) | ||
153 | return True | ||
154 | |||
155 | def _validate_protocol(self): | ||
156 | |||
157 | self._validate_option('protocol') | ||
158 | |||
159 | if self.section['protocol'] not in self.PROTOCOLS: | ||
160 | raise DatabaseProtocolException(self.name, | ||
161 | self.section['protocol'], self.DB_TYPE, | ||
162 | self.PROTOCOLS) | ||
163 | return True | ||
164 | |||
165 | @staticmethod | ||
166 | def load(section, name): | ||
167 | """Static method (factory) that loads the correct database | ||
168 | validation class. | ||
169 | |||
170 | Attributes: | ||
171 | section: Dictionary of key val connection information | ||
172 | name: String name of the section | ||
173 | |||
174 | """ | ||
175 | action = None | ||
176 | validators = [DatabaseConnectionValidatorServer, | ||
177 | DatabaseConnectionValidatorFile] | ||
178 | |||
179 | for validator in validators: | ||
180 | obj = validator.load(section, name) | ||
181 | if obj: | ||
182 | return obj | ||
183 | |||
184 | return DatabaseConnectionValidatorUnknown(section, name, validators) | ||
185 | |||
186 | class DatabaseConnectionValidatorServer(DatabaseConnectionValidator): | ||
187 | |||
188 | DB_TYPE = 'server' | ||
189 | REQUIRED = ['protocol', 'hostname', 'port', 'username', 'password', | ||
190 | 'database'] | ||
191 | PROTOCOLS = ['postgresql', 'mysql', 'mssql', 'oracle'] | ||
192 | |||
193 | def _validate_port(self): | ||
194 | self._validate_option('port') | ||
195 | |||
196 | try: | ||
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) | ||
204 | |||
205 | def validate(self): | ||
206 | """Validates the connection. Returns true if valid. Throws | ||
207 | DodaiDatabaseConnectionConfigurationError on any errors | ||
208 | |||
209 | """ | ||
210 | self._validate_protocol() | ||
211 | self._validate_port() | ||
212 | self._validate_option('hostname') | ||
213 | self._validate_option('username') | ||
214 | self._validate_option('password') | ||
215 | self._validate_option('database') | ||
216 | return True | ||
217 | |||
218 | @classmethod | ||
219 | def load(cls, section, name): | ||
220 | """Return this validation class if it is possible that the | ||
221 | given connection information contains enough data to make | ||
222 | a database server connection. | ||
223 | |||
224 | Attributes: | ||
225 | section: Dictionary of key val connection information | ||
226 | name: String name of the section | ||
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) | ||
236 | |||
237 | |||
238 | class DatabaseConnectionValidatorFile(DatabaseConnectionValidator): | ||
239 | |||
240 | DB_TYPE = 'file' | ||
241 | REQUIRED = ['protocol', 'filename'] | ||
242 | PROTOCOLS = ['sqlite'] | ||
243 | |||
244 | def validate(self): | ||
245 | """Validates the connection. Returns true if valid. Throws | ||
246 | DodaiDatabaseConnectionConfigurationError on any errors | ||
247 | |||
248 | """ | ||
249 | self._validate_protocol() | ||
250 | self._validate_option('filename') | ||
251 | return True | ||
252 | |||
253 | @classmethod | ||
254 | def load(cls, section, name): | ||
255 | """Return this validation class if it is possible that the | ||
256 | given connection information contains enough data to make | ||
257 | a database file connection. | ||
258 | |||
259 | Attributes: | ||
260 | section: Dictionary of key val connection information | ||
261 | name: String name of the section | ||
262 | |||
263 | """ | ||
264 | if section.has_key('protocol'): | ||
265 | if section['protocol'] in cls.PROTOCOLS: | ||
266 | return cls(section, name) | ||
267 | keys = section.keys() | ||
268 | for key in keys: | ||
269 | if key in ['filename']: | ||
270 | return cls(section, name) | ||
271 | |||
272 | |||
273 | class DatabaseConnectionValidatorUnknown(DatabaseConnectionValidator): | ||
274 | |||
275 | DB_TYPE = 'unkonwn' | ||
276 | REQUIRED = [] | ||
277 | PROTOCOLS = [] | ||
278 | |||
279 | def validate(self): | ||
280 | """Validates the connection. Returns true if valid. Throws | ||
281 | DodaiDatabaseConnectionConfigurationError on any errors | ||
282 | |||
283 | """ | ||
284 | |||
285 | raise DatabaseConnectionException(self.name, self.validators) | ||