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 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
|
import posixpath
import re
import py
from pkg_resources import parse_version, Requirement
from .types import CompareMixin
from .validation import normalize_name
ALLOWED_ARCHIVE_EXTS = set(
".dmg .deb .msi .rpm .exe .egg .whl .tar.gz "
".tar.bz2 .tar .tgz .zip .doc.zip".split())
_releasefile_suffix_rx = re.compile(r"(\.zip|\.tar\.gz|\.tgz|\.tar\.bz2|"
"\.doc\.zip|"
"\.macosx-\d+.*|"
"\.linux-.*|"
"\.[^\.]*\.rpm|"
"\.win-amd68-py[23]\.\d\..*|"
"\.win32-py[23]\.\d\..*|"
"\.win.*\..*|"
"-(?:py|cp|ip|pp|jy)[23][\d\.]*.*\..*|"
")$", re.IGNORECASE)
# see also PEP425 for supported "python tags"
_pyversion_type_rex = re.compile(
r"(?:py|cp|ip|pp|jy)([\d\.py]+).*\.(exe|egg|msi|whl)", re.IGNORECASE)
_ext2type = dict(exe="bdist_wininst", egg="bdist_egg", msi="bdist_msi",
whl="bdist_wheel")
_wheel_file_re = re.compile(
r"""^(?P<namever>(?P<name>.+?)-(?P<ver>.*?))
((-(?P<build>\d.*?))?-(?P<pyver>.+?)-(?P<abi>.+?)-(?P<plat>.+?)
\.whl|\.dist-info)$""",
re.VERBOSE)
_pep404_nameversion_re = re.compile(
r"^(?P<name>[^.]+?)-(?P<ver>"
r"(?:[1-9]\d*!)?" # [N!]
r"(?:0|[1-9]\d*)" # N
r"(?:\.(?:0|[1-9]\d*))*" # (.N)*
r"(?:(?:a|b|rc)(?:0|[1-9]\d*))?" # [{a|b|rc}N]
r"(?:\.post(?:0|[1-9]\d*))?" # [.postN]
r"(?:\.dev(?:0|[1-9]\d*))?" # [.devN]
r"(?:\+(?:[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?" # local version
r")$")
_legacy_nameversion_re = re.compile(
r"^(?P<name>[^.]+?)-(?P<ver>"
r"(?:[1-9]\d*!)?" # [N!]
r"(?:0|[1-9]\d*)" # N
r"(?:\.(?:0|[1-9]\d*))*" # (.N)*
r"(?:(?:a|b|rc|alpha|beta)(?:0|[1-9]\d*))?" # [{a|b|rc}N]
r"(?:\.post(?:0|[1-9]\d*))?" # [.postN]
r"(?:\.dev(?:0|[1-9]\d*))?" # [.devN]
r"(?:\-(?:[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?" # local version
r")$")
def get_pyversion_filetype(basename):
_,_,suffix = splitbasename(basename)
if suffix in (".zip", ".tar.gz", ".tgz", "tar.bz2"):
return ("source", "sdist")
m = _pyversion_type_rex.search(suffix)
if not m:
return ("any", "bdist_dumb")
pyversion, ext = m.groups()
if pyversion == "2.py3": # "universal" wheel with no C
pyversion = "2.7" # arbitrary but pypi/devpi makes no special use
# of "pyversion" anyway?!
elif "." not in pyversion:
pyversion = ".".join(pyversion)
return (pyversion, _ext2type[ext])
def splitbasename(path, checkarch=True):
nameversion, ext = splitext_archive(path)
if ext == '.whl':
m = _wheel_file_re.match(path)
if m:
info = m.groupdict()
return (
info['name'],
info['ver'],
'-%s-%s-%s.whl' % (info['pyver'], info['abi'], info['plat']))
if checkarch and ext.lower() not in ALLOWED_ARCHIVE_EXTS:
raise ValueError("invalid archive type %r in: %s" % (ext, path))
m = _releasefile_suffix_rx.search(path)
if m:
ext = m.group(1)
if len(ext):
nameversion = path[:-len(ext)]
else:
nameversion = path
if '-' not in nameversion: # no version
return nameversion, "", ext
m = _pep404_nameversion_re.match(nameversion)
if m:
(projectname, version) = m.groups()
return projectname, version, ext
m = _legacy_nameversion_re.match(nameversion)
if m:
(projectname, version) = m.groups()
return projectname, version, ext
(projectname, version) = nameversion.rsplit('-', 1)
return projectname, version, ext
DOCZIPSUFFIX = ".doc.zip"
def splitext_archive(basename):
basename = getattr(basename, "basename", basename)
if basename.lower().endswith(DOCZIPSUFFIX):
ext = basename[-len(DOCZIPSUFFIX):]
base = basename[:-len(DOCZIPSUFFIX)]
else:
base, ext = posixpath.splitext(basename)
if base.lower().endswith('.tar'):
ext = base[-4:] + ext
base = base[:-4]
return base, ext
class Version(CompareMixin):
def __init__(self, versionstring):
self.string = versionstring
self.cmpval = parse_version(versionstring)
def __str__(self):
return self.string
def is_prerelease(self):
if hasattr(self.cmpval, 'is_prerelease'):
return self.cmpval.is_prerelease
# backward compatibility
for x in self.cmpval:
if x.startswith('*') and x < '*final':
return True
return False
class BasenameMeta(CompareMixin):
def __init__(self, obj, sameproject=False):
self.obj = obj
basename = getattr(obj, "basename", obj)
if not isinstance(basename, py.builtin._basestring):
raise ValueError("need object with basename attribute")
assert "/" not in basename, (obj, basename)
name, version, ext = splitbasename(basename, checkarch=False)
self.name = name
self.version = version
self.ext = ext
if sameproject:
self.cmpval = (parse_version(version), normalize_name(name), ext)
else:
self.cmpval = (normalize_name(name), parse_version(version), ext)
def __repr__(self):
return "<BasenameMeta name=%r version=%r>" %(self.name, self.version)
def sorted_sameproject_links(links):
s = sorted((BasenameMeta(link, sameproject=True)
for link in links), reverse=True)
return [x.obj for x in s]
def get_latest_version(seq, stable=False):
if not seq:
return
versions = map(Version, seq)
if stable:
versions = [x for x in versions if not x.is_prerelease()]
if not versions:
return
return max(versions).string
def get_sorted_versions(versions, reverse=True, stable=False):
versions = sorted(map(Version, versions), reverse=reverse)
if stable:
versions = [x for x in versions if not x.is_prerelease()]
return [x.string for x in versions]
def is_archive_of_project(basename, targetname):
nameversion, ext = splitext_archive(basename)
# we don't check for strict equality because pypi currently
# shows "x-docs-1.0.tar.gz" for targetname "x" (however it was uploaded)
if not normalize_name(nameversion).startswith(targetname):
return False
if ext.lower() not in ALLOWED_ARCHIVE_EXTS:
return False
return True
def parse_requirement(s):
return Requirement.parse(s)
|