Source code for whey.config

#!/usr/bin/env python3
#
#  __init__.py
"""
``pyproject.toml`` configuration parsing.
"""
#
#  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
import re
from typing import Any, Dict

# 3rd party
import dom_toml
import dom_toml.decoder
from dom_toml.parser import BadConfigError
from domdf_python_tools.iterative import natmin
from domdf_python_tools.paths import PathPlus, in_directory
from domdf_python_tools.typing import PathLike
from packaging.requirements import InvalidRequirement
from packaging.specifiers import Specifier
from shippinglabel.requirements import combine_requirements, read_requirements

# this package
from whey.config.pep621 import PEP621Parser
from whey.config.whey import WheyParser, backfill_classifiers

__all__ = (
		"PEP621Parser",
		"WheyParser",
		"backfill_classifiers",
		"load_toml",
		)

_name_to_package_re = re.compile("-(?!stubs)")


def _get_default_package(name: str) -> str:
	return _name_to_package_re.sub('_', name.split('.', 1)[0])


[docs]def load_toml(filename: PathLike) -> Dict[str, Any]: # TODO: TypedDict """ Load the ``whey`` configuration mapping from the given TOML file. :param filename: """ filename = PathPlus(filename) project_dir = filename.parent config = dom_toml.load(filename, decoder=dom_toml.decoder.TomlPureDecoder) parsed_config = {} tool_table = config.get("tool", {}) with in_directory(project_dir): parsed_config.update(WheyParser().parse(tool_table.get("whey", {}), set_defaults=True)) if "project" in config: parsed_config.update(PEP621Parser().parse(config["project"], set_defaults=True)) else: raise KeyError(f"'project' table not found in '{filename!s}'") if parsed_config.get("readme", None) is not None: parsed_config["readme"] = parsed_config["readme"].resolve() if parsed_config.get("license", None) is not None: parsed_config["license"] = parsed_config["license"].resolve() # set defaults parsed_config.setdefault("package", _get_default_package(config["project"]["name"])) dynamic_fields = parsed_config.get("dynamic", []) if "classifiers" in dynamic_fields: dynamic_fields.remove("classifiers") parsed_config["classifiers"] = backfill_classifiers(parsed_config) if "requires-python" in dynamic_fields: if parsed_config["python-versions"]: dynamic_fields.remove("requires-python") parsed_config["requires-python"] = Specifier(f">={natmin(parsed_config['python-versions'])}") else: raise BadConfigError( "'requires-python' was listed in 'project.dynamic', " "but whey cannot determine the minimum supported Python version. \n" "Set 'tool.whey.python-versions' to a list of supported Python versions to fix this." ) if "dependencies" in dynamic_fields: dynamic_fields.remove("dependencies") if (project_dir / "requirements.txt").is_file(): dependencies, comments, invalid = read_requirements(project_dir / "requirements.txt", include_invalid=True) for bad_string in invalid: raise InvalidRequirement(bad_string) parsed_config["dependencies"] = sorted(combine_requirements(dependencies)) else: raise BadConfigError( "'project.dependencies' was listed as a dynamic field " "but no 'requirements.txt' file was found." ) parsed_config["dynamic"] = dynamic_fields if "base-classifiers" in parsed_config: del parsed_config["base-classifiers"] return parsed_config