mirror of
https://github.com/pytorch/pytorch.git
synced 2025-10-20 21:14:14 +08:00
As per title. Sorry for the churn in the main commit. Pull Request resolved: https://github.com/pytorch/pytorch/pull/158853 Approved by: https://github.com/seemethere, https://github.com/Skylion007, https://github.com/jingsh, https://github.com/malfet, https://github.com/ZainRizvi
250 lines
7.9 KiB
Python
250 lines
7.9 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import concurrent.futures
|
|
import json
|
|
import logging
|
|
import os
|
|
import sys
|
|
from enum import Enum
|
|
from pathlib import Path
|
|
from typing import NamedTuple
|
|
|
|
from packaging.specifiers import SpecifierSet
|
|
from packaging.version import Version
|
|
|
|
|
|
if sys.version_info >= (3, 11):
|
|
import tomllib
|
|
else:
|
|
import tomli as tomllib # type: ignore[import-not-found]
|
|
|
|
|
|
class LintSeverity(str, Enum):
|
|
ERROR = "error"
|
|
WARNING = "warning"
|
|
ADVICE = "advice"
|
|
DISABLED = "disabled"
|
|
|
|
|
|
class LintMessage(NamedTuple):
|
|
path: str | None
|
|
line: int | None
|
|
char: int | None
|
|
code: str
|
|
severity: LintSeverity
|
|
name: str
|
|
original: str | None
|
|
replacement: str | None
|
|
description: str | None
|
|
|
|
|
|
def format_error_message(
|
|
filename: str,
|
|
error: Exception | None = None,
|
|
*,
|
|
message: str | None = None,
|
|
) -> LintMessage:
|
|
if message is None and error is not None:
|
|
message = f"Failed due to {error.__class__.__name__}:\n{error}"
|
|
return LintMessage(
|
|
path=filename,
|
|
line=None,
|
|
char=None,
|
|
code="PYPROJECT",
|
|
severity=LintSeverity.ERROR,
|
|
name="pyproject.toml consistency",
|
|
original=None,
|
|
replacement=None,
|
|
description=message,
|
|
)
|
|
|
|
|
|
def check_file(filename: str) -> list[LintMessage]:
|
|
path = Path(filename).absolute()
|
|
try:
|
|
pyproject = tomllib.loads(path.read_text(encoding="utf-8"))
|
|
except (tomllib.TOMLDecodeError, OSError) as err:
|
|
return [format_error_message(filename, err)]
|
|
|
|
if not (isinstance(pyproject, dict) and isinstance(pyproject.get("project"), dict)):
|
|
return [
|
|
format_error_message(
|
|
filename,
|
|
message=(
|
|
"'project' section in pyproject.toml must present and be a table."
|
|
),
|
|
)
|
|
]
|
|
|
|
project = pyproject["project"]
|
|
requires_python = project.get("requires-python")
|
|
if requires_python is not None:
|
|
if not isinstance(requires_python, str):
|
|
return [
|
|
format_error_message(
|
|
filename,
|
|
message="'project.requires-python' must be a string.",
|
|
)
|
|
]
|
|
|
|
python_major = 3
|
|
specifier_set = SpecifierSet(requires_python)
|
|
for specifier in specifier_set:
|
|
if Version(specifier.version).major != python_major:
|
|
return [
|
|
format_error_message(
|
|
filename,
|
|
message=(
|
|
"'project.requires-python' must only specify "
|
|
f"Python {python_major} versions, but found {specifier.version}."
|
|
),
|
|
)
|
|
]
|
|
|
|
large_minor = 1000
|
|
supported_python_versions = list(
|
|
specifier_set.filter(
|
|
f"{python_major}.{minor}" for minor in range(large_minor + 1)
|
|
)
|
|
)
|
|
if not supported_python_versions:
|
|
return [
|
|
format_error_message(
|
|
filename,
|
|
message=(
|
|
"'project.requires-python' must specify at least one "
|
|
f"Python {python_major} version, but found {requires_python!r}."
|
|
),
|
|
)
|
|
]
|
|
if f"{python_major}.0" in supported_python_versions:
|
|
return [
|
|
format_error_message(
|
|
filename,
|
|
message=(
|
|
"'project.requires-python' must specify a minimum version, "
|
|
f"but found {requires_python!r}."
|
|
),
|
|
)
|
|
]
|
|
# if f"{python_major}.{large_minor}" in supported_python_versions:
|
|
# return [
|
|
# format_error_message(
|
|
# filename,
|
|
# message=(
|
|
# "'project.requires-python' must specify a maximum version, "
|
|
# f"but found {requires_python!r}."
|
|
# ),
|
|
# )
|
|
# ]
|
|
|
|
classifiers = project.get("classifiers")
|
|
if not (
|
|
isinstance(classifiers, list)
|
|
and all(isinstance(c, str) for c in classifiers)
|
|
):
|
|
return [
|
|
format_error_message(
|
|
filename,
|
|
message="'project.classifiers' must be an array of strings.",
|
|
)
|
|
]
|
|
if len(set(classifiers)) != len(classifiers):
|
|
return [
|
|
format_error_message(
|
|
filename,
|
|
message="'project.classifiers' must not contain duplicates.",
|
|
)
|
|
]
|
|
|
|
# python_version_classifiers = [
|
|
# c
|
|
# for c in classifiers
|
|
# if (
|
|
# c.startswith("Programming Language :: Python :: ")
|
|
# and not c.endswith((f":: {python_major}", f":: {python_major} :: Only"))
|
|
# )
|
|
# ]
|
|
# if python_version_classifiers:
|
|
# python_version_classifier_set = set(python_version_classifiers)
|
|
# supported_python_version_classifier_set = {
|
|
# f"Programming Language :: Python :: {v}"
|
|
# for v in supported_python_versions
|
|
# }
|
|
# if python_version_classifier_set != supported_python_version_classifier_set:
|
|
# missing_classifiers = sorted(
|
|
# supported_python_version_classifier_set
|
|
# - python_version_classifier_set
|
|
# )
|
|
# extra_classifiers = sorted(
|
|
# python_version_classifier_set
|
|
# - supported_python_version_classifier_set
|
|
# )
|
|
# if missing_classifiers:
|
|
# return [
|
|
# format_error_message(
|
|
# filename,
|
|
# message=(
|
|
# "'project.classifiers' is missing the following classifier(s):\n"
|
|
# + "\n".join(f" {c!r}" for c in missing_classifiers)
|
|
# ),
|
|
# )
|
|
# ]
|
|
# if extra_classifiers:
|
|
# return [
|
|
# format_error_message(
|
|
# filename,
|
|
# message=(
|
|
# "'project.classifiers' contains extra classifier(s):\n"
|
|
# + "\n".join(f" {c!r}" for c in extra_classifiers)
|
|
# ),
|
|
# )
|
|
# ]
|
|
|
|
return []
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(
|
|
description="Check consistency of pyproject.toml files.",
|
|
fromfile_prefix_chars="@",
|
|
)
|
|
parser.add_argument(
|
|
"--verbose",
|
|
action="store_true",
|
|
help="verbose logging",
|
|
)
|
|
parser.add_argument(
|
|
"filenames",
|
|
nargs="+",
|
|
help="paths to lint",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
logging.basicConfig(
|
|
format="<%(processName)s:%(levelname)s> %(message)s",
|
|
level=logging.NOTSET
|
|
if args.verbose
|
|
else logging.DEBUG
|
|
if len(args.filenames) < 1000
|
|
else logging.INFO,
|
|
stream=sys.stderr,
|
|
)
|
|
|
|
with concurrent.futures.ProcessPoolExecutor(
|
|
max_workers=os.cpu_count(),
|
|
) as executor:
|
|
futures = {executor.submit(check_file, x): x for x in args.filenames}
|
|
for future in concurrent.futures.as_completed(futures):
|
|
try:
|
|
for lint_message in future.result():
|
|
print(json.dumps(lint_message._asdict()), flush=True)
|
|
except Exception:
|
|
logging.critical('Failed at "%s".', futures[future])
|
|
raise
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|