diff --git a/docs/api/config.rst b/docs/api/config.rst new file mode 100644 index 00000000..240040eb --- /dev/null +++ b/docs/api/config.rst @@ -0,0 +1,37 @@ +.. _config-api: + +********** +Config API +********** + +.. automodule:: mopidy.config + :synopsis: Config API for config loading and validation + :members: + + +Config section schemas +====================== + +.. inheritance-diagram:: mopidy.config.schemas + +.. automodule:: mopidy.config.schemas + :synopsis: Config section validation schemas + :members: + + +Config value types +================== + +.. inheritance-diagram:: mopidy.config.types + +.. automodule:: mopidy.config.types + :synopsis: Config value validation types + :members: + + +Config value validators +======================= + +.. automodule:: mopidy.config.validators + :synopsis: Config value validators + :members: diff --git a/docs/api/index.rst b/docs/api/index.rst index cb7014f1..bb29890b 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -14,4 +14,5 @@ API reference audio frontends ext + config http diff --git a/docs/conf.py b/docs/conf.py index 1e9f9cb2..fa13c3bd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -81,6 +81,7 @@ on_rtd = os.environ.get('READTHEDOCS', None) == 'True' extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.extlinks', + 'sphinx.ext.inheritance_diagram', 'sphinx.ext.graphviz', 'sphinx.ext.viewcode', ] diff --git a/mopidy/config/__init__.py b/mopidy/config/__init__.py index 6cf9a802..97a073f9 100644 --- a/mopidy/config/__init__.py +++ b/mopidy/config/__init__.py @@ -37,7 +37,7 @@ core_schemas = [_logging_schema, _loglevels_schema, _audio_schema, _proxy_schema def read(config_file): - """Helper to load defaults in same way across core and extensions.""" + """Helper to load config defaults in same way across core and extensions""" with io.open(config_file, 'rb') as filehandle: return filehandle.read() @@ -118,7 +118,7 @@ def _validate(raw_config, schemas): def parse_override(override): - """Parse section/key=value override.""" + """Parse ``section/key=value`` command line overrides""" section, remainder = override.split('/', 1) key, value = remainder.split('=', 1) return (section.strip(), key.strip(), value.strip()) diff --git a/mopidy/config/schemas.py b/mopidy/config/schemas.py index 19b5d665..9e117d67 100644 --- a/mopidy/config/schemas.py +++ b/mopidy/config/schemas.py @@ -59,6 +59,8 @@ class ConfigSchema(object): return self._schema[key] def format(self, values): + """Returns the schema as a config section with the given ``values`` + filled in""" # TODO: should the output be encoded utf-8 since we use that in # serialize for strings? lines = ['[%s]' % self.name] @@ -70,6 +72,8 @@ class ConfigSchema(object): return '\n'.join(lines) def convert(self, items): + """Validates the given ``items`` using the config schema and returns + clean values""" errors = {} values = {} diff --git a/mopidy/config/types.py b/mopidy/config/types.py index 43878e87..35ec0a44 100644 --- a/mopidy/config/types.py +++ b/mopidy/config/types.py @@ -25,23 +25,32 @@ class ConfigValue(object): the code interacting with the config should simply skip None config values. """ - #: Collection of valid choices for converted value. Must be combined with - #: :function:`validate_choices` in :method:`validate` do any thing. choices = None + """ + Collection of valid choices for converted value. Must be combined with + :func:`~mopidy.config.validators.validate_choice` in :meth:`deserialize` + do any thing. + """ - #: Minimum of converted value. Must be combined with - #: :function:`validate_minimum` in :method:`validate` do any thing. minimum = None + """ + Minimum of converted value. Must be combined with + :func:`~mopidy.config.validators.validate_minimum` in :meth:`deserialize` + do any thing. + """ - #: Maximum of converted value. Must be combined with - #: :function:`validate_maximum` in :method:`validate` do any thing. maximum = None + """ + Maximum of converted value. Must be combined with + :func:`~mopidy.config.validators.validate_maximum` in :meth:`deserialize` + do any thing. + """ - #: Indicate if this field is required. optional = None + """Indicate if this field is required.""" - #: Indicate if we should mask the when printing for human consumption. secret = None + """Indicate if we should mask the when printing for human consumption.""" def __init__(self, **kwargs): self.choices = kwargs.get('choices') @@ -66,9 +75,9 @@ class ConfigValue(object): class String(ConfigValue): - """String values. + """String value - Supports: optional, choices and secret. + Supported kwargs: ``optional``, ``choices``, and ``secret``. """ def deserialize(self, value): value = value.strip() @@ -83,9 +92,9 @@ class String(ConfigValue): class Integer(ConfigValue): - """Integer values. + """Integer value - Supports: choices, minimum, maximum and secret. + Supported kwargs: ``choices``, ``minimum``, ``maximum``, and ``secret`` """ def deserialize(self, value): value = int(value) @@ -96,9 +105,15 @@ class Integer(ConfigValue): class Boolean(ConfigValue): - """Boolean values. + """Boolean value - Supports: secret. + Accepts ``1``, ``yes``, ``true``, and ``on`` with any casing as + :class:`True`. + + Accepts ``0``, ``no``, ``false``, and ``off`` with any casing as + :class:`False`. + + Supported kwargs: ``secret`` """ true_values = ('1', 'yes', 'true', 'on') false_values = ('0', 'no', 'false', 'off') @@ -119,9 +134,11 @@ class Boolean(ConfigValue): class List(ConfigValue): - """List values split by comma or newline. + """List value - Supports: optional and secret. + Supports elements split by commas or newlines. + + Supported kwargs: ``optional`` and ``secret`` """ def deserialize(self, value): validators.validate_required(value, not self.optional) @@ -136,9 +153,12 @@ class List(ConfigValue): class LogLevel(ConfigValue): - """Log level values. + """Log level value - Supports: secret. + Expects one of ``critical``, ``error``, ``warning``, ``info``, ``debug`` + with any casing. + + Supported kwargs: ``secret`` """ levels = { 'critical': logging.CRITICAL, @@ -157,9 +177,9 @@ class LogLevel(ConfigValue): class Hostname(ConfigValue): - """Hostname values. + """Hostname value - Supports: optional and secret. + Supported kwargs: ``optional`` and ``secret`` """ def deserialize(self, value): validators.validate_required(value, not self.optional) @@ -173,9 +193,11 @@ class Hostname(ConfigValue): class Port(Integer): - """Port values limited to 1-65535. + """Port value - Supports: choices and secret. + Expects integer in the range 1-65535 + + Supported kwargs: ``choices`` and ``secret`` """ # TODO: consider probing if port is free or not? def __init__(self, **kwargs): @@ -194,9 +216,21 @@ class ExpandedPath(bytes): class Path(ConfigValue): - """File system path that will be expanded. + """File system path - Supports: optional, choices and secret. + The following expansions of the path will be done: + + - ``~`` to the current user's home directory + + - ``$XDG_CACHE_DIR`` according to the XDG spec + + - ``$XDG_CONFIG_DIR`` according to the XDG spec + + - ``$XDG_DATA_DIR`` according to the XDG spec + + - ``$XDG_MUSIC_DIR`` according to the XDG spec + + Supported kwargs: ``optional``, ``choices``, and ``secret`` """ def deserialize(self, value): value = value.strip() diff --git a/mopidy/config/validators.py b/mopidy/config/validators.py index 0fda118d..9e374ce5 100644 --- a/mopidy/config/validators.py +++ b/mopidy/config/validators.py @@ -4,26 +4,38 @@ from __future__ import unicode_literals def validate_required(value, required): - """Required validation, normally called in config value's validate() on the - raw string, _not_ the converted value.""" + """Validate that ``value`` is set if ``required`` + + Normally called in :meth:`~mopidy.config.types.ConfigValue.deserialize` on + the raw string, _not_ the converted value. + """ if required and not value.strip(): raise ValueError('must be set.') def validate_choice(value, choices): - """Choice validation, normally called in config value's validate().""" + """Validate that ``value`` is one of the ``choices`` + + Normally called in :meth:`~mopidy.config.types.ConfigValue.deserialize`. + """ if choices is not None and value not in choices: names = ', '.join(repr(c) for c in choices) raise ValueError('must be one of %s, not %s.' % (names, value)) def validate_minimum(value, minimum): - """Minimum validation, normally called in config value's validate().""" + """Validate that ``value`` is at least ``minimum`` + + Normally called in :meth:`~mopidy.config.types.ConfigValue.deserialize`. + """ if minimum is not None and value < minimum: raise ValueError('%r must be larger than %r.' % (value, minimum)) def validate_maximum(value, maximum): - """Maximum validation, normally called in config value's validate().""" + """Validate that ``value`` is at most ``maximum`` + + Normally called in :meth:`~mopidy.config.types.ConfigValue.deserialize`. + """ if maximum is not None and value > maximum: raise ValueError('%r must be smaller than %r.' % (value, maximum))