Add optional requires_python field to third-party stubs metadata (#10724)

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Ali Hamdan
2023-09-24 19:39:12 +02:00
committed by GitHub
parent b1d4f29ed4
commit c5dde1e720
6 changed files with 127 additions and 29 deletions

View File

@@ -25,8 +25,9 @@ from typing_extensions import Annotated, TypeAlias
import tomli
from parse_metadata import PackageDependencies, get_recursive_requirements
from parse_metadata import PackageDependencies, get_recursive_requirements, read_metadata
from utils import (
PYTHON_VERSION,
VERSIONS_RE as VERSION_LINE_RE,
VenvInfo,
colored,
@@ -307,6 +308,7 @@ def add_third_party_files(
class TestResults(NamedTuple):
exit_code: int
files_checked: int
packages_skipped: int = 0
def test_third_party_distribution(
@@ -393,6 +395,9 @@ def install_requirements_for_venv(venv_info: VenvInfo, args: TestConfig, externa
def setup_virtual_environments(distributions: dict[str, PackageDependencies], args: TestConfig, tempdir: Path) -> None:
"""Logic necessary for testing stubs with non-types dependencies in isolated environments."""
if not distributions:
return # hooray! Nothing to do
# STAGE 1: Determine which (if any) stubs packages require virtual environments.
# Group stubs packages according to their external-requirements sets
@@ -471,6 +476,7 @@ def setup_virtual_environments(distributions: dict[str, PackageDependencies], ar
def test_third_party_stubs(code: int, args: TestConfig, tempdir: Path) -> TestResults:
print("Testing third-party packages...")
files_checked = 0
packages_skipped = 0
gitignore_spec = get_gitignore_spec()
distributions_to_check: dict[str, PackageDependencies] = {}
@@ -480,6 +486,21 @@ def test_third_party_stubs(code: int, args: TestConfig, tempdir: Path) -> TestRe
if spec_matches_path(gitignore_spec, distribution_path):
continue
metadata = read_metadata(distribution)
if not metadata.requires_python.contains(PYTHON_VERSION):
msg = (
f"skipping {distribution!r} (requires Python {metadata.requires_python}; "
f"test is being run using Python {PYTHON_VERSION})"
)
print(colored(msg, "yellow"))
packages_skipped += 1
continue
if not metadata.requires_python.contains(args.version):
msg = f"skipping {distribution!r} for target Python {args.version} (requires Python {metadata.requires_python})"
print(colored(msg, "yellow"))
packages_skipped += 1
continue
if (
distribution_path in args.filter
or Path("stubs") in args.filter
@@ -487,40 +508,51 @@ def test_third_party_stubs(code: int, args: TestConfig, tempdir: Path) -> TestRe
):
distributions_to_check[distribution] = get_recursive_requirements(distribution)
# If it's the first time test_third_party_stubs() has been called during this session,
# setup the necessary virtual environments for testing the third-party stubs.
# It should only be necessary to call setup_virtual_environments() once per session.
if not _DISTRIBUTION_TO_VENV_MAPPING:
setup_virtual_environments(distributions_to_check, args, tempdir)
# Setup the necessary virtual environments for testing the third-party stubs.
# Note that some stubs may not be tested on all Python versions
# (due to version incompatibilities),
# so we can't guarantee that setup_virtual_environments()
# will only be called once per session.
distributions_without_venv = {
distribution: requirements
for distribution, requirements in distributions_to_check.items()
if distribution not in _DISTRIBUTION_TO_VENV_MAPPING
}
setup_virtual_environments(distributions_without_venv, args, tempdir)
assert _DISTRIBUTION_TO_VENV_MAPPING.keys() == distributions_to_check.keys()
# Check that there is a venv for every distribution we're testing.
# Some venvs may exist from previous runs but are skipped in this run.
assert _DISTRIBUTION_TO_VENV_MAPPING.keys() >= distributions_to_check.keys()
for distribution, venv_info in _DISTRIBUTION_TO_VENV_MAPPING.items():
for distribution in distributions_to_check:
venv_info = _DISTRIBUTION_TO_VENV_MAPPING[distribution]
non_types_dependencies = venv_info.python_exe != sys.executable
this_code, checked = test_third_party_distribution(
this_code, checked, _ = test_third_party_distribution(
distribution, args, venv_info=venv_info, non_types_dependencies=non_types_dependencies
)
code = max(code, this_code)
files_checked += checked
return TestResults(code, files_checked)
return TestResults(code, files_checked, packages_skipped)
def test_typeshed(code: int, args: TestConfig, tempdir: Path) -> TestResults:
print(f"*** Testing Python {args.version} on {args.platform}")
files_checked_this_version = 0
packages_skipped_this_version = 0
stdlib_dir, stubs_dir = Path("stdlib"), Path("stubs")
if stdlib_dir in args.filter or any(stdlib_dir in path.parents for path in args.filter):
code, stdlib_files_checked = test_stdlib(code, args)
code, stdlib_files_checked, _ = test_stdlib(code, args)
files_checked_this_version += stdlib_files_checked
print()
if stubs_dir in args.filter or any(stubs_dir in path.parents for path in args.filter):
code, third_party_files_checked = test_third_party_stubs(code, args, tempdir)
code, third_party_files_checked, third_party_packages_skipped = test_third_party_stubs(code, args, tempdir)
files_checked_this_version += third_party_files_checked
packages_skipped_this_version = third_party_packages_skipped
print()
return TestResults(code, files_checked_this_version)
return TestResults(code, files_checked_this_version, packages_skipped_this_version)
def main() -> None:
@@ -531,19 +563,27 @@ def main() -> None:
exclude = args.exclude or []
code = 0
total_files_checked = 0
total_packages_skipped = 0
with tempfile.TemporaryDirectory() as td:
td_path = Path(td)
for version, platform in product(versions, platforms):
config = TestConfig(args.verbose, filter, exclude, version, platform)
code, files_checked_this_version = test_typeshed(code, args=config, tempdir=td_path)
code, files_checked_this_version, packages_skipped_this_version = test_typeshed(code, args=config, tempdir=td_path)
total_files_checked += files_checked_this_version
total_packages_skipped += packages_skipped_this_version
if code:
print_error(f"--- exit status {code}, {total_files_checked} files checked ---")
plural = "" if total_files_checked == 1 else "s"
print_error(f"--- exit status {code}, {total_files_checked} file{plural} checked ---")
sys.exit(code)
if not total_files_checked:
if total_packages_skipped:
plural = "" if total_packages_skipped == 1 else "s"
print(colored(f"--- {total_packages_skipped} package{plural} skipped ---", "yellow"))
if total_files_checked:
plural = "" if total_files_checked == 1 else "s"
print(colored(f"--- success, {total_files_checked} file{plural} checked ---", "green"))
else:
print_error("--- nothing to do; exit 1 ---")
sys.exit(1)
print(colored(f"--- success, {total_files_checked} files checked ---", "green"))
if __name__ == "__main__":