update
This commit is contained in:
@@ -8,33 +8,38 @@ To read project metadata, consider using
|
||||
For simple scenarios, you can also try parsing the file directly
|
||||
with the help of ``tomllib`` or ``tomli``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, Callable, Dict, Mapping, Optional, Set, Union
|
||||
from types import TracebackType
|
||||
from typing import TYPE_CHECKING, Any, Callable, Mapping
|
||||
|
||||
from ..errors import FileError, OptionError
|
||||
from .._path import StrPath
|
||||
from ..errors import FileError, InvalidConfigError
|
||||
from ..warnings import SetuptoolsWarning
|
||||
from . import expand as _expand
|
||||
from ._apply_pyprojecttoml import _PREVIOUSLY_DEFINED, _WouldIgnoreField
|
||||
from ._apply_pyprojecttoml import apply as _apply
|
||||
from ._apply_pyprojecttoml import _PREVIOUSLY_DEFINED, _MissingDynamic, apply as _apply
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from setuptools.dist import Distribution # noqa
|
||||
from typing_extensions import Self
|
||||
|
||||
from setuptools.dist import Distribution
|
||||
|
||||
_Path = Union[str, os.PathLike]
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def load_file(filepath: _Path) -> dict:
|
||||
from setuptools.extern import tomli # type: ignore
|
||||
def load_file(filepath: StrPath) -> dict:
|
||||
from ..compat.py310 import tomllib
|
||||
|
||||
with open(filepath, "rb") as file:
|
||||
return tomli.load(file)
|
||||
return tomllib.load(file)
|
||||
|
||||
|
||||
def validate(config: dict, filepath: _Path) -> bool:
|
||||
def validate(config: dict, filepath: StrPath) -> bool:
|
||||
from . import _validate_pyproject as validator
|
||||
|
||||
trove_classifier = validator.FORMAT_FUNCTIONS.get("trove-classifier")
|
||||
@@ -56,10 +61,10 @@ def validate(config: dict, filepath: _Path) -> bool:
|
||||
|
||||
|
||||
def apply_configuration(
|
||||
dist: "Distribution",
|
||||
filepath: _Path,
|
||||
dist: Distribution,
|
||||
filepath: StrPath,
|
||||
ignore_option_errors=False,
|
||||
) -> "Distribution":
|
||||
) -> Distribution:
|
||||
"""Apply the configuration from a ``pyproject.toml`` file into an existing
|
||||
distribution object.
|
||||
"""
|
||||
@@ -68,11 +73,11 @@ def apply_configuration(
|
||||
|
||||
|
||||
def read_configuration(
|
||||
filepath: _Path,
|
||||
filepath: StrPath,
|
||||
expand=True,
|
||||
ignore_option_errors=False,
|
||||
dist: Optional["Distribution"] = None,
|
||||
):
|
||||
dist: Distribution | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Read given configuration file and returns options from it as a dict.
|
||||
|
||||
:param str|unicode filepath: Path to configuration file in the ``pyproject.toml``
|
||||
@@ -106,6 +111,10 @@ def read_configuration(
|
||||
if not asdict or not (project_table or setuptools_table):
|
||||
return {} # User is not using pyproject to configure setuptools
|
||||
|
||||
if "setuptools" in asdict.get("tools", {}):
|
||||
# let the user know they probably have a typo in their metadata
|
||||
_ToolsTypoInMetadata.emit()
|
||||
|
||||
if "distutils" in tool_table:
|
||||
_ExperimentalConfiguration.emit(subject="[tool.distutils]")
|
||||
|
||||
@@ -113,7 +122,7 @@ def read_configuration(
|
||||
# the default would be an improvement.
|
||||
# `ini2toml` backfills include_package_data=False when nothing is explicitly given,
|
||||
# therefore setting a default here is backwards compatible.
|
||||
if dist and getattr(dist, "include_package_data", None) is not None:
|
||||
if dist and dist.include_package_data is not None:
|
||||
setuptools_table.setdefault("include-package-data", dist.include_package_data)
|
||||
else:
|
||||
setuptools_table.setdefault("include-package-data", True)
|
||||
@@ -121,6 +130,9 @@ def read_configuration(
|
||||
asdict["tool"] = tool_table
|
||||
tool_table["setuptools"] = setuptools_table
|
||||
|
||||
if "ext-modules" in setuptools_table:
|
||||
_ExperimentalConfiguration.emit(subject="[tool.setuptools.ext-modules]")
|
||||
|
||||
with _ignore_errors(ignore_option_errors):
|
||||
# Don't complain about unrelated errors (e.g. tools not using the "tool" table)
|
||||
subset = {"project": project_table, "tool": {"setuptools": setuptools_table}}
|
||||
@@ -135,9 +147,9 @@ def read_configuration(
|
||||
|
||||
def expand_configuration(
|
||||
config: dict,
|
||||
root_dir: Optional[_Path] = None,
|
||||
root_dir: StrPath | None = None,
|
||||
ignore_option_errors: bool = False,
|
||||
dist: Optional["Distribution"] = None,
|
||||
dist: Distribution | None = None,
|
||||
) -> dict:
|
||||
"""Given a configuration with unresolved fields (e.g. dynamic, cmdclass, ...)
|
||||
find their final values.
|
||||
@@ -160,9 +172,9 @@ class _ConfigExpander:
|
||||
def __init__(
|
||||
self,
|
||||
config: dict,
|
||||
root_dir: Optional[_Path] = None,
|
||||
root_dir: StrPath | None = None,
|
||||
ignore_option_errors: bool = False,
|
||||
dist: Optional["Distribution"] = None,
|
||||
dist: Distribution | None = None,
|
||||
):
|
||||
self.config = config
|
||||
self.root_dir = root_dir or os.getcwd()
|
||||
@@ -172,9 +184,9 @@ class _ConfigExpander:
|
||||
self.dynamic_cfg = self.setuptools_cfg.get("dynamic", {})
|
||||
self.ignore_option_errors = ignore_option_errors
|
||||
self._dist = dist
|
||||
self._referenced_files: Set[str] = set()
|
||||
self._referenced_files: set[str] = set()
|
||||
|
||||
def _ensure_dist(self) -> "Distribution":
|
||||
def _ensure_dist(self) -> Distribution:
|
||||
from setuptools.dist import Distribution
|
||||
|
||||
attrs = {"src_root": self.root_dir, "name": self.project_cfg.get("name", None)}
|
||||
@@ -227,7 +239,7 @@ class _ConfigExpander:
|
||||
cmdclass = partial(_expand.cmdclass, package_dir=package_dir, root_dir=root_dir)
|
||||
self._process_field(self.setuptools_cfg, "cmdclass", cmdclass)
|
||||
|
||||
def _expand_all_dynamic(self, dist: "Distribution", package_dir: Mapping[str, str]):
|
||||
def _expand_all_dynamic(self, dist: Distribution, package_dir: Mapping[str, str]):
|
||||
special = ( # need special handling
|
||||
"version",
|
||||
"readme",
|
||||
@@ -257,7 +269,7 @@ class _ConfigExpander:
|
||||
updates = {k: v for k, v in obtained_dynamic.items() if v is not None}
|
||||
self.project_cfg.update(updates)
|
||||
|
||||
def _ensure_previously_set(self, dist: "Distribution", field: str):
|
||||
def _ensure_previously_set(self, dist: Distribution, field: str):
|
||||
previous = _PREVIOUSLY_DEFINED[field](dist)
|
||||
if previous is None and not self.ignore_option_errors:
|
||||
msg = (
|
||||
@@ -265,12 +277,12 @@ class _ConfigExpander:
|
||||
"Some dynamic fields need to be specified via `tool.setuptools.dynamic`"
|
||||
"\nothers must be specified via the equivalent attribute in `setup.py`."
|
||||
)
|
||||
raise OptionError(msg)
|
||||
raise InvalidConfigError(msg)
|
||||
|
||||
def _expand_directive(
|
||||
self, specifier: str, directive, package_dir: Mapping[str, str]
|
||||
):
|
||||
from setuptools.extern.more_itertools import always_iterable # type: ignore
|
||||
from more_itertools import always_iterable
|
||||
|
||||
with _ignore_errors(self.ignore_option_errors):
|
||||
root_dir = self.root_dir
|
||||
@@ -282,7 +294,7 @@ class _ConfigExpander:
|
||||
raise ValueError(f"invalid `{specifier}`: {directive!r}")
|
||||
return None
|
||||
|
||||
def _obtain(self, dist: "Distribution", field: str, package_dir: Mapping[str, str]):
|
||||
def _obtain(self, dist: Distribution, field: str, package_dir: Mapping[str, str]):
|
||||
if field in self.dynamic_cfg:
|
||||
return self._expand_directive(
|
||||
f"tool.setuptools.dynamic.{field}",
|
||||
@@ -292,29 +304,33 @@ class _ConfigExpander:
|
||||
self._ensure_previously_set(dist, field)
|
||||
return None
|
||||
|
||||
def _obtain_version(self, dist: "Distribution", package_dir: Mapping[str, str]):
|
||||
def _obtain_version(self, dist: Distribution, package_dir: Mapping[str, str]):
|
||||
# Since plugins can set version, let's silently skip if it cannot be obtained
|
||||
if "version" in self.dynamic and "version" in self.dynamic_cfg:
|
||||
return _expand.version(self._obtain(dist, "version", package_dir))
|
||||
return _expand.version(
|
||||
# We already do an early check for the presence of "version"
|
||||
self._obtain(dist, "version", package_dir) # pyright: ignore[reportArgumentType]
|
||||
)
|
||||
return None
|
||||
|
||||
def _obtain_readme(self, dist: "Distribution") -> Optional[Dict[str, str]]:
|
||||
def _obtain_readme(self, dist: Distribution) -> dict[str, str] | None:
|
||||
if "readme" not in self.dynamic:
|
||||
return None
|
||||
|
||||
dynamic_cfg = self.dynamic_cfg
|
||||
if "readme" in dynamic_cfg:
|
||||
return {
|
||||
# We already do an early check for the presence of "readme"
|
||||
"text": self._obtain(dist, "readme", {}),
|
||||
"content-type": dynamic_cfg["readme"].get("content-type", "text/x-rst"),
|
||||
}
|
||||
} # pyright: ignore[reportReturnType]
|
||||
|
||||
self._ensure_previously_set(dist, "readme")
|
||||
return None
|
||||
|
||||
def _obtain_entry_points(
|
||||
self, dist: "Distribution", package_dir: Mapping[str, str]
|
||||
) -> Optional[Dict[str, dict]]:
|
||||
self, dist: Distribution, package_dir: Mapping[str, str]
|
||||
) -> dict[str, dict] | None:
|
||||
fields = ("entry-points", "scripts", "gui-scripts")
|
||||
if not any(field in self.dynamic for field in fields):
|
||||
return None
|
||||
@@ -330,9 +346,7 @@ class _ConfigExpander:
|
||||
if group in groups:
|
||||
value = groups.pop(group)
|
||||
if field not in self.dynamic:
|
||||
_WouldIgnoreField.emit(field=field, value=value)
|
||||
# TODO: Don't set field when support for pyproject.toml stabilizes
|
||||
# instead raise an error as specified in PEP 621
|
||||
raise InvalidConfigError(_MissingDynamic.details(field, value))
|
||||
expanded[field] = value
|
||||
|
||||
_set_scripts("scripts", "console_scripts")
|
||||
@@ -340,21 +354,21 @@ class _ConfigExpander:
|
||||
|
||||
return expanded
|
||||
|
||||
def _obtain_classifiers(self, dist: "Distribution"):
|
||||
def _obtain_classifiers(self, dist: Distribution):
|
||||
if "classifiers" in self.dynamic:
|
||||
value = self._obtain(dist, "classifiers", {})
|
||||
if value:
|
||||
return value.splitlines()
|
||||
return None
|
||||
|
||||
def _obtain_dependencies(self, dist: "Distribution"):
|
||||
def _obtain_dependencies(self, dist: Distribution):
|
||||
if "dependencies" in self.dynamic:
|
||||
value = self._obtain(dist, "dependencies", {})
|
||||
if value:
|
||||
return _parse_requirements_list(value)
|
||||
return None
|
||||
|
||||
def _obtain_optional_dependencies(self, dist: "Distribution"):
|
||||
def _obtain_optional_dependencies(self, dist: Distribution):
|
||||
if "optional-dependencies" not in self.dynamic:
|
||||
return None
|
||||
if "optional-dependencies" in self.dynamic_cfg:
|
||||
@@ -396,18 +410,18 @@ def _ignore_errors(ignore_option_errors: bool):
|
||||
|
||||
class _EnsurePackagesDiscovered(_expand.EnsurePackagesDiscovered):
|
||||
def __init__(
|
||||
self, distribution: "Distribution", project_cfg: dict, setuptools_cfg: dict
|
||||
self, distribution: Distribution, project_cfg: dict, setuptools_cfg: dict
|
||||
):
|
||||
super().__init__(distribution)
|
||||
self._project_cfg = project_cfg
|
||||
self._setuptools_cfg = setuptools_cfg
|
||||
|
||||
def __enter__(self):
|
||||
def __enter__(self) -> Self:
|
||||
"""When entering the context, the values of ``packages``, ``py_modules`` and
|
||||
``package_dir`` that are missing in ``dist`` are copied from ``setuptools_cfg``.
|
||||
"""
|
||||
dist, cfg = self._dist, self._setuptools_cfg
|
||||
package_dir: Dict[str, str] = cfg.setdefault("package-dir", {})
|
||||
package_dir: dict[str, str] = cfg.setdefault("package-dir", {})
|
||||
package_dir.update(dist.package_dir or {})
|
||||
dist.package_dir = package_dir # needs to be the same object
|
||||
|
||||
@@ -424,7 +438,12 @@ class _EnsurePackagesDiscovered(_expand.EnsurePackagesDiscovered):
|
||||
|
||||
return super().__enter__()
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: type[BaseException] | None,
|
||||
exc_value: BaseException | None,
|
||||
traceback: TracebackType | None,
|
||||
) -> None:
|
||||
"""When exiting the context, if values of ``packages``, ``py_modules`` and
|
||||
``package_dir`` are missing in ``setuptools_cfg``, copy from ``dist``.
|
||||
"""
|
||||
@@ -439,3 +458,9 @@ class _ExperimentalConfiguration(SetuptoolsWarning):
|
||||
"`{subject}` in `pyproject.toml` is still *experimental* "
|
||||
"and likely to change in future releases."
|
||||
)
|
||||
|
||||
|
||||
class _ToolsTypoInMetadata(SetuptoolsWarning):
|
||||
_SUMMARY = (
|
||||
"Ignoring [tools.setuptools] in pyproject.toml, did you mean [tool.setuptools]?"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user