Source code for configconfig.configvar

#!/usr/bin/env python3
#
#  configvar.py
"""
Base class for ``YAML`` configuration values.
"""
#
#  Copyright © 2020 Dominic Davis-Foster <dominic@davis-foster.co.uk>
#
#  Permission is hereby granted, free of charge, to any person obtaining a copy
#  of this software and associated documentation files (the "Software"), to deal
#  in the Software without restriction, including without limitation the rights
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#  copies of the Software, and to permit persons to whom the Software is
#  furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included in all
#  copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
#  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
#  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
#  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
#  OR OTHER DEALINGS IN THE SOFTWARE.
#

# stdlib
from textwrap import dedent, indent
from typing import Any, Callable, Optional, Type, Union

# 3rd party
from domdf_python_tools.stringlist import StringList
from typing_inspect import is_literal_type  # type: ignore[import]

# this package
from configconfig.metaclass import ConfigVarMeta
from configconfig.utils import RawConfigVarsType, get_yaml_type, tab
from configconfig.validator import Validator

__all__ = ["ConfigVar"]


[docs]class ConfigVar(metaclass=ConfigVarMeta): """ Base class for ``YAML`` configuration values. The class docstring should be the description of the config var, with an example, and the name of the class should be the variable name. Alternatively, for a more Pythonic naming approach, the variable name can be set with the ``name`` class variable. .. latex:vspace:: -5px :bold-title:`Example:` .. code-block:: python class platforms(ConfigVar): \"\"\" A case-insensitive list of platforms to perform tests for. Example: .. code-block:: yaml platforms: - Windows - Linux These values determine the GitHub test workflows to enable, and the Trove classifiers used on PyPI. \"\"\" dtype = List[Literal["Windows", "macOS", "Linux"]] default: List[str] = ["Windows", "macOS", "Linux"] category: str = "packaging" .. latex:vspace:: -10px """ # noqa: D300,D301 dtype: Type """ The allowed type or types in the ``YAML`` configuration file. """ rtype: Type """ The variable type passed to Jinja2. If ``None`` :attr:`~configconfig.configvar.ConfigVar.dtype` is used. Ignored for ``dtype=bool``. """ required: bool """ Flag to indicate whether the configuration value is required. Default :py:obj:`False`. """ default: Union[Callable[[RawConfigVarsType], Any], Any] """ The default value of the configuration value if it is optional. Defaults to ``''`` if unset. May also be set to a callable which returns a dynamic or mutable default value. """ category: str """ The category the :class:`~configconfig.configvar.ConfigVar` is listed under in the documentation. """ __name__: str
[docs] @classmethod def validator(cls, value: Any) -> Any: """ Function to call to validate the values. * The callable must have a single required argument (the value). * Should raise :exc:`ValueError` if values are invalid, and return the values if they are valid. * May change the values (e.g. make lowercase) before returning. """ return value
def __new__(cls, raw_config_vars: RawConfigVarsType) -> Any: # noqa: D102 # Exists purely so mypy knows about the signature return cls.get(raw_config_vars) # pragma: no cover
[docs] @classmethod def get(cls, raw_config_vars: Optional[RawConfigVarsType] = None) -> Any: """ Returns the value of this :class:`~configconfig.configvar.ConfigVar`. :param raw_config_vars: Dictionary to obtain the value from. :rtype: See the :attr:`~.ConfigVar.rtype` attribute. """ return cls.validator(cls.validate(raw_config_vars))
[docs] @classmethod def validate(cls, raw_config_vars: Optional[RawConfigVarsType] = None) -> Any: """ Validate the value obtained from the ``YAML`` file and coerce into the appropriate return type. :param raw_config_vars: Dictionary to obtain the value from. :rtype: See the :attr:`~.ConfigVar.rtype` attribute. """ if raw_config_vars is None: raw_config_vars = {} if cls.rtype is None: cls.rtype = cls.dtype validator = Validator(cls) return validator.validate(raw_config_vars)
[docs] @classmethod def make_documentation(cls) -> str: """ Returns the reStructuredText documentation for the :class:`~.ConfigVar`. """ docstring = cls.__doc__ or '' docstring = (indent(dedent(docstring), tab)) if not docstring.startswith('\n'): docstring = '\n' + docstring buf = StringList() buf.indent_type = " " buf.blankline(ensure_single=True) buf.append(f".. conf:: {cls.__name__}") buf.append(docstring) buf.blankline() buf.indent_size += 1 buf.append(f"**Required**: {'yes' if cls.required else 'no'}") buf.blankline() buf.blankline() if not cls.required: if cls.default == []: buf.append("**Default**: [ ]") elif cls.default == {}: buf.append("**Default**: { }") elif isinstance(cls.default, Callable): # type: ignore[arg-type] buf.append(f"**Default**: The value of :conf:`{cls.default.__name__}`") elif isinstance(cls.default, bool): buf.append(f"**Default**: :py:obj:`{cls.default}`") elif isinstance(cls.default, str): if cls.default == '': buf.append("**Default**: <blank>") else: buf.append(f"**Default**: ``{cls.default}``") else: buf.append(f"**Default**: {cls.default}") buf.blankline() buf.blankline() buf.append(f"**Type**: {get_yaml_type(cls.dtype)}") if is_literal_type(cls.dtype): valid_values = ", ".join(f"``{x}``" for x in cls.dtype.__args__) buf.blankline() buf.blankline() buf.append(f"**Allowed values**: {valid_values}") elif hasattr(cls.dtype, "__args__") and is_literal_type(cls.dtype.__args__[0]): valid_values = ", ".join(f"``{x}``" for x in cls.dtype.__args__[0].__args__) buf.blankline() buf.blankline() buf.append(f"**Allowed values**: {valid_values}") buf.indent_size -= 1 return str(buf)