Source code for dectest.config
configuration, via the .ini format, or via a pure python file. Each method
is implemented in a different class, but they both provide the same interface.
"""
import imp
import re
DEFAULTS = {
'testing': {
'testasrun': True,
'sideaffects': [],
'runtests': True,
'pretest': None,
'posttest': None,
}
}
class ConfigInterface():
"""
[docs] An interface that all classes implementing config methods should inherit.
Methods that implementing classes must provide the following
methods/properties. More information on the required behaviour of each
attribute can be found in their individual documentation in this class.
* store
* reload() (optional)
"""
@property
def store(self):
"""
[docs] This property must be implemented by all classes implementing the config
interface. It should be a mapping of section names to a mapping of
item names to values.
If that dosn't make sense, this may help:
>>> conf = DefaultConfig()
>>> conf.store
{'section': {'item1': True, 'item2': 3, 'item4': "foo"}}
"""
raise NotImplementedError()
def reload(self):
"""
[docs] This method can be implemented by implementing classes, though it is
optional. When called it should reload the config from it's source.
"""
return
def get(self, section_name, item_name):
"""
[docs] Returns the config value in the given section with the given name.
If the value does not exist, then we look in the DEFAULTS global, and
if we can't find it there, we raise a warning, and return None.
"""
values = self.store
if section_name not in values:
if section_name not in DEFAULTS:
# Then we have a program error
raise Warning(
"Config section " + section_name +
" does not exist in config or as default")
return
section = DEFAULTS[section_name]
else:
section = values[section_name]
if item_name not in section:
if item_name not in DEFAULTS[section_name]:
raise Warning(
"Config value {0}.{1}".format(
section_name, item_name) +
" does not exist in config or as a default")
return
return DEFAULTS[section_name][item_name]
return section[item_name]
def get_bool(self, section_name, item_name):
"""
[docs] Converts the config value into a boolean. If the value is a string, then
a number of values will be checked. The mappings are:
+-------+------+
| str | bool |
+=======+======+
|'yes' | |
+-------+ |
|'true' | True |
+-------+ |
|'y' | |
+-------+------+
|'no' | |
+-------+ |
|'false'| False|
+-------+ |
|'n' | |
+-------+------+
These values are not case sensitive. If the value is not a string or a
boolean, then `None` will be returned.
"""
value = self.get(section_name, item_name)
if isinstance(value, (str, unicode)):
bool_mapping = {
'yes': True,
'true': True,
'y': True,
'no': False,
'false': False,
'n': False,
}
if value.lower() in bool_mapping:
return bool_mapping[value.lower()]
elif isinstance(value, bool):
return value
else:
return None
def get_default(self, section_name, item_name):
"""
[docs] Returns the default value.
"""
return DEFAULTS[section_name][item_name]
def get_list(self, section_name, item_name):
"""
[docs] Returns the config value at the given section as a list.
If the value is not a string, then it will be converted into a list by
calling `list(value)`. If the value is a string, it will be treated as a
CSV, and split then returned.
If the value converted to a list is empty, or the value could not be
converted, then `None` will be returned.
"""
value = self.get(section_name, item_name)
if isinstance(value, (str, unicode)):
return value.split(",") or None
else:
try:
return list(value) or None
except TypeError:
return None
def get_python(self, name):
"""
[docs] Tries to return the object at the given name. If the object cannot be
found, then `None` is returned.
The name of an object is it's module name and then the identifier that
you would have to type to access the object. For example, in the module
`dectest.suite`, there is the class `TestSuite`. The name for that item
would be `dectest.suite.TestSuite`.
If the `name` argument is not a `str` or `unicode`, then it will just be
returned.
"""
if not isinstance(name, (str, unicode)):
return name
path = name.split(".")
if len(path) < 2:
raise Warning("Invalid python path: " + name)
return
module_path = path[:-1]
attribute_path = [path[-1]]
module = self._import_module('.'.join(module_path))
while not module:
if not path:
raise Warning("Could not find module for python path " +
name)
return
attribute_path.insert(0, module_path.pop())
module = self._import_module('.'.join(module_path))
current = module
for attribute in attribute_path:
current = getattr(current, attribute)
if not current:
raise Warning("Could not find attribute for python path " +
name)
return
return current
def _import_module(self, name):
"""
Imports an arbitrarily named module, and returns the module object if
the import succeeds, otherwise returns `None`.
"""
try:
module = __import__(name, fromlist=name.split(".")[:-1])
except ImportError:
return
else:
return module
def __getattr__(self, section_name):
"""
Allow us to do `config.section.item` instead of
`config.get("section", "item")`
"""
if re.match("__(\w*)__", section_name):
raise AttributeError()
class Section():
"""
Just a little class to wrap a ConfigInterface.get call.
"""
def __getattr__(_, item_name):
"""
Return the actual value.
"""
return self.get(section_name, item_name)
return Section()
class DefaultConfig(ConfigInterface):
"""
[docs] A config class that only provides the default values for every setting. Use
this when no config file is provided.
"""
store = DEFAULTS
class PythonFileConfig(ConfigInterface):
"""
[docs] A config class that loads the dictionary from the global namespace of any
given python file.
The file should define a class for each section, with attributes for each
value. The class names and attribute names are not case sensitive
For example::
# dectest_config.py
class section1:
item1 = 0
item2 = "one"
item3 = True
class section2:
foo = "bar"
"""
def __init__(self, filename):
"""
Set the filename, and load it.
"""
self.filename = filename
# We set this as if the reload method fails, store may be unbound
self.store = {}
self.reload()
def reload(self):
"""
[docs] Reloads the configuration file, and populates the store attribute.
"""
try:
module = imp.load_source('config', self.filename)
except Exception as e:
self.store = DEFAULTS
raise Warning("Could not load configuration file " + self.filename +
", raised exception " + str(e))
return
self.store = {}
for s_name, s_value in module.__dict__.items():
if s_name.startswith("_") or not type(s_value) == type:
continue
self.store[s_name] = {}
for i_name, i_value in s_value.__dict__.items():
self.store[s_name][i_name] = i_value
class DictConfig(ConfigInterface):
"""
[docs] Provides a simple way of configuring dectest via a dictionary.
Takes a dictionary as it's only argument.
"""
def __init__(self, dictionary):
self.store = dictionary