summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/tools/third_party/packaging/tasks/check.py
blob: b0896e1f764bb6dd4bdaed053e9c60b4eebbb477 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.

import itertools
import json
import os.path
import xmlrpc.client

import invoke
import pkg_resources
import progress.bar

from packaging.version import Version

from .paths import CACHE


def _parse_version(value):
    try:
        return Version(value)
    except ValueError:
        return None


@invoke.task
def pep440(cached=False):
    cache_path = os.path.join(CACHE, "pep440.json")

    # If we were given --cached, then we want to attempt to use cached data if
    # possible
    if cached:
        try:
            with open(cache_path) as fp:
                data = json.load(fp)
        except Exception:
            data = None
    else:
        data = None

    # If we don't have data, then let's go fetch it from PyPI
    if data is None:
        bar = progress.bar.ShadyBar("Fetching Versions")
        client = xmlrpc.client.Server("https://pypi.python.org/pypi")

        data = {
            project: client.package_releases(project, True)
            for project in bar.iter(client.list_packages())
        }

        os.makedirs(os.path.dirname(cache_path), exist_ok=True)
        with open(cache_path, "w") as fp:
            json.dump(data, fp)

    # Get a list of all of the version numbers on PyPI
    all_versions = list(itertools.chain.from_iterable(data.values()))

    # Determine the total number of versions which are compatible with the
    # current routine
    parsed_versions = [
        _parse_version(v) for v in all_versions if _parse_version(v) is not None
    ]

    # Determine a list of projects that sort exactly the same between
    # pkg_resources and PEP 440
    compatible_sorting = [
        project
        for project, versions in data.items()
        if (
            sorted(versions, key=pkg_resources.parse_version)
            == sorted((x for x in versions if _parse_version(x)), key=Version)
        )
    ]

    # Determine a list of projects that sort exactly the same between
    # pkg_resources and PEP 440 when invalid versions are filtered out
    filtered_compatible_sorting = [
        project
        for project, versions in (
            (p, [v for v in vs if _parse_version(v) is not None])
            for p, vs in data.items()
        )
        if (
            sorted(versions, key=pkg_resources.parse_version)
            == sorted(versions, key=Version)
        )
    ]

    # Determine a list of projects which do not have any versions that are
    # valid with PEP 440 and which have any versions registered
    only_invalid_versions = [
        project
        for project, versions in data.items()
        if (versions and not [v for v in versions if _parse_version(v) is not None])
    ]

    # Determine a list of projects which have matching latest versions between
    # pkg_resources and PEP 440
    differing_latest_versions = [
        project
        for project, versions in data.items()
        if (
            sorted(versions, key=pkg_resources.parse_version)[-1:]
            != sorted((x for x in versions if _parse_version(x)), key=Version)[-1:]
        )
    ]

    # Print out our findings
    print(
        "Total Version Compatibility:              {}/{} ({:.2%})".format(
            len(parsed_versions),
            len(all_versions),
            len(parsed_versions) / len(all_versions),
        )
    )
    print(
        "Total Sorting Compatibility (Unfiltered): {}/{} ({:.2%})".format(
            len(compatible_sorting), len(data), len(compatible_sorting) / len(data)
        )
    )
    print(
        "Total Sorting Compatibility (Filtered):   {}/{} ({:.2%})".format(
            len(filtered_compatible_sorting),
            len(data),
            len(filtered_compatible_sorting) / len(data),
        )
    )
    print(
        "Projects with No Compatible Versions:     {}/{} ({:.2%})".format(
            len(only_invalid_versions),
            len(data),
            len(only_invalid_versions) / len(data),
        )
    )
    print(
        "Projects with Differing Latest Version:   {}/{} ({:.2%})".format(
            len(differing_latest_versions),
            len(data),
            len(differing_latest_versions) / len(data),
        )
    )