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),
)
)
|