# Copyright (C) 2010 Leonard Thomas # # This file is part of Dodai. # # Dodai is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Dodai is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Dodai. If not, see . from dodai.exception import DatabaseEmptyOptionException from dodai.exception import DatabasePortException from dodai.exception import DatabaseHostnameException from dodai.exception import DatabaseProtocolException from dodai.exception import DatabaseConnectionException from dodai.exception import UnknownDatabaseConnectionException from dodai.tools import list_to_english class ConfigDatabases(object): """An object that is used for creating database connection objects """ def __init__(self, default_handler, default_name): self.connections = {} self._handlers = {} DatabaseConnection.DEFAULT_HANDLER = default_name self.add_handler(default_name, default_handler) def add(self, sections): """Adds a dictionary of sections to this object that could be used for creating database connection objects. The dictionary should be formatted as follows: sections[name] = {option: value, option: value, ....} The 'name' is the section name. It is an identifier that is used to load the database: The options and values can have the following: protocol: The database engine (postgresql, mysql, sqlite, oracle, mssql etc..) hostname: The hostname or ip address of the database server port: The port that should be used to connect to the database server should be a number between 1 and 65535 username: The username used to connect to the database server password: The password used to connect to the database server database: The database to connect to once connected to the server schema: The schema that will be used. This option does not do anything but will exist in the db object for use filename: The file path to the database file (sqlite) protocol_extra: Used to tell the handler which python db engine to use. For example one might want to tell the sqlalchemy handler to use the psycopg2 python object. handler: The handler used to build the orm object. Dodai only works with sqlalchemy so the default 'sa' is used. """ for name, section in sections.items(): self.connections[name] = DatabaseConnection(section, name, self._handlers) def load(self, section_name): """Returns a database connection object of the given section_name. Throws Exceptions for any type of configuration errors or missing configuration data """ if section_name in self.connections: return self.connections[section_name].load() else: raise UnknownDatabaseConnectionException(section_name) def add_handler(self, name, handler): """Adds the given handler and name to this objects handlers """ self._handlers[name] = handler class DatabaseConnection(object): DEFAULT_HANDLER = None def __init__(self, section, name, handlers): self.section = section self.name = name self.handlers = handlers self.validate = DatabaseConnectionValidator.load(section, name) self.obj = None def load(self): if not self.obj: self.validate() handler = self._get_handler() self.obj = handler(self.section, self.name) return self.obj def _get_handler(self): if self.section.has_key('handler'): name = self.section['handler'] if name in self.handlers: return self.handlers[name] return self.handlers[self.DEFAULT_HANDLER] class DatabaseConnectionValidator(object): def __init__(self, section, name, validators=[]): self.section = section self.name = name self.validators = validators def __call__(self): return self.validate() def validate(self): """Validates the connection. Returns true if valid. Throws DodaiDatabaseConnectionConfigurationError on any errors """ raise NotImplementedError() def _validate_option(self, name): try: value = self.section[name] except KeyError: value = None if not value: raise DatabaseEmptyOptionException(self.name, name) return True def _validate_protocol(self): self._validate_option('protocol') if self.section['protocol'] not in self.PROTOCOLS: raise DatabaseProtocolException(self.name, self.section['protocol'], self.DB_TYPE, self.PROTOCOLS) return True @staticmethod def load(section, name): """Static method (factory) that loads the correct database validation class. Attributes: section: Dictionary of key val connection information name: String name of the section """ action = None validators = [DatabaseConnectionValidatorServer, DatabaseConnectionValidatorFile] for validator in validators: obj = validator.load(section, name) if obj: return obj return DatabaseConnectionValidatorUnknown(section, name, validators) class DatabaseConnectionValidatorServer(DatabaseConnectionValidator): DB_TYPE = 'server' REQUIRED = ['protocol', 'hostname', 'port', 'username', 'password', 'database'] PROTOCOLS = ['postgresql', 'mysql', 'mssql', 'oracle'] def _validate_port(self): self._validate_option('port') try: port = int(self.section['port']) except ValueError: port = self.section['port'] else: if port >=1 and port <=65535: return True raise DatabasePortException(self.name, port) def validate(self): """Validates the connection. Returns true if valid. Throws DodaiDatabaseConnectionConfigurationError on any errors """ self._validate_protocol() self._validate_port() self._validate_option('hostname') self._validate_option('username') self._validate_option('password') self._validate_option('database') return True @classmethod def load(cls, section, name): """Return this validation class if it is possible that the given connection information contains enough data to make a database server connection. Attributes: section: Dictionary of key val connection information name: String name of the section """ if section.has_key('protocol'): if section['protocol'] in cls.PROTOCOLS: return cls(section, name) keys = section.keys() for key in keys: if key in ['hostname', 'port']: return cls(section, name) class DatabaseConnectionValidatorFile(DatabaseConnectionValidator): DB_TYPE = 'file' REQUIRED = ['protocol', 'filename'] PROTOCOLS = ['sqlite'] def validate(self): """Validates the connection. Returns true if valid. Throws DodaiDatabaseConnectionConfigurationError on any errors """ self._validate_protocol() self._validate_option('filename') return True @classmethod def load(cls, section, name): """Return this validation class if it is possible that the given connection information contains enough data to make a database file connection. Attributes: section: Dictionary of key val connection information name: String name of the section """ if section.has_key('protocol'): if section['protocol'] in cls.PROTOCOLS: return cls(section, name) keys = section.keys() for key in keys: if key in ['filename']: return cls(section, name) class DatabaseConnectionValidatorUnknown(DatabaseConnectionValidator): DB_TYPE = 'unkonwn' REQUIRED = [] PROTOCOLS = [] def validate(self): """Validates the connection. Returns true if valid. Throws DodaiDatabaseConnectionConfigurationError on any errors """ raise DatabaseConnectionException(self.name, self.validators)