check.py (4345B)
1 # This file is dual licensed under the terms of the Apache License, Version 2 # 2.0, and the BSD License. See the LICENSE file in the root of this repository 3 # for complete details. 4 5 import itertools 6 import json 7 import os.path 8 import xmlrpc.client 9 10 import invoke 11 import pkg_resources 12 import progress.bar 13 14 from packaging.version import Version 15 16 from .paths import CACHE 17 18 19 def _parse_version(value): 20 try: 21 return Version(value) 22 except ValueError: 23 return None 24 25 26 @invoke.task 27 def pep440(cached=False): 28 cache_path = os.path.join(CACHE, "pep440.json") 29 30 # If we were given --cached, then we want to attempt to use cached data if 31 # possible 32 if cached: 33 try: 34 with open(cache_path) as fp: 35 data = json.load(fp) 36 except Exception: 37 data = None 38 else: 39 data = None 40 41 # If we don't have data, then let's go fetch it from PyPI 42 if data is None: 43 bar = progress.bar.ShadyBar("Fetching Versions") 44 client = xmlrpc.client.Server("https://pypi.python.org/pypi") 45 46 data = { 47 project: client.package_releases(project, True) 48 for project in bar.iter(client.list_packages()) 49 } 50 51 os.makedirs(os.path.dirname(cache_path), exist_ok=True) 52 with open(cache_path, "w") as fp: 53 json.dump(data, fp) 54 55 # Get a list of all of the version numbers on PyPI 56 all_versions = list(itertools.chain.from_iterable(data.values())) 57 58 # Determine the total number of versions which are compatible with the 59 # current routine 60 parsed_versions = [ 61 _parse_version(v) for v in all_versions if _parse_version(v) is not None 62 ] 63 64 # Determine a list of projects that sort exactly the same between 65 # pkg_resources and PEP 440 66 compatible_sorting = [ 67 project 68 for project, versions in data.items() 69 if ( 70 sorted(versions, key=pkg_resources.parse_version) 71 == sorted((x for x in versions if _parse_version(x)), key=Version) 72 ) 73 ] 74 75 # Determine a list of projects that sort exactly the same between 76 # pkg_resources and PEP 440 when invalid versions are filtered out 77 filtered_compatible_sorting = [ 78 project 79 for project, versions in ( 80 (p, [v for v in vs if _parse_version(v) is not None]) 81 for p, vs in data.items() 82 ) 83 if ( 84 sorted(versions, key=pkg_resources.parse_version) 85 == sorted(versions, key=Version) 86 ) 87 ] 88 89 # Determine a list of projects which do not have any versions that are 90 # valid with PEP 440 and which have any versions registered 91 only_invalid_versions = [ 92 project 93 for project, versions in data.items() 94 if (versions and not [v for v in versions if _parse_version(v) is not None]) 95 ] 96 97 # Determine a list of projects which have matching latest versions between 98 # pkg_resources and PEP 440 99 differing_latest_versions = [ 100 project 101 for project, versions in data.items() 102 if ( 103 sorted(versions, key=pkg_resources.parse_version)[-1:] 104 != sorted((x for x in versions if _parse_version(x)), key=Version)[-1:] 105 ) 106 ] 107 108 # Print out our findings 109 print( 110 "Total Version Compatibility: {}/{} ({:.2%})".format( 111 len(parsed_versions), 112 len(all_versions), 113 len(parsed_versions) / len(all_versions), 114 ) 115 ) 116 print( 117 "Total Sorting Compatibility (Unfiltered): {}/{} ({:.2%})".format( 118 len(compatible_sorting), len(data), len(compatible_sorting) / len(data) 119 ) 120 ) 121 print( 122 "Total Sorting Compatibility (Filtered): {}/{} ({:.2%})".format( 123 len(filtered_compatible_sorting), 124 len(data), 125 len(filtered_compatible_sorting) / len(data), 126 ) 127 ) 128 print( 129 "Projects with No Compatible Versions: {}/{} ({:.2%})".format( 130 len(only_invalid_versions), 131 len(data), 132 len(only_invalid_versions) / len(data), 133 ) 134 ) 135 print( 136 "Projects with Differing Latest Version: {}/{} ({:.2%})".format( 137 len(differing_latest_versions), 138 len(data), 139 len(differing_latest_versions) / len(data), 140 ) 141 )