blob: 71938f1dc2ff542ab0298cdf489614527cc3fb6a [file] [log] [blame]
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
#
# $Id: 3146e78057582e96c2d0ea9f276166badd9668fe $
"""
Configuration classes for *sqlcmd*.
COPYRIGHT AND LICENSE
Copyright © 2008 Brian M. Clapper
This is free software, released under the following BSD-like license:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. The end-user documentation included with the redistribution, if any,
must include the following acknowlegement:
This product includes software developed by Brian M. Clapper
(bmc@clapper.org, http://www.clapper.org/bmc/). That software is
copyright © 2008 Brian M. Clapper.
Alternately, this acknowlegement may appear in the software itself, if
and wherever such third-party acknowlegements normally appear.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL BRIAN M. CLAPPER BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
$Id: 3146e78057582e96c2d0ea9f276166badd9668fe $
"""
# ---------------------------------------------------------------------------
# Imports
# ---------------------------------------------------------------------------
import logging
import os
import re
import sys
from grizzled import db, system
from grizzled.config import Configuration
from sqlcmd.exception import *
# ---------------------------------------------------------------------------
# Exports
# ---------------------------------------------------------------------------
__all__ = ['SQLCmdConfig']
# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# Globals
# ---------------------------------------------------------------------------
log = logging.getLogger('sqlcmd.config')
# ---------------------------------------------------------------------------
# Classes
# ---------------------------------------------------------------------------
class DBInstanceConfigItem(object):
"""
Captures information about a database configuration item read from the
.sqlcmd file in the user's home directory.
"""
def __init__(self,
section,
aliases,
host,
database,
user,
password,
type,
port,
on_connect,
config_dir):
self.section = section
self.aliases = aliases
self.primary_alias = aliases[0]
self.host = host
self.database = database
self.user = user
self.password = password
self.port = port
self.db_type = type
self.on_connect = None
if on_connect:
if on_connect[0] == '~':
on_connect = os.path.expanduser(on_connect)
if not os.path.isabs(on_connect):
on_connect = os.path.abspath(os.path.join(config_dir,
on_connect))
if not os.access(on_connect, os.R_OK|os.F_OK):
log.warning('Database "%s": on-connect script "%s" either '
'does not exist, is not readable, or is not a '
'file.' % (database, on_connect))
on_connect = None
self.on_connect = on_connect
@property
def db_key(self):
port = self.port if self.port else ''
return '%s|%s|%s|%s' %\
(self.host, self.db_type, self.port, self.database)
def __hash__(self):
return self.primary_alias.__hash__()
def __str__(self):
return 'host=%s, db=%s, type=%s' %\
(self.host, self.database, self.db_type)
def __repr__(self):
return self.__str__()
class SQLCmdConfig(object):
""" Data from the .sqlcmd file in the user's home directory"""
def __init__(self, config_dir):
self.__config = {}
self.__config_dir = config_dir
self.settings = {}
def total_databases(self):
return len(self.__config.keys())
def load_file(self, path):
self.path = path
if os.access(path, os.R_OK|os.F_OK):
cfg = Configuration()
cfg.read(path)
handler_table = (
# section name regex function
# --------------------------------------------------
(re.compile('^db\.'), self.__config_db),
(re.compile('^driver\.'), self.__config_driver),
(re.compile('^settings$'), self.__set_vars),
)
for section in cfg.sections:
for regex, handler in handler_table:
if regex.match(section):
handler(cfg, section)
def __config_db(self, cfg, section):
primary_name = section[3:] # assumes it starts with 'db.'
if len(primary_name) == 0:
raise ConfigurationError('Bad database section name "%s"' % section)
aliases = cfg.getlist(section, 'aliases', sep=',', optional=True)
if aliases:
aliases = [primary_name] + [a.strip() for a in aliases]
else:
aliases = []
host = cfg.get(section, 'host', optional=True)
port = cfg.get(section, 'port', optional=True)
db_name = cfg.get(section, 'database')
user = cfg.get(section, 'user', optional=True)
password = cfg.get(section, 'password', optional=True)
db_type = cfg.get(section, 'type')
on_connect = cfg.get(section, 'onconnect', optional=True)
aliases += [db_name]
try:
cfg_item = DBInstanceConfigItem(section,
aliases,
host,
db_name,
user,
password,
db_type,
port,
on_connect,
self.__config_dir)
except ValueError, msg:
raise ConfigurationError(
'Configuration section [%s]: %s' % (section, msg)
)
for alias in aliases:
self.__config[alias] = cfg_item
def __config_driver(self, cfg, section):
class_name = cfg.get(section, 'class')
cls = system.class_for_name(class_name)
name = cfg.get(section, 'name')
db.add_driver(name, cls)
def __set_vars(self, cfg, section):
self.settings_section = section
for option in cfg.options(section):
self.settings[option] = cfg.get(section, option)
def add(self, section, alias, host, port, database, type, user, password):
try:
self.__config[alias]
raise ConfigurationError(
'Alias "%s" is already in the configuration' % alias
)
except KeyError:
try:
cfg = DBInstanceConfigItem(section,
[alias],
host,
database,
user,
password,
type,
port,
None,
self.__config_dir)
except ValueError, msg:
raise ConfigurationError(
'Error in configuration for alias "%s": %s' % (alias, msg)
)
self.__config[alias] = cfg
def get(self, alias):
return self.__config[alias]
def get_aliases(self):
aliases = self.__config.keys()
aliases.sort()
return aliases
def find_match(self, alias):
try:
config_item = self.__config[alias]
# Exact match. Use that one.
except KeyError:
# No match. Try to find one or more that come close.
matches = {}
for a in self.__config.keys():
if a.startswith(alias):
config_item = self.__config[a]
matches[config_item.db_key] = config_item
total_matches = len(matches)
if total_matches == 0:
raise ConfigurationError(
'No configuration item for database "%s"' % alias)
if total_matches > 1:
raise ConfigurationError(
'%d databases match partial alias "%s": %s' %\
(total_matches, alias, \
', '.join([cfg.section for cfg in matches.values()]))
)
config_item = matches.values()[0]
return config_item