Source code for whey.config.pep621

#!/usr/bin/env python3
#
#  pep621.py
"""
:pep:`621` configuration parser.
"""
#
#  Copyright © 2021 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 typing import Dict, cast

# 3rd party
import pyproject_parser.parsers
from dom_toml.parser import TOML_TYPES, BadConfigError
from domdf_python_tools.words import Plural, word_join
from pyproject_parser.type_hints import ProjectDict

__all__ = ("PEP621Parser", )

_field = Plural("field", "fields")


[docs]class PEP621Parser(pyproject_parser.parsers.PEP621Parser, inherit_defaults=True): """ Parser for :pep:`621` metadata from ``pyproject.toml``. """ defaults = { "description": None, "readme": None, "requires-python": None, "license": None, } def _parse( self, config: Dict[str, TOML_TYPES], set_defaults: bool = False, ) -> ProjectDict: dynamic_fields = config.get("dynamic", []) parsed_config = {"dynamic": dynamic_fields} for key in self.keys: if key in config and key in dynamic_fields: raise BadConfigError(f"{key!r} was listed in 'project.dynamic' but a value was given.") elif key not in config: # Ignore absent values pass elif hasattr(self, f"parse_{key.replace('-', '_')}"): parsed_config[key] = getattr(self, f"parse_{key.replace('-', '_')}")(config) elif key in config: parsed_config[key] = config[key] if set_defaults: for key, value in self.defaults.items(): parsed_config.setdefault(key, value) for key, factory in self.factories.items(): value = factory() parsed_config.setdefault(key, value) return cast(ProjectDict, parsed_config)
[docs] def parse( # type: ignore[override] self, config: Dict[str, TOML_TYPES], set_defaults: bool = False, ) -> ProjectDict: """ Parse the TOML configuration. :param config: :param set_defaults: If :py:obj:`True`, the values in :attr:`dom_toml.parser.AbstractConfigParser.defaults` and :attr:`dom_toml.parser.AbstractConfigParser.factories` will be set as defaults for the returned mapping. """ dynamic_fields = set(config.get("dynamic", [])) if "name" in dynamic_fields: raise BadConfigError("The 'project.name' field may not be dynamic.") elif "name" not in config: raise BadConfigError("The 'project.name' field must be provided.") if dynamic_fields: # TODO: Support the remaining fields as dynamic # TODO: dynamic version numbers by parsing AST for __version__ in __init__.py supported_dynamic = {"classifiers", "requires-python", "dependencies"} unsupported_fields = dynamic_fields - supported_dynamic if unsupported_fields: supported = word_join(sorted(supported_dynamic), oxford=True, use_repr=True) unsupported = word_join(sorted(unsupported_fields), oxford=True, use_repr=True) raise BadConfigError( f"Unsupported dynamic {_field(len(unsupported_fields))} {unsupported}.\n" f"note: whey only supports {supported} as dynamic fields." ) if "version" not in config: raise BadConfigError("The 'project.version' field must be provided.") return self._parse(config, set_defaults)