From 57c0e1b947d050d94013f83d4d677c8535a35350 Mon Sep 17 00:00:00 2001 From: Six Date: Sat, 1 May 2010 22:15:49 -0400 Subject: changed the dodai.config.databases to work with dictionaries. Created a dodai.exceptions. --- dodai/config/__init__.py | 9 +- dodai/config/databases/__init__.py | 445 +++++++++++++++---------------------- dodai/config/databases/sa.py | 80 ++++--- dodai/exception.py | 236 ++++++++++++++++++++ dodai/tools/__init__.py | 16 +- dodai/tools/himo.py | 5 +- test/test_config/test_databases.py | 341 ++++++++++++++++++++++++++++ test/test_config/test_sa.py | 45 ++-- 8 files changed, 848 insertions(+), 329 deletions(-) create mode 100644 dodai/exception.py create mode 100644 test/test_config/test_databases.py 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): self.logs = ConfigLog() return self.logs elif 'databases' == key: + # Wire up the sqlalchemy objects to use in the sa object + # which will be used as the default database handler from dodai.config.databases import ConfigDatabases - self.databases = ConfigDatabases() + from dodai.config.databases.sa import Sa + from sqlalchemy.orm import sessionmaker + from sqlalchemy import create_engine + from dodai.db import Db + sa = Sa(create_engine, sessionmaker, Db) + self.databases = ConfigDatabases(sa, 'sa') return self.databases else: 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 @@ # 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): - self.sections = {} + 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 section (objects) to this object that could - be used for creating database connection objects - - """ - self._build_default_handler() - for name, section in sections.items(): - self.sections[name] = DatabaseConnectionValidator( - name, section, self._handlers) + """Adds a dictionary of sections to this object that could + be used for creating database connection objects. The dictionary + should be formatted as follows: - 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 + sections[name] = {option: value, option: value, ....} - """ - if section_name in self.sections: - return self.sections[section_name].load() - else: - raise UnknownDatabaseConnectionException(section_name) + The 'name' is the section name. It is an identifier that is used + to load the database: - def _build_default_handler(self): - # Creates the default sqlalchemy handler - if 'sa' not in self._handlers: - from dodai.config.databases.sa import Sa - from sqlalchemy.orm import sessionmaker - from sqlalchemy import create_engine - from dodai.db import Db - sa = Sa(create_engine, sessionmaker, Db) - self._handlers['sa'] = sa - - def add_handler(self, name, handler): - """Adds the given handler and name to this objects handlers - - """ - self._handlers[name] = obj - - -class DatabaseConnectionValidator(object): - """Database validation object that is used to validate a section - object which is basically a python object that can have the following - properties: + The options and values can have the following: protocol: The database engine (postgresql, mysql, sqlite, oracle, mssql etc..) @@ -97,276 +76,210 @@ class DatabaseConnectionValidator(object): handler: The handler used to build the orm object. Dodai only works with sqlalchemy so the default 'sa' is used. - Not all of the above options are required. - """ - OPTIONS_REQUIRED = { - 'server':['protocol', 'hostname', 'port', 'username', 'password', - 'database'], - 'file' :['protocol', 'filename'] - } - OPTIONS_EXTRA = ['protocol_extra', 'handler', 'schema'] - SERVER_PROTOCOLS = ['postgresql', 'mysql', 'sqlite', 'mssql', 'oracle'] - FILE_PROTOCOLS = ['sqlite'] - DEFAULT_HANDLER = 'sa' - - def __init__(self, name, section, handlers): + """ + 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.database_type = self._database_type() - self._handlers = handlers + self.handlers = handlers + self.validate = DatabaseConnectionValidator.load(section, name) self.obj = None def load(self): if not self.obj: - self._validate() + self.validate() handler = self._get_handler() self.obj = handler(self.name, self.section) return self.obj def _get_handler(self): - if hasattr(self.section, 'handler'): - name = self.section.handler.lower() - if handler in self._handlers.keys(): - return self._handlers[name] - return self._handlers[self.DEFAULT_HANDLER] - - def _validate(self): - if self.database_type == 'server': - self._validate_protocol() - self._validate_port() - self._validate_option('username') - self._validate_option('password') - self._validate_option('database') - elif self.database_type == 'file': - self._validate_protocol() - self._validate_option('filename') - else: - raise DatabaseConnectionException(self.name, self.OPTIONS_REQUIRED) + 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): - if not self.section[name]: + try: + value = self.section[name] + except KeyError: + value = None + + if not value: raise DatabaseEmptyOptionException(self.name, name) return True - def _validate_hostname(self): - if not self.section.hostname: - raise DatabaseHostnameException(self.name, self.section.hostname) - return True + def _validate_protocol(self): - def _validate_port(self): - try: - port = int(self.section.port) - except ValueError: - pass - else: - if port >=1 and port <=65535: - return True - raise DatabasePortException(self.name, self.section.port) + self._validate_option('protocol') - def _validate_protocol(self): - if self.database_type == 'server': - if self.section.protocol not in self.SERVER_PROTOCOLS: - raise DatabaseProtocolException(self.name, - self.section.protocol, self.database_type, - self.SERVER_PROTOCOLS) - else: - return True - elif self.database_type == 'file': - if self.section.protocol not in self.FILE_PROTOCOLS: - raise DatabaseProtocolException(self.name, - self.section.protocol, self.database_type, - self.FILE_PROTOCOLS) - else: - return True - else: - return False + if self.section['protocol'] not in self.PROTOCOLS: + raise DatabaseProtocolException(self.name, + self.section['protocol'], self.DB_TYPE, + self.PROTOCOLS) + return True - def _database_type(self): - keys = self.section.___options___.keys() - for type_, pool in self.OPTIONS_REQUIRED.items(): - out = True - for key in keys: - if key not in pool: - out = False - if out: - return type_ - return False + @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 -class DatabaseEmptyOptionException(Exception): - """Exception raised for an emption option in the config + """ + action = None + validators = [DatabaseConnectionValidatorServer, + DatabaseConnectionValidatorFile] - Attributes: - section_name: The section of the config file that contains - the invalid protocol - option_name: The name of the empty option + for validator in validators: + obj = validator.load(section, name) + if obj: + return obj - """ - MESSAGE = "In the '{section_name}' section, the '{option_name}' "\ - "was not set. Please set this option." + return DatabaseConnectionValidatorUnknown(section, name, validators) - def __init__(self, section_name, option_name): - self.section_name = section_name - self.option_name = option_name - self.msg = self._build_message() +class DatabaseConnectionValidatorServer(DatabaseConnectionValidator): - def _build_message(self): - return self.MESSAGE.format(section_name=self.section_name, - option_name=self.option_name) + DB_TYPE = 'server' + REQUIRED = ['protocol', 'hostname', 'port', 'username', 'password', + 'database'] + PROTOCOLS = ['postgresql', 'mysql', 'mssql', 'oracle'] + def _validate_port(self): + self._validate_option('port') -class DatabasePortException(Exception): - """Exception raised for invalid database port connection + 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) - Attributes: - section_name: The section of the config file that contains - the invalid protocol - section_port: The port value that was listed in the - config file + def validate(self): + """Validates the connection. Returns true if valid. Throws + DodaiDatabaseConnectionConfigurationError on any errors - """ - MESSAGE = "In the '{section_name}' section, the port of "\ - "'{section_port}' is invalid. The port must be a "\ - "number between 1 and 65535" + """ + self._validate_protocol() + self._validate_port() + self._validate_option('hostname') + self._validate_option('username') + self._validate_option('password') + self._validate_option('database') + return True - def __init__(self, section_name, section_port): - self.section_name = section_name - self.section_port = section_port - self.msg = self._build_message() + @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. - def _build_message(self): - return self.MESSAGE.format(section_name=self.section_name, - section_port=self.section_port) + 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 DatabaseHostnameException(Exception): - """Exception raised for invalid database hostname - Attributes: - section_name: The section of the config file that contains - the invalid protocol - section_hostname: The hostname value that was listed in the - config file +class DatabaseConnectionValidatorFile(DatabaseConnectionValidator): - """ - MESSAGE = "In the '{section_name}' section, the hostname of "\ - "'{section_hostname}' is invalid. Please use a valid "\ - "hostname." - - MSG_NON = "In the '{section_name}' section, the hostname was "\ - "not set. Please set the hostname." - - def __init__(self, section_name, section_hostname): - self.section_name = section_name - self.section_hostname = section_hostname - self.msg = self._build_message() - - def _build_message(self): - if self.section_hostname: - return self.MESSAGE.format(section_name=self.section_name, - section_hostname=self.section_hostname) - else: - return self.MSG_NON.format(section_name=self.section_name) + DB_TYPE = 'file' + REQUIRED = ['protocol', 'filename'] + PROTOCOLS = ['sqlite'] + def validate(self): + """Validates the connection. Returns true if valid. Throws + DodaiDatabaseConnectionConfigurationError on any errors -class DatabaseProtocolException(Exception): - """Exception raised for invalid database connection protocols + """ + self._validate_protocol() + self._validate_option('filename') + return True - Attributes: - section_name: The section of the config file that contains - the invalid protocol - section_protocol: The protocol value that was listed in the - config file - database_type: Usually 'server' or 'file' - protocols: List of valid protocols + @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. - """ - MESSAGE = "In the '{section_name}' section, the protocol of "\ - "'{section_protocol}' is invalid. The valid protocols "\ - "for a '{database_type}' connection are: {protocols}" - - def __init__(self, section_name, section_protocol, database_type, - protocols): - self.section_name = section_name - self.section_protocol = section_protocol - self.database_type = database_type - self.protocols = protocols - self.msg = self._build_message() - - def _build_message(self): - protocols = ', '.join(self.protocols) - return self.MESSAGE.format(section_name=self.section_name, - section_protocol=self.section_protocol, - database_type=self.database_type, - protocols=protocols) - - -class DatabaseConnectionException(Exception): - """Exception raised for missing database connection parameters - - Attributes: - section_name: The section of the config file that contains - the invalid connection information - options_required: A dictionary containing the database_type - as the key and a list of required options - as the value + Attributes: + section: Dictionary of key val connection information + name: String name of the section - """ - MESSAGE = "The '{section_name}' section does not contain all of the "\ - "correct information needed to make a database connection." - MSG_TYPE = "To make a '{database_type}' connection please make sure "\ - "To have all of the following options: {options}." - MSG_END = "Please remember that the option names are case sensitive." - - def __init__(self, section_name, options_required): - self.section_name = section_name - self.options_required = options_required - self.msg = self._build_message() - - def _build_message(self): - out = [] - out.append(self.MESSAGE.format(section_name=self.section_name)) - for database_type, options in self.options_required.items(): - options = list_to_english(options) - out.append(self.MSG_TYPE.format(database_type=database_type, - options=options)) - out.append(self.MSG_END) - return ' '.join(out) - - -class UnknownDatabaseConnectionException(Exception): - """Exception raised for missing database connection parameters - - Attributes: - section: The requested section of the config file that can not - be found. + """ + 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) - """ - MESSAGE = "Unable to find the '{section}' section to create a "\ - "database connection." - def __init__(self, section): - self.section = section - self.msg = self._build_message() +class DatabaseConnectionValidatorUnknown(DatabaseConnectionValidator): - def _build_message(self): - return self.MESSAGE.format(section=self.section) + DB_TYPE = 'unkonwn' + REQUIRED = [] + PROTOCOLS = [] + def validate(self): + """Validates the connection. Returns true if valid. Throws + DodaiDatabaseConnectionConfigurationError on any errors -def list_to_english(data): - """Takes the input list and creates a string with each option - encased in single quotes and seperated by a comma with exception - of the last option which is prefixed with the word 'and' + """ - """ - if data: - if len(data) > 1: - out = [] - last = "'{0}'".format(data.pop()) - for row in data: - out.append("'{0}'".format(row)) - out = ', '.join(out) - return "{0} and {1}".format(out, last) - else: - return "'{0}'".format(data.pop()) + raise DatabaseConnectionException(self.name, self.validators) 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 @@ class Sa(object): + """Callable object that will wire up sqlalchemy engine and + session objects + Attributes: + create_engine: The sqlalchemy create_engine object + session_maker: The sqlalchemy session_maker object + result_object: The object that will be populated with + all the connection information along with + the sqlalchemy objects + + """ def __init__(self, create_engine, session_maker, result_object): self._create_engine = create_engine self._session_maker = session_maker self._result_object = result_object - def __call__(self, name, obj): + def __call__(self, section, name): db = self._result_object() db.name = name - db.protocol = obj.protocol - if hasattr(obj, 'protocol_extra') and obj.protocol_extra: - db.protocol_extra = obj.protocol_extra - if hasattr(obj, 'filename') and obj.filename: - db.filename = obj.filename - if hasattr(obj, 'port') and obj.port: - db.port = int(obj.port) - if hasattr(obj, 'database') and obj.database: - db.database = obj.database - if hasattr(obj, 'schema') and obj.schema: - db.schema = obj.schema - if hasattr(obj, 'username') and obj.username: - db.username = obj.username - if hasattr(obj, 'hostname') and obj.hostname: - db.hostname = obj.hostname - db.engine = self._build_engine(obj) + db.protocol = section['protocol'] + + if section.has_key('protocol_extra') and section['protocol_extra']: + db.protocol_extra = section['protocol_extra'] + + if section.has_key('filename') and section['filename']: + db.filename = section['filename'] + + if section.has_key('port') and section['port']: + db.port = int(section['port']) + + if section.has_key('database') and section['database']: + db.database = section['database'] + + if section.has_key('schema') and section['schema']: + db.schema = section['schema'] + + if section.has_key('username') and section['username']: + db.username = section['username'] + + if section.has_key('hostname') and section['hostname']: + db.hostname = section['hostname'] + + db.engine = self._build_engine(section) db.session = self._build_session(db.engine) return db @@ -49,25 +67,25 @@ class Sa(object): session = self._session_maker(bind=engine) return session() - def _build_connection_string(self, obj): + def _build_connection_string(self, section): out = [] - out.append('{db.protocol}') - if hasattr(obj, 'protocol_extra') and obj.protocol_extra: - out.append('+{db.protocol_extra}') + out.append('{section[protocol]}') + if section.has_key('protocol_extra') and section['protocol_extra']: + out.append('+{section[protocol_extra]}') out.append('://') - if hasattr(obj, 'filename') and obj.filename: - out.append('{db.filename}') + if section.has_key('filename') and section['filename']: + out.append('{section[filename]}') else: - out.append('{db.username}:{db.password}@') - out.append('{db.hostname}') - if hasattr(obj, 'port') and obj.port: - out.append(':{db.port}') - out.append('/{db.database}') + out.append('{section[username]}:{section[password]}@') + out.append('{section[hostname]}') + if section.has_key('port') and section['port']: + out.append(':{section[port]}') + out.append('/{section[database]}') out = ''.join(out) - out = out.format(db=obj) + out = out.format(section=section) return out - def _build_engine(self, obj): - connection_string = self._build_connection_string(obj) + def _build_engine(self, section): + connection_string = self._build_connection_string(section) db_obj = self._create_engine(connection_string) 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 @@ +# 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.tools import list_to_english + +class DodaiException(Exception): + + def _system_encoding(self): + # Returns the character encoding of the operating system + + encoding = sys.getdefaultencoding() + filesystem_encoding = sys.getfilesystemencoding() + if filesystem_encoding: + encoding = filesystem_encoding + return encoding + + +class HimoAsciiError(DodaiException): + """Exception raised when the Himo object can't convert a character + down to it's root character. + + Attributes: + char: The character that can't be converted down to ascii + """ + MESSAGE="Unable to convert the '{char}' character to ascii" + + def __init__(self, char): + self.char = char + self.msg = self._build_message() + + def _build_message(self): + encoding = self._system_encoding() + try: + char = self.char.encode(encoding) + except UnicodeEncodeError: + char = 'unichr({0})'.format(ord(self.char)) + return self.MESSAGE.format(char=self.char) + + def __str__(self): + return self.msg + +class DodaiDatabaseConnectionConfigurationError(DodaiException): + pass + + +class DatabaseEmptyOptionException(DodaiDatabaseConnectionConfigurationError): + """Exception raised for an empty option in the config + + Attributes: + section_name: The section of the config file that contains + the invalid protocol + option_name: The name of the empty option + + """ + MESSAGE = "In the '{section_name}' section, the '{option_name}' "\ + "was not set or is missing. Please set this option." + + def __init__(self, section_name, option_name): + self.section_name = section_name + self.option_name = option_name + self.msg = self._build_message() + + def _build_message(self): + return self.MESSAGE.format(section_name=self.section_name, + option_name=self.option_name) + + def __str__(self): + return self.msg + + +class DatabasePortException(DodaiDatabaseConnectionConfigurationError): + """Exception raised for invalid database port connection + + Attributes: + section_name: The section of the config file that contains + the invalid protocol + section_port: The port value that was listed in the + config file + + """ + MESSAGE = "In the '{section_name}' section, the port of "\ + "'{section_port}' is invalid. The port must be a "\ + "number between 1 and 65535" + + def __init__(self, section_name, section_port): + self.section_name = section_name + self.section_port = section_port + self.msg = self._build_message() + + def _build_message(self): + return self.MESSAGE.format(section_name=self.section_name, + section_port=self.section_port) + + def __str__(self): + return self.msg + + +class DatabaseHostnameException(DodaiDatabaseConnectionConfigurationError): + """Exception raised for invalid database hostname + + Attributes: + section_name: The section of the config file that contains + the invalid protocol + section_hostname: The hostname value that was listed in the + config file + + """ + MESSAGE = "In the '{section_name}' section, the hostname of "\ + "'{section_hostname}' is invalid. Please use a valid "\ + "hostname." + + MSG_NON = "In the '{section_name}' section, the hostname was "\ + "not set. Please set the hostname." + + def __init__(self, section_name, section_hostname): + self.section_name = section_name + self.section_hostname = section_hostname + self.msg = self._build_message() + + def _build_message(self): + if self.section_hostname: + return self.MESSAGE.format(section_name=self.section_name, + section_hostname=self.section_hostname) + else: + return self.MSG_NON.format(section_name=self.section_name) + + def __str__(self): + return self.msg + + +class DatabaseProtocolException(DodaiDatabaseConnectionConfigurationError): + """Exception raised for invalid database connection protocols + + Attributes: + section_name: The section of the config file that contains + the invalid protocol + section_protocol: The protocol value that was listed in the + config file + database_type: Usually 'server' or 'file' + protocols: List of valid protocols + + """ + MESSAGE = "In the '{section_name}' section, the protocol of "\ + "'{section_protocol}' is invalid. The valid protocols "\ + "for a '{database_type}' connection are: {protocols}" + + def __init__(self, section_name, section_protocol, database_type, + protocols): + self.section_name = section_name + self.section_protocol = section_protocol + self.database_type = database_type + self.protocols = protocols + self.msg = self._build_message() + + def _build_message(self): + protocols = list_to_english(self.protocols) + return self.MESSAGE.format(section_name=self.section_name, + section_protocol=self.section_protocol, + database_type=self.database_type, + protocols=protocols) + + def __str__(self): + return self.msg + + +class DatabaseConnectionException(DodaiDatabaseConnectionConfigurationError): + """Exception raised for missing database connection parameters + + Attributes: + section_name: The section of the config file that contains + the invalid connection information + options_required: A dictionary containing the database_type + as the key and a list of required options + as the value + + """ + MESSAGE = "The '{section_name}' section does not contain all of the "\ + "correct information needed to make a database connection." + MSG_TYPE = "To make a '{database_type}' connection please make sure "\ + "To have all of the following options: {options}." + MSG_END = "Please remember that the option names are case sensitive." + + def __init__(self, section_name, validators): + self.section_name = section_name + self.validators = validators + self.msg = self._build_message() + + def _build_message(self): + out = [] + out.append(self.MESSAGE.format(section_name=self.section_name)) + for validator in self.validators: + options = list_to_english(validator.REQUIRED) + out.append(self.MSG_TYPE.format(database_type=validator.DB_TYPE, + options=options)) + out.append(self.MSG_END) + return ' '.join(out) + + def __str__(self): + return self.msg + + +class UnknownDatabaseConnectionException( + DodaiDatabaseConnectionConfigurationError): + """Exception raised for missing database connection parameters + + Attributes: + section: The requested section of the config file that can not + be found. + + """ + MESSAGE = "Unable to find the '{section}' section to create a "\ + "database connection." + + def __init__(self, section): + self.section = section + self.msg = self._build_message() + + def _build_message(self): + return self.MESSAGE.format(section=self.section) + + def __str__(self): + 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(): path = os.path.dirname(os.path.abspath(sys.argv[0])) return os.path.join(path, 'config') - def config_directories(project_name): """Returns a list of possible project directories @@ -82,4 +81,19 @@ def config_directories(project_name): dirs.append(dir) return dirs +def list_to_english(data): + """Takes the input list and creates a string with each option + encased in single quotes and seperated by a comma with exception + of the last option which is prefixed with the word 'and' + """ + if data: + if len(data) > 1: + out = [] + last = "{0}".format(data.pop()) + for row in data: + out.append("{0}".format(row)) + out = ', '.join(out) + return "{0} and {1}".format(out, last) + else: + 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 from htmlentitydefs import name2codepoint from htmlentitydefs import codepoint2name from decimal import Decimal as D +from dodai.exception import HimoAsciiError class String2Himo(object): """ @@ -164,9 +165,7 @@ class Himo(unicode): if num: out.append(unichr(int(num, 16))) else: - print char - raise HimoAsciiError("Unable to convert 'u{0}' "\ - "character to ascii".format(ord(char))) + raise HimoAsciiError(char) return str(''.join(out)) 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 @@ +# 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 . + + +import sys +import os +import unittest +import ConfigParser +from dingus import Dingus +path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..')) +sys.path.append(path) +from dodai.config.databases import ConfigDatabases +from dodai.config.databases import DatabaseConnectionValidator +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 +from dodai.tools.himo import String2Himo +from dodai.config.sections import ConfigSections + + +class TestConfigDatabases(unittest.TestCase): + + SECTIONS = {} + + # valid server connection + SECTIONS['db_1'] = {} + SECTIONS['db_1']['protocol'] = 'postgresql' + SECTIONS['db_1']['hostname'] = '127.0.0.1' + SECTIONS['db_1']['username'] = 'test' + SECTIONS['db_1']['password'] = 'test' + SECTIONS['db_1']['port'] = '12345' + SECTIONS['db_1']['database'] = 'testing' + SECTIONS['db_1']['schema'] = 'foo' + + # valid file connection + SECTIONS['db_2'] = {} + SECTIONS['db_2']['protocol'] = 'sqlite' + SECTIONS['db_2']['filename'] = '/tmp/test' + + # missing protocol server + SECTIONS['db_3'] = {} + SECTIONS['db_3']['hostname'] = '127.0.0.1' + SECTIONS['db_3']['username'] = 'test' + SECTIONS['db_3']['password'] = 'test' + SECTIONS['db_3']['port'] = '12345' + SECTIONS['db_3']['database'] = 'testing' + + # missing protocol file + SECTIONS['db_4'] = {} + SECTIONS['db_4']['filename'] = '/tmp/test' + + # empty protocol server + SECTIONS['db_5'] = {} + SECTIONS['db_5']['protocol'] = '' + SECTIONS['db_5']['hostname'] = '127.0.0.1' + SECTIONS['db_5']['username'] = 'test' + SECTIONS['db_5']['password'] = 'test' + SECTIONS['db_5']['port'] = '12345' + SECTIONS['db_5']['database'] = 'testing' + + # empty protocol file + SECTIONS['db_6'] = {} + SECTIONS['db_6']['protocol'] = None + SECTIONS['db_6']['filename'] ='/tmp/test' + + # Missing port + SECTIONS['db_7'] = {} + SECTIONS['db_7']['protocol'] = 'postgresql' + SECTIONS['db_7']['protocol_extra'] = 'psycopg2' + SECTIONS['db_7']['hostname'] = '127.0.0.1' + SECTIONS['db_7']['username'] = 'test' + SECTIONS['db_7']['password'] = 'test' + SECTIONS['db_7']['database'] = 'testing' + + # Empty port + SECTIONS['db_8'] = {} + SECTIONS['db_8']['protocol'] ='postgresql' + SECTIONS['db_8']['hostname'] ='127.0.0.1' + SECTIONS['db_8']['username'] ='test' + SECTIONS['db_8']['password'] ='test' + SECTIONS['db_8']['port'] ='' + SECTIONS['db_8']['database'] ='testing' + + # Invalid port because it's a string + SECTIONS['db_9'] = {} + SECTIONS['db_9']['protocol'] ='postgresql' + SECTIONS['db_9']['hostname'] ='127.0.0.1' + SECTIONS['db_9']['username'] ='test' + SECTIONS['db_9']['password'] ='test' + SECTIONS['db_9']['port'] ='blah' + SECTIONS['db_9']['database'] ='testing' + + # Invalid port low + SECTIONS['db_10'] = {} + SECTIONS['db_10']['protocol'] ='postgresql' + SECTIONS['db_10']['hostname'] ='127.0.0.1' + SECTIONS['db_10']['username'] ='test' + SECTIONS['db_10']['password'] ='test' + SECTIONS['db_10']['port'] ='0' + SECTIONS['db_10']['database'] ='testing' + + # Invalid port high + SECTIONS['db_11'] = {} + SECTIONS['db_11']['protocol'] ='postgresql' + SECTIONS['db_11']['hostname'] ='127.0.0.1' + SECTIONS['db_11']['username'] ='test' + SECTIONS['db_11']['password'] ='test' + SECTIONS['db_11']['port'] ='655555' + SECTIONS['db_11']['database'] ='testing' + + # missing hostname + SECTIONS['db_12'] = {} + SECTIONS['db_12']['protocol'] ='postgresql' + SECTIONS['db_12']['username'] ='test' + SECTIONS['db_12']['password'] ='test' + SECTIONS['db_12']['port'] ='655' + SECTIONS['db_12']['database'] ='testing' + + # empty hostname + SECTIONS['db_13'] = {} + SECTIONS['db_13']['protocol'] ='postgresql' + SECTIONS['db_13']['hostname'] ='' + SECTIONS['db_13']['username'] ='test' + SECTIONS['db_13']['password'] ='test' + SECTIONS['db_13']['port'] ='655' + SECTIONS['db_13']['database'] ='testing' + + # missing username + SECTIONS['db_14'] = {} + SECTIONS['db_14']['protocol'] = 'postgresql' + SECTIONS['db_14']['hostname'] = '127.0.0.1' + SECTIONS['db_14']['password'] = 'test' + SECTIONS['db_14']['port'] = '12345' + SECTIONS['db_14']['database'] = 'testing' + SECTIONS['db_14']['schema'] = 'foo' + + # empty username + SECTIONS['db_15'] = {} + SECTIONS['db_15']['protocol'] = 'postgresql' + SECTIONS['db_15']['hostname'] = '127.0.0.1' + SECTIONS['db_15']['username'] = '' + SECTIONS['db_15']['password'] = 'test' + SECTIONS['db_15']['port'] = '12345' + SECTIONS['db_15']['database'] = 'testing' + + # missing password + SECTIONS['db_16'] = {} + SECTIONS['db_16']['protocol'] = 'postgresql' + SECTIONS['db_16']['hostname'] = '127.0.0.1' + SECTIONS['db_16']['username'] = 'test' + SECTIONS['db_16']['port'] = '12345' + SECTIONS['db_16']['database'] = 'testing' + + # empty password + SECTIONS['db_17'] = {} + SECTIONS['db_17']['protocol'] = 'postgresql' + SECTIONS['db_17']['hostname'] = '127.0.0.1' + SECTIONS['db_17']['username'] = 'test' + SECTIONS['db_17']['password'] = '' + SECTIONS['db_17']['port'] = '12345' + SECTIONS['db_17']['database'] = 'testing' + + # missing database + SECTIONS['db_18'] = {} + SECTIONS['db_18']['protocol'] = 'postgresql' + SECTIONS['db_18']['hostname'] = '127.0.0.1' + SECTIONS['db_18']['username'] = 'test' + SECTIONS['db_18']['password'] = 'test' + SECTIONS['db_18']['port'] = '12345' + + # empty database + SECTIONS['db_19'] = {} + SECTIONS['db_19']['protocol'] = 'postgresql' + SECTIONS['db_19']['hostname'] = '127.0.0.1' + SECTIONS['db_19']['username'] = 'test' + SECTIONS['db_19']['password'] = 'test' + SECTIONS['db_19']['port'] = '12345' + SECTIONS['db_19']['database'] = '' + + # missing filename + SECTIONS['db_20'] = {} + SECTIONS['db_20']['protocol'] = 'sqlite' + + # empty filename + SECTIONS['db_21'] = {} + SECTIONS['db_21']['protocol'] = 'sqlite' + SECTIONS['db_21']['filename'] = '' + + # not a database + SECTIONS['db_22'] = {} + SECTIONS['db_22']['foo'] = 'bar' + + # valid server test handler + SECTIONS['db_23'] = {} + SECTIONS['db_23']['handler'] = 'test_handler' + SECTIONS['db_23']['protocol'] = 'postgresql' + SECTIONS['db_23']['hostname'] = '127.0.0.1' + SECTIONS['db_23']['username'] = 'test' + SECTIONS['db_23']['password'] = 'test' + SECTIONS['db_23']['port'] = '12345' + SECTIONS['db_23']['database'] = 'testing' + + # invalid protocol + SECTIONS['db_24'] = {} + SECTIONS['db_24']['protocol'] = 'foo' + SECTIONS['db_24']['hostname'] = '127.0.0.1' + SECTIONS['db_24']['username'] = 'test' + SECTIONS['db_24']['password'] = 'test' + SECTIONS['db_24']['port'] = '12345' + SECTIONS['db_24']['database'] = 'testing' + + def setUp(self): + sa = Dingus('sa') + sa.name = 'test' + self.db = ConfigDatabases(sa, 'sa') + self.db.add(self.SECTIONS) + + def test_default_handler(self): + self.assertTrue('sa' in self.db._handlers) + val = self.db._handlers['sa'] + self.assertTrue('test' == val.name) + + def test_sections(self): + self.assertTrue(len(self.db.connections) == len(self.SECTIONS)) + + def test_load_server(self): + obj = self.db.load('db_1') + + def test_load_file(self): + obj = self.db.load('db_2') + + def test_unable_to_load(self): + self.failUnlessRaises(UnknownDatabaseConnectionException, + self.db.load, 'db_twenty_seven') + + def test_missing_protocol_server(self): + self.failUnlessRaises(DatabaseEmptyOptionException, + self.db.load, 'db_3') + + def test_missing_protocol_file(self): + self.failUnlessRaises(DatabaseEmptyOptionException, + self.db.load, 'db_4') + + def test_empty_protocol_server(self): + self.failUnlessRaises(DatabaseEmptyOptionException, + self.db.load, 'db_5') + + def test_empty_protocol_file(self): + self.failUnlessRaises(DatabaseEmptyOptionException, + self.db.load, 'db_6') + + def test_missing_port(self): + self.failUnlessRaises(DatabaseEmptyOptionException, + self.db.load, 'db_7') + + def test_empty_port(self): + self.failUnlessRaises(DatabaseEmptyOptionException, + self.db.load, 'db_8') + + def test_invalid_port_because_its_a_string(self): + self.failUnlessRaises(DatabasePortException, + self.db.load, 'db_9') + + def test_invalid_port_because_its_to_low(self): + self.failUnlessRaises(DatabasePortException, + self.db.load, 'db_10') + + def test_invalid_port_because_its_to_high(self): + self.failUnlessRaises(DatabasePortException, + self.db.load, 'db_11') + + def test_missing_hostname(self): + self.failUnlessRaises(DatabaseEmptyOptionException, + self.db.load, 'db_12') + + def test_empty_hostname(self): + self.failUnlessRaises(DatabaseEmptyOptionException, + self.db.load, 'db_13') + + def test_missing_username(self): + self.failUnlessRaises(DatabaseEmptyOptionException, + self.db.load, 'db_14') + + def test_empty_username(self): + self.failUnlessRaises(DatabaseEmptyOptionException, + self.db.load, 'db_15') + + def test_missing_password(self): + self.failUnlessRaises(DatabaseEmptyOptionException, + self.db.load, 'db_16') + + def test_empty_password(self): + self.failUnlessRaises(DatabaseEmptyOptionException, + self.db.load, 'db_17') + + def test_missing_database(self): + self.failUnlessRaises(DatabaseEmptyOptionException, + self.db.load, 'db_18') + + def test_empty_database(self): + self.failUnlessRaises(DatabaseEmptyOptionException, + self.db.load, 'db_19') + + def test_missing_filename(self): + self.failUnlessRaises(DatabaseEmptyOptionException, + self.db.load, 'db_20') + + def test_empty_filename(self): + self.failUnlessRaises(DatabaseEmptyOptionException, + self.db.load, 'db_21') + + def test_not_a_connection(self): + self.failUnlessRaises(DatabaseConnectionException, + self.db.load, 'db_22') + + def test_non_default_handler(self): + test_handler = Dingus('test_handler') + self.db.add_handler('test_handler', test_handler) + self.db.load('db_23') + + def test_invalid_protocol(self): + self.failUnlessRaises(DatabaseProtocolException, + 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 class TestSa(unittest.TestCase): + DB_ONE = {} + DB_ONE['protocol'] ='postgresql' + DB_ONE['protocol_extra'] ='psycopg2' + DB_ONE['hostname'] ='127.0.0.1' + DB_ONE['username'] ='test' + DB_ONE['password'] ='test' + DB_ONE['port'] ='12345' + DB_ONE['database'] ='testing' + DB_ONE['schema'] ='foo' + + DB_TWO = {} + DB_TWO['protocol'] ='sqlite' + DB_TWO['filename'] ='/tmp/test' + def setUp(self): self.create_engine = Dingus('create_engine') self.session_maker = Dingus('session_maker') @@ -32,33 +46,10 @@ class TestSa(unittest.TestCase): self.obj = Sa(self.create_engine, self.session_maker, self.result_object) - def test_sa_one(self): - name = 'test_db_two' - obj = DbOne() - db = self.obj(name, obj) - + name = 'test_db_one' + db = self.obj(self.DB_ONE, name) def test_sa_two(self): - name = 'test_db_six' - obj = DbTwo() - db = self.obj(name, obj) - - -class DbOne(object): - - def __init__(self): - self.protocol='postgresql' - self.protocol_extra='psycopg2' - self.hostname='127.0.0.1' - self.username='test' - self.password='test' - self.port='12345' - self.database='testing' - self.schema='foo' - -class DbTwo(object): - - def __init__(self): - self.protocol='sqlite' - self.filename='/tmp/test' + name = 'test_db_two' + db = self.obj(self.DB_TWO, name) -- cgit v1.2.3