Skip to content

Commit

Permalink
Mark values from pyproject.toml as static
Browse files Browse the repository at this point in the history
  • Loading branch information
abravalheri committed Jan 8, 2025
1 parent f699fd8 commit 8b22d73
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 25 deletions.
4 changes: 4 additions & 0 deletions setuptools/_static.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,7 @@ def is_static(value: Any) -> bool:
False
"""
return isinstance(value, Static) and not value._mutated_


EMPTY_LIST = List()
EMPTY_DICT = Dict()
76 changes: 51 additions & 25 deletions setuptools/config/_apply_pyprojecttoml.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from types import MappingProxyType
from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union

from .. import _static
from .._path import StrPath
from ..errors import RemovedConfigError
from ..extension import Extension
Expand Down Expand Up @@ -65,10 +66,11 @@ def apply(dist: Distribution, config: dict, filename: StrPath) -> Distribution:


def _apply_project_table(dist: Distribution, config: dict, root_dir: StrPath):
project_table = config.get("project", {}).copy()
if not project_table:
orig_config = config.get("project", {})
if not orig_config:
return # short-circuit

project_table = {k: _static.attempt_conversion(v) for k, v in orig_config.items()}
_handle_missing_dynamic(dist, project_table)
_unify_entry_points(project_table)

Expand Down Expand Up @@ -98,7 +100,11 @@ def _apply_tool_table(dist: Distribution, config: dict, filename: StrPath):
raise RemovedConfigError("\n".join([cleandoc(msg), suggestion]))

norm_key = TOOL_TABLE_RENAMES.get(norm_key, norm_key)
_set_config(dist, norm_key, value)
corresp = TOOL_TABLE_CORRESPONDENCE.get(norm_key, norm_key)
if callable(corresp):
corresp(dist, value)
else:
_set_config(dist, corresp, value)

_copy_command_options(config, dist, filename)

Expand Down Expand Up @@ -143,7 +149,7 @@ def _guess_content_type(file: str) -> str | None:
return None

if ext in _CONTENT_TYPES:
return _CONTENT_TYPES[ext]
return _static.Str(_CONTENT_TYPES[ext])

valid = ", ".join(f"{k} ({v})" for k, v in _CONTENT_TYPES.items())
msg = f"only the following file extensions are recognized: {valid}."
Expand All @@ -165,10 +171,11 @@ def _long_description(
text = val.get("text") or expand.read_files(file, root_dir)
ctype = val["content-type"]

_set_config(dist, "long_description", text)
# XXX: Is it completely safe to assume static?
_set_config(dist, "long_description", _static.Str(text))

if ctype:
_set_config(dist, "long_description_content_type", ctype)
_set_config(dist, "long_description_content_type", _static.Str(ctype))

if file:
dist._referenced_files.add(file)
Expand All @@ -178,10 +185,12 @@ def _license(dist: Distribution, val: dict, root_dir: StrPath | None):
from setuptools.config import expand

if "file" in val:
_set_config(dist, "license", expand.read_files([val["file"]], root_dir))
# XXX: Is it completely safe to assume static?
value = expand.read_files([val["file"]], root_dir)
_set_config(dist, "license", _static.Str(value))
dist._referenced_files.add(val["file"])
else:
_set_config(dist, "license", val["text"])
_set_config(dist, "license", _static.Str(val["text"]))


def _people(dist: Distribution, val: list[dict], _root_dir: StrPath | None, kind: str):
Expand All @@ -197,19 +206,17 @@ def _people(dist: Distribution, val: list[dict], _root_dir: StrPath | None, kind
email_field.append(str(addr))

if field:
_set_config(dist, kind, ", ".join(field))
_set_config(dist, kind, _static.Str(", ".join(field)))
if email_field:
_set_config(dist, f"{kind}_email", ", ".join(email_field))
_set_config(dist, f"{kind}_email", _static.Str(", ".join(email_field)))


def _project_urls(dist: Distribution, val: dict, _root_dir: StrPath | None):
_set_config(dist, "project_urls", val)


def _python_requires(dist: Distribution, val: str, _root_dir: StrPath | None):
from packaging.specifiers import SpecifierSet

_set_config(dist, "python_requires", SpecifierSet(val))
_set_config(dist, "python_requires", _static.SpecifierSet(val))


def _dependencies(dist: Distribution, val: list, _root_dir: StrPath | None):
Expand Down Expand Up @@ -237,9 +244,14 @@ def _noop(_dist: Distribution, val: _T) -> _T:
return val


def _identity(val: _T) -> _T:
return val


def _unify_entry_points(project_table: dict):
project = project_table
entry_points = project.pop("entry-points", project.pop("entry_points", {}))
given = project.pop("entry-points", project.pop("entry_points", {}))
entry_points = dict(given) # Avoid problems with static
renaming = {"scripts": "console_scripts", "gui_scripts": "gui_scripts"}
for key, value in list(project.items()): # eager to allow modifications
norm_key = json_compatible_key(key)
Expand Down Expand Up @@ -333,6 +345,14 @@ def _get_previous_gui_scripts(dist: Distribution) -> list | None:
return value.get("gui_scripts")


def _set_static_list_metadata(attr: str, dist: Distribution, val: list) -> None:
"""Apply distutils metadata validation but preserve "static" behaviour"""
meta = dist.metadata
setter, getter = getattr(meta, f"set_{attr}"), getattr(meta, f"get_{attr}")
setter(val)
setattr(meta, attr, _static.List(getter()))


def _attrgetter(attr):
"""
Similar to ``operator.attrgetter`` but returns None if ``attr`` is not found
Expand Down Expand Up @@ -386,6 +406,12 @@ def _acessor(obj):
See https://packaging.python.org/en/latest/guides/packaging-namespace-packages/.
""",
}
TOOL_TABLE_CORRESPONDENCE = {
# Fields with corresponding core metadata need to be marked as static:
"obsoletes": partial(_set_static_list_metadata, "obsoletes"),
"provides": partial(_set_static_list_metadata, "provides"),
"platforms": partial(_set_static_list_metadata, "platforms"),
}

SETUPTOOLS_PATCHES = {
"long_description_content_type",
Expand Down Expand Up @@ -422,17 +448,17 @@ def _acessor(obj):
_RESET_PREVIOUSLY_DEFINED: dict = {
# Fix improper setting: given in `setup.py`, but not listed in `dynamic`
# dict: pyproject name => value to which reset
"license": {},
"authors": [],
"maintainers": [],
"keywords": [],
"classifiers": [],
"urls": {},
"entry-points": {},
"scripts": {},
"gui-scripts": {},
"dependencies": [],
"optional-dependencies": {},
"license": _static.EMPTY_DICT,
"authors": _static.EMPTY_LIST,
"maintainers": _static.EMPTY_LIST,
"keywords": _static.EMPTY_LIST,
"classifiers": _static.EMPTY_LIST,
"urls": _static.EMPTY_DICT,
"entry-points": _static.EMPTY_DICT,
"scripts": _static.EMPTY_DICT,
"gui-scripts": _static.EMPTY_DICT,
"dependencies": _static.EMPTY_LIST,
"optional-dependencies": _static.EMPTY_DICT,
}


Expand Down
27 changes: 27 additions & 0 deletions setuptools/tests/config/test_apply_pyprojecttoml.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from packaging.metadata import Metadata

import setuptools # noqa: F401 # ensure monkey patch to metadata
from setuptools._static import is_static
from setuptools.command.egg_info import write_requirements
from setuptools.config import expand, pyprojecttoml, setupcfg
from setuptools.config._apply_pyprojecttoml import _MissingDynamic, _some_attrgetter
Expand Down Expand Up @@ -480,6 +481,32 @@ def test_version(self, tmp_path, monkeypatch, capsys):
assert "42.0" in captured.out


class TestStaticConfig:
def test_mark_static_fields(self, tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
toml_config = """
[project]
name = "test"
version = "42.0"
dependencies = ["hello"]
keywords = ["world"]
classifiers = ["private :: hello world"]
[tool.setuptools]
obsoletes = ["abcd"]
provides = ["abcd"]
platforms = ["abcd"]
"""
pyproject = Path(tmp_path, "pyproject.toml")
pyproject.write_text(cleandoc(toml_config), encoding="utf-8")
dist = pyprojecttoml.apply_configuration(Distribution({}), pyproject)
assert is_static(dist.install_requires)
assert is_static(dist.metadata.keywords)
assert is_static(dist.metadata.classifiers)
assert is_static(dist.metadata.obsoletes)
assert is_static(dist.metadata.provides)
assert is_static(dist.metadata.platforms)


# --- Auxiliary Functions ---


Expand Down

0 comments on commit 8b22d73

Please sign in to comment.