зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1255450 - [mach] Replace ConfigProvider class with config_settings attribute, r=gps
Defining settings was a little complicated. First it required overriding a '_register_settings' method, and then it required making N calls to a second 'register_setting' method within that. This patch simplifies the process of defining settings by only requiring a 'config_settings' attribute. This attribute should be a list of tuples: [ ('<section>.<option>', '<type>', <default>, set(<choices)), ] `default` and `choices` are optional. Alternatively, 'config_settings' can be a callable that returns a list of tuples in the same format. This still allows for greater flexibility for more advanced cases. MozReview-Commit-ID: F4DTTNJdJsa --HG-- extra : rebase_source : e3dd455ba559cd3992c9c1b3eaf021c9e0707cc1
This commit is contained in:
Родитель
de4c3d11fe
Коммит
5167efa8e4
|
@ -0,0 +1,91 @@
|
|||
.. _mach_settings:
|
||||
|
||||
========
|
||||
Settings
|
||||
========
|
||||
|
||||
Mach can read settings in from a set of configuration files. These
|
||||
configuration files are either named ``mach.ini`` or ``.machrc`` and
|
||||
are specified by the bootstrap script. In mozilla-central, these files
|
||||
can live in ``~/.mozbuild`` and/or ``topsrcdir``.
|
||||
|
||||
Settings can be specified anywhere, and used both by mach core or
|
||||
individual commands.
|
||||
|
||||
|
||||
Defining Settings
|
||||
=================
|
||||
|
||||
Settings need to be explicitly defined, along with their type,
|
||||
otherwise mach will throw when loading the configuration files.
|
||||
|
||||
To define settings, use the :func:`~decorators.SettingsProvider`
|
||||
decorator in an existing mach command module. E.g:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from mach.decorators import SettingsProvider
|
||||
|
||||
@SettingsProvider
|
||||
class ArbitraryClassName(object):
|
||||
config_settings = [
|
||||
('foo.bar', 'string'),
|
||||
('foo.baz', 'int', 0, set([0,1,2])),
|
||||
]
|
||||
|
||||
``@SettingsProvider``'s must specify a variable called ``config_settings``
|
||||
that returns a list of tuples. Alternatively, it can specify a function
|
||||
called ``config_settings`` that returns a list of tuples.
|
||||
|
||||
Each tuple is of the form:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
('<section>.<option>', '<type>', default, extra)
|
||||
|
||||
``type`` is a string and can be one of:
|
||||
string, boolean, int, pos_int, path
|
||||
|
||||
``default`` is optional, and provides a default value in case none was
|
||||
specified by any of the configuration files.
|
||||
|
||||
``extra`` is also optional and is a dict containing additional key/value
|
||||
pairs to add to the setting's metadata. The following keys may be specified
|
||||
in the ``extra`` dict:
|
||||
* ``choices`` - A set of allowed values for the setting.
|
||||
|
||||
|
||||
Accessing Settings
|
||||
==================
|
||||
|
||||
Now that the settings are defined and documented, they're accessible from
|
||||
individual mach commands if the command receives a context in its constructor.
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from mach.decorators import (
|
||||
Command,
|
||||
CommandProvider,
|
||||
SettingsProvider,
|
||||
)
|
||||
|
||||
@SettingsProvider
|
||||
class ExampleSettings(object):
|
||||
config_settings = [
|
||||
('a.b', 'string', 'default'),
|
||||
('foo.bar', 'string'),
|
||||
('foo.baz', 'int', 0, {'choices': set([0,1,2])}),
|
||||
]
|
||||
|
||||
@CommandProvider
|
||||
class Commands(object):
|
||||
def __init__(self, context):
|
||||
self.settings = context.settings
|
||||
|
||||
@Command('command', category='misc',
|
||||
description='Prints a setting')
|
||||
def command(self):
|
||||
print(self.settings.a.b)
|
||||
for option in self.settings.foo:
|
||||
print(self.settings.foo[option])
|
|
@ -31,15 +31,20 @@ import collections
|
|||
import gettext
|
||||
import os
|
||||
import sys
|
||||
from functools import wraps
|
||||
|
||||
if sys.version_info[0] == 3:
|
||||
from configparser import RawConfigParser
|
||||
from configparser import RawConfigParser, NoSectionError
|
||||
str_type = str
|
||||
else:
|
||||
from ConfigParser import RawConfigParser
|
||||
from ConfigParser import RawConfigParser, NoSectionError
|
||||
str_type = basestring
|
||||
|
||||
|
||||
class ConfigException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ConfigType(object):
|
||||
"""Abstract base class for config values."""
|
||||
|
||||
|
@ -128,122 +133,31 @@ class PathType(StringType):
|
|||
return config.get(section, option)
|
||||
|
||||
|
||||
class AbsolutePathType(PathType):
|
||||
@staticmethod
|
||||
def validate(value):
|
||||
if not isinstance(value, str_type):
|
||||
raise TypeError()
|
||||
|
||||
if not os.path.isabs(value):
|
||||
raise ValueError()
|
||||
|
||||
|
||||
class RelativePathType(PathType):
|
||||
@staticmethod
|
||||
def validate(value):
|
||||
if not isinstance(value, str_type):
|
||||
raise TypeError()
|
||||
|
||||
if os.path.isabs(value):
|
||||
raise ValueError()
|
||||
TYPE_CLASSES = {
|
||||
'string': StringType,
|
||||
'boolean': BooleanType,
|
||||
'int': IntegerType,
|
||||
'pos_int': PositiveIntegerType,
|
||||
'path': PathType,
|
||||
}
|
||||
|
||||
|
||||
class DefaultValue(object):
|
||||
pass
|
||||
|
||||
|
||||
class ConfigProvider(object):
|
||||
"""Abstract base class for an object providing config settings.
|
||||
|
||||
Classes implementing this interface expose configurable settings. Settings
|
||||
are typically only relevant to that component itself. But, nothing says
|
||||
settings can't be shared by multiple components.
|
||||
def reraise_attribute_error(func):
|
||||
"""Used to make sure __getattr__ wrappers around __getitem__
|
||||
raise AttributeError instead of KeyError.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def register_settings(cls):
|
||||
"""Registers config settings.
|
||||
|
||||
This is called automatically. Child classes should likely not touch it.
|
||||
See _register_settings() instead.
|
||||
"""
|
||||
if hasattr(cls, '_settings_registered'):
|
||||
return
|
||||
|
||||
cls._settings_registered = True
|
||||
|
||||
cls.config_settings = {}
|
||||
|
||||
ourdir = os.path.dirname(__file__)
|
||||
cls.config_settings_locale_directory = os.path.join(ourdir, 'locale')
|
||||
|
||||
cls._register_settings()
|
||||
|
||||
@classmethod
|
||||
def _register_settings(cls):
|
||||
"""The actual implementation of register_settings().
|
||||
|
||||
This is what child classes should implement. They should not touch
|
||||
register_settings().
|
||||
|
||||
Implementations typically make 1 or more calls to _register_setting().
|
||||
"""
|
||||
raise NotImplemented('%s must implement _register_settings.' %
|
||||
__name__)
|
||||
|
||||
@classmethod
|
||||
def register_setting(cls, section, option, type_cls, default=DefaultValue,
|
||||
choices=None, domain=None):
|
||||
"""Register a config setting with this type.
|
||||
|
||||
This is a convenience method to populate available settings. It is
|
||||
typically called in the class's _register_settings() implementation.
|
||||
|
||||
Each setting must have:
|
||||
|
||||
section -- str section to which the setting belongs. This is how
|
||||
settings are grouped.
|
||||
|
||||
option -- str id for the setting. This must be unique within the
|
||||
section it appears.
|
||||
|
||||
type -- a ConfigType-derived type defining the type of the setting.
|
||||
|
||||
Each setting has the following optional parameters:
|
||||
|
||||
default -- The default value for the setting. If None (the default)
|
||||
there is no default.
|
||||
|
||||
choices -- A set of values this setting can hold. Values not in
|
||||
this set are invalid.
|
||||
|
||||
domain -- Translation domain for this setting. By default, the
|
||||
domain is the same as the section name.
|
||||
"""
|
||||
if not section in cls.config_settings:
|
||||
cls.config_settings[section] = {}
|
||||
|
||||
if option in cls.config_settings[section]:
|
||||
raise Exception('Setting has already been registered: %s.%s' % (
|
||||
section, option))
|
||||
|
||||
domain = domain if domain is not None else section
|
||||
|
||||
meta = {
|
||||
'short': '%s.short' % option,
|
||||
'full': '%s.full' % option,
|
||||
'type_cls': type_cls,
|
||||
'domain': domain,
|
||||
'localedir': cls.config_settings_locale_directory,
|
||||
}
|
||||
|
||||
if default != DefaultValue:
|
||||
meta['default'] = default
|
||||
|
||||
if choices is not None:
|
||||
meta['choices'] = choices
|
||||
|
||||
cls.config_settings[section][option] = meta
|
||||
@wraps(func)
|
||||
def _(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except KeyError:
|
||||
exc_class, exc, tb = sys.exc_info()
|
||||
raise AttributeError().__class__, exc, tb
|
||||
return _
|
||||
|
||||
|
||||
class ConfigSettings(collections.Mapping):
|
||||
|
@ -299,38 +213,53 @@ class ConfigSettings(collections.Mapping):
|
|||
object.__setattr__(self, '_name', name)
|
||||
object.__setattr__(self, '_settings', settings)
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
try:
|
||||
return self._config.options(self._name)
|
||||
except NoSectionError:
|
||||
return []
|
||||
|
||||
def _validate(self, option, value):
|
||||
if option not in self._settings:
|
||||
raise KeyError('Option not registered with provider: %s' % option)
|
||||
|
||||
meta = self._settings[option]
|
||||
meta['type_cls'].validate(value)
|
||||
|
||||
if 'choices' in meta and value not in meta['choices']:
|
||||
raise ValueError("Value '%s' must be one of: %s" % (
|
||||
value, ', '.join(sorted(meta['choices']))))
|
||||
|
||||
# MutableMapping interface
|
||||
def __len__(self):
|
||||
return len(self._settings)
|
||||
return len(self.options)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._settings.keys())
|
||||
return iter(self.options)
|
||||
|
||||
def __contains__(self, k):
|
||||
return k in self._settings
|
||||
return self._config.has_option(self._name, k)
|
||||
|
||||
def __getitem__(self, k):
|
||||
if k not in self._settings:
|
||||
raise KeyError('Option not registered with provider: %s' % k)
|
||||
|
||||
meta = self._settings[k]
|
||||
|
||||
if self._config.has_option(self._name, k):
|
||||
return meta['type_cls'].from_config(self._config, self._name, k)
|
||||
v = meta['type_cls'].from_config(self._config, self._name, k)
|
||||
else:
|
||||
v = meta.get('default', DefaultValue)
|
||||
|
||||
if not 'default' in meta:
|
||||
if v == DefaultValue:
|
||||
raise KeyError('No default value registered: %s' % k)
|
||||
|
||||
return meta['default']
|
||||
self._validate(k, v)
|
||||
return v
|
||||
|
||||
|
||||
def __setitem__(self, k, v):
|
||||
if k not in self._settings:
|
||||
raise KeyError('Option not registered with provider: %s' % k)
|
||||
self._validate(k, v)
|
||||
|
||||
meta = self._settings[k]
|
||||
|
||||
meta['type_cls'].validate(v)
|
||||
|
||||
if not self._config.has_section(self._name):
|
||||
self._config.add_section(self._name)
|
||||
|
||||
|
@ -343,12 +272,15 @@ class ConfigSettings(collections.Mapping):
|
|||
if not len(self._config.options(self._name)):
|
||||
self._config.remove_section(self._name)
|
||||
|
||||
@reraise_attribute_error
|
||||
def __getattr__(self, k):
|
||||
return self.__getitem__(k)
|
||||
|
||||
@reraise_attribute_error
|
||||
def __setattr__(self, k, v):
|
||||
self.__setitem__(k, v)
|
||||
|
||||
@reraise_attribute_error
|
||||
def __delattr__(self, k):
|
||||
self.__delitem__(k)
|
||||
|
||||
|
@ -392,31 +324,75 @@ class ConfigSettings(collections.Mapping):
|
|||
"""Write the config to a file object."""
|
||||
self._config.write(fh)
|
||||
|
||||
def validate(self):
|
||||
"""Ensure that the current config passes validation.
|
||||
@classmethod
|
||||
def _format_metadata(cls, provider, section, option, type_cls,
|
||||
default=DefaultValue, extra=None):
|
||||
"""Formats and returns the metadata for a setting.
|
||||
|
||||
This is a generator of tuples describing any validation errors. The
|
||||
elements of the tuple are:
|
||||
Each setting must have:
|
||||
|
||||
(bool) True if error is fatal. False if just a warning.
|
||||
(str) Type of validation issue. Can be one of ('unknown-section',
|
||||
'missing-required', 'type-error')
|
||||
section -- str section to which the setting belongs. This is how
|
||||
settings are grouped.
|
||||
|
||||
option -- str id for the setting. This must be unique within the
|
||||
section it appears.
|
||||
|
||||
type -- a ConfigType-derived type defining the type of the setting.
|
||||
|
||||
Each setting has the following optional parameters:
|
||||
|
||||
default -- The default value for the setting. If None (the default)
|
||||
there is no default.
|
||||
|
||||
extra -- A dict of additional key/value pairs to add to the
|
||||
setting metadata.
|
||||
"""
|
||||
if isinstance(type_cls, basestring):
|
||||
type_cls = TYPE_CLASSES[type_cls]
|
||||
|
||||
meta = {
|
||||
'short': '%s.short' % option,
|
||||
'full': '%s.full' % option,
|
||||
'type_cls': type_cls,
|
||||
'domain': section,
|
||||
'localedir': provider.config_settings_locale_directory,
|
||||
}
|
||||
|
||||
if default != DefaultValue:
|
||||
meta['default'] = default
|
||||
|
||||
if extra:
|
||||
meta.update(extra)
|
||||
|
||||
return meta
|
||||
|
||||
def register_provider(self, provider):
|
||||
"""Register a ConfigProvider with this settings interface."""
|
||||
"""Register a SettingsProvider with this settings interface."""
|
||||
|
||||
if self._finalized:
|
||||
raise Exception('Providers cannot be registered after finalized.')
|
||||
raise ConfigException('Providers cannot be registered after finalized.')
|
||||
|
||||
provider.register_settings()
|
||||
settings = provider.config_settings
|
||||
if callable(settings):
|
||||
settings = settings()
|
||||
|
||||
for section_name, settings in provider.config_settings.items():
|
||||
config_settings = collections.defaultdict(dict)
|
||||
for setting in settings:
|
||||
section, option = setting[0].split('.')
|
||||
|
||||
if option in config_settings[section]:
|
||||
raise ConfigException('Setting has already been registered: %s.%s' % (
|
||||
section, option))
|
||||
|
||||
meta = self._format_metadata(provider, section, option, *setting[1:])
|
||||
config_settings[section][option] = meta
|
||||
|
||||
for section_name, settings in config_settings.items():
|
||||
section = self._settings.get(section_name, {})
|
||||
|
||||
for k, v in settings.items():
|
||||
if k in section:
|
||||
raise Exception('Setting already registered: %s.%s' %
|
||||
raise ConfigException('Setting already registered: %s.%s' %
|
||||
section_name, k)
|
||||
|
||||
section[k] = v
|
||||
|
@ -484,5 +460,6 @@ class ConfigSettings(collections.Mapping):
|
|||
return self._sections[k]
|
||||
|
||||
# Allow attribute access because it looks nice.
|
||||
@reraise_attribute_error
|
||||
def __getattr__(self, k):
|
||||
return self.__getitem__(k)
|
||||
|
|
|
@ -7,10 +7,10 @@ from __future__ import absolute_import, unicode_literals
|
|||
import argparse
|
||||
import collections
|
||||
import inspect
|
||||
import os
|
||||
import types
|
||||
|
||||
from .base import MachError
|
||||
from .config import ConfigProvider
|
||||
from .registrar import Registrar
|
||||
|
||||
|
||||
|
@ -336,14 +336,18 @@ def SettingsProvider(cls):
|
|||
When this decorator is encountered, the underlying class will automatically
|
||||
be registered with the Mach registrar and will (likely) be hooked up to the
|
||||
mach driver.
|
||||
|
||||
This decorator is only allowed on mach.config.ConfigProvider classes.
|
||||
"""
|
||||
if not issubclass(cls, ConfigProvider):
|
||||
raise MachError('@SettingsProvider encountered on class that does ' +
|
||||
'not derived from mach.config.ConfigProvider.')
|
||||
if not hasattr(cls, 'config_settings'):
|
||||
raise MachError('@SettingsProvider must contain a config_settings attribute. It '
|
||||
'may either be a list of tuples, or a callable that returns a list '
|
||||
'of tuples. Each tuple must be of the form:\n'
|
||||
'(<section>.<option>, <type_cls>, <default>, <choices>)\n'
|
||||
'as specified by ConfigSettings._format_metadata.')
|
||||
|
||||
if not hasattr(cls, 'config_settings_locale_directory'):
|
||||
cls_dir = os.path.dirname(inspect.getfile(cls))
|
||||
cls.config_settings_locale_directory = os.path.join(cls_dir, 'locale')
|
||||
|
||||
Registrar.register_settings_provider(cls)
|
||||
|
||||
return cls
|
||||
|
||||
|
|
|
@ -9,16 +9,15 @@ import unittest
|
|||
from mozfile.mozfile import NamedTemporaryFile
|
||||
|
||||
from mach.config import (
|
||||
AbsolutePathType,
|
||||
BooleanType,
|
||||
ConfigProvider,
|
||||
ConfigException,
|
||||
ConfigSettings,
|
||||
IntegerType,
|
||||
PathType,
|
||||
PositiveIntegerType,
|
||||
RelativePathType,
|
||||
StringType,
|
||||
)
|
||||
from mach.decorators import SettingsProvider
|
||||
|
||||
from mozunit import main
|
||||
|
||||
|
@ -41,48 +40,53 @@ CONFIG2 = r"""
|
|||
bar = value2
|
||||
"""
|
||||
|
||||
class Provider1(ConfigProvider):
|
||||
@SettingsProvider
|
||||
class Provider1(object):
|
||||
config_settings = [
|
||||
('foo.bar', StringType),
|
||||
('foo.baz', PathType),
|
||||
]
|
||||
|
||||
|
||||
@SettingsProvider
|
||||
class ProviderDuplicate(object):
|
||||
config_settings = [
|
||||
('dupesect.foo', StringType),
|
||||
('dupesect.foo', StringType),
|
||||
]
|
||||
|
||||
|
||||
@SettingsProvider
|
||||
class Provider2(object):
|
||||
config_settings = [
|
||||
('a.string', StringType),
|
||||
('a.boolean', BooleanType),
|
||||
('a.pos_int', PositiveIntegerType),
|
||||
('a.int', IntegerType),
|
||||
('a.path', PathType),
|
||||
]
|
||||
|
||||
|
||||
@SettingsProvider
|
||||
class Provider3(object):
|
||||
@classmethod
|
||||
def _register_settings(cls):
|
||||
cls.register_setting('foo', 'bar', StringType)
|
||||
cls.register_setting('foo', 'baz', AbsolutePathType)
|
||||
|
||||
Provider1.register_settings()
|
||||
|
||||
class ProviderDuplicate(ConfigProvider):
|
||||
@classmethod
|
||||
def _register_settings(cls):
|
||||
cls.register_setting('dupesect', 'foo', StringType)
|
||||
cls.register_setting('dupesect', 'foo', StringType)
|
||||
|
||||
class TestConfigProvider(unittest.TestCase):
|
||||
def test_construct(self):
|
||||
s = Provider1.config_settings
|
||||
|
||||
self.assertEqual(len(s), 1)
|
||||
self.assertIn('foo', s)
|
||||
|
||||
self.assertEqual(len(s['foo']), 2)
|
||||
self.assertIn('bar', s['foo'])
|
||||
self.assertIn('baz', s['foo'])
|
||||
|
||||
def test_duplicate_option(self):
|
||||
with self.assertRaises(Exception):
|
||||
ProviderDuplicate.register_settings()
|
||||
def config_settings(cls):
|
||||
return [
|
||||
('a.string', 'string'),
|
||||
('a.boolean', 'boolean'),
|
||||
('a.pos_int', 'pos_int'),
|
||||
('a.int', 'int'),
|
||||
('a.path', 'path'),
|
||||
]
|
||||
|
||||
|
||||
class Provider2(ConfigProvider):
|
||||
@classmethod
|
||||
def _register_settings(cls):
|
||||
cls.register_setting('a', 'string', StringType)
|
||||
cls.register_setting('a', 'boolean', BooleanType)
|
||||
cls.register_setting('a', 'pos_int', PositiveIntegerType)
|
||||
cls.register_setting('a', 'int', IntegerType)
|
||||
cls.register_setting('a', 'abs_path', AbsolutePathType)
|
||||
cls.register_setting('a', 'rel_path', RelativePathType)
|
||||
cls.register_setting('a', 'path', PathType)
|
||||
@SettingsProvider
|
||||
class Provider4(object):
|
||||
config_settings = [
|
||||
('foo.abc', StringType, 'a', {'choices': set('abc')}),
|
||||
('foo.xyz', StringType, 'w', {'choices': set('xyz')}),
|
||||
]
|
||||
|
||||
Provider2.register_settings()
|
||||
|
||||
class TestConfigSettings(unittest.TestCase):
|
||||
def test_empty(self):
|
||||
|
@ -91,6 +95,12 @@ class TestConfigSettings(unittest.TestCase):
|
|||
self.assertEqual(len(s), 0)
|
||||
self.assertNotIn('foo', s)
|
||||
|
||||
def test_duplicate_option(self):
|
||||
s = ConfigSettings()
|
||||
|
||||
with self.assertRaises(ConfigException):
|
||||
s.register_provider(ProviderDuplicate)
|
||||
|
||||
def test_simple(self):
|
||||
s = ConfigSettings()
|
||||
s.register_provider(Provider1)
|
||||
|
@ -101,14 +111,18 @@ class TestConfigSettings(unittest.TestCase):
|
|||
foo = s['foo']
|
||||
foo = s.foo
|
||||
|
||||
self.assertEqual(len(foo), 2)
|
||||
self.assertEqual(len(foo), 0)
|
||||
self.assertEqual(len(foo._settings), 2)
|
||||
|
||||
self.assertIn('bar', foo)
|
||||
self.assertIn('baz', foo)
|
||||
self.assertIn('bar', foo._settings)
|
||||
self.assertIn('baz', foo._settings)
|
||||
|
||||
self.assertNotIn('bar', foo)
|
||||
foo['bar'] = 'value1'
|
||||
self.assertIn('bar', foo)
|
||||
|
||||
self.assertEqual(foo['bar'], 'value1')
|
||||
self.assertEqual(foo['bar'], 'value1')
|
||||
self.assertEqual(foo.bar, 'value1')
|
||||
|
||||
def test_assignment_validation(self):
|
||||
s = ConfigSettings()
|
||||
|
@ -117,7 +131,7 @@ class TestConfigSettings(unittest.TestCase):
|
|||
a = s.a
|
||||
|
||||
# Assigning an undeclared setting raises.
|
||||
with self.assertRaises(KeyError):
|
||||
with self.assertRaises(AttributeError):
|
||||
a.undefined = True
|
||||
|
||||
with self.assertRaises(KeyError):
|
||||
|
@ -155,26 +169,14 @@ class TestConfigSettings(unittest.TestCase):
|
|||
with self.assertRaises(TypeError):
|
||||
a.int = 'foo'
|
||||
|
||||
a.abs_path = '/home/gps'
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
a.abs_path = 'home/gps'
|
||||
|
||||
a.rel_path = 'home/gps'
|
||||
a.rel_path = './foo/bar'
|
||||
a.rel_path = 'foo.c'
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
a.rel_path = '/foo/bar'
|
||||
|
||||
a.path = '/home/gps'
|
||||
a.path = 'foo.c'
|
||||
a.path = 'foo/bar'
|
||||
a.path = './foo'
|
||||
|
||||
def test_retrieval_type(self):
|
||||
def retrieval_type_helper(self, provider):
|
||||
s = ConfigSettings()
|
||||
s.register_provider(Provider2)
|
||||
s.register_provider(provider)
|
||||
|
||||
a = s.a
|
||||
|
||||
|
@ -182,18 +184,33 @@ class TestConfigSettings(unittest.TestCase):
|
|||
a.boolean = True
|
||||
a.pos_int = 12
|
||||
a.int = -4
|
||||
a.abs_path = '/home/gps'
|
||||
a.rel_path = 'foo.c'
|
||||
a.path = './foo/bar'
|
||||
|
||||
self.assertIsInstance(a.string, str_type)
|
||||
self.assertIsInstance(a.boolean, bool)
|
||||
self.assertIsInstance(a.pos_int, int)
|
||||
self.assertIsInstance(a.int, int)
|
||||
self.assertIsInstance(a.abs_path, str_type)
|
||||
self.assertIsInstance(a.rel_path, str_type)
|
||||
self.assertIsInstance(a.path, str_type)
|
||||
|
||||
def test_retrieval_type(self):
|
||||
self.retrieval_type_helper(Provider2)
|
||||
self.retrieval_type_helper(Provider3)
|
||||
|
||||
def test_choices_validation(self):
|
||||
s = ConfigSettings()
|
||||
s.register_provider(Provider4)
|
||||
|
||||
foo = s.foo
|
||||
foo.abc
|
||||
with self.assertRaises(ValueError):
|
||||
foo.xyz
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
foo.abc = 'e'
|
||||
|
||||
foo.abc = 'b'
|
||||
foo.xyz = 'y'
|
||||
|
||||
def test_file_reading_single(self):
|
||||
temp = NamedTemporaryFile(mode='wt')
|
||||
temp.write(CONFIG1)
|
||||
|
|
Загрузка…
Ссылка в новой задаче