# -*- coding: utf-8 -*-
############################################################################
#
# Copyright © 2014 OnlineGroups.net and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
############################################################################
from __future__ import absolute_import, unicode_literals
from os.path import isfile, join as path_join
import sys
if (sys.version_info >= (3, )):
from configparser import SafeConfigParser
else:
from ConfigParser import SafeConfigParser # lint:ok
from logging import getLogger
log = getLogger('gs.config')
from .errors import (ConfigPathError, ConfigFileError, ConfigSetError,
ConfigNoSchemaError, ConfigNoOptionError,
ConfigNoSectionError, ConfigConvertError)
from .errors import ConfigError # To make an import happy # lint:ok
try:
from App.config import getConfiguration
from zope.globalrequest import getRequest
USINGZOPE = True
except ImportError:
USINGZOPE = False
getConfiguration = getRequest = None # lint:ok
def bool_(val):
'''Turn the plain-text ways of writing Boolean values into a Boolean
value.'''
if val.lower() in ('true', 'yes', 'on'):
val = True
elif val.lower() in ('false', 'no', 'off'):
val = False
else:
raise ValueError("Not a bool")
return val
[docs]def getInstanceId():
'''Get the ID of the current instance.
:returns: The ID the of the instance, or ``default``
:rtype: ``str``
It can be useful to have multiple *instances* running on one server, but
configured seperately. The ``getInstanceId`` function gets the ID of the
current instance by looking it up in the ``HTTP_INSTANCEID`` property of
the current Zope HTTP request. If Zope (and HTTP) are not being used
then ``default`` is returned.
This function is defined in :mod:`gs.config` mostly out of convinience, as
GroupServer normally organises its configuration into *instances.*
'''
instance_id = ''
if USINGZOPE:
request = getRequest()
if request:
instance_id = request.get('HTTP_INSTANCEID', '')
retval = instance_id if instance_id else 'default'
return retval
[docs]class Config(object):
'''The configuration.
:method: Config(configset, configpath=None)
:param str configset: The name of the configuration set to read.
:param str configpath: The path to the configration file. If ``None`` *and*
Zope is being used then ``etc/gsconfig.ini`` is read
from instance directory.
:raises ConfigPathError: No path could be found.
:raises ConfigFileError: The configration file could not be read.
:raises ConfigSetError: The configuration file does not contain
``configset``.
The actual parsing of the configuration file is done by the
:mod:`ConfigParser` module.
'''
schema = {}
def __init__(self, configset, configpath=None):
'''Initialise the configuration'''
if USINGZOPE and not configpath:
# again, try and figure out from our groupserver config,
# otherwise abort.
cfg = getConfiguration()
configpath = path_join(cfg.instancehome, 'etc/gsconfig.ini')
elif configpath is None:
msg = "No configpath set, unable to read configfile"
raise ConfigPathError(msg)
if not isfile(configpath):
m = 'Could not read the configuration, as the configuration '\
'file "{0}" does not exist.'
msg = m.format(configpath)
raise ConfigFileError(msg)
log.info("Reading the config file <%s>" % configpath)
self.parser = SafeConfigParser()
self.parser.read(configpath)
if not self.parser.has_section('config-' + configset):
m = 'No configuration set "{0}" defined in file "{1}".'
msg = m.format(configset, configpath)
raise ConfigSetError(msg)
self.configset = configset
[docs] def set_schema(self, configtype, schema):
'''Set the schema that is used for parsing the options.
:param str configtype: The identifier for the section of the configration.
:param dict schema: The schema for the section, as ``optionId: type`` pars.
When the value for an option in a section is retrieved its *type* is coerced
from a string to one of the types passed in as :param:`schema`.
**Example**::
s = {'station': str,
'frequency': int,}
conf.set_schema('radio', s)
'''
if not isinstance(schema, dict):
m = "Schema must be a dictionary of converters, not a {0}"
msg = m.format(type(schema))
raise ValueError(msg)
self.schema[configtype] = schema
[docs] def get_schema(self, configtype):
'''Get the schema that is currently set for a section.
:param str confgitype: The identifier for the section.
:returns: The schema that is currently set for the section.
:rtype: A ``dict``, containing ``optionId: type`` pairs.
:raises ConfigNoSchemaError: No schema with the identifer ``configtype``
found.
'''
if configtype not in self.schema:
m = 'No schema defined for configuration type "{0}".'
msg = m.format(configtype)
raise ConfigNoSchemaError(msg)
return self.schema[configtype]
[docs] def keys(self):
'''Get the list of sections that are currently defined.
:returns: A list of sections for the configuration set.
:rtype: ``list``'''
return self.parser.options('config-' + self.configset)
[docs] def get(self, configtype, strict=True):
'''Get the values defined in a section
:param str configtype: The ID for the section to retrieve.
:param bool strict: If ``True`` (the default) then a ``ConfigNoOption``
error is raised when an option is present in the configuration file
but absent from the schema. When ``False`` the option is ignored.
:returns: The values for all options in the provided section.
:rtype: A ``dict`` containing ``optionId: value`` pairs. The values are
coerced using the schema set by :meth:`set_schema`.
:raises ConfigNoSectionError: No section for the ID in ``configtype``
exists.
:raises ConfigNoOption: An option was present in the configuration file but
absent from the section.
:raises ConfigConvertError: An option could not be coerced.
**Example**::
smtpConfig = config.get('smtp')
'''
if configtype not in self.keys():
m = 'No configuration defined for "{0}" in set "{1}".'
msg = m.format(configtype, self.configset)
raise ConfigNoSectionError(msg)
secval = self.parser.get('config-' + self.configset, configtype)
if not self.parser.has_section(configtype + '-' + secval):
m = 'No configuration section for type "{0}" with value "{1}".'
msg = m.format(configtype, secval)
raise ConfigNoSectionError(msg)
schema = self.get_schema(configtype)
retval = {}
for option in self.parser.options(configtype + '-' + secval):
if (option not in schema):
if strict:
m = 'No option "{0}" defined in schema for "{1}".'
msg = m.format(option, configtype)
raise ConfigNoOptionError(msg)
else:
continue
val = self.parser.get(configtype + '-' + secval, option)
try:
val = schema[option](val)
except:
m = 'Unable to convert option "{0}" value "{1}" using "{2}'
msg = m.format(option, val, schema[option])
raise ConfigConvertError(msg)
retval[option] = val
return retval