|
@@ -20,6 +20,7 @@ from itertools import chain
|
|
|
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
|
|
@@ -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)
|
|
|
|
|
@@ -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)
|
|
|
|
|
@@ -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}."
|
|
@@ -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)
|
|
@@ -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):
|
|
@@ -197,9 +206,9 @@ 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):
|
|
@@ -207,9 +216,7 @@ def _project_urls(dist: Distribution, val: dict, _root_dir: StrPath | None):
|
|
|
|
|
|
|
|
|
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):
|
|
@@ -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)
|
|
@@ -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
|
|
@@ -386,6 +406,12 @@ TOOL_TABLE_REMOVALS = {
|
|
|
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",
|
|
@@ -422,17 +448,17 @@ _PREVIOUSLY_DEFINED = {
|
|
|
_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,
|
|
|
}
|
|
|
|
|
|
|