Voice et bot modif

This commit is contained in:
pi 2026-06-16 17:09:34 +00:00
parent 189d56026b
commit 7333a22bcd
10774 changed files with 634644 additions and 933308 deletions

View file

@ -268,10 +268,18 @@ def _optional_dependencies(dist: Distribution, val: dict, _root_dir: StrPath | N
def _ext_modules(dist: Distribution, val: list[dict]) -> list[Extension]:
existing = dist.ext_modules or []
args = ({k.replace("-", "_"): v for k, v in x.items()} for x in val)
new = [Extension(**kw) for kw in args]
new = (Extension(**_adjust_ext_attrs(kw)) for kw in args)
return [*existing, *new]
def _adjust_ext_attrs(attrs: dict) -> dict:
# https://github.com/pypa/setuptools/issues/4810
# In TOML there is no differentiation between tuples and lists,
# and distutils requires tuples...
attrs["define_macros"] = list(map(tuple, attrs.get("define_macros") or []))
return attrs
def _noop(_dist: Distribution, val: _T) -> _T:
return val

View file

@ -1,7 +1,7 @@
The code contained in this directory was automatically generated using the
following command:
python -m validate_pyproject.pre_compile --output-dir=setuptools/config/_validate_pyproject --enable-plugins setuptools distutils --very-verbose -t setuptools=setuptools/config/setuptools.schema.json -t distutils=setuptools/config/distutils.schema.json
python -m validate_pyproject.pre_compile --output-dir=setuptools/config/_validate_pyproject --enable-plugins setuptools distutils --very-verbose -t distutils=setuptools/config/distutils.schema.json -t setuptools=setuptools/config/setuptools.schema.json
Please avoid changing it manually.

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import io
import json
import logging
@ -6,7 +8,7 @@ import re
import typing
from contextlib import contextmanager
from textwrap import indent, wrap
from typing import Any, Dict, Generator, Iterator, List, Optional, Sequence, Union
from typing import Any, Generator, Iterator, Sequence
from .fastjsonschema_exceptions import JsonSchemaValueException
@ -36,7 +38,7 @@ _SKIP_DETAILS = (
_NEED_DETAILS = {"anyOf", "oneOf", "allOf", "contains", "propertyNames", "not", "items"}
_CAMEL_CASE_SPLITTER = re.compile(r"\W+|([A-Z][^A-Z\W]*)")
_IDENTIFIER = re.compile(r"^[\w_]+$", re.I)
_IDENTIFIER = re.compile(r"^[\w_]+$", re.IGNORECASE)
_TOML_JARGON = {
"object": "table",
@ -73,7 +75,7 @@ class ValidationError(JsonSchemaValueException):
_original_message = ""
@classmethod
def _from_jsonschema(cls, ex: JsonSchemaValueException) -> "Self":
def _from_jsonschema(cls, ex: JsonSchemaValueException) -> Self:
formatter = _ErrorFormatting(ex)
obj = cls(str(formatter), ex.value, formatter.name, ex.definition, ex.rule)
debug_code = os.getenv("JSONSCHEMA_DEBUG_CODE_GENERATION", "false").lower()
@ -173,8 +175,8 @@ class _ErrorFormatting:
class _SummaryWriter:
_IGNORE = frozenset(("description", "default", "title", "examples"))
def __init__(self, jargon: Optional[Dict[str, str]] = None):
self.jargon: Dict[str, str] = jargon or {}
def __init__(self, jargon: dict[str, str] | None = None):
self.jargon: dict[str, str] = jargon or {}
# Clarify confusing terms
self._terms = {
"anyOf": "at least one of the following",
@ -207,14 +209,14 @@ class _SummaryWriter:
"multipleOf",
]
def _jargon(self, term: Union[str, List[str]]) -> Union[str, List[str]]:
def _jargon(self, term: str | list[str]) -> str | list[str]:
if isinstance(term, list):
return [self.jargon.get(t, t) for t in term]
return self.jargon.get(term, term)
def __call__(
self,
schema: Union[dict, List[dict]],
schema: dict | list[dict],
prefix: str = "",
*,
_path: Sequence[str] = (),
@ -261,15 +263,15 @@ class _SummaryWriter:
return any(key.startswith(k) for k in "$_") or key in self._IGNORE
def _filter_unecessary(
self, schema: Dict[str, Any], path: Sequence[str]
) -> Dict[str, Any]:
self, schema: dict[str, Any], path: Sequence[str]
) -> dict[str, Any]:
return {
key: value
for key, value in schema.items()
if not self._is_unecessary([*path, key])
}
def _handle_simple_dict(self, value: dict, path: Sequence[str]) -> Optional[str]:
def _handle_simple_dict(self, value: dict, path: Sequence[str]) -> str | None:
inline = any(p in value for p in self._guess_inline_defs)
simple = not any(isinstance(v, (list, dict)) for v in value.values())
if inline or simple:
@ -328,7 +330,7 @@ class _SummaryWriter:
return len(parent_prefix) * " " + child_prefix
def _separate_terms(word: str) -> List[str]:
def _separate_terms(word: str) -> list[str]:
"""
>>> _separate_terms("FooBar-foo")
['foo', 'bar', 'foo']

View file

@ -3,8 +3,10 @@ difficult to express as a JSON Schema (or that are not supported by the current
JSON Schema library).
"""
import collections
import itertools
from inspect import cleandoc
from typing import Mapping, TypeVar
from typing import Generator, Iterable, Mapping, TypeVar
from .error_reporting import ValidationError
@ -19,8 +21,7 @@ class RedefiningStaticFieldAsDynamic(ValidationError):
"""
__doc__ = _DESC
_URL = (
"https://packaging.python.org/en/latest/specifications/"
"pyproject-toml/#dynamic"
"https://packaging.python.org/en/latest/specifications/pyproject-toml/#dynamic"
)
@ -31,6 +32,24 @@ class IncludedDependencyGroupMustExist(ValidationError):
_URL = "https://peps.python.org/pep-0735/"
class ImportNameCollision(ValidationError):
_DESC = """According to PEP 794:
All import-names and import-namespaces items must be unique.
"""
__doc__ = _DESC
_URL = "https://peps.python.org/pep-0794/"
class ImportNameMissing(ValidationError):
_DESC = """According to PEP 794:
An import name must have all parents listed.
"""
__doc__ = _DESC
_URL = "https://peps.python.org/pep-0794/"
def validate_project_dynamic(pyproject: T) -> T:
project_table = pyproject.get("project", {})
dynamic = project_table.get("dynamic", [])
@ -79,4 +98,54 @@ def validate_include_depenency(pyproject: T) -> T:
return pyproject
EXTRA_VALIDATIONS = (validate_project_dynamic, validate_include_depenency)
def _remove_private(items: Iterable[str]) -> Generator[str, None, None]:
for item in items:
yield item.partition(";")[0].rstrip()
def validate_import_name_issues(pyproject: T) -> T:
project = pyproject.get("project", {})
import_names = collections.Counter(_remove_private(project.get("import-names", [])))
import_namespaces = collections.Counter(
_remove_private(project.get("import-namespaces", []))
)
duplicated = [k for k, v in (import_names + import_namespaces).items() if v > 1]
if duplicated:
raise ImportNameCollision(
message="Duplicated names are not allowed in import-names/import-namespaces",
value=duplicated,
name="data.project.importnames(paces)",
definition={
"description": cleandoc(ImportNameCollision._DESC),
"see": ImportNameCollision._URL,
},
rule="PEP 794",
)
names = frozenset(import_names + import_namespaces)
for name in names:
for parent in itertools.accumulate(
name.split(".")[:-1], lambda a, b: f"{a}.{b}"
):
if parent not in names:
raise ImportNameMissing(
message="All parents of an import name must also be listed in import-namespace/import-names",
value=name,
name="data.project.importnames(paces)",
definition={
"description": cleandoc(ImportNameMissing._DESC),
"see": ImportNameMissing._URL,
},
rule="PEP 794",
)
return pyproject
EXTRA_VALIDATIONS = (
validate_project_dynamic,
validate_include_depenency,
validate_import_name_issues,
)

View file

@ -7,7 +7,9 @@ The correspondence is given by replacing the ``_`` character in the name of the
function with a ``-`` to obtain the format name and vice versa.
"""
import builtins
from __future__ import annotations
import keyword
import logging
import os
import re
@ -16,6 +18,8 @@ import typing
from itertools import chain as _chain
if typing.TYPE_CHECKING:
import builtins
from typing_extensions import Literal
_logger = logging.getLogger(__name__)
@ -54,7 +58,9 @@ VERSION_PATTERN = r"""
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
"""
VERSION_REGEX = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.X | re.I)
VERSION_REGEX = re.compile(
r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE
)
def pep440(version: str) -> bool:
@ -68,7 +74,7 @@ def pep440(version: str) -> bool:
# PEP 508
PEP508_IDENTIFIER_PATTERN = r"([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])"
PEP508_IDENTIFIER_REGEX = re.compile(f"^{PEP508_IDENTIFIER_PATTERN}$", re.I)
PEP508_IDENTIFIER_REGEX = re.compile(f"^{PEP508_IDENTIFIER_PATTERN}$", re.IGNORECASE)
def pep508_identifier(name: str) -> bool:
@ -93,9 +99,9 @@ try:
"""
try:
_req.Requirement(value)
return True
except _req.InvalidRequirement:
return False
return True
except ImportError: # pragma: no cover
_logger.warning(
@ -104,7 +110,7 @@ except ImportError: # pragma: no cover
"To enforce validation, please install `packaging`."
)
def pep508(value: str) -> bool:
def pep508(value: str) -> bool: # noqa: ARG001
return True
@ -163,7 +169,7 @@ class _TroveClassifier:
option (classifiers will be validated anyway during the upload to PyPI).
"""
downloaded: typing.Union[None, "Literal[False]", typing.Set[str]]
downloaded: None | Literal[False] | set[str]
"""
None => not cached yet
False => unavailable
@ -200,7 +206,7 @@ class _TroveClassifier:
_logger.debug(msg)
try:
self.downloaded = set(_download_classifiers().splitlines())
except Exception:
except Exception: # noqa: BLE001
self.downloaded = False
_logger.debug("Problem with download, skipping validation")
return True
@ -253,21 +259,23 @@ def url(value: str) -> bool:
"`scheme` prefix in your URL (e.g. 'http://'). "
f"Given value: {value}"
)
if not (value.startswith("/") or value.startswith("\\") or "@" in value):
if not (value.startswith(("/", "\\")) or "@" in value):
parts = urlparse(f"http://{value}")
return bool(parts.scheme and parts.netloc)
except Exception:
except Exception: # noqa: BLE001
return False
# https://packaging.python.org/specifications/entry-points/
ENTRYPOINT_PATTERN = r"[^\[\s=]([^=]*[^\s=])?"
ENTRYPOINT_REGEX = re.compile(f"^{ENTRYPOINT_PATTERN}$", re.I)
ENTRYPOINT_REGEX = re.compile(f"^{ENTRYPOINT_PATTERN}$", re.IGNORECASE)
RECOMMEDED_ENTRYPOINT_PATTERN = r"[\w.-]+"
RECOMMEDED_ENTRYPOINT_REGEX = re.compile(f"^{RECOMMEDED_ENTRYPOINT_PATTERN}$", re.I)
RECOMMEDED_ENTRYPOINT_REGEX = re.compile(
f"^{RECOMMEDED_ENTRYPOINT_PATTERN}$", re.IGNORECASE
)
ENTRYPOINT_GROUP_PATTERN = r"\w+(\.\w+)*"
ENTRYPOINT_GROUP_REGEX = re.compile(f"^{ENTRYPOINT_GROUP_PATTERN}$", re.I)
ENTRYPOINT_GROUP_REGEX = re.compile(f"^{ENTRYPOINT_GROUP_PATTERN}$", re.IGNORECASE)
def python_identifier(value: str) -> bool:
@ -368,11 +376,41 @@ def uint16(value: builtins.int) -> bool:
return 0 <= value < 2**16
def uint(value: builtins.int) -> bool:
def uint32(value: builtins.int) -> bool:
r"""Unsigned 32-bit integer (:math:`0 \leq x < 2^{32}`)"""
return 0 <= value < 2**32
def uint64(value: builtins.int) -> bool:
r"""Unsigned 64-bit integer (:math:`0 \leq x < 2^{64}`)"""
return 0 <= value < 2**64
def uint(value: builtins.int) -> bool:
r"""Signed 64-bit integer (:math:`0 \leq x < 2^{64}`)"""
return 0 <= value < 2**64
def int8(value: builtins.int) -> bool:
r"""Signed 8-bit integer (:math:`-2^{7} \leq x < 2^{7}`)"""
return -(2**7) <= value < 2**7
def int16(value: builtins.int) -> bool:
r"""Signed 16-bit integer (:math:`-2^{15} \leq x < 2^{15}`)"""
return -(2**15) <= value < 2**15
def int32(value: builtins.int) -> bool:
r"""Signed 32-bit integer (:math:`-2^{31} \leq x < 2^{31}`)"""
return -(2**31) <= value < 2**31
def int64(value: builtins.int) -> bool:
r"""Signed 64-bit integer (:math:`-2^{63} \leq x < 2^{63}`)"""
return -(2**63) <= value < 2**63
def int(value: builtins.int) -> bool:
r"""Signed 64-bit integer (:math:`-2^{63} \leq x < 2^{63}`)"""
return -(2**63) <= value < 2**63
@ -387,9 +425,9 @@ try:
"""
try:
_licenses.canonicalize_license_expression(value)
return True
except _licenses.InvalidLicenseExpression:
return False
return True
except ImportError: # pragma: no cover
_logger.warning(
@ -398,5 +436,29 @@ except ImportError: # pragma: no cover
"To enforce validation, please install `packaging>=24.2`."
)
def SPDX(value: str) -> bool:
def SPDX(value: str) -> bool: # noqa: ARG001
return True
VALID_IMPORT_NAME = re.compile(
r"""
^ # start of string
[A-Za-z_][A-Za-z_0-9]+ # a valid Python identifier
(?:\.[A-Za-z_][A-Za-z_0-9]*)* # optionally followed by .identifier's
(?:\s*;\s*private)? # optionally followed by ; private
$ # end of string
""",
re.VERBOSE,
)
def import_name(value: str) -> bool:
"""This is a valid import name. It has to be series of python identifiers
(not keywords), separated by dots, optionally followed by a semicolon and
the keyword "private".
"""
if VALID_IMPORT_NAME.match(value) is None:
return False
idents, _, _ = value.partition(";")
return all(not keyword.iskeyword(ident) for ident in idents.rstrip().split("."))

View file

@ -134,6 +134,15 @@ def read_configuration(
if "ext-modules" in setuptools_table:
_ExperimentalConfiguration.emit(subject="[tool.setuptools.ext-modules]")
fields = ("import-names", "import-namespaces")
places = (project_table, project_table.get("dynamic", []))
if any(field in place for field in fields for place in places):
raise NotImplementedError(
"Setuptools does not support `import-names` and `import-namespaces`"
" in `pyproject.toml` yet. If your are interested in this feature, "
" please consider submitting a contribution via pull requests."
)
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}}

View file

@ -116,7 +116,7 @@
"type": "object",
"additionalProperties": false,
"propertyNames": {
"anyOf": [{"type": "string", "format": "python-module-name"}, {"const": "*"}]
"anyOf": [{"$ref": "#/definitions/package-name"}, {"const": "*"}]
},
"patternProperties": {
"^.*$": {"type": "array", "items": {"type": "string"}}
@ -140,7 +140,7 @@
"type": "object",
"additionalProperties": false,
"propertyNames": {
"anyOf": [{"type": "string", "format": "python-module-name"}, {"const": "*"}]
"anyOf": [{"$ref": "#/definitions/package-name"}, {"const": "*"}]
},
"patternProperties": {
"^.*$": {"type": "array", "items": {"type": "string"}}