mirror of
https://github.com/welton89/RRBEC.git
synced 2026-04-06 22:14:12 +00:00
nem sei pq tantos arquivos
This commit is contained in:
@@ -1,23 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012-2022 Vinay Sajip.
|
||||
# Copyright (C) 2012-2023 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
import logging
|
||||
|
||||
__version__ = '0.3.6'
|
||||
__version__ = '0.3.9'
|
||||
|
||||
|
||||
class DistlibException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
from logging import NullHandler
|
||||
except ImportError: # pragma: no cover
|
||||
except ImportError: # pragma: no cover
|
||||
|
||||
class NullHandler(logging.Handler):
|
||||
def handle(self, record): pass
|
||||
def emit(self, record): pass
|
||||
def createLock(self): self.lock = None
|
||||
|
||||
def handle(self, record):
|
||||
pass
|
||||
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
def createLock(self):
|
||||
self.lock = None
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.addHandler(NullHandler())
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -8,6 +8,7 @@ from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
try:
|
||||
@@ -33,9 +34,8 @@ if sys.version_info[0] < 3: # pragma: no cover
|
||||
|
||||
import urllib2
|
||||
from urllib2 import (Request, urlopen, URLError, HTTPError,
|
||||
HTTPBasicAuthHandler, HTTPPasswordMgr,
|
||||
HTTPHandler, HTTPRedirectHandler,
|
||||
build_opener)
|
||||
HTTPBasicAuthHandler, HTTPPasswordMgr, HTTPHandler,
|
||||
HTTPRedirectHandler, build_opener)
|
||||
if ssl:
|
||||
from urllib2 import HTTPSHandler
|
||||
import httplib
|
||||
@@ -50,15 +50,15 @@ if sys.version_info[0] < 3: # pragma: no cover
|
||||
# Leaving this around for now, in case it needs resurrecting in some way
|
||||
# _userprog = None
|
||||
# def splituser(host):
|
||||
# """splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'."""
|
||||
# global _userprog
|
||||
# if _userprog is None:
|
||||
# import re
|
||||
# _userprog = re.compile('^(.*)@(.*)$')
|
||||
# """splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'."""
|
||||
# global _userprog
|
||||
# if _userprog is None:
|
||||
# import re
|
||||
# _userprog = re.compile('^(.*)@(.*)$')
|
||||
|
||||
# match = _userprog.match(host)
|
||||
# if match: return match.group(1, 2)
|
||||
# return None, host
|
||||
# match = _userprog.match(host)
|
||||
# if match: return match.group(1, 2)
|
||||
# return None, host
|
||||
|
||||
else: # pragma: no cover
|
||||
from io import StringIO
|
||||
@@ -67,14 +67,12 @@ else: # pragma: no cover
|
||||
from io import TextIOWrapper as file_type
|
||||
import builtins
|
||||
import configparser
|
||||
import shutil
|
||||
from urllib.parse import (urlparse, urlunparse, urljoin, quote,
|
||||
unquote, urlsplit, urlunsplit, splittype)
|
||||
from urllib.parse import (urlparse, urlunparse, urljoin, quote, unquote,
|
||||
urlsplit, urlunsplit, splittype)
|
||||
from urllib.request import (urlopen, urlretrieve, Request, url2pathname,
|
||||
pathname2url,
|
||||
HTTPBasicAuthHandler, HTTPPasswordMgr,
|
||||
HTTPHandler, HTTPRedirectHandler,
|
||||
build_opener)
|
||||
pathname2url, HTTPBasicAuthHandler,
|
||||
HTTPPasswordMgr, HTTPHandler,
|
||||
HTTPRedirectHandler, build_opener)
|
||||
if ssl:
|
||||
from urllib.request import HTTPSHandler
|
||||
from urllib.error import HTTPError, URLError, ContentTooShortError
|
||||
@@ -88,14 +86,13 @@ else: # pragma: no cover
|
||||
from itertools import filterfalse
|
||||
filter = filter
|
||||
|
||||
|
||||
try:
|
||||
from ssl import match_hostname, CertificateError
|
||||
except ImportError: # pragma: no cover
|
||||
except ImportError: # pragma: no cover
|
||||
|
||||
class CertificateError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def _dnsname_match(dn, hostname, max_wildcards=1):
|
||||
"""Matching according to RFC 6125, section 6.4.3
|
||||
|
||||
@@ -145,7 +142,6 @@ except ImportError: # pragma: no cover
|
||||
pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
|
||||
return pat.match(hostname)
|
||||
|
||||
|
||||
def match_hostname(cert, hostname):
|
||||
"""Verify that *cert* (in decoded format as returned by
|
||||
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
|
||||
@@ -178,24 +174,26 @@ except ImportError: # pragma: no cover
|
||||
dnsnames.append(value)
|
||||
if len(dnsnames) > 1:
|
||||
raise CertificateError("hostname %r "
|
||||
"doesn't match either of %s"
|
||||
% (hostname, ', '.join(map(repr, dnsnames))))
|
||||
"doesn't match either of %s" %
|
||||
(hostname, ', '.join(map(repr, dnsnames))))
|
||||
elif len(dnsnames) == 1:
|
||||
raise CertificateError("hostname %r "
|
||||
"doesn't match %r"
|
||||
% (hostname, dnsnames[0]))
|
||||
"doesn't match %r" %
|
||||
(hostname, dnsnames[0]))
|
||||
else:
|
||||
raise CertificateError("no appropriate commonName or "
|
||||
"subjectAltName fields were found")
|
||||
"subjectAltName fields were found")
|
||||
|
||||
|
||||
try:
|
||||
from types import SimpleNamespace as Container
|
||||
except ImportError: # pragma: no cover
|
||||
|
||||
class Container(object):
|
||||
"""
|
||||
A generic container for when multiple values need to be returned
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
@@ -214,12 +212,12 @@ except ImportError: # pragma: no cover
|
||||
path.
|
||||
|
||||
"""
|
||||
|
||||
# Check that a given file can be accessed with the correct mode.
|
||||
# Additionally check that `file` is not a directory, as on Windows
|
||||
# directories pass the os.access check.
|
||||
def _access_check(fn, mode):
|
||||
return (os.path.exists(fn) and os.access(fn, mode)
|
||||
and not os.path.isdir(fn))
|
||||
return (os.path.exists(fn) and os.access(fn, mode) and not os.path.isdir(fn))
|
||||
|
||||
# If we're given a path with a directory part, look it up directly rather
|
||||
# than referring to PATH directories. This includes checking relative to the
|
||||
@@ -237,7 +235,7 @@ except ImportError: # pragma: no cover
|
||||
|
||||
if sys.platform == "win32":
|
||||
# The current directory takes precedence on Windows.
|
||||
if not os.curdir in path:
|
||||
if os.curdir not in path:
|
||||
path.insert(0, os.curdir)
|
||||
|
||||
# PATHEXT is necessary to check on Windows.
|
||||
@@ -258,7 +256,7 @@ except ImportError: # pragma: no cover
|
||||
seen = set()
|
||||
for dir in path:
|
||||
normdir = os.path.normcase(dir)
|
||||
if not normdir in seen:
|
||||
if normdir not in seen:
|
||||
seen.add(normdir)
|
||||
for thefile in files:
|
||||
name = os.path.join(dir, thefile)
|
||||
@@ -277,6 +275,7 @@ else: # pragma: no cover
|
||||
from zipfile import ZipExtFile as BaseZipExtFile
|
||||
|
||||
class ZipExtFile(BaseZipExtFile):
|
||||
|
||||
def __init__(self, base):
|
||||
self.__dict__.update(base.__dict__)
|
||||
|
||||
@@ -288,6 +287,7 @@ else: # pragma: no cover
|
||||
# return None, so if an exception occurred, it will propagate
|
||||
|
||||
class ZipFile(BaseZipFile):
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
@@ -299,9 +299,11 @@ else: # pragma: no cover
|
||||
base = BaseZipFile.open(self, *args, **kwargs)
|
||||
return ZipExtFile(base)
|
||||
|
||||
|
||||
try:
|
||||
from platform import python_implementation
|
||||
except ImportError: # pragma: no cover
|
||||
except ImportError: # pragma: no cover
|
||||
|
||||
def python_implementation():
|
||||
"""Return a string identifying the Python implementation."""
|
||||
if 'PyPy' in sys.version:
|
||||
@@ -312,12 +314,12 @@ except ImportError: # pragma: no cover
|
||||
return 'IronPython'
|
||||
return 'CPython'
|
||||
|
||||
import shutil
|
||||
|
||||
import sysconfig
|
||||
|
||||
try:
|
||||
callable = callable
|
||||
except NameError: # pragma: no cover
|
||||
except NameError: # pragma: no cover
|
||||
from collections.abc import Callable
|
||||
|
||||
def callable(obj):
|
||||
@@ -358,11 +360,11 @@ except AttributeError: # pragma: no cover
|
||||
raise TypeError("expect bytes or str, not %s" %
|
||||
type(filename).__name__)
|
||||
|
||||
|
||||
try:
|
||||
from tokenize import detect_encoding
|
||||
except ImportError: # pragma: no cover
|
||||
except ImportError: # pragma: no cover
|
||||
from codecs import BOM_UTF8, lookup
|
||||
import re
|
||||
|
||||
cookie_re = re.compile(r"coding[:=]\s*([-\w.]+)")
|
||||
|
||||
@@ -401,6 +403,7 @@ except ImportError: # pragma: no cover
|
||||
bom_found = False
|
||||
encoding = None
|
||||
default = 'utf-8'
|
||||
|
||||
def read_or_stop():
|
||||
try:
|
||||
return readline()
|
||||
@@ -430,8 +433,8 @@ except ImportError: # pragma: no cover
|
||||
if filename is None:
|
||||
msg = "unknown encoding: " + encoding
|
||||
else:
|
||||
msg = "unknown encoding for {!r}: {}".format(filename,
|
||||
encoding)
|
||||
msg = "unknown encoding for {!r}: {}".format(
|
||||
filename, encoding)
|
||||
raise SyntaxError(msg)
|
||||
|
||||
if bom_found:
|
||||
@@ -440,7 +443,8 @@ except ImportError: # pragma: no cover
|
||||
if filename is None:
|
||||
msg = 'encoding problem: utf-8'
|
||||
else:
|
||||
msg = 'encoding problem for {!r}: utf-8'.format(filename)
|
||||
msg = 'encoding problem for {!r}: utf-8'.format(
|
||||
filename)
|
||||
raise SyntaxError(msg)
|
||||
encoding += '-sig'
|
||||
return encoding
|
||||
@@ -467,6 +471,7 @@ except ImportError: # pragma: no cover
|
||||
|
||||
return default, [first, second]
|
||||
|
||||
|
||||
# For converting & <-> & etc.
|
||||
try:
|
||||
from html import escape
|
||||
@@ -479,12 +484,13 @@ else:
|
||||
|
||||
try:
|
||||
from collections import ChainMap
|
||||
except ImportError: # pragma: no cover
|
||||
except ImportError: # pragma: no cover
|
||||
from collections import MutableMapping
|
||||
|
||||
try:
|
||||
from reprlib import recursive_repr as _recursive_repr
|
||||
except ImportError:
|
||||
|
||||
def _recursive_repr(fillvalue='...'):
|
||||
'''
|
||||
Decorator to make a repr function return fillvalue for a recursive
|
||||
@@ -509,13 +515,15 @@ except ImportError: # pragma: no cover
|
||||
wrapper.__module__ = getattr(user_function, '__module__')
|
||||
wrapper.__doc__ = getattr(user_function, '__doc__')
|
||||
wrapper.__name__ = getattr(user_function, '__name__')
|
||||
wrapper.__annotations__ = getattr(user_function, '__annotations__', {})
|
||||
wrapper.__annotations__ = getattr(user_function,
|
||||
'__annotations__', {})
|
||||
return wrapper
|
||||
|
||||
return decorating_function
|
||||
|
||||
class ChainMap(MutableMapping):
|
||||
''' A ChainMap groups multiple dicts (or other mappings) together
|
||||
'''
|
||||
A ChainMap groups multiple dicts (or other mappings) together
|
||||
to create a single, updateable view.
|
||||
|
||||
The underlying mappings are stored in a list. That list is public and can
|
||||
@@ -524,7 +532,6 @@ except ImportError: # pragma: no cover
|
||||
Lookups search the underlying mappings successively until a key is found.
|
||||
In contrast, writes, updates, and deletions only operate on the first
|
||||
mapping.
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, *maps):
|
||||
@@ -532,7 +539,7 @@ except ImportError: # pragma: no cover
|
||||
If no mappings are provided, a single empty dictionary is used.
|
||||
|
||||
'''
|
||||
self.maps = list(maps) or [{}] # always at least one map
|
||||
self.maps = list(maps) or [{}] # always at least one map
|
||||
|
||||
def __missing__(self, key):
|
||||
raise KeyError(key)
|
||||
@@ -540,16 +547,19 @@ except ImportError: # pragma: no cover
|
||||
def __getitem__(self, key):
|
||||
for mapping in self.maps:
|
||||
try:
|
||||
return mapping[key] # can't use 'key in mapping' with defaultdict
|
||||
return mapping[
|
||||
key] # can't use 'key in mapping' with defaultdict
|
||||
except KeyError:
|
||||
pass
|
||||
return self.__missing__(key) # support subclasses that define __missing__
|
||||
return self.__missing__(
|
||||
key) # support subclasses that define __missing__
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self[key] if key in self else default
|
||||
|
||||
def __len__(self):
|
||||
return len(set().union(*self.maps)) # reuses stored hash values if possible
|
||||
return len(set().union(
|
||||
*self.maps)) # reuses stored hash values if possible
|
||||
|
||||
def __iter__(self):
|
||||
return iter(set().union(*self.maps))
|
||||
@@ -576,12 +586,12 @@ except ImportError: # pragma: no cover
|
||||
|
||||
__copy__ = copy
|
||||
|
||||
def new_child(self): # like Django's Context.push()
|
||||
def new_child(self): # like Django's Context.push()
|
||||
'New ChainMap with a new dict followed by all previous maps.'
|
||||
return self.__class__({}, *self.maps)
|
||||
|
||||
@property
|
||||
def parents(self): # like Django's Context.pop()
|
||||
def parents(self): # like Django's Context.pop()
|
||||
'New ChainMap from maps[1:].'
|
||||
return self.__class__(*self.maps[1:])
|
||||
|
||||
@@ -592,7 +602,8 @@ except ImportError: # pragma: no cover
|
||||
try:
|
||||
del self.maps[0][key]
|
||||
except KeyError:
|
||||
raise KeyError('Key not found in the first mapping: {!r}'.format(key))
|
||||
raise KeyError(
|
||||
'Key not found in the first mapping: {!r}'.format(key))
|
||||
|
||||
def popitem(self):
|
||||
'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.'
|
||||
@@ -606,15 +617,18 @@ except ImportError: # pragma: no cover
|
||||
try:
|
||||
return self.maps[0].pop(key, *args)
|
||||
except KeyError:
|
||||
raise KeyError('Key not found in the first mapping: {!r}'.format(key))
|
||||
raise KeyError(
|
||||
'Key not found in the first mapping: {!r}'.format(key))
|
||||
|
||||
def clear(self):
|
||||
'Clear maps[0], leaving maps[1:] intact.'
|
||||
self.maps[0].clear()
|
||||
|
||||
|
||||
try:
|
||||
from importlib.util import cache_from_source # Python >= 3.4
|
||||
except ImportError: # pragma: no cover
|
||||
|
||||
def cache_from_source(path, debug_override=None):
|
||||
assert path.endswith('.py')
|
||||
if debug_override is None:
|
||||
@@ -625,12 +639,13 @@ except ImportError: # pragma: no cover
|
||||
suffix = 'o'
|
||||
return path + suffix
|
||||
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError: # pragma: no cover
|
||||
## {{{ http://code.activestate.com/recipes/576693/ (r9)
|
||||
# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
|
||||
# Passes Python2.7's test suite and incorporates all the latest updates.
|
||||
except ImportError: # pragma: no cover
|
||||
# {{{ http://code.activestate.com/recipes/576693/ (r9)
|
||||
# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
|
||||
# Passes Python2.7's test suite and incorporates all the latest updates.
|
||||
try:
|
||||
from thread import get_ident as _get_ident
|
||||
except ImportError:
|
||||
@@ -641,9 +656,9 @@ except ImportError: # pragma: no cover
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class OrderedDict(dict):
|
||||
'Dictionary that remembers insertion order'
|
||||
|
||||
# An inherited dict maps keys to values.
|
||||
# The inherited dict provides __getitem__, __len__, __contains__, and get.
|
||||
# The remaining methods are order-aware.
|
||||
@@ -661,11 +676,12 @@ except ImportError: # pragma: no cover
|
||||
|
||||
'''
|
||||
if len(args) > 1:
|
||||
raise TypeError('expected at most 1 arguments, got %d' % len(args))
|
||||
raise TypeError('expected at most 1 arguments, got %d' %
|
||||
len(args))
|
||||
try:
|
||||
self.__root
|
||||
except AttributeError:
|
||||
self.__root = root = [] # sentinel node
|
||||
self.__root = root = [] # sentinel node
|
||||
root[:] = [root, root, None]
|
||||
self.__map = {}
|
||||
self.__update(*args, **kwds)
|
||||
@@ -779,7 +795,7 @@ except ImportError: # pragma: no cover
|
||||
'''
|
||||
if len(args) > 2:
|
||||
raise TypeError('update() takes at most 2 positional '
|
||||
'arguments (%d given)' % (len(args),))
|
||||
'arguments (%d given)' % (len(args), ))
|
||||
elif not args:
|
||||
raise TypeError('update() takes at least 1 argument (0 given)')
|
||||
self = args[0]
|
||||
@@ -825,14 +841,15 @@ except ImportError: # pragma: no cover
|
||||
|
||||
def __repr__(self, _repr_running=None):
|
||||
'od.__repr__() <==> repr(od)'
|
||||
if not _repr_running: _repr_running = {}
|
||||
if not _repr_running:
|
||||
_repr_running = {}
|
||||
call_key = id(self), _get_ident()
|
||||
if call_key in _repr_running:
|
||||
return '...'
|
||||
_repr_running[call_key] = 1
|
||||
try:
|
||||
if not self:
|
||||
return '%s()' % (self.__class__.__name__,)
|
||||
return '%s()' % (self.__class__.__name__, )
|
||||
return '%s(%r)' % (self.__class__.__name__, self.items())
|
||||
finally:
|
||||
del _repr_running[call_key]
|
||||
@@ -844,8 +861,8 @@ except ImportError: # pragma: no cover
|
||||
for k in vars(OrderedDict()):
|
||||
inst_dict.pop(k, None)
|
||||
if inst_dict:
|
||||
return (self.__class__, (items,), inst_dict)
|
||||
return self.__class__, (items,)
|
||||
return (self.__class__, (items, ), inst_dict)
|
||||
return self.__class__, (items, )
|
||||
|
||||
def copy(self):
|
||||
'od.copy() -> a shallow copy of od'
|
||||
@@ -868,7 +885,8 @@ except ImportError: # pragma: no cover
|
||||
|
||||
'''
|
||||
if isinstance(other, OrderedDict):
|
||||
return len(self)==len(other) and self.items() == other.items()
|
||||
return len(self) == len(
|
||||
other) and self.items() == other.items()
|
||||
return dict.__eq__(self, other)
|
||||
|
||||
def __ne__(self, other):
|
||||
@@ -888,19 +906,18 @@ except ImportError: # pragma: no cover
|
||||
"od.viewitems() -> a set-like object providing a view on od's items"
|
||||
return ItemsView(self)
|
||||
|
||||
|
||||
try:
|
||||
from logging.config import BaseConfigurator, valid_ident
|
||||
except ImportError: # pragma: no cover
|
||||
except ImportError: # pragma: no cover
|
||||
IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
|
||||
|
||||
|
||||
def valid_ident(s):
|
||||
m = IDENTIFIER.match(s)
|
||||
if not m:
|
||||
raise ValueError('Not a valid Python identifier: %r' % s)
|
||||
return True
|
||||
|
||||
|
||||
# The ConvertingXXX classes are wrappers around standard Python containers,
|
||||
# and they serve to convert any suitable values in the container. The
|
||||
# conversion converts base dicts, lists and tuples to their wrapped
|
||||
@@ -916,7 +933,7 @@ except ImportError: # pragma: no cover
|
||||
def __getitem__(self, key):
|
||||
value = dict.__getitem__(self, key)
|
||||
result = self.configurator.convert(value)
|
||||
#If the converted value is different, save for next time
|
||||
# If the converted value is different, save for next time
|
||||
if value is not result:
|
||||
self[key] = result
|
||||
if type(result) in (ConvertingDict, ConvertingList,
|
||||
@@ -928,7 +945,7 @@ except ImportError: # pragma: no cover
|
||||
def get(self, key, default=None):
|
||||
value = dict.get(self, key, default)
|
||||
result = self.configurator.convert(value)
|
||||
#If the converted value is different, save for next time
|
||||
# If the converted value is different, save for next time
|
||||
if value is not result:
|
||||
self[key] = result
|
||||
if type(result) in (ConvertingDict, ConvertingList,
|
||||
@@ -949,10 +966,11 @@ except ImportError: # pragma: no cover
|
||||
|
||||
class ConvertingList(list):
|
||||
"""A converting list wrapper."""
|
||||
|
||||
def __getitem__(self, key):
|
||||
value = list.__getitem__(self, key)
|
||||
result = self.configurator.convert(value)
|
||||
#If the converted value is different, save for next time
|
||||
# If the converted value is different, save for next time
|
||||
if value is not result:
|
||||
self[key] = result
|
||||
if type(result) in (ConvertingDict, ConvertingList,
|
||||
@@ -972,6 +990,7 @@ except ImportError: # pragma: no cover
|
||||
|
||||
class ConvertingTuple(tuple):
|
||||
"""A converting tuple wrapper."""
|
||||
|
||||
def __getitem__(self, key):
|
||||
value = tuple.__getitem__(self, key)
|
||||
result = self.configurator.convert(value)
|
||||
@@ -995,8 +1014,8 @@ except ImportError: # pragma: no cover
|
||||
DIGIT_PATTERN = re.compile(r'^\d+$')
|
||||
|
||||
value_converters = {
|
||||
'ext' : 'ext_convert',
|
||||
'cfg' : 'cfg_convert',
|
||||
'ext': 'ext_convert',
|
||||
'cfg': 'cfg_convert',
|
||||
}
|
||||
|
||||
# We might want to use a different one, e.g. importlib
|
||||
@@ -1042,7 +1061,6 @@ except ImportError: # pragma: no cover
|
||||
else:
|
||||
rest = rest[m.end():]
|
||||
d = self.config[m.groups()[0]]
|
||||
#print d, rest
|
||||
while rest:
|
||||
m = self.DOT_PATTERN.match(rest)
|
||||
if m:
|
||||
@@ -1055,7 +1073,9 @@ except ImportError: # pragma: no cover
|
||||
d = d[idx]
|
||||
else:
|
||||
try:
|
||||
n = int(idx) # try as number first (most likely)
|
||||
n = int(
|
||||
idx
|
||||
) # try as number first (most likely)
|
||||
d = d[n]
|
||||
except TypeError:
|
||||
d = d[idx]
|
||||
@@ -1064,7 +1084,7 @@ except ImportError: # pragma: no cover
|
||||
else:
|
||||
raise ValueError('Unable to convert '
|
||||
'%r at %r' % (value, rest))
|
||||
#rest should be empty
|
||||
# rest should be empty
|
||||
return d
|
||||
|
||||
def convert(self, value):
|
||||
@@ -1073,14 +1093,15 @@ except ImportError: # pragma: no cover
|
||||
replaced by their converting alternatives. Strings are checked to
|
||||
see if they have a conversion format and are converted if they do.
|
||||
"""
|
||||
if not isinstance(value, ConvertingDict) and isinstance(value, dict):
|
||||
if not isinstance(value, ConvertingDict) and isinstance(
|
||||
value, dict):
|
||||
value = ConvertingDict(value)
|
||||
value.configurator = self
|
||||
elif not isinstance(value, ConvertingList) and isinstance(value, list):
|
||||
elif not isinstance(value, ConvertingList) and isinstance(
|
||||
value, list):
|
||||
value = ConvertingList(value)
|
||||
value.configurator = self
|
||||
elif not isinstance(value, ConvertingTuple) and\
|
||||
isinstance(value, tuple):
|
||||
elif not isinstance(value, ConvertingTuple) and isinstance(value, tuple):
|
||||
value = ConvertingTuple(value)
|
||||
value.configurator = self
|
||||
elif isinstance(value, string_types):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012-2017 The Python Software Foundation.
|
||||
# Copyright (C) 2012-2023 The Python Software Foundation.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
"""PEP 376 implementation."""
|
||||
@@ -20,24 +20,20 @@ import zipimport
|
||||
from . import DistlibException, resources
|
||||
from .compat import StringIO
|
||||
from .version import get_scheme, UnsupportedVersionError
|
||||
from .metadata import (Metadata, METADATA_FILENAME, WHEEL_METADATA_FILENAME,
|
||||
LEGACY_METADATA_FILENAME)
|
||||
from .util import (parse_requirement, cached_property, parse_name_and_version,
|
||||
read_exports, write_exports, CSVReader, CSVWriter)
|
||||
|
||||
|
||||
__all__ = ['Distribution', 'BaseInstalledDistribution',
|
||||
'InstalledDistribution', 'EggInfoDistribution',
|
||||
'DistributionPath']
|
||||
from .metadata import (Metadata, METADATA_FILENAME, WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME)
|
||||
from .util import (parse_requirement, cached_property, parse_name_and_version, read_exports, write_exports, CSVReader,
|
||||
CSVWriter)
|
||||
|
||||
__all__ = [
|
||||
'Distribution', 'BaseInstalledDistribution', 'InstalledDistribution', 'EggInfoDistribution', 'DistributionPath'
|
||||
]
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
EXPORTS_FILENAME = 'pydist-exports.json'
|
||||
COMMANDS_FILENAME = 'pydist-commands.json'
|
||||
|
||||
DIST_FILES = ('INSTALLER', METADATA_FILENAME, 'RECORD', 'REQUESTED',
|
||||
'RESOURCES', EXPORTS_FILENAME, 'SHARED')
|
||||
DIST_FILES = ('INSTALLER', METADATA_FILENAME, 'RECORD', 'REQUESTED', 'RESOURCES', EXPORTS_FILENAME, 'SHARED')
|
||||
|
||||
DISTINFO_EXT = '.dist-info'
|
||||
|
||||
@@ -46,6 +42,7 @@ class _Cache(object):
|
||||
"""
|
||||
A simple cache mapping names and .dist-info paths to distributions
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialise an instance. There is normally one for each DistributionPath.
|
||||
@@ -76,6 +73,7 @@ class DistributionPath(object):
|
||||
"""
|
||||
Represents a set of distributions installed on a path (typically sys.path).
|
||||
"""
|
||||
|
||||
def __init__(self, path=None, include_egg=False):
|
||||
"""
|
||||
Create an instance from a path, optionally including legacy (distutils/
|
||||
@@ -111,7 +109,6 @@ class DistributionPath(object):
|
||||
self._cache.clear()
|
||||
self._cache_egg.clear()
|
||||
|
||||
|
||||
def _yield_distributions(self):
|
||||
"""
|
||||
Yield .dist-info and/or .egg(-info) distributions.
|
||||
@@ -134,9 +131,7 @@ class DistributionPath(object):
|
||||
continue
|
||||
try:
|
||||
if self._include_dist and entry.endswith(DISTINFO_EXT):
|
||||
possible_filenames = [METADATA_FILENAME,
|
||||
WHEEL_METADATA_FILENAME,
|
||||
LEGACY_METADATA_FILENAME]
|
||||
possible_filenames = [METADATA_FILENAME, WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME]
|
||||
for metadata_filename in possible_filenames:
|
||||
metadata_path = posixpath.join(entry, metadata_filename)
|
||||
pydist = finder.find(metadata_path)
|
||||
@@ -149,10 +144,8 @@ class DistributionPath(object):
|
||||
metadata = Metadata(fileobj=stream, scheme='legacy')
|
||||
logger.debug('Found %s', r.path)
|
||||
seen.add(r.path)
|
||||
yield new_dist_class(r.path, metadata=metadata,
|
||||
env=self)
|
||||
elif self._include_egg and entry.endswith(('.egg-info',
|
||||
'.egg')):
|
||||
yield new_dist_class(r.path, metadata=metadata, env=self)
|
||||
elif self._include_egg and entry.endswith(('.egg-info', '.egg')):
|
||||
logger.debug('Found %s', r.path)
|
||||
seen.add(r.path)
|
||||
yield old_dist_class(r.path, self)
|
||||
@@ -270,8 +263,7 @@ class DistributionPath(object):
|
||||
try:
|
||||
matcher = self._scheme.matcher('%s (%s)' % (name, version))
|
||||
except ValueError:
|
||||
raise DistlibException('invalid name or version: %r, %r' %
|
||||
(name, version))
|
||||
raise DistlibException('invalid name or version: %r, %r' % (name, version))
|
||||
|
||||
for dist in self.get_distributions():
|
||||
# We hit a problem on Travis where enum34 was installed and doesn't
|
||||
@@ -346,12 +338,12 @@ class Distribution(object):
|
||||
"""
|
||||
self.metadata = metadata
|
||||
self.name = metadata.name
|
||||
self.key = self.name.lower() # for case-insensitive comparisons
|
||||
self.key = self.name.lower() # for case-insensitive comparisons
|
||||
self.version = metadata.version
|
||||
self.locator = None
|
||||
self.digest = None
|
||||
self.extras = None # additional features requested
|
||||
self.context = None # environment marker overrides
|
||||
self.extras = None # additional features requested
|
||||
self.context = None # environment marker overrides
|
||||
self.download_urls = set()
|
||||
self.digests = {}
|
||||
|
||||
@@ -362,7 +354,7 @@ class Distribution(object):
|
||||
"""
|
||||
return self.metadata.source_url
|
||||
|
||||
download_url = source_url # Backward compatibility
|
||||
download_url = source_url # Backward compatibility
|
||||
|
||||
@property
|
||||
def name_and_version(self):
|
||||
@@ -386,10 +378,8 @@ class Distribution(object):
|
||||
def _get_requirements(self, req_attr):
|
||||
md = self.metadata
|
||||
reqts = getattr(md, req_attr)
|
||||
logger.debug('%s: got requirements %r from metadata: %r', self.name, req_attr,
|
||||
reqts)
|
||||
return set(md.get_requirements(reqts, extras=self.extras,
|
||||
env=self.context))
|
||||
logger.debug('%s: got requirements %r from metadata: %r', self.name, req_attr, reqts)
|
||||
return set(md.get_requirements(reqts, extras=self.extras, env=self.context))
|
||||
|
||||
@property
|
||||
def run_requires(self):
|
||||
@@ -426,12 +416,11 @@ class Distribution(object):
|
||||
matcher = scheme.matcher(r.requirement)
|
||||
except UnsupportedVersionError:
|
||||
# XXX compat-mode if cannot read the version
|
||||
logger.warning('could not read version %r - using name only',
|
||||
req)
|
||||
logger.warning('could not read version %r - using name only', req)
|
||||
name = req.split()[0]
|
||||
matcher = scheme.matcher(name)
|
||||
|
||||
name = matcher.key # case-insensitive
|
||||
name = matcher.key # case-insensitive
|
||||
|
||||
result = False
|
||||
for p in self.provides:
|
||||
@@ -466,9 +455,7 @@ class Distribution(object):
|
||||
if type(other) is not type(self):
|
||||
result = False
|
||||
else:
|
||||
result = (self.name == other.name and
|
||||
self.version == other.version and
|
||||
self.source_url == other.source_url)
|
||||
result = (self.name == other.name and self.version == other.version and self.source_url == other.source_url)
|
||||
return result
|
||||
|
||||
def __hash__(self):
|
||||
@@ -559,8 +546,7 @@ class InstalledDistribution(BaseInstalledDistribution):
|
||||
if r is None:
|
||||
r = finder.find(LEGACY_METADATA_FILENAME)
|
||||
if r is None:
|
||||
raise ValueError('no %s found in %s' % (METADATA_FILENAME,
|
||||
path))
|
||||
raise ValueError('no %s found in %s' % (METADATA_FILENAME, path))
|
||||
with contextlib.closing(r.as_stream()) as stream:
|
||||
metadata = Metadata(fileobj=stream, scheme='legacy')
|
||||
|
||||
@@ -571,15 +557,14 @@ class InstalledDistribution(BaseInstalledDistribution):
|
||||
|
||||
r = finder.find('REQUESTED')
|
||||
self.requested = r is not None
|
||||
p = os.path.join(path, 'top_level.txt')
|
||||
p = os.path.join(path, 'top_level.txt')
|
||||
if os.path.exists(p):
|
||||
with open(p, 'rb') as f:
|
||||
data = f.read().decode('utf-8')
|
||||
self.modules = data.splitlines()
|
||||
|
||||
def __repr__(self):
|
||||
return '<InstalledDistribution %r %s at %r>' % (
|
||||
self.name, self.version, self.path)
|
||||
return '<InstalledDistribution %r %s at %r>' % (self.name, self.version, self.path)
|
||||
|
||||
def __str__(self):
|
||||
return "%s %s" % (self.name, self.version)
|
||||
@@ -596,14 +581,14 @@ class InstalledDistribution(BaseInstalledDistribution):
|
||||
with contextlib.closing(r.as_stream()) as stream:
|
||||
with CSVReader(stream=stream) as record_reader:
|
||||
# Base location is parent dir of .dist-info dir
|
||||
#base_location = os.path.dirname(self.path)
|
||||
#base_location = os.path.abspath(base_location)
|
||||
# base_location = os.path.dirname(self.path)
|
||||
# base_location = os.path.abspath(base_location)
|
||||
for row in record_reader:
|
||||
missing = [None for i in range(len(row), 3)]
|
||||
path, checksum, size = row + missing
|
||||
#if not os.path.isabs(path):
|
||||
# path = path.replace('/', os.sep)
|
||||
# path = os.path.join(base_location, path)
|
||||
# if not os.path.isabs(path):
|
||||
# path = path.replace('/', os.sep)
|
||||
# path = os.path.join(base_location, path)
|
||||
results.append((path, checksum, size))
|
||||
return results
|
||||
|
||||
@@ -701,8 +686,7 @@ class InstalledDistribution(BaseInstalledDistribution):
|
||||
size = '%d' % os.path.getsize(path)
|
||||
with open(path, 'rb') as fp:
|
||||
hash_value = self.get_hash(fp.read())
|
||||
if path.startswith(base) or (base_under_prefix and
|
||||
path.startswith(prefix)):
|
||||
if path.startswith(base) or (base_under_prefix and path.startswith(prefix)):
|
||||
path = os.path.relpath(path, base)
|
||||
writer.writerow((path, hash_value, size))
|
||||
|
||||
@@ -791,7 +775,7 @@ class InstalledDistribution(BaseInstalledDistribution):
|
||||
for key in ('prefix', 'lib', 'headers', 'scripts', 'data'):
|
||||
path = paths[key]
|
||||
if os.path.isdir(paths[key]):
|
||||
lines.append('%s=%s' % (key, path))
|
||||
lines.append('%s=%s' % (key, path))
|
||||
for ns in paths.get('namespace', ()):
|
||||
lines.append('namespace=%s' % ns)
|
||||
|
||||
@@ -826,9 +810,8 @@ class InstalledDistribution(BaseInstalledDistribution):
|
||||
# it's an absolute path?
|
||||
distinfo_dirname, path = path.split(os.sep)[-2:]
|
||||
if distinfo_dirname != self.path.split(os.sep)[-1]:
|
||||
raise DistlibException(
|
||||
'dist-info file %r does not belong to the %r %s '
|
||||
'distribution' % (path, self.name, self.version))
|
||||
raise DistlibException('dist-info file %r does not belong to the %r %s '
|
||||
'distribution' % (path, self.name, self.version))
|
||||
|
||||
# The file must be relative
|
||||
if path not in DIST_FILES:
|
||||
@@ -854,8 +837,7 @@ class InstalledDistribution(BaseInstalledDistribution):
|
||||
yield path
|
||||
|
||||
def __eq__(self, other):
|
||||
return (isinstance(other, InstalledDistribution) and
|
||||
self.path == other.path)
|
||||
return (isinstance(other, InstalledDistribution) and self.path == other.path)
|
||||
|
||||
# See http://docs.python.org/reference/datamodel#object.__hash__
|
||||
__hash__ = object.__hash__
|
||||
@@ -867,13 +849,14 @@ class EggInfoDistribution(BaseInstalledDistribution):
|
||||
if the given path happens to be a directory, the metadata is read from the
|
||||
file ``PKG-INFO`` under that directory."""
|
||||
|
||||
requested = True # as we have no way of knowing, assume it was
|
||||
requested = True # as we have no way of knowing, assume it was
|
||||
shared_locations = {}
|
||||
|
||||
def __init__(self, path, env=None):
|
||||
|
||||
def set_name_and_version(s, n, v):
|
||||
s.name = n
|
||||
s.key = n.lower() # for case-insensitive comparisons
|
||||
s.key = n.lower() # for case-insensitive comparisons
|
||||
s.version = v
|
||||
|
||||
self.path = path
|
||||
@@ -903,15 +886,17 @@ class EggInfoDistribution(BaseInstalledDistribution):
|
||||
lines = data.splitlines()
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if line.startswith('['):
|
||||
logger.warning('Unexpected line: quitting requirement scan: %r',
|
||||
line)
|
||||
# sectioned files have bare newlines (separating sections)
|
||||
if not line: # pragma: no cover
|
||||
continue
|
||||
if line.startswith('['): # pragma: no cover
|
||||
logger.warning('Unexpected line: quitting requirement scan: %r', line)
|
||||
break
|
||||
r = parse_requirement(line)
|
||||
if not r:
|
||||
if not r: # pragma: no cover
|
||||
logger.warning('Not recognised as a requirement: %r', line)
|
||||
continue
|
||||
if r.extras:
|
||||
if r.extras: # pragma: no cover
|
||||
logger.warning('extra requirements in requires.txt are '
|
||||
'not supported')
|
||||
if not r.constraints:
|
||||
@@ -947,8 +932,7 @@ class EggInfoDistribution(BaseInstalledDistribution):
|
||||
else:
|
||||
# FIXME handle the case where zipfile is not available
|
||||
zipf = zipimport.zipimporter(path)
|
||||
fileobj = StringIO(
|
||||
zipf.get_data('EGG-INFO/PKG-INFO').decode('utf8'))
|
||||
fileobj = StringIO(zipf.get_data('EGG-INFO/PKG-INFO').decode('utf8'))
|
||||
metadata = Metadata(fileobj=fileobj, scheme='legacy')
|
||||
try:
|
||||
data = zipf.get_data('EGG-INFO/requires.txt')
|
||||
@@ -982,8 +966,7 @@ class EggInfoDistribution(BaseInstalledDistribution):
|
||||
return metadata
|
||||
|
||||
def __repr__(self):
|
||||
return '<EggInfoDistribution %r %s at %r>' % (
|
||||
self.name, self.version, self.path)
|
||||
return '<EggInfoDistribution %r %s at %r>' % (self.name, self.version, self.path)
|
||||
|
||||
def __str__(self):
|
||||
return "%s %s" % (self.name, self.version)
|
||||
@@ -1039,7 +1022,7 @@ class EggInfoDistribution(BaseInstalledDistribution):
|
||||
logger.warning('Non-existent file: %s', p)
|
||||
if p.endswith(('.pyc', '.pyo')):
|
||||
continue
|
||||
#otherwise fall through and fail
|
||||
# otherwise fall through and fail
|
||||
if not os.path.isdir(p):
|
||||
result.append((p, _md5(p), _size(p)))
|
||||
result.append((record_path, None, None))
|
||||
@@ -1075,12 +1058,12 @@ class EggInfoDistribution(BaseInstalledDistribution):
|
||||
yield line
|
||||
|
||||
def __eq__(self, other):
|
||||
return (isinstance(other, EggInfoDistribution) and
|
||||
self.path == other.path)
|
||||
return (isinstance(other, EggInfoDistribution) and self.path == other.path)
|
||||
|
||||
# See http://docs.python.org/reference/datamodel#object.__hash__
|
||||
__hash__ = object.__hash__
|
||||
|
||||
|
||||
new_dist_class = InstalledDistribution
|
||||
old_dist_class = EggInfoDistribution
|
||||
|
||||
@@ -1114,7 +1097,7 @@ class DependencyGraph(object):
|
||||
"""
|
||||
self.adjacency_list[distribution] = []
|
||||
self.reverse_list[distribution] = []
|
||||
#self.missing[distribution] = []
|
||||
# self.missing[distribution] = []
|
||||
|
||||
def add_edge(self, x, y, label=None):
|
||||
"""Add an edge from distribution *x* to distribution *y* with the given
|
||||
@@ -1174,9 +1157,8 @@ class DependencyGraph(object):
|
||||
if len(adjs) == 0 and not skip_disconnected:
|
||||
disconnected.append(dist)
|
||||
for other, label in adjs:
|
||||
if not label is None:
|
||||
f.write('"%s" -> "%s" [label="%s"]\n' %
|
||||
(dist.name, other.name, label))
|
||||
if label is not None:
|
||||
f.write('"%s" -> "%s" [label="%s"]\n' % (dist.name, other.name, label))
|
||||
else:
|
||||
f.write('"%s" -> "%s"\n' % (dist.name, other.name))
|
||||
if not skip_disconnected and len(disconnected) > 0:
|
||||
@@ -1216,8 +1198,7 @@ class DependencyGraph(object):
|
||||
# Remove from the adjacency list of others
|
||||
for k, v in alist.items():
|
||||
alist[k] = [(d, r) for d, r in v if d not in to_remove]
|
||||
logger.debug('Moving to result: %s',
|
||||
['%s (%s)' % (d.name, d.version) for d in to_remove])
|
||||
logger.debug('Moving to result: %s', ['%s (%s)' % (d.name, d.version) for d in to_remove])
|
||||
result.extend(to_remove)
|
||||
return result, list(alist.keys())
|
||||
|
||||
@@ -1252,19 +1233,17 @@ def make_graph(dists, scheme='default'):
|
||||
|
||||
# now make the edges
|
||||
for dist in dists:
|
||||
requires = (dist.run_requires | dist.meta_requires |
|
||||
dist.build_requires | dist.dev_requires)
|
||||
requires = (dist.run_requires | dist.meta_requires | dist.build_requires | dist.dev_requires)
|
||||
for req in requires:
|
||||
try:
|
||||
matcher = scheme.matcher(req)
|
||||
except UnsupportedVersionError:
|
||||
# XXX compat-mode if cannot read the version
|
||||
logger.warning('could not read version %r - using name only',
|
||||
req)
|
||||
logger.warning('could not read version %r - using name only', req)
|
||||
name = req.split()[0]
|
||||
matcher = scheme.matcher(name)
|
||||
|
||||
name = matcher.key # case-insensitive
|
||||
name = matcher.key # case-insensitive
|
||||
|
||||
matched = False
|
||||
if name in provided:
|
||||
@@ -1324,7 +1303,7 @@ def get_required_dists(dists, dist):
|
||||
|
||||
req = set() # required distributions
|
||||
todo = graph.adjacency_list[dist] # list of nodes we should inspect
|
||||
seen = set(t[0] for t in todo) # already added to todo
|
||||
seen = set(t[0] for t in todo) # already added to todo
|
||||
|
||||
while todo:
|
||||
d = todo.pop()[0]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2013 Vinay Sajip.
|
||||
# Copyright (C) 2013-2023 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
@@ -25,6 +25,7 @@ logger = logging.getLogger(__name__)
|
||||
DEFAULT_INDEX = 'https://pypi.org/pypi'
|
||||
DEFAULT_REALM = 'pypi'
|
||||
|
||||
|
||||
class PackageIndex(object):
|
||||
"""
|
||||
This class represents a package index compatible with PyPI, the Python
|
||||
@@ -119,7 +120,7 @@ class PackageIndex(object):
|
||||
d = metadata.todict()
|
||||
d[':action'] = 'verify'
|
||||
request = self.encode_request(d.items(), [])
|
||||
response = self.send_request(request)
|
||||
self.send_request(request)
|
||||
d[':action'] = 'submit'
|
||||
request = self.encode_request(d.items(), [])
|
||||
return self.send_request(request)
|
||||
@@ -358,8 +359,7 @@ class PackageIndex(object):
|
||||
keystore)
|
||||
rc, stdout, stderr = self.run_command(cmd)
|
||||
if rc not in (0, 1):
|
||||
raise DistlibException('verify command failed with error '
|
||||
'code %s' % rc)
|
||||
raise DistlibException('verify command failed with error code %s' % rc)
|
||||
return rc == 0
|
||||
|
||||
def download_file(self, url, destfile, digest=None, reporthook=None):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012-2015 Vinay Sajip.
|
||||
# Copyright (C) 2012-2023 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
@@ -19,15 +19,12 @@ except ImportError: # pragma: no cover
|
||||
import zlib
|
||||
|
||||
from . import DistlibException
|
||||
from .compat import (urljoin, urlparse, urlunparse, url2pathname, pathname2url,
|
||||
queue, quote, unescape, build_opener,
|
||||
HTTPRedirectHandler as BaseRedirectHandler, text_type,
|
||||
Request, HTTPError, URLError)
|
||||
from .compat import (urljoin, urlparse, urlunparse, url2pathname, pathname2url, queue, quote, unescape, build_opener,
|
||||
HTTPRedirectHandler as BaseRedirectHandler, text_type, Request, HTTPError, URLError)
|
||||
from .database import Distribution, DistributionPath, make_dist
|
||||
from .metadata import Metadata, MetadataInvalidError
|
||||
from .util import (cached_property, ensure_slash, split_filename, get_project_data,
|
||||
parse_requirement, parse_name_and_version, ServerProxy,
|
||||
normalize_name)
|
||||
from .util import (cached_property, ensure_slash, split_filename, get_project_data, parse_requirement,
|
||||
parse_name_and_version, ServerProxy, normalize_name)
|
||||
from .version import get_scheme, UnsupportedVersionError
|
||||
from .wheel import Wheel, is_compatible
|
||||
|
||||
@@ -38,6 +35,7 @@ CHARSET = re.compile(r';\s*charset\s*=\s*(.*)\s*$', re.I)
|
||||
HTML_CONTENT_TYPE = re.compile('text/html|application/x(ht)?ml')
|
||||
DEFAULT_INDEX = 'https://pypi.org/pypi'
|
||||
|
||||
|
||||
def get_all_distribution_names(url=None):
|
||||
"""
|
||||
Return all distribution names known by an index.
|
||||
@@ -52,10 +50,12 @@ def get_all_distribution_names(url=None):
|
||||
finally:
|
||||
client('close')()
|
||||
|
||||
|
||||
class RedirectHandler(BaseRedirectHandler):
|
||||
"""
|
||||
A class to work around a bug in some Python 3.2.x releases.
|
||||
"""
|
||||
|
||||
# There's a bug in the base version for some 3.2.x
|
||||
# (e.g. 3.2.2 on Ubuntu Oneiric). If a Location header
|
||||
# returns e.g. /abc, it bails because it says the scheme ''
|
||||
@@ -78,18 +78,18 @@ class RedirectHandler(BaseRedirectHandler):
|
||||
headers.replace_header(key, newurl)
|
||||
else:
|
||||
headers[key] = newurl
|
||||
return BaseRedirectHandler.http_error_302(self, req, fp, code, msg,
|
||||
headers)
|
||||
return BaseRedirectHandler.http_error_302(self, req, fp, code, msg, headers)
|
||||
|
||||
http_error_301 = http_error_303 = http_error_307 = http_error_302
|
||||
|
||||
|
||||
class Locator(object):
|
||||
"""
|
||||
A base class for locators - things that locate distributions.
|
||||
"""
|
||||
source_extensions = ('.tar.gz', '.tar.bz2', '.tar', '.zip', '.tgz', '.tbz')
|
||||
binary_extensions = ('.egg', '.exe', '.whl')
|
||||
excluded_extensions = ('.pdf',)
|
||||
excluded_extensions = ('.pdf', )
|
||||
|
||||
# A list of tags indicating which wheels you want to match. The default
|
||||
# value of None matches against the tags compatible with the running
|
||||
@@ -97,7 +97,7 @@ class Locator(object):
|
||||
# instance to a list of tuples (pyver, abi, arch) which you want to match.
|
||||
wheel_tags = None
|
||||
|
||||
downloadable_extensions = source_extensions + ('.whl',)
|
||||
downloadable_extensions = source_extensions + ('.whl', )
|
||||
|
||||
def __init__(self, scheme='default'):
|
||||
"""
|
||||
@@ -197,8 +197,7 @@ class Locator(object):
|
||||
is_downloadable = basename.endswith(self.downloadable_extensions)
|
||||
if is_wheel:
|
||||
compatible = is_compatible(Wheel(basename), self.wheel_tags)
|
||||
return (t.scheme == 'https', 'pypi.org' in t.netloc,
|
||||
is_downloadable, is_wheel, compatible, basename)
|
||||
return (t.scheme == 'https', 'pypi.org' in t.netloc, is_downloadable, is_wheel, compatible, basename)
|
||||
|
||||
def prefer_url(self, url1, url2):
|
||||
"""
|
||||
@@ -236,14 +235,14 @@ class Locator(object):
|
||||
If it is, a dictionary is returned with keys "name", "version",
|
||||
"filename" and "url"; otherwise, None is returned.
|
||||
"""
|
||||
|
||||
def same_project(name1, name2):
|
||||
return normalize_name(name1) == normalize_name(name2)
|
||||
|
||||
result = None
|
||||
scheme, netloc, path, params, query, frag = urlparse(url)
|
||||
if frag.lower().startswith('egg='): # pragma: no cover
|
||||
logger.debug('%s: version hint in fragment: %r',
|
||||
project_name, frag)
|
||||
logger.debug('%s: version hint in fragment: %r', project_name, frag)
|
||||
m = HASHER_HASH.match(frag)
|
||||
if m:
|
||||
algo, digest = m.groups()
|
||||
@@ -267,12 +266,10 @@ class Locator(object):
|
||||
'name': wheel.name,
|
||||
'version': wheel.version,
|
||||
'filename': wheel.filename,
|
||||
'url': urlunparse((scheme, netloc, origpath,
|
||||
params, query, '')),
|
||||
'python-version': ', '.join(
|
||||
['.'.join(list(v[2:])) for v in wheel.pyver]),
|
||||
'url': urlunparse((scheme, netloc, origpath, params, query, '')),
|
||||
'python-version': ', '.join(['.'.join(list(v[2:])) for v in wheel.pyver]),
|
||||
}
|
||||
except Exception as e: # pragma: no cover
|
||||
except Exception: # pragma: no cover
|
||||
logger.warning('invalid path for wheel: %s', path)
|
||||
elif not path.endswith(self.downloadable_extensions): # pragma: no cover
|
||||
logger.debug('Not downloadable: %s', path)
|
||||
@@ -291,9 +288,7 @@ class Locator(object):
|
||||
'name': name,
|
||||
'version': version,
|
||||
'filename': filename,
|
||||
'url': urlunparse((scheme, netloc, origpath,
|
||||
params, query, '')),
|
||||
#'packagetype': 'sdist',
|
||||
'url': urlunparse((scheme, netloc, origpath, params, query, '')),
|
||||
}
|
||||
if pyver: # pragma: no cover
|
||||
result['python-version'] = pyver
|
||||
@@ -369,7 +364,7 @@ class Locator(object):
|
||||
self.matcher = matcher = scheme.matcher(r.requirement)
|
||||
logger.debug('matcher: %s (%s)', matcher, type(matcher).__name__)
|
||||
versions = self.get_project(r.name)
|
||||
if len(versions) > 2: # urls and digests keys are present
|
||||
if len(versions) > 2: # urls and digests keys are present
|
||||
# sometimes, versions are invalid
|
||||
slist = []
|
||||
vcls = matcher.version_class
|
||||
@@ -382,12 +377,9 @@ class Locator(object):
|
||||
else:
|
||||
if prereleases or not vcls(k).is_prerelease:
|
||||
slist.append(k)
|
||||
# else:
|
||||
# logger.debug('skipping pre-release '
|
||||
# 'version %s of %s', k, matcher.name)
|
||||
except Exception: # pragma: no cover
|
||||
logger.warning('error matching %s with %r', matcher, k)
|
||||
pass # slist.append(k)
|
||||
pass # slist.append(k)
|
||||
if len(slist) > 1:
|
||||
slist = sorted(slist, key=scheme.key)
|
||||
if slist:
|
||||
@@ -413,6 +405,7 @@ class PyPIRPCLocator(Locator):
|
||||
This locator uses XML-RPC to locate distributions. It therefore
|
||||
cannot be used with simple mirrors (that only mirror file content).
|
||||
"""
|
||||
|
||||
def __init__(self, url, **kwargs):
|
||||
"""
|
||||
Initialise an instance.
|
||||
@@ -456,11 +449,13 @@ class PyPIRPCLocator(Locator):
|
||||
result['digests'][url] = digest
|
||||
return result
|
||||
|
||||
|
||||
class PyPIJSONLocator(Locator):
|
||||
"""
|
||||
This locator uses PyPI's JSON interface. It's very limited in functionality
|
||||
and probably not worth using.
|
||||
"""
|
||||
|
||||
def __init__(self, url, **kwargs):
|
||||
super(PyPIJSONLocator, self).__init__(**kwargs)
|
||||
self.base_url = ensure_slash(url)
|
||||
@@ -476,7 +471,7 @@ class PyPIJSONLocator(Locator):
|
||||
url = urljoin(self.base_url, '%s/json' % quote(name))
|
||||
try:
|
||||
resp = self.opener.open(url)
|
||||
data = resp.read().decode() # for now
|
||||
data = resp.read().decode() # for now
|
||||
d = json.loads(data)
|
||||
md = Metadata(scheme=self.scheme)
|
||||
data = d['info']
|
||||
@@ -487,7 +482,7 @@ class PyPIJSONLocator(Locator):
|
||||
md.summary = data.get('summary')
|
||||
dist = Distribution(md)
|
||||
dist.locator = self
|
||||
urls = d['urls']
|
||||
# urls = d['urls']
|
||||
result[md.version] = dist
|
||||
for info in d['urls']:
|
||||
url = info['url']
|
||||
@@ -498,7 +493,7 @@ class PyPIJSONLocator(Locator):
|
||||
# Now get other releases
|
||||
for version, infos in d['releases'].items():
|
||||
if version == md.version:
|
||||
continue # already done
|
||||
continue # already done
|
||||
omd = Metadata(scheme=self.scheme)
|
||||
omd.name = md.name
|
||||
omd.version = version
|
||||
@@ -511,6 +506,8 @@ class PyPIJSONLocator(Locator):
|
||||
odist.digests[url] = self._get_digest(info)
|
||||
result['urls'].setdefault(version, set()).add(url)
|
||||
result['digests'][url] = self._get_digest(info)
|
||||
|
||||
|
||||
# for info in urls:
|
||||
# md.source_url = info['url']
|
||||
# dist.digest = self._get_digest(info)
|
||||
@@ -534,7 +531,8 @@ class Page(object):
|
||||
# or immediately followed by a "rel" attribute. The attribute values can be
|
||||
# declared with double quotes, single quotes or no quotes - which leads to
|
||||
# the length of the expression.
|
||||
_href = re.compile("""
|
||||
_href = re.compile(
|
||||
"""
|
||||
(rel\\s*=\\s*(?:"(?P<rel1>[^"]*)"|'(?P<rel2>[^']*)'|(?P<rel3>[^>\\s\n]*))\\s+)?
|
||||
href\\s*=\\s*(?:"(?P<url1>[^"]*)"|'(?P<url2>[^']*)'|(?P<url3>[^>\\s\n]*))
|
||||
(\\s+rel\\s*=\\s*(?:"(?P<rel4>[^"]*)"|'(?P<rel5>[^']*)'|(?P<rel6>[^>\\s\n]*)))?
|
||||
@@ -561,17 +559,16 @@ href\\s*=\\s*(?:"(?P<url1>[^"]*)"|'(?P<url2>[^']*)'|(?P<url3>[^>\\s\n]*))
|
||||
about their "rel" attribute, for determining which ones to treat as
|
||||
downloads and which ones to queue for further scraping.
|
||||
"""
|
||||
|
||||
def clean(url):
|
||||
"Tidy up an URL."
|
||||
scheme, netloc, path, params, query, frag = urlparse(url)
|
||||
return urlunparse((scheme, netloc, quote(path),
|
||||
params, query, frag))
|
||||
return urlunparse((scheme, netloc, quote(path), params, query, frag))
|
||||
|
||||
result = set()
|
||||
for match in self._href.finditer(self.data):
|
||||
d = match.groupdict('')
|
||||
rel = (d['rel1'] or d['rel2'] or d['rel3'] or
|
||||
d['rel4'] or d['rel5'] or d['rel6'])
|
||||
rel = (d['rel1'] or d['rel2'] or d['rel3'] or d['rel4'] or d['rel5'] or d['rel6'])
|
||||
url = d['url1'] or d['url2'] or d['url3']
|
||||
url = urljoin(self.base_url, url)
|
||||
url = unescape(url)
|
||||
@@ -645,7 +642,7 @@ class SimpleScrapingLocator(Locator):
|
||||
# Note that you need two loops, since you can't say which
|
||||
# thread will get each sentinel
|
||||
for t in self._threads:
|
||||
self._to_fetch.put(None) # sentinel
|
||||
self._to_fetch.put(None) # sentinel
|
||||
for t in self._threads:
|
||||
t.join()
|
||||
self._threads = []
|
||||
@@ -693,7 +690,7 @@ class SimpleScrapingLocator(Locator):
|
||||
info = self.convert_url_to_download_info(url, self.project_name)
|
||||
logger.debug('process_download: %s -> %s', url, info)
|
||||
if info:
|
||||
with self._lock: # needed because self.result is shared
|
||||
with self._lock: # needed because self.result is shared
|
||||
self._update_version_data(self.result, info)
|
||||
return info
|
||||
|
||||
@@ -703,8 +700,7 @@ class SimpleScrapingLocator(Locator):
|
||||
particular "rel" attribute should be queued for scraping.
|
||||
"""
|
||||
scheme, netloc, path, _, _, _ = urlparse(link)
|
||||
if path.endswith(self.source_extensions + self.binary_extensions +
|
||||
self.excluded_extensions):
|
||||
if path.endswith(self.source_extensions + self.binary_extensions + self.excluded_extensions):
|
||||
result = False
|
||||
elif self.skip_externals and not link.startswith(self.base_url):
|
||||
result = False
|
||||
@@ -722,8 +718,7 @@ class SimpleScrapingLocator(Locator):
|
||||
result = False
|
||||
else:
|
||||
result = True
|
||||
logger.debug('should_queue: %s (%s) from %s -> %s', link, rel,
|
||||
referrer, result)
|
||||
logger.debug('should_queue: %s (%s) from %s -> %s', link, rel, referrer, result)
|
||||
return result
|
||||
|
||||
def _fetch(self):
|
||||
@@ -738,14 +733,13 @@ class SimpleScrapingLocator(Locator):
|
||||
try:
|
||||
if url:
|
||||
page = self.get_page(url)
|
||||
if page is None: # e.g. after an error
|
||||
if page is None: # e.g. after an error
|
||||
continue
|
||||
for link, rel in page.links:
|
||||
if link not in self._seen:
|
||||
try:
|
||||
self._seen.add(link)
|
||||
if (not self._process_download(link) and
|
||||
self._should_queue(link, url, rel)):
|
||||
if (not self._process_download(link) and self._should_queue(link, url, rel)):
|
||||
logger.debug('Queueing %s from %s', link, url)
|
||||
self._to_fetch.put(link)
|
||||
except MetadataInvalidError: # e.g. invalid versions
|
||||
@@ -756,7 +750,7 @@ class SimpleScrapingLocator(Locator):
|
||||
# always do this, to avoid hangs :-)
|
||||
self._to_fetch.task_done()
|
||||
if not url:
|
||||
#logger.debug('Sentinel seen, quitting.')
|
||||
# logger.debug('Sentinel seen, quitting.')
|
||||
break
|
||||
|
||||
def get_page(self, url):
|
||||
@@ -793,7 +787,7 @@ class SimpleScrapingLocator(Locator):
|
||||
data = resp.read()
|
||||
encoding = headers.get('Content-Encoding')
|
||||
if encoding:
|
||||
decoder = self.decoders[encoding] # fail if not found
|
||||
decoder = self.decoders[encoding] # fail if not found
|
||||
data = decoder(data)
|
||||
encoding = 'utf-8'
|
||||
m = CHARSET.search(content_type)
|
||||
@@ -802,7 +796,7 @@ class SimpleScrapingLocator(Locator):
|
||||
try:
|
||||
data = data.decode(encoding)
|
||||
except UnicodeError: # pragma: no cover
|
||||
data = data.decode('latin-1') # fallback
|
||||
data = data.decode('latin-1') # fallback
|
||||
result = Page(data, final_url)
|
||||
self._page_cache[final_url] = result
|
||||
except HTTPError as e:
|
||||
@@ -815,7 +809,7 @@ class SimpleScrapingLocator(Locator):
|
||||
except Exception as e: # pragma: no cover
|
||||
logger.exception('Fetch failed: %s: %s', url, e)
|
||||
finally:
|
||||
self._page_cache[url] = result # even if None (failure)
|
||||
self._page_cache[url] = result # even if None (failure)
|
||||
return result
|
||||
|
||||
_distname_re = re.compile('<a href=[^>]*>([^<]+)<')
|
||||
@@ -832,6 +826,7 @@ class SimpleScrapingLocator(Locator):
|
||||
result.add(match.group(1))
|
||||
return result
|
||||
|
||||
|
||||
class DirectoryLocator(Locator):
|
||||
"""
|
||||
This class locates distributions in a directory tree.
|
||||
@@ -868,9 +863,7 @@ class DirectoryLocator(Locator):
|
||||
for fn in files:
|
||||
if self.should_include(fn, root):
|
||||
fn = os.path.join(root, fn)
|
||||
url = urlunparse(('file', '',
|
||||
pathname2url(os.path.abspath(fn)),
|
||||
'', '', ''))
|
||||
url = urlunparse(('file', '', pathname2url(os.path.abspath(fn)), '', '', ''))
|
||||
info = self.convert_url_to_download_info(url, name)
|
||||
if info:
|
||||
self._update_version_data(result, info)
|
||||
@@ -887,9 +880,7 @@ class DirectoryLocator(Locator):
|
||||
for fn in files:
|
||||
if self.should_include(fn, root):
|
||||
fn = os.path.join(root, fn)
|
||||
url = urlunparse(('file', '',
|
||||
pathname2url(os.path.abspath(fn)),
|
||||
'', '', ''))
|
||||
url = urlunparse(('file', '', pathname2url(os.path.abspath(fn)), '', '', ''))
|
||||
info = self.convert_url_to_download_info(url, None)
|
||||
if info:
|
||||
result.add(info['name'])
|
||||
@@ -897,6 +888,7 @@ class DirectoryLocator(Locator):
|
||||
break
|
||||
return result
|
||||
|
||||
|
||||
class JSONLocator(Locator):
|
||||
"""
|
||||
This locator uses special extended metadata (not available on PyPI) and is
|
||||
@@ -904,6 +896,7 @@ class JSONLocator(Locator):
|
||||
require archive downloads before dependencies can be determined! As you
|
||||
might imagine, that can be slow.
|
||||
"""
|
||||
|
||||
def get_distribution_names(self):
|
||||
"""
|
||||
Return all the distribution names known to this locator.
|
||||
@@ -920,9 +913,9 @@ class JSONLocator(Locator):
|
||||
# We don't store summary in project metadata as it makes
|
||||
# the data bigger for no benefit during dependency
|
||||
# resolution
|
||||
dist = make_dist(data['name'], info['version'],
|
||||
summary=data.get('summary',
|
||||
'Placeholder for summary'),
|
||||
dist = make_dist(data['name'],
|
||||
info['version'],
|
||||
summary=data.get('summary', 'Placeholder for summary'),
|
||||
scheme=self.scheme)
|
||||
md = dist.metadata
|
||||
md.source_url = info['url']
|
||||
@@ -935,11 +928,13 @@ class JSONLocator(Locator):
|
||||
result['urls'].setdefault(dist.version, set()).add(info['url'])
|
||||
return result
|
||||
|
||||
|
||||
class DistPathLocator(Locator):
|
||||
"""
|
||||
This locator finds installed distributions in a path. It can be useful for
|
||||
adding to an :class:`AggregatingLocator`.
|
||||
"""
|
||||
|
||||
def __init__(self, distpath, **kwargs):
|
||||
"""
|
||||
Initialise an instance.
|
||||
@@ -957,8 +952,12 @@ class DistPathLocator(Locator):
|
||||
else:
|
||||
result = {
|
||||
dist.version: dist,
|
||||
'urls': {dist.version: set([dist.source_url])},
|
||||
'digests': {dist.version: set([None])}
|
||||
'urls': {
|
||||
dist.version: set([dist.source_url])
|
||||
},
|
||||
'digests': {
|
||||
dist.version: set([None])
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -967,6 +966,7 @@ class AggregatingLocator(Locator):
|
||||
"""
|
||||
This class allows you to chain and/or merge a list of locators.
|
||||
"""
|
||||
|
||||
def __init__(self, *locators, **kwargs):
|
||||
"""
|
||||
Initialise an instance.
|
||||
@@ -1055,10 +1055,9 @@ class AggregatingLocator(Locator):
|
||||
# We use a legacy scheme simply because most of the dists on PyPI use legacy
|
||||
# versions which don't conform to PEP 440.
|
||||
default_locator = AggregatingLocator(
|
||||
# JSONLocator(), # don't use as PEP 426 is withdrawn
|
||||
SimpleScrapingLocator('https://pypi.org/simple/',
|
||||
timeout=3.0),
|
||||
scheme='legacy')
|
||||
# JSONLocator(), # don't use as PEP 426 is withdrawn
|
||||
SimpleScrapingLocator('https://pypi.org/simple/', timeout=3.0),
|
||||
scheme='legacy')
|
||||
|
||||
locate = default_locator.locate
|
||||
|
||||
@@ -1134,7 +1133,7 @@ class DependencyFinder(object):
|
||||
:return: A set of distribution which can fulfill the requirement.
|
||||
"""
|
||||
matcher = self.get_matcher(reqt)
|
||||
name = matcher.key # case-insensitive
|
||||
name = matcher.key # case-insensitive
|
||||
result = set()
|
||||
provided = self.provided
|
||||
if name in provided:
|
||||
@@ -1176,8 +1175,7 @@ class DependencyFinder(object):
|
||||
unmatched.add(s)
|
||||
if unmatched:
|
||||
# can't replace other with provider
|
||||
problems.add(('cantreplace', provider, other,
|
||||
frozenset(unmatched)))
|
||||
problems.add(('cantreplace', provider, other, frozenset(unmatched)))
|
||||
result = False
|
||||
else:
|
||||
# can replace other with provider
|
||||
@@ -1230,8 +1228,7 @@ class DependencyFinder(object):
|
||||
dist = odist = requirement
|
||||
logger.debug('passed %s as requirement', odist)
|
||||
else:
|
||||
dist = odist = self.locator.locate(requirement,
|
||||
prereleases=prereleases)
|
||||
dist = odist = self.locator.locate(requirement, prereleases=prereleases)
|
||||
if dist is None:
|
||||
raise DistlibException('Unable to locate %r' % requirement)
|
||||
logger.debug('located %s', odist)
|
||||
@@ -1241,11 +1238,11 @@ class DependencyFinder(object):
|
||||
install_dists = set([odist])
|
||||
while todo:
|
||||
dist = todo.pop()
|
||||
name = dist.key # case-insensitive
|
||||
name = dist.key # case-insensitive
|
||||
if name not in self.dists_by_name:
|
||||
self.add_distribution(dist)
|
||||
else:
|
||||
#import pdb; pdb.set_trace()
|
||||
# import pdb; pdb.set_trace()
|
||||
other = self.dists_by_name[name]
|
||||
if other != dist:
|
||||
self.try_to_replace(dist, other, problems)
|
||||
@@ -1278,8 +1275,7 @@ class DependencyFinder(object):
|
||||
providers.add(provider)
|
||||
if r in ireqts and dist in install_dists:
|
||||
install_dists.add(provider)
|
||||
logger.debug('Adding %s to install_dists',
|
||||
provider.name_and_version)
|
||||
logger.debug('Adding %s to install_dists', provider.name_and_version)
|
||||
for p in providers:
|
||||
name = p.key
|
||||
if name not in self.dists_by_name:
|
||||
@@ -1294,7 +1290,6 @@ class DependencyFinder(object):
|
||||
for dist in dists:
|
||||
dist.build_time_dependency = dist not in install_dists
|
||||
if dist.build_time_dependency:
|
||||
logger.debug('%s is a build-time dependency only.',
|
||||
dist.name_and_version)
|
||||
logger.debug('%s is a build-time dependency only.', dist.name_and_version)
|
||||
logger.debug('find done for %s', odist)
|
||||
return dists, problems
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012-2013 Python Software Foundation.
|
||||
# Copyright (C) 2012-2023 Python Software Foundation.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
"""
|
||||
@@ -34,9 +34,11 @@ _COMMENTED_LINE = re.compile('#.*?(?=\n)|\n(?=$)', re.M | re.S)
|
||||
#
|
||||
_PYTHON_VERSION = sys.version_info[:2]
|
||||
|
||||
|
||||
class Manifest(object):
|
||||
"""A list of files built by on exploring the filesystem and filtered by
|
||||
applying various patterns to what we find there.
|
||||
"""
|
||||
A list of files built by exploring the filesystem and filtered by applying various
|
||||
patterns to what we find there.
|
||||
"""
|
||||
|
||||
def __init__(self, base=None):
|
||||
@@ -154,10 +156,7 @@ class Manifest(object):
|
||||
|
||||
elif action == 'exclude':
|
||||
for pattern in patterns:
|
||||
found = self._exclude_pattern(pattern, anchor=True)
|
||||
#if not found:
|
||||
# logger.warning('no previously-included files '
|
||||
# 'found matching %r', pattern)
|
||||
self._exclude_pattern(pattern, anchor=True)
|
||||
|
||||
elif action == 'global-include':
|
||||
for pattern in patterns:
|
||||
@@ -167,11 +166,7 @@ class Manifest(object):
|
||||
|
||||
elif action == 'global-exclude':
|
||||
for pattern in patterns:
|
||||
found = self._exclude_pattern(pattern, anchor=False)
|
||||
#if not found:
|
||||
# logger.warning('no previously-included files '
|
||||
# 'matching %r found anywhere in '
|
||||
# 'distribution', pattern)
|
||||
self._exclude_pattern(pattern, anchor=False)
|
||||
|
||||
elif action == 'recursive-include':
|
||||
for pattern in patterns:
|
||||
@@ -181,11 +176,7 @@ class Manifest(object):
|
||||
|
||||
elif action == 'recursive-exclude':
|
||||
for pattern in patterns:
|
||||
found = self._exclude_pattern(pattern, prefix=thedir)
|
||||
#if not found:
|
||||
# logger.warning('no previously-included files '
|
||||
# 'matching %r found under directory %r',
|
||||
# pattern, thedir)
|
||||
self._exclude_pattern(pattern, prefix=thedir)
|
||||
|
||||
elif action == 'graft':
|
||||
if not self._include_pattern(None, prefix=dirpattern):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012-2017 Vinay Sajip.
|
||||
# Copyright (C) 2012-2023 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
@@ -19,26 +19,31 @@ import platform
|
||||
|
||||
from .compat import string_types
|
||||
from .util import in_venv, parse_marker
|
||||
from .version import NormalizedVersion as NV
|
||||
from .version import LegacyVersion as LV
|
||||
|
||||
__all__ = ['interpret']
|
||||
|
||||
_VERSION_PATTERN = re.compile(r'((\d+(\.\d+)*\w*)|\'(\d+(\.\d+)*\w*)\'|\"(\d+(\.\d+)*\w*)\")')
|
||||
_VERSION_MARKERS = {'python_version', 'python_full_version'}
|
||||
|
||||
|
||||
def _is_version_marker(s):
|
||||
return isinstance(s, string_types) and s in _VERSION_MARKERS
|
||||
|
||||
|
||||
def _is_literal(o):
|
||||
if not isinstance(o, string_types) or not o:
|
||||
return False
|
||||
return o[0] in '\'"'
|
||||
|
||||
|
||||
def _get_versions(s):
|
||||
result = []
|
||||
for m in _VERSION_PATTERN.finditer(s):
|
||||
result.append(NV(m.groups()[0]))
|
||||
return set(result)
|
||||
return {LV(m.groups()[0]) for m in _VERSION_PATTERN.finditer(s)}
|
||||
|
||||
|
||||
class Evaluator(object):
|
||||
"""
|
||||
This class is used to evaluate marker expessions.
|
||||
This class is used to evaluate marker expressions.
|
||||
"""
|
||||
|
||||
operations = {
|
||||
@@ -46,10 +51,10 @@ class Evaluator(object):
|
||||
'===': lambda x, y: x == y,
|
||||
'~=': lambda x, y: x == y or x > y,
|
||||
'!=': lambda x, y: x != y,
|
||||
'<': lambda x, y: x < y,
|
||||
'<=': lambda x, y: x == y or x < y,
|
||||
'>': lambda x, y: x > y,
|
||||
'>=': lambda x, y: x == y or x > y,
|
||||
'<': lambda x, y: x < y,
|
||||
'<=': lambda x, y: x == y or x < y,
|
||||
'>': lambda x, y: x > y,
|
||||
'>=': lambda x, y: x == y or x > y,
|
||||
'and': lambda x, y: x and y,
|
||||
'or': lambda x, y: x or y,
|
||||
'in': lambda x, y: x in y,
|
||||
@@ -80,19 +85,22 @@ class Evaluator(object):
|
||||
|
||||
lhs = self.evaluate(elhs, context)
|
||||
rhs = self.evaluate(erhs, context)
|
||||
if ((elhs == 'python_version' or erhs == 'python_version') and
|
||||
op in ('<', '<=', '>', '>=', '===', '==', '!=', '~=')):
|
||||
lhs = NV(lhs)
|
||||
rhs = NV(rhs)
|
||||
elif elhs == 'python_version' and op in ('in', 'not in'):
|
||||
lhs = NV(lhs)
|
||||
if ((_is_version_marker(elhs) or _is_version_marker(erhs)) and
|
||||
op in ('<', '<=', '>', '>=', '===', '==', '!=', '~=')):
|
||||
lhs = LV(lhs)
|
||||
rhs = LV(rhs)
|
||||
elif _is_version_marker(elhs) and op in ('in', 'not in'):
|
||||
lhs = LV(lhs)
|
||||
rhs = _get_versions(rhs)
|
||||
result = self.operations[op](lhs, rhs)
|
||||
return result
|
||||
|
||||
|
||||
_DIGITS = re.compile(r'\d+\.\d+')
|
||||
|
||||
|
||||
def default_context():
|
||||
|
||||
def format_full_version(info):
|
||||
version = '%s.%s.%s' % (info.major, info.minor, info.micro)
|
||||
kind = info.releaselevel
|
||||
@@ -126,11 +134,13 @@ def default_context():
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
DEFAULT_CONTEXT = default_context()
|
||||
del default_context
|
||||
|
||||
evaluator = Evaluator()
|
||||
|
||||
|
||||
def interpret(marker, execution_context=None):
|
||||
"""
|
||||
Interpret a marker and return a result depending on environment.
|
||||
|
||||
@@ -15,7 +15,6 @@ import json
|
||||
import logging
|
||||
import re
|
||||
|
||||
|
||||
from . import DistlibException, __version__
|
||||
from .compat import StringIO, string_types, text_type
|
||||
from .markers import interpret
|
||||
@@ -40,6 +39,7 @@ class MetadataUnrecognizedVersionError(DistlibException):
|
||||
class MetadataInvalidError(DistlibException):
|
||||
"""A metadata value is invalid"""
|
||||
|
||||
|
||||
# public API of this module
|
||||
__all__ = ['Metadata', 'PKG_INFO_ENCODING', 'PKG_INFO_PREFERRED_VERSION']
|
||||
|
||||
@@ -52,53 +52,38 @@ PKG_INFO_PREFERRED_VERSION = '1.1'
|
||||
|
||||
_LINE_PREFIX_1_2 = re.compile('\n \\|')
|
||||
_LINE_PREFIX_PRE_1_2 = re.compile('\n ')
|
||||
_241_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform',
|
||||
'Summary', 'Description',
|
||||
'Keywords', 'Home-page', 'Author', 'Author-email',
|
||||
'License')
|
||||
_241_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Summary', 'Description', 'Keywords', 'Home-page',
|
||||
'Author', 'Author-email', 'License')
|
||||
|
||||
_314_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform',
|
||||
'Supported-Platform', 'Summary', 'Description',
|
||||
'Keywords', 'Home-page', 'Author', 'Author-email',
|
||||
'License', 'Classifier', 'Download-URL', 'Obsoletes',
|
||||
_314_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description',
|
||||
'Keywords', 'Home-page', 'Author', 'Author-email', 'License', 'Classifier', 'Download-URL', 'Obsoletes',
|
||||
'Provides', 'Requires')
|
||||
|
||||
_314_MARKERS = ('Obsoletes', 'Provides', 'Requires', 'Classifier',
|
||||
'Download-URL')
|
||||
_314_MARKERS = ('Obsoletes', 'Provides', 'Requires', 'Classifier', 'Download-URL')
|
||||
|
||||
_345_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform',
|
||||
'Supported-Platform', 'Summary', 'Description',
|
||||
'Keywords', 'Home-page', 'Author', 'Author-email',
|
||||
'Maintainer', 'Maintainer-email', 'License',
|
||||
'Classifier', 'Download-URL', 'Obsoletes-Dist',
|
||||
'Project-URL', 'Provides-Dist', 'Requires-Dist',
|
||||
_345_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description',
|
||||
'Keywords', 'Home-page', 'Author', 'Author-email', 'Maintainer', 'Maintainer-email', 'License',
|
||||
'Classifier', 'Download-URL', 'Obsoletes-Dist', 'Project-URL', 'Provides-Dist', 'Requires-Dist',
|
||||
'Requires-Python', 'Requires-External')
|
||||
|
||||
_345_MARKERS = ('Provides-Dist', 'Requires-Dist', 'Requires-Python',
|
||||
'Obsoletes-Dist', 'Requires-External', 'Maintainer',
|
||||
'Maintainer-email', 'Project-URL')
|
||||
_345_MARKERS = ('Provides-Dist', 'Requires-Dist', 'Requires-Python', 'Obsoletes-Dist', 'Requires-External',
|
||||
'Maintainer', 'Maintainer-email', 'Project-URL')
|
||||
|
||||
_426_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform',
|
||||
'Supported-Platform', 'Summary', 'Description',
|
||||
'Keywords', 'Home-page', 'Author', 'Author-email',
|
||||
'Maintainer', 'Maintainer-email', 'License',
|
||||
'Classifier', 'Download-URL', 'Obsoletes-Dist',
|
||||
'Project-URL', 'Provides-Dist', 'Requires-Dist',
|
||||
'Requires-Python', 'Requires-External', 'Private-Version',
|
||||
'Obsoleted-By', 'Setup-Requires-Dist', 'Extension',
|
||||
'Provides-Extra')
|
||||
_426_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description',
|
||||
'Keywords', 'Home-page', 'Author', 'Author-email', 'Maintainer', 'Maintainer-email', 'License',
|
||||
'Classifier', 'Download-URL', 'Obsoletes-Dist', 'Project-URL', 'Provides-Dist', 'Requires-Dist',
|
||||
'Requires-Python', 'Requires-External', 'Private-Version', 'Obsoleted-By', 'Setup-Requires-Dist',
|
||||
'Extension', 'Provides-Extra')
|
||||
|
||||
_426_MARKERS = ('Private-Version', 'Provides-Extra', 'Obsoleted-By',
|
||||
'Setup-Requires-Dist', 'Extension')
|
||||
_426_MARKERS = ('Private-Version', 'Provides-Extra', 'Obsoleted-By', 'Setup-Requires-Dist', 'Extension')
|
||||
|
||||
# See issue #106: Sometimes 'Requires' and 'Provides' occur wrongly in
|
||||
# the metadata. Include them in the tuple literal below to allow them
|
||||
# (for now).
|
||||
# Ditto for Obsoletes - see issue #140.
|
||||
_566_FIELDS = _426_FIELDS + ('Description-Content-Type',
|
||||
'Requires', 'Provides', 'Obsoletes')
|
||||
_566_FIELDS = _426_FIELDS + ('Description-Content-Type', 'Requires', 'Provides', 'Obsoletes')
|
||||
|
||||
_566_MARKERS = ('Description-Content-Type',)
|
||||
_566_MARKERS = ('Description-Content-Type', )
|
||||
|
||||
_643_MARKERS = ('Dynamic', 'License-File')
|
||||
|
||||
@@ -135,18 +120,11 @@ def _version2fieldlist(version):
|
||||
|
||||
def _best_version(fields):
|
||||
"""Detect the best version depending on the fields used."""
|
||||
|
||||
def _has_marker(keys, markers):
|
||||
for marker in markers:
|
||||
if marker in keys:
|
||||
return True
|
||||
return False
|
||||
|
||||
keys = []
|
||||
for key, value in fields.items():
|
||||
if value in ([], 'UNKNOWN', None):
|
||||
continue
|
||||
keys.append(key)
|
||||
return any(marker in keys for marker in markers)
|
||||
|
||||
keys = [key for key, value in fields.items() if value not in ([], 'UNKNOWN', None)]
|
||||
possible_versions = ['1.0', '1.1', '1.2', '1.3', '2.1', '2.2'] # 2.0 removed
|
||||
|
||||
# first let's try to see if a field is not part of one of the version
|
||||
@@ -171,12 +149,12 @@ def _best_version(fields):
|
||||
possible_versions.remove('2.2')
|
||||
logger.debug('Removed 2.2 due to %s', key)
|
||||
# if key not in _426_FIELDS and '2.0' in possible_versions:
|
||||
# possible_versions.remove('2.0')
|
||||
# logger.debug('Removed 2.0 due to %s', key)
|
||||
# possible_versions.remove('2.0')
|
||||
# logger.debug('Removed 2.0 due to %s', key)
|
||||
|
||||
# possible_version contains qualified versions
|
||||
if len(possible_versions) == 1:
|
||||
return possible_versions[0] # found !
|
||||
return possible_versions[0] # found !
|
||||
elif len(possible_versions) == 0:
|
||||
logger.debug('Out of options - unknown metadata set: %s', fields)
|
||||
raise MetadataConflictError('Unknown metadata set')
|
||||
@@ -207,28 +185,25 @@ def _best_version(fields):
|
||||
if is_2_1:
|
||||
return '2.1'
|
||||
# if is_2_2:
|
||||
# return '2.2'
|
||||
# return '2.2'
|
||||
|
||||
return '2.2'
|
||||
|
||||
|
||||
# This follows the rules about transforming keys as described in
|
||||
# https://www.python.org/dev/peps/pep-0566/#id17
|
||||
_ATTR2FIELD = {
|
||||
name.lower().replace("-", "_"): name for name in _ALL_FIELDS
|
||||
}
|
||||
_ATTR2FIELD = {name.lower().replace("-", "_"): name for name in _ALL_FIELDS}
|
||||
_FIELD2ATTR = {field: attr for attr, field in _ATTR2FIELD.items()}
|
||||
|
||||
_PREDICATE_FIELDS = ('Requires-Dist', 'Obsoletes-Dist', 'Provides-Dist')
|
||||
_VERSIONS_FIELDS = ('Requires-Python',)
|
||||
_VERSION_FIELDS = ('Version',)
|
||||
_LISTFIELDS = ('Platform', 'Classifier', 'Obsoletes',
|
||||
'Requires', 'Provides', 'Obsoletes-Dist',
|
||||
'Provides-Dist', 'Requires-Dist', 'Requires-External',
|
||||
'Project-URL', 'Supported-Platform', 'Setup-Requires-Dist',
|
||||
_VERSIONS_FIELDS = ('Requires-Python', )
|
||||
_VERSION_FIELDS = ('Version', )
|
||||
_LISTFIELDS = ('Platform', 'Classifier', 'Obsoletes', 'Requires', 'Provides', 'Obsoletes-Dist', 'Provides-Dist',
|
||||
'Requires-Dist', 'Requires-External', 'Project-URL', 'Supported-Platform', 'Setup-Requires-Dist',
|
||||
'Provides-Extra', 'Extension', 'License-File')
|
||||
_LISTTUPLEFIELDS = ('Project-URL',)
|
||||
_LISTTUPLEFIELDS = ('Project-URL', )
|
||||
|
||||
_ELEMENTSFIELD = ('Keywords',)
|
||||
_ELEMENTSFIELD = ('Keywords', )
|
||||
|
||||
_UNICODEFIELDS = ('Author', 'Maintainer', 'Summary', 'Description')
|
||||
|
||||
@@ -260,10 +235,10 @@ class LegacyMetadata(object):
|
||||
- *mapping* is a dict-like object
|
||||
- *scheme* is a version scheme name
|
||||
"""
|
||||
|
||||
# TODO document the mapping API and UNKNOWN default key
|
||||
|
||||
def __init__(self, path=None, fileobj=None, mapping=None,
|
||||
scheme='default'):
|
||||
def __init__(self, path=None, fileobj=None, mapping=None, scheme='default'):
|
||||
if [path, fileobj, mapping].count(None) < 2:
|
||||
raise TypeError('path, fileobj and mapping are exclusive')
|
||||
self._fields = {}
|
||||
@@ -298,8 +273,7 @@ class LegacyMetadata(object):
|
||||
raise KeyError(name)
|
||||
|
||||
def __contains__(self, name):
|
||||
return (name in self._fields or
|
||||
self._convert_name(name) in self._fields)
|
||||
return (name in self._fields or self._convert_name(name) in self._fields)
|
||||
|
||||
def _convert_name(self, name):
|
||||
if name in _ALL_FIELDS:
|
||||
@@ -327,12 +301,12 @@ class LegacyMetadata(object):
|
||||
# Public API
|
||||
#
|
||||
|
||||
# dependencies = property(_get_dependencies, _set_dependencies)
|
||||
|
||||
def get_fullname(self, filesafe=False):
|
||||
"""Return the distribution name with version.
|
||||
"""
|
||||
Return the distribution name with version.
|
||||
|
||||
If filesafe is true, return a filename-escaped form."""
|
||||
If filesafe is true, return a filename-escaped form.
|
||||
"""
|
||||
return _get_name_and_version(self['Name'], self['Version'], filesafe)
|
||||
|
||||
def is_field(self, name):
|
||||
@@ -423,6 +397,7 @@ class LegacyMetadata(object):
|
||||
Keys that don't match a metadata field or that have an empty value are
|
||||
dropped.
|
||||
"""
|
||||
|
||||
def _set(key, value):
|
||||
if key in _ATTR2FIELD and value:
|
||||
self.set(self._convert_name(key), value)
|
||||
@@ -445,14 +420,12 @@ class LegacyMetadata(object):
|
||||
"""Control then set a metadata field."""
|
||||
name = self._convert_name(name)
|
||||
|
||||
if ((name in _ELEMENTSFIELD or name == 'Platform') and
|
||||
not isinstance(value, (list, tuple))):
|
||||
if ((name in _ELEMENTSFIELD or name == 'Platform') and not isinstance(value, (list, tuple))):
|
||||
if isinstance(value, string_types):
|
||||
value = [v.strip() for v in value.split(',')]
|
||||
else:
|
||||
value = []
|
||||
elif (name in _LISTFIELDS and
|
||||
not isinstance(value, (list, tuple))):
|
||||
elif (name in _LISTFIELDS and not isinstance(value, (list, tuple))):
|
||||
if isinstance(value, string_types):
|
||||
value = [value]
|
||||
else:
|
||||
@@ -466,18 +439,14 @@ class LegacyMetadata(object):
|
||||
for v in value:
|
||||
# check that the values are valid
|
||||
if not scheme.is_valid_matcher(v.split(';')[0]):
|
||||
logger.warning(
|
||||
"'%s': '%s' is not valid (field '%s')",
|
||||
project_name, v, name)
|
||||
logger.warning("'%s': '%s' is not valid (field '%s')", project_name, v, name)
|
||||
# FIXME this rejects UNKNOWN, is that right?
|
||||
elif name in _VERSIONS_FIELDS and value is not None:
|
||||
if not scheme.is_valid_constraint_list(value):
|
||||
logger.warning("'%s': '%s' is not a valid version (field '%s')",
|
||||
project_name, value, name)
|
||||
logger.warning("'%s': '%s' is not a valid version (field '%s')", project_name, value, name)
|
||||
elif name in _VERSION_FIELDS and value is not None:
|
||||
if not scheme.is_valid_version(value):
|
||||
logger.warning("'%s': '%s' is not a valid version (field '%s')",
|
||||
project_name, value, name)
|
||||
logger.warning("'%s': '%s' is not a valid version (field '%s')", project_name, value, name)
|
||||
|
||||
if name in _UNICODEFIELDS:
|
||||
if name == 'Description':
|
||||
@@ -547,10 +516,8 @@ class LegacyMetadata(object):
|
||||
return True
|
||||
|
||||
for fields, controller in ((_PREDICATE_FIELDS, are_valid_constraints),
|
||||
(_VERSIONS_FIELDS,
|
||||
scheme.is_valid_constraint_list),
|
||||
(_VERSION_FIELDS,
|
||||
scheme.is_valid_version)):
|
||||
(_VERSIONS_FIELDS, scheme.is_valid_constraint_list), (_VERSION_FIELDS,
|
||||
scheme.is_valid_version)):
|
||||
for field in fields:
|
||||
value = self.get(field, None)
|
||||
if value is not None and not controller(value):
|
||||
@@ -606,8 +573,7 @@ class LegacyMetadata(object):
|
||||
return [(key, self[key]) for key in self.keys()]
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %s %s>' % (self.__class__.__name__, self.name,
|
||||
self.version)
|
||||
return '<%s %s %s>' % (self.__class__.__name__, self.name, self.version)
|
||||
|
||||
|
||||
METADATA_FILENAME = 'pydist.json'
|
||||
@@ -639,7 +605,7 @@ class Metadata(object):
|
||||
MANDATORY_KEYS = {
|
||||
'name': (),
|
||||
'version': (),
|
||||
'summary': ('legacy',),
|
||||
'summary': ('legacy', ),
|
||||
}
|
||||
|
||||
INDEX_KEYS = ('name version license summary description author '
|
||||
@@ -652,22 +618,21 @@ class Metadata(object):
|
||||
|
||||
SYNTAX_VALIDATORS = {
|
||||
'metadata_version': (METADATA_VERSION_MATCHER, ()),
|
||||
'name': (NAME_MATCHER, ('legacy',)),
|
||||
'version': (VERSION_MATCHER, ('legacy',)),
|
||||
'summary': (SUMMARY_MATCHER, ('legacy',)),
|
||||
'dynamic': (FIELDNAME_MATCHER, ('legacy',)),
|
||||
'name': (NAME_MATCHER, ('legacy', )),
|
||||
'version': (VERSION_MATCHER, ('legacy', )),
|
||||
'summary': (SUMMARY_MATCHER, ('legacy', )),
|
||||
'dynamic': (FIELDNAME_MATCHER, ('legacy', )),
|
||||
}
|
||||
|
||||
__slots__ = ('_legacy', '_data', 'scheme')
|
||||
|
||||
def __init__(self, path=None, fileobj=None, mapping=None,
|
||||
scheme='default'):
|
||||
def __init__(self, path=None, fileobj=None, mapping=None, scheme='default'):
|
||||
if [path, fileobj, mapping].count(None) < 2:
|
||||
raise TypeError('path, fileobj and mapping are exclusive')
|
||||
self._legacy = None
|
||||
self._data = None
|
||||
self.scheme = scheme
|
||||
#import pdb; pdb.set_trace()
|
||||
# import pdb; pdb.set_trace()
|
||||
if mapping is not None:
|
||||
try:
|
||||
self._validate_mapping(mapping, scheme)
|
||||
@@ -701,8 +666,7 @@ class Metadata(object):
|
||||
# The ValueError comes from the json.load - if that
|
||||
# succeeds and we get a validation error, we want
|
||||
# that to propagate
|
||||
self._legacy = LegacyMetadata(fileobj=StringIO(data),
|
||||
scheme=scheme)
|
||||
self._legacy = LegacyMetadata(fileobj=StringIO(data), scheme=scheme)
|
||||
self.validate()
|
||||
|
||||
common_keys = set(('name', 'version', 'license', 'keywords', 'summary'))
|
||||
@@ -740,8 +704,7 @@ class Metadata(object):
|
||||
result = self._legacy.get(lk)
|
||||
else:
|
||||
value = None if maker is None else maker()
|
||||
if key not in ('commands', 'exports', 'modules', 'namespaces',
|
||||
'classifiers'):
|
||||
if key not in ('commands', 'exports', 'modules', 'namespaces', 'classifiers'):
|
||||
result = self._data.get(key, value)
|
||||
else:
|
||||
# special cases for PEP 459
|
||||
@@ -778,8 +741,7 @@ class Metadata(object):
|
||||
m = pattern.match(value)
|
||||
if not m:
|
||||
raise MetadataInvalidError("'%s' is an invalid value for "
|
||||
"the '%s' property" % (value,
|
||||
key))
|
||||
"the '%s' property" % (value, key))
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
self._validate_value(key, value)
|
||||
@@ -791,8 +753,7 @@ class Metadata(object):
|
||||
if lk is None:
|
||||
raise NotImplementedError
|
||||
self._legacy[lk] = value
|
||||
elif key not in ('commands', 'exports', 'modules', 'namespaces',
|
||||
'classifiers'):
|
||||
elif key not in ('commands', 'exports', 'modules', 'namespaces', 'classifiers'):
|
||||
self._data[key] = value
|
||||
else:
|
||||
# special cases for PEP 459
|
||||
@@ -880,8 +841,7 @@ class Metadata(object):
|
||||
# A recursive call, but it should terminate since 'test'
|
||||
# has been removed from the extras
|
||||
reqts = self._data.get('%s_requires' % key, [])
|
||||
result.extend(self.get_requirements(reqts, extras=extras,
|
||||
env=env))
|
||||
result.extend(self.get_requirements(reqts, extras=extras, env=env))
|
||||
return result
|
||||
|
||||
@property
|
||||
@@ -922,8 +882,7 @@ class Metadata(object):
|
||||
if self._legacy:
|
||||
missing, warnings = self._legacy.check(True)
|
||||
if missing or warnings:
|
||||
logger.warning('Metadata: missing: %s, warnings: %s',
|
||||
missing, warnings)
|
||||
logger.warning('Metadata: missing: %s, warnings: %s', missing, warnings)
|
||||
else:
|
||||
self._validate_mapping(self._data, self.scheme)
|
||||
|
||||
@@ -940,9 +899,8 @@ class Metadata(object):
|
||||
'metadata_version': self.METADATA_VERSION,
|
||||
'generator': self.GENERATOR,
|
||||
}
|
||||
lmd = self._legacy.todict(True) # skip missing ones
|
||||
for k in ('name', 'version', 'license', 'summary', 'description',
|
||||
'classifier'):
|
||||
lmd = self._legacy.todict(True) # skip missing ones
|
||||
for k in ('name', 'version', 'license', 'summary', 'description', 'classifier'):
|
||||
if k in lmd:
|
||||
if k == 'classifier':
|
||||
nk = 'classifiers'
|
||||
@@ -953,14 +911,13 @@ class Metadata(object):
|
||||
if kw == ['']:
|
||||
kw = []
|
||||
result['keywords'] = kw
|
||||
keys = (('requires_dist', 'run_requires'),
|
||||
('setup_requires_dist', 'build_requires'))
|
||||
keys = (('requires_dist', 'run_requires'), ('setup_requires_dist', 'build_requires'))
|
||||
for ok, nk in keys:
|
||||
if ok in lmd and lmd[ok]:
|
||||
result[nk] = [{'requires': lmd[ok]}]
|
||||
result['provides'] = self.provides
|
||||
author = {}
|
||||
maintainer = {}
|
||||
# author = {}
|
||||
# maintainer = {}
|
||||
return result
|
||||
|
||||
LEGACY_MAPPING = {
|
||||
@@ -977,6 +934,7 @@ class Metadata(object):
|
||||
}
|
||||
|
||||
def _to_legacy(self):
|
||||
|
||||
def process_entries(entries):
|
||||
reqts = set()
|
||||
for e in entries:
|
||||
@@ -1045,12 +1003,10 @@ class Metadata(object):
|
||||
else:
|
||||
d = self._data
|
||||
if fileobj:
|
||||
json.dump(d, fileobj, ensure_ascii=True, indent=2,
|
||||
sort_keys=True)
|
||||
json.dump(d, fileobj, ensure_ascii=True, indent=2, sort_keys=True)
|
||||
else:
|
||||
with codecs.open(path, 'w', 'utf-8') as f:
|
||||
json.dump(d, f, ensure_ascii=True, indent=2,
|
||||
sort_keys=True)
|
||||
json.dump(d, f, ensure_ascii=True, indent=2, sort_keys=True)
|
||||
|
||||
def add_requirements(self, requirements):
|
||||
if self._legacy:
|
||||
@@ -1063,7 +1019,7 @@ class Metadata(object):
|
||||
always = entry
|
||||
break
|
||||
if always is None:
|
||||
always = { 'requires': requirements }
|
||||
always = {'requires': requirements}
|
||||
run_requires.insert(0, always)
|
||||
else:
|
||||
rset = set(always['requires']) | set(requirements)
|
||||
@@ -1072,5 +1028,4 @@ class Metadata(object):
|
||||
def __repr__(self):
|
||||
name = self.name or '(no name)'
|
||||
version = self.version or 'no version'
|
||||
return '<%s %s %s (%s)>' % (self.__class__.__name__,
|
||||
self.metadata_version, name, version)
|
||||
return '<%s %s %s (%s)>' % (self.__class__.__name__, self.metadata_version, name, version)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2013-2015 Vinay Sajip.
|
||||
# Copyright (C) 2013-2023 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
@@ -15,8 +15,7 @@ from zipfile import ZipInfo
|
||||
|
||||
from .compat import sysconfig, detect_encoding, ZipFile
|
||||
from .resources import finder
|
||||
from .util import (FileOperator, get_export_entry, convert_path,
|
||||
get_executable, get_platform, in_venv)
|
||||
from .util import (FileOperator, get_export_entry, convert_path, get_executable, get_platform, in_venv)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -49,6 +48,25 @@ if __name__ == '__main__':
|
||||
sys.exit(%(func)s())
|
||||
'''
|
||||
|
||||
# Pre-fetch the contents of all executable wrapper stubs.
|
||||
# This is to address https://github.com/pypa/pip/issues/12666.
|
||||
# When updating pip, we rename the old pip in place before installing the
|
||||
# new version. If we try to fetch a wrapper *after* that rename, the finder
|
||||
# machinery will be confused as the package is no longer available at the
|
||||
# location where it was imported from. So we load everything into memory in
|
||||
# advance.
|
||||
|
||||
if os.name == 'nt' or (os.name == 'java' and os._name == 'nt'):
|
||||
# Issue 31: don't hardcode an absolute package name, but
|
||||
# determine it relative to the current package
|
||||
DISTLIB_PACKAGE = __name__.rsplit('.', 1)[0]
|
||||
|
||||
WRAPPERS = {
|
||||
r.name: r.bytes
|
||||
for r in finder(DISTLIB_PACKAGE).iterator("")
|
||||
if r.name.endswith(".exe")
|
||||
}
|
||||
|
||||
|
||||
def enquote_executable(executable):
|
||||
if ' ' in executable:
|
||||
@@ -65,9 +83,11 @@ def enquote_executable(executable):
|
||||
executable = '"%s"' % executable
|
||||
return executable
|
||||
|
||||
|
||||
# Keep the old name around (for now), as there is at least one project using it!
|
||||
_enquote_executable = enquote_executable
|
||||
|
||||
|
||||
class ScriptMaker(object):
|
||||
"""
|
||||
A class to copy or create scripts from source scripts or callable
|
||||
@@ -77,21 +97,18 @@ class ScriptMaker(object):
|
||||
|
||||
executable = None # for shebangs
|
||||
|
||||
def __init__(self, source_dir, target_dir, add_launchers=True,
|
||||
dry_run=False, fileop=None):
|
||||
def __init__(self, source_dir, target_dir, add_launchers=True, dry_run=False, fileop=None):
|
||||
self.source_dir = source_dir
|
||||
self.target_dir = target_dir
|
||||
self.add_launchers = add_launchers
|
||||
self.force = False
|
||||
self.clobber = False
|
||||
# It only makes sense to set mode bits on POSIX.
|
||||
self.set_mode = (os.name == 'posix') or (os.name == 'java' and
|
||||
os._name == 'posix')
|
||||
self.set_mode = (os.name == 'posix') or (os.name == 'java' and os._name == 'posix')
|
||||
self.variants = set(('', 'X.Y'))
|
||||
self._fileop = fileop or FileOperator(dry_run)
|
||||
|
||||
self._is_nt = os.name == 'nt' or (
|
||||
os.name == 'java' and os._name == 'nt')
|
||||
self._is_nt = os.name == 'nt' or (os.name == 'java' and os._name == 'nt')
|
||||
self.version_info = sys.version_info
|
||||
|
||||
def _get_alternate_executable(self, executable, options):
|
||||
@@ -102,6 +119,7 @@ class ScriptMaker(object):
|
||||
return executable
|
||||
|
||||
if sys.platform.startswith('java'): # pragma: no cover
|
||||
|
||||
def _is_shell(self, executable):
|
||||
"""
|
||||
Determine if the specified executable is a script
|
||||
@@ -139,6 +157,12 @@ class ScriptMaker(object):
|
||||
"""
|
||||
if os.name != 'posix':
|
||||
simple_shebang = True
|
||||
elif getattr(sys, "cross_compiling", False):
|
||||
# In a cross-compiling environment, the shebang will likely be a
|
||||
# script; this *must* be invoked with the "safe" version of the
|
||||
# shebang, or else using os.exec() to run the entry script will
|
||||
# fail, raising "OSError 8 [Errno 8] Exec format error".
|
||||
simple_shebang = False
|
||||
else:
|
||||
# Add 3 for '#!' prefix and newline suffix.
|
||||
shebang_length = len(executable) + len(post_interp) + 3
|
||||
@@ -146,37 +170,35 @@ class ScriptMaker(object):
|
||||
max_shebang_length = 512
|
||||
else:
|
||||
max_shebang_length = 127
|
||||
simple_shebang = ((b' ' not in executable) and
|
||||
(shebang_length <= max_shebang_length))
|
||||
simple_shebang = ((b' ' not in executable) and (shebang_length <= max_shebang_length))
|
||||
|
||||
if simple_shebang:
|
||||
result = b'#!' + executable + post_interp + b'\n'
|
||||
else:
|
||||
result = b'#!/bin/sh\n'
|
||||
result += b"'''exec' " + executable + post_interp + b' "$0" "$@"\n'
|
||||
result += b"' '''"
|
||||
result += b"' '''\n"
|
||||
return result
|
||||
|
||||
def _get_shebang(self, encoding, post_interp=b'', options=None):
|
||||
enquote = True
|
||||
if self.executable:
|
||||
executable = self.executable
|
||||
enquote = False # assume this will be taken care of
|
||||
enquote = False # assume this will be taken care of
|
||||
elif not sysconfig.is_python_build():
|
||||
executable = get_executable()
|
||||
elif in_venv(): # pragma: no cover
|
||||
executable = os.path.join(sysconfig.get_path('scripts'),
|
||||
'python%s' % sysconfig.get_config_var('EXE'))
|
||||
executable = os.path.join(sysconfig.get_path('scripts'), 'python%s' % sysconfig.get_config_var('EXE'))
|
||||
else: # pragma: no cover
|
||||
executable = os.path.join(
|
||||
sysconfig.get_config_var('BINDIR'),
|
||||
'python%s%s' % (sysconfig.get_config_var('VERSION'),
|
||||
sysconfig.get_config_var('EXE')))
|
||||
if not os.path.isfile(executable):
|
||||
if os.name == 'nt':
|
||||
# for Python builds from source on Windows, no Python executables with
|
||||
# a version suffix are created, so we use python.exe
|
||||
executable = os.path.join(sysconfig.get_config_var('BINDIR'),
|
||||
'python%s' % (sysconfig.get_config_var('EXE')))
|
||||
'python%s' % (sysconfig.get_config_var('EXE')))
|
||||
else:
|
||||
executable = os.path.join(
|
||||
sysconfig.get_config_var('BINDIR'),
|
||||
'python%s%s' % (sysconfig.get_config_var('VERSION'), sysconfig.get_config_var('EXE')))
|
||||
if options:
|
||||
executable = self._get_alternate_executable(executable, options)
|
||||
|
||||
@@ -200,8 +222,8 @@ class ScriptMaker(object):
|
||||
# check that the shebang is decodable using utf-8.
|
||||
executable = executable.encode('utf-8')
|
||||
# in case of IronPython, play safe and enable frames support
|
||||
if (sys.platform == 'cli' and '-X:Frames' not in post_interp
|
||||
and '-X:FullFrames' not in post_interp): # pragma: no cover
|
||||
if (sys.platform == 'cli' and '-X:Frames' not in post_interp and
|
||||
'-X:FullFrames' not in post_interp): # pragma: no cover
|
||||
post_interp += b' -X:Frames'
|
||||
shebang = self._build_shebang(executable, post_interp)
|
||||
# Python parser starts to read a script using UTF-8 until
|
||||
@@ -212,8 +234,7 @@ class ScriptMaker(object):
|
||||
try:
|
||||
shebang.decode('utf-8')
|
||||
except UnicodeDecodeError: # pragma: no cover
|
||||
raise ValueError(
|
||||
'The shebang (%r) is not decodable from utf-8' % shebang)
|
||||
raise ValueError('The shebang (%r) is not decodable from utf-8' % shebang)
|
||||
# If the script is encoded to a custom encoding (use a
|
||||
# #coding:xxx cookie), the shebang has to be decodable from
|
||||
# the script encoding too.
|
||||
@@ -221,15 +242,13 @@ class ScriptMaker(object):
|
||||
try:
|
||||
shebang.decode(encoding)
|
||||
except UnicodeDecodeError: # pragma: no cover
|
||||
raise ValueError(
|
||||
'The shebang (%r) is not decodable '
|
||||
'from the script encoding (%r)' % (shebang, encoding))
|
||||
raise ValueError('The shebang (%r) is not decodable '
|
||||
'from the script encoding (%r)' % (shebang, encoding))
|
||||
return shebang
|
||||
|
||||
def _get_script_text(self, entry):
|
||||
return self.script_template % dict(module=entry.prefix,
|
||||
import_name=entry.suffix.split('.')[0],
|
||||
func=entry.suffix)
|
||||
return self.script_template % dict(
|
||||
module=entry.prefix, import_name=entry.suffix.split('.')[0], func=entry.suffix)
|
||||
|
||||
manifest = _DEFAULT_MANIFEST
|
||||
|
||||
@@ -239,9 +258,6 @@ class ScriptMaker(object):
|
||||
|
||||
def _write_script(self, names, shebang, script_bytes, filenames, ext):
|
||||
use_launcher = self.add_launchers and self._is_nt
|
||||
linesep = os.linesep.encode('utf-8')
|
||||
if not shebang.endswith(linesep):
|
||||
shebang += linesep
|
||||
if not use_launcher:
|
||||
script_bytes = shebang + script_bytes
|
||||
else: # pragma: no cover
|
||||
@@ -275,7 +291,7 @@ class ScriptMaker(object):
|
||||
'use .deleteme logic')
|
||||
dfname = '%s.deleteme' % outname
|
||||
if os.path.exists(dfname):
|
||||
os.remove(dfname) # Not allowed to fail here
|
||||
os.remove(dfname) # Not allowed to fail here
|
||||
os.rename(outname, dfname) # nor here
|
||||
self._fileop.write_binary_file(outname, script_bytes)
|
||||
logger.debug('Able to replace executable using '
|
||||
@@ -283,7 +299,7 @@ class ScriptMaker(object):
|
||||
try:
|
||||
os.remove(dfname)
|
||||
except Exception:
|
||||
pass # still in use - ignore error
|
||||
pass # still in use - ignore error
|
||||
else:
|
||||
if self._is_nt and not outname.endswith('.' + ext): # pragma: no cover
|
||||
outname = '%s.%s' % (outname, ext)
|
||||
@@ -304,8 +320,7 @@ class ScriptMaker(object):
|
||||
if 'X' in self.variants:
|
||||
result.add('%s%s' % (name, self.version_info[0]))
|
||||
if 'X.Y' in self.variants:
|
||||
result.add('%s%s%s.%s' % (name, self.variant_separator,
|
||||
self.version_info[0], self.version_info[1]))
|
||||
result.add('%s%s%s.%s' % (name, self.variant_separator, self.version_info[0], self.version_info[1]))
|
||||
return result
|
||||
|
||||
def _make_script(self, entry, filenames, options=None):
|
||||
@@ -360,8 +375,7 @@ class ScriptMaker(object):
|
||||
self._fileop.set_executable_mode([outname])
|
||||
filenames.append(outname)
|
||||
else:
|
||||
logger.info('copying and adjusting %s -> %s', script,
|
||||
self.target_dir)
|
||||
logger.info('copying and adjusting %s -> %s', script, self.target_dir)
|
||||
if not self._fileop.dry_run:
|
||||
encoding, lines = detect_encoding(f.readline)
|
||||
f.seek(0)
|
||||
@@ -388,21 +402,17 @@ class ScriptMaker(object):
|
||||
# Launchers are from https://bitbucket.org/vinay.sajip/simple_launcher/
|
||||
|
||||
def _get_launcher(self, kind):
|
||||
if struct.calcsize('P') == 8: # 64-bit
|
||||
if struct.calcsize('P') == 8: # 64-bit
|
||||
bits = '64'
|
||||
else:
|
||||
bits = '32'
|
||||
platform_suffix = '-arm' if get_platform() == 'win-arm64' else ''
|
||||
name = '%s%s%s.exe' % (kind, bits, platform_suffix)
|
||||
# Issue 31: don't hardcode an absolute package name, but
|
||||
# determine it relative to the current package
|
||||
distlib_package = __name__.rsplit('.', 1)[0]
|
||||
resource = finder(distlib_package).find(name)
|
||||
if not resource:
|
||||
msg = ('Unable to find resource %s in package %s' % (name,
|
||||
distlib_package))
|
||||
if name not in WRAPPERS:
|
||||
msg = ('Unable to find resource %s in package %s' %
|
||||
(name, DISTLIB_PACKAGE))
|
||||
raise ValueError(msg)
|
||||
return resource.bytes
|
||||
return WRAPPERS[name]
|
||||
|
||||
# Public API follows
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (C) 2012-2021 The Python Software Foundation.
|
||||
# Copyright (C) 2012-2023 The Python Software Foundation.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
import codecs
|
||||
@@ -31,11 +31,9 @@ except ImportError: # pragma: no cover
|
||||
import time
|
||||
|
||||
from . import DistlibException
|
||||
from .compat import (string_types, text_type, shutil, raw_input, StringIO,
|
||||
cache_from_source, urlopen, urljoin, httplib, xmlrpclib,
|
||||
splittype, HTTPHandler, BaseConfigurator, valid_ident,
|
||||
Container, configparser, URLError, ZipFile, fsdecode,
|
||||
unquote, urlparse)
|
||||
from .compat import (string_types, text_type, shutil, raw_input, StringIO, cache_from_source, urlopen, urljoin, httplib,
|
||||
xmlrpclib, HTTPHandler, BaseConfigurator, valid_ident, Container, configparser, URLError, ZipFile,
|
||||
fsdecode, unquote, urlparse)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -62,6 +60,7 @@ def parse_marker(marker_string):
|
||||
interpreted as a literal string, and a string not contained in quotes is a
|
||||
variable (such as os_name).
|
||||
"""
|
||||
|
||||
def marker_var(remaining):
|
||||
# either identifier, or literal string
|
||||
m = IDENTIFIER.match(remaining)
|
||||
@@ -95,7 +94,7 @@ def parse_marker(marker_string):
|
||||
raise SyntaxError('unterminated string: %s' % s)
|
||||
parts.append(q)
|
||||
result = ''.join(parts)
|
||||
remaining = remaining[1:].lstrip() # skip past closing quote
|
||||
remaining = remaining[1:].lstrip() # skip past closing quote
|
||||
return result, remaining
|
||||
|
||||
def marker_expr(remaining):
|
||||
@@ -263,8 +262,7 @@ def parse_requirement(req):
|
||||
rs = distname
|
||||
else:
|
||||
rs = '%s %s' % (distname, ', '.join(['%s %s' % con for con in versions]))
|
||||
return Container(name=distname, extras=extras, constraints=versions,
|
||||
marker=mark_expr, url=uri, requirement=rs)
|
||||
return Container(name=distname, extras=extras, constraints=versions, marker=mark_expr, url=uri, requirement=rs)
|
||||
|
||||
|
||||
def get_resources_dests(resources_root, rules):
|
||||
@@ -304,15 +302,15 @@ def in_venv():
|
||||
|
||||
|
||||
def get_executable():
|
||||
# The __PYVENV_LAUNCHER__ dance is apparently no longer needed, as
|
||||
# changes to the stub launcher mean that sys.executable always points
|
||||
# to the stub on OS X
|
||||
# if sys.platform == 'darwin' and ('__PYVENV_LAUNCHER__'
|
||||
# in os.environ):
|
||||
# result = os.environ['__PYVENV_LAUNCHER__']
|
||||
# else:
|
||||
# result = sys.executable
|
||||
# return result
|
||||
# The __PYVENV_LAUNCHER__ dance is apparently no longer needed, as
|
||||
# changes to the stub launcher mean that sys.executable always points
|
||||
# to the stub on OS X
|
||||
# if sys.platform == 'darwin' and ('__PYVENV_LAUNCHER__'
|
||||
# in os.environ):
|
||||
# result = os.environ['__PYVENV_LAUNCHER__']
|
||||
# else:
|
||||
# result = sys.executable
|
||||
# return result
|
||||
# Avoid normcasing: see issue #143
|
||||
# result = os.path.normcase(sys.executable)
|
||||
result = sys.executable
|
||||
@@ -346,6 +344,7 @@ def extract_by_key(d, keys):
|
||||
result[key] = d[key]
|
||||
return result
|
||||
|
||||
|
||||
def read_exports(stream):
|
||||
if sys.version_info[0] >= 3:
|
||||
# needs to be a text stream
|
||||
@@ -388,7 +387,7 @@ def read_exports(stream):
|
||||
s = '%s = %s' % (name, value)
|
||||
entry = get_export_entry(s)
|
||||
assert entry is not None
|
||||
#entry.dist = self
|
||||
# entry.dist = self
|
||||
entries[name] = entry
|
||||
return result
|
||||
|
||||
@@ -420,6 +419,7 @@ def tempdir():
|
||||
finally:
|
||||
shutil.rmtree(td)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def chdir(d):
|
||||
cwd = os.getcwd()
|
||||
@@ -441,19 +441,21 @@ def socket_timeout(seconds=15):
|
||||
|
||||
|
||||
class cached_property(object):
|
||||
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
#for attr in ('__name__', '__module__', '__doc__'):
|
||||
# setattr(self, attr, getattr(func, attr, None))
|
||||
# for attr in ('__name__', '__module__', '__doc__'):
|
||||
# setattr(self, attr, getattr(func, attr, None))
|
||||
|
||||
def __get__(self, obj, cls=None):
|
||||
if obj is None:
|
||||
return self
|
||||
value = self.func(obj)
|
||||
object.__setattr__(obj, self.func.__name__, value)
|
||||
#obj.__dict__[self.func.__name__] = value = self.func(obj)
|
||||
# obj.__dict__[self.func.__name__] = value = self.func(obj)
|
||||
return value
|
||||
|
||||
|
||||
def convert_path(pathname):
|
||||
"""Return 'pathname' as a name that will work on the native filesystem.
|
||||
|
||||
@@ -482,6 +484,7 @@ def convert_path(pathname):
|
||||
|
||||
|
||||
class FileOperator(object):
|
||||
|
||||
def __init__(self, dry_run=False):
|
||||
self.dry_run = dry_run
|
||||
self.ensured = set()
|
||||
@@ -509,8 +512,7 @@ class FileOperator(object):
|
||||
second will have the same "age".
|
||||
"""
|
||||
if not os.path.exists(source):
|
||||
raise DistlibException("file '%r' does not exist" %
|
||||
os.path.abspath(source))
|
||||
raise DistlibException("file '%r' does not exist" % os.path.abspath(source))
|
||||
if not os.path.exists(target):
|
||||
return True
|
||||
|
||||
@@ -598,8 +600,10 @@ class FileOperator(object):
|
||||
diagpath = path[len(prefix):]
|
||||
compile_kwargs = {}
|
||||
if hashed_invalidation and hasattr(py_compile, 'PycInvalidationMode'):
|
||||
compile_kwargs['invalidation_mode'] = py_compile.PycInvalidationMode.CHECKED_HASH
|
||||
py_compile.compile(path, dpath, diagpath, True, **compile_kwargs) # raise error
|
||||
if not isinstance(hashed_invalidation, py_compile.PycInvalidationMode):
|
||||
hashed_invalidation = py_compile.PycInvalidationMode.CHECKED_HASH
|
||||
compile_kwargs['invalidation_mode'] = hashed_invalidation
|
||||
py_compile.compile(path, dpath, diagpath, True, **compile_kwargs) # raise error
|
||||
self.record_as_written(dpath)
|
||||
return dpath
|
||||
|
||||
@@ -661,9 +665,10 @@ class FileOperator(object):
|
||||
assert flist == ['__pycache__']
|
||||
sd = os.path.join(d, flist[0])
|
||||
os.rmdir(sd)
|
||||
os.rmdir(d) # should fail if non-empty
|
||||
os.rmdir(d) # should fail if non-empty
|
||||
self._init_record()
|
||||
|
||||
|
||||
def resolve(module_name, dotted_path):
|
||||
if module_name in sys.modules:
|
||||
mod = sys.modules[module_name]
|
||||
@@ -680,6 +685,7 @@ def resolve(module_name, dotted_path):
|
||||
|
||||
|
||||
class ExportEntry(object):
|
||||
|
||||
def __init__(self, name, prefix, suffix, flags):
|
||||
self.name = name
|
||||
self.prefix = prefix
|
||||
@@ -691,27 +697,26 @@ class ExportEntry(object):
|
||||
return resolve(self.prefix, self.suffix)
|
||||
|
||||
def __repr__(self): # pragma: no cover
|
||||
return '<ExportEntry %s = %s:%s %s>' % (self.name, self.prefix,
|
||||
self.suffix, self.flags)
|
||||
return '<ExportEntry %s = %s:%s %s>' % (self.name, self.prefix, self.suffix, self.flags)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, ExportEntry):
|
||||
result = False
|
||||
else:
|
||||
result = (self.name == other.name and
|
||||
self.prefix == other.prefix and
|
||||
self.suffix == other.suffix and
|
||||
result = (self.name == other.name and self.prefix == other.prefix and self.suffix == other.suffix and
|
||||
self.flags == other.flags)
|
||||
return result
|
||||
|
||||
__hash__ = object.__hash__
|
||||
|
||||
|
||||
ENTRY_RE = re.compile(r'''(?P<name>(\w|[-.+])+)
|
||||
ENTRY_RE = re.compile(
|
||||
r'''(?P<name>([^\[]\S*))
|
||||
\s*=\s*(?P<callable>(\w+)([:\.]\w+)*)
|
||||
\s*(\[\s*(?P<flags>[\w-]+(=\w+)?(,\s*\w+(=\w+)?)*)\s*\])?
|
||||
''', re.VERBOSE)
|
||||
|
||||
|
||||
def get_export_entry(specification):
|
||||
m = ENTRY_RE.search(specification)
|
||||
if not m:
|
||||
@@ -784,7 +789,7 @@ def get_cache_base(suffix=None):
|
||||
return os.path.join(result, suffix)
|
||||
|
||||
|
||||
def path_to_cache_dir(path):
|
||||
def path_to_cache_dir(path, use_abspath=True):
|
||||
"""
|
||||
Convert an absolute path to a directory name for use in a cache.
|
||||
|
||||
@@ -794,7 +799,7 @@ def path_to_cache_dir(path):
|
||||
#. Any occurrence of ``os.sep`` is replaced with ``'--'``.
|
||||
#. ``'.cache'`` is appended.
|
||||
"""
|
||||
d, p = os.path.splitdrive(os.path.abspath(path))
|
||||
d, p = os.path.splitdrive(os.path.abspath(path) if use_abspath else path)
|
||||
if d:
|
||||
d = d.replace(':', '---')
|
||||
p = p.replace(os.sep, '--')
|
||||
@@ -827,6 +832,7 @@ def get_process_umask():
|
||||
os.umask(result)
|
||||
return result
|
||||
|
||||
|
||||
def is_string_sequence(seq):
|
||||
result = True
|
||||
i = None
|
||||
@@ -837,6 +843,7 @@ def is_string_sequence(seq):
|
||||
assert i is not None
|
||||
return result
|
||||
|
||||
|
||||
PROJECT_NAME_AND_VERSION = re.compile('([a-z0-9_]+([.-][a-z_][a-z0-9_]*)*)-'
|
||||
'([a-z0-9_.+-]+)', re.I)
|
||||
PYTHON_VERSION = re.compile(r'-py(\d\.?\d?)')
|
||||
@@ -866,10 +873,12 @@ def split_filename(filename, project_name=None):
|
||||
result = m.group(1), m.group(3), pyver
|
||||
return result
|
||||
|
||||
|
||||
# Allow spaces in name because of legacy dists like "Twisted Core"
|
||||
NAME_VERSION_RE = re.compile(r'(?P<name>[\w .-]+)\s*'
|
||||
r'\(\s*(?P<ver>[^\s)]+)\)$')
|
||||
|
||||
|
||||
def parse_name_and_version(p):
|
||||
"""
|
||||
A utility method used to get name and version from a string.
|
||||
@@ -885,6 +894,7 @@ def parse_name_and_version(p):
|
||||
d = m.groupdict()
|
||||
return d['name'].strip().lower(), d['ver']
|
||||
|
||||
|
||||
def get_extras(requested, available):
|
||||
result = set()
|
||||
requested = set(requested or [])
|
||||
@@ -906,10 +916,13 @@ def get_extras(requested, available):
|
||||
logger.warning('undeclared extra: %s' % r)
|
||||
result.add(r)
|
||||
return result
|
||||
|
||||
|
||||
#
|
||||
# Extended metadata functionality
|
||||
#
|
||||
|
||||
|
||||
def _get_external_data(url):
|
||||
result = {}
|
||||
try:
|
||||
@@ -923,21 +936,24 @@ def _get_external_data(url):
|
||||
logger.debug('Unexpected response for JSON request: %s', ct)
|
||||
else:
|
||||
reader = codecs.getreader('utf-8')(resp)
|
||||
#data = reader.read().decode('utf-8')
|
||||
#result = json.loads(data)
|
||||
# data = reader.read().decode('utf-8')
|
||||
# result = json.loads(data)
|
||||
result = json.load(reader)
|
||||
except Exception as e:
|
||||
logger.exception('Failed to get external data for %s: %s', url, e)
|
||||
return result
|
||||
|
||||
|
||||
_external_data_base_url = 'https://www.red-dove.com/pypi/projects/'
|
||||
|
||||
|
||||
def get_project_data(name):
|
||||
url = '%s/%s/project.json' % (name[0].upper(), name)
|
||||
url = urljoin(_external_data_base_url, url)
|
||||
result = _get_external_data(url)
|
||||
return result
|
||||
|
||||
|
||||
def get_package_data(name, version):
|
||||
url = '%s/%s/package-%s.json' % (name[0].upper(), name, version)
|
||||
url = urljoin(_external_data_base_url, url)
|
||||
@@ -965,11 +981,11 @@ class Cache(object):
|
||||
logger.warning('Directory \'%s\' is not private', base)
|
||||
self.base = os.path.abspath(os.path.normpath(base))
|
||||
|
||||
def prefix_to_dir(self, prefix):
|
||||
def prefix_to_dir(self, prefix, use_abspath=True):
|
||||
"""
|
||||
Converts a resource prefix to a directory name in the cache.
|
||||
"""
|
||||
return path_to_cache_dir(prefix)
|
||||
return path_to_cache_dir(prefix, use_abspath=use_abspath)
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
@@ -992,6 +1008,7 @@ class EventMixin(object):
|
||||
"""
|
||||
A very simple publish/subscribe system.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._subscribers = {}
|
||||
|
||||
@@ -1053,18 +1070,19 @@ class EventMixin(object):
|
||||
logger.exception('Exception during event publication')
|
||||
value = None
|
||||
result.append(value)
|
||||
logger.debug('publish %s: args = %s, kwargs = %s, result = %s',
|
||||
event, args, kwargs, result)
|
||||
logger.debug('publish %s: args = %s, kwargs = %s, result = %s', event, args, kwargs, result)
|
||||
return result
|
||||
|
||||
|
||||
#
|
||||
# Simple sequencing
|
||||
#
|
||||
class Sequencer(object):
|
||||
|
||||
def __init__(self):
|
||||
self._preds = {}
|
||||
self._succs = {}
|
||||
self._nodes = set() # nodes with no preds/succs
|
||||
self._nodes = set() # nodes with no preds/succs
|
||||
|
||||
def add_node(self, node):
|
||||
self._nodes.add(node)
|
||||
@@ -1104,8 +1122,7 @@ class Sequencer(object):
|
||||
raise ValueError('%r not a successor of %r' % (succ, pred))
|
||||
|
||||
def is_step(self, step):
|
||||
return (step in self._preds or step in self._succs or
|
||||
step in self._nodes)
|
||||
return (step in self._preds or step in self._succs or step in self._nodes)
|
||||
|
||||
def get_steps(self, final):
|
||||
if not self.is_step(final):
|
||||
@@ -1134,7 +1151,7 @@ class Sequencer(object):
|
||||
|
||||
@property
|
||||
def strong_connections(self):
|
||||
#http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
|
||||
# http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
|
||||
index_counter = [0]
|
||||
stack = []
|
||||
lowlinks = {}
|
||||
@@ -1159,11 +1176,11 @@ class Sequencer(object):
|
||||
if successor not in lowlinks:
|
||||
# Successor has not yet been visited
|
||||
strongconnect(successor)
|
||||
lowlinks[node] = min(lowlinks[node],lowlinks[successor])
|
||||
lowlinks[node] = min(lowlinks[node], lowlinks[successor])
|
||||
elif successor in stack:
|
||||
# the successor is in the stack and hence in the current
|
||||
# strongly connected component (SCC)
|
||||
lowlinks[node] = min(lowlinks[node],index[successor])
|
||||
lowlinks[node] = min(lowlinks[node], index[successor])
|
||||
|
||||
# If `node` is a root node, pop the stack and generate an SCC
|
||||
if lowlinks[node] == index[node]:
|
||||
@@ -1172,7 +1189,8 @@ class Sequencer(object):
|
||||
while True:
|
||||
successor = stack.pop()
|
||||
connected_component.append(successor)
|
||||
if successor == node: break
|
||||
if successor == node:
|
||||
break
|
||||
component = tuple(connected_component)
|
||||
# storing the result
|
||||
result.append(component)
|
||||
@@ -1195,12 +1213,13 @@ class Sequencer(object):
|
||||
result.append('}')
|
||||
return '\n'.join(result)
|
||||
|
||||
|
||||
#
|
||||
# Unarchiving functionality for zip, tar, tgz, tbz, whl
|
||||
#
|
||||
|
||||
ARCHIVE_EXTENSIONS = ('.tar.gz', '.tar.bz2', '.tar', '.zip',
|
||||
'.tgz', '.tbz', '.whl')
|
||||
ARCHIVE_EXTENSIONS = ('.tar.gz', '.tar.bz2', '.tar', '.zip', '.tgz', '.tbz', '.whl')
|
||||
|
||||
|
||||
def unarchive(archive_filename, dest_dir, format=None, check=True):
|
||||
|
||||
@@ -1249,6 +1268,20 @@ def unarchive(archive_filename, dest_dir, format=None, check=True):
|
||||
for tarinfo in archive.getmembers():
|
||||
if not isinstance(tarinfo.name, text_type):
|
||||
tarinfo.name = tarinfo.name.decode('utf-8')
|
||||
|
||||
# Limit extraction of dangerous items, if this Python
|
||||
# allows it easily. If not, just trust the input.
|
||||
# See: https://docs.python.org/3/library/tarfile.html#extraction-filters
|
||||
def extraction_filter(member, path):
|
||||
"""Run tarfile.tar_filter, but raise the expected ValueError"""
|
||||
# This is only called if the current Python has tarfile filters
|
||||
try:
|
||||
return tarfile.tar_filter(member, path)
|
||||
except tarfile.FilterError as exc:
|
||||
raise ValueError(str(exc))
|
||||
|
||||
archive.extraction_filter = extraction_filter
|
||||
|
||||
archive.extractall(dest_dir)
|
||||
|
||||
finally:
|
||||
@@ -1269,11 +1302,12 @@ def zip_dir(directory):
|
||||
zf.write(full, dest)
|
||||
return result
|
||||
|
||||
|
||||
#
|
||||
# Simple progress bar
|
||||
#
|
||||
|
||||
UNITS = ('', 'K', 'M', 'G','T','P')
|
||||
UNITS = ('', 'K', 'M', 'G', 'T', 'P')
|
||||
|
||||
|
||||
class Progress(object):
|
||||
@@ -1328,8 +1362,8 @@ class Progress(object):
|
||||
def format_duration(self, duration):
|
||||
if (duration <= 0) and self.max is None or self.cur == self.min:
|
||||
result = '??:??:??'
|
||||
#elif duration < 1:
|
||||
# result = '--:--:--'
|
||||
# elif duration < 1:
|
||||
# result = '--:--:--'
|
||||
else:
|
||||
result = time.strftime('%H:%M:%S', time.gmtime(duration))
|
||||
return result
|
||||
@@ -1339,7 +1373,7 @@ class Progress(object):
|
||||
if self.done:
|
||||
prefix = 'Done'
|
||||
t = self.elapsed
|
||||
#import pdb; pdb.set_trace()
|
||||
# import pdb; pdb.set_trace()
|
||||
else:
|
||||
prefix = 'ETA '
|
||||
if self.max is None:
|
||||
@@ -1347,7 +1381,7 @@ class Progress(object):
|
||||
elif self.elapsed == 0 or (self.cur == self.min):
|
||||
t = 0
|
||||
else:
|
||||
#import pdb; pdb.set_trace()
|
||||
# import pdb; pdb.set_trace()
|
||||
t = float(self.max - self.min)
|
||||
t /= self.cur - self.min
|
||||
t = (t - 1) * self.elapsed
|
||||
@@ -1365,6 +1399,7 @@ class Progress(object):
|
||||
result /= 1000.0
|
||||
return '%d %sB/s' % (result, unit)
|
||||
|
||||
|
||||
#
|
||||
# Glob functionality
|
||||
#
|
||||
@@ -1412,18 +1447,17 @@ def _iglob(path_glob):
|
||||
for fn in _iglob(os.path.join(path, radical)):
|
||||
yield fn
|
||||
|
||||
|
||||
if ssl:
|
||||
from .compat import (HTTPSHandler as BaseHTTPSHandler, match_hostname,
|
||||
CertificateError)
|
||||
from .compat import (HTTPSHandler as BaseHTTPSHandler, match_hostname, CertificateError)
|
||||
|
||||
|
||||
#
|
||||
# HTTPSConnection which verifies certificates/matches domains
|
||||
#
|
||||
#
|
||||
# HTTPSConnection which verifies certificates/matches domains
|
||||
#
|
||||
|
||||
class HTTPSConnection(httplib.HTTPSConnection):
|
||||
ca_certs = None # set this to the path to the certs file (.pem)
|
||||
check_domain = True # only used if ca_certs is not None
|
||||
ca_certs = None # set this to the path to the certs file (.pem)
|
||||
check_domain = True # only used if ca_certs is not None
|
||||
|
||||
# noinspection PyPropertyAccess
|
||||
def connect(self):
|
||||
@@ -1435,7 +1469,7 @@ if ssl:
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
||||
if hasattr(ssl, 'OP_NO_SSLv2'):
|
||||
context.options |= ssl.OP_NO_SSLv2
|
||||
if self.cert_file:
|
||||
if getattr(self, 'cert_file', None):
|
||||
context.load_cert_chain(self.cert_file, self.key_file)
|
||||
kwargs = {}
|
||||
if self.ca_certs:
|
||||
@@ -1455,6 +1489,7 @@ if ssl:
|
||||
raise
|
||||
|
||||
class HTTPSHandler(BaseHTTPSHandler):
|
||||
|
||||
def __init__(self, ca_certs, check_domain=True):
|
||||
BaseHTTPSHandler.__init__(self)
|
||||
self.ca_certs = ca_certs
|
||||
@@ -1496,14 +1531,17 @@ if ssl:
|
||||
# handler for HTTP itself.
|
||||
#
|
||||
class HTTPSOnlyHandler(HTTPSHandler, HTTPHandler):
|
||||
|
||||
def http_open(self, req):
|
||||
raise URLError('Unexpected HTTP request on what should be a secure '
|
||||
'connection: %s' % req)
|
||||
|
||||
|
||||
#
|
||||
# XML-RPC with timeouts
|
||||
#
|
||||
class Transport(xmlrpclib.Transport):
|
||||
|
||||
def __init__(self, timeout, use_datetime=0):
|
||||
self.timeout = timeout
|
||||
xmlrpclib.Transport.__init__(self, use_datetime)
|
||||
@@ -1515,8 +1553,11 @@ class Transport(xmlrpclib.Transport):
|
||||
self._connection = host, httplib.HTTPConnection(h)
|
||||
return self._connection[1]
|
||||
|
||||
|
||||
if ssl:
|
||||
|
||||
class SafeTransport(xmlrpclib.SafeTransport):
|
||||
|
||||
def __init__(self, timeout, use_datetime=0):
|
||||
self.timeout = timeout
|
||||
xmlrpclib.SafeTransport.__init__(self, use_datetime)
|
||||
@@ -1528,12 +1569,12 @@ if ssl:
|
||||
kwargs['timeout'] = self.timeout
|
||||
if not self._connection or host != self._connection[0]:
|
||||
self._extra_headers = eh
|
||||
self._connection = host, httplib.HTTPSConnection(h, None,
|
||||
**kwargs)
|
||||
self._connection = host, httplib.HTTPSConnection(h, None, **kwargs)
|
||||
return self._connection[1]
|
||||
|
||||
|
||||
class ServerProxy(xmlrpclib.ServerProxy):
|
||||
|
||||
def __init__(self, uri, **kwargs):
|
||||
self.timeout = timeout = kwargs.pop('timeout', None)
|
||||
# The above classes only come into play if a timeout
|
||||
@@ -1550,11 +1591,13 @@ class ServerProxy(xmlrpclib.ServerProxy):
|
||||
self.transport = t
|
||||
xmlrpclib.ServerProxy.__init__(self, uri, **kwargs)
|
||||
|
||||
|
||||
#
|
||||
# CSV functionality. This is provided because on 2.x, the csv module can't
|
||||
# handle Unicode. However, we need to deal with Unicode in e.g. RECORD files.
|
||||
#
|
||||
|
||||
|
||||
def _csv_open(fn, mode, **kwargs):
|
||||
if sys.version_info[0] < 3:
|
||||
mode += 'b'
|
||||
@@ -1568,9 +1611,9 @@ def _csv_open(fn, mode, **kwargs):
|
||||
|
||||
class CSVBase(object):
|
||||
defaults = {
|
||||
'delimiter': str(','), # The strs are used because we need native
|
||||
'quotechar': str('"'), # str in the csv API (2.x won't take
|
||||
'lineterminator': str('\n') # Unicode)
|
||||
'delimiter': str(','), # The strs are used because we need native
|
||||
'quotechar': str('"'), # str in the csv API (2.x won't take
|
||||
'lineterminator': str('\n') # Unicode)
|
||||
}
|
||||
|
||||
def __enter__(self):
|
||||
@@ -1581,6 +1624,7 @@ class CSVBase(object):
|
||||
|
||||
|
||||
class CSVReader(CSVBase):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
if 'stream' in kwargs:
|
||||
stream = kwargs['stream']
|
||||
@@ -1605,7 +1649,9 @@ class CSVReader(CSVBase):
|
||||
|
||||
__next__ = next
|
||||
|
||||
|
||||
class CSVWriter(CSVBase):
|
||||
|
||||
def __init__(self, fn, **kwargs):
|
||||
self.stream = _csv_open(fn, 'w')
|
||||
self.writer = csv.writer(self.stream, **self.defaults)
|
||||
@@ -1620,10 +1666,12 @@ class CSVWriter(CSVBase):
|
||||
row = r
|
||||
self.writer.writerow(row)
|
||||
|
||||
|
||||
#
|
||||
# Configurator functionality
|
||||
#
|
||||
|
||||
|
||||
class Configurator(BaseConfigurator):
|
||||
|
||||
value_converters = dict(BaseConfigurator.value_converters)
|
||||
@@ -1634,6 +1682,7 @@ class Configurator(BaseConfigurator):
|
||||
self.base = base or os.getcwd()
|
||||
|
||||
def configure_custom(self, config):
|
||||
|
||||
def convert(o):
|
||||
if isinstance(o, (list, tuple)):
|
||||
result = type(o)([convert(i) for i in o])
|
||||
@@ -1683,6 +1732,7 @@ class SubprocessMixin(object):
|
||||
"""
|
||||
Mixin for running subprocesses and capturing their output
|
||||
"""
|
||||
|
||||
def __init__(self, verbose=False, progress=None):
|
||||
self.verbose = verbose
|
||||
self.progress = progress
|
||||
@@ -1709,8 +1759,7 @@ class SubprocessMixin(object):
|
||||
stream.close()
|
||||
|
||||
def run_command(self, cmd, **kwargs):
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE, **kwargs)
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
|
||||
t1 = threading.Thread(target=self.reader, args=(p.stdout, 'stdout'))
|
||||
t1.start()
|
||||
t2 = threading.Thread(target=self.reader, args=(p.stderr, 'stderr'))
|
||||
@@ -1730,15 +1779,17 @@ def normalize_name(name):
|
||||
# https://www.python.org/dev/peps/pep-0503/#normalized-names
|
||||
return re.sub('[-_.]+', '-', name).lower()
|
||||
|
||||
|
||||
# def _get_pypirc_command():
|
||||
# """
|
||||
# Get the distutils command for interacting with PyPI configurations.
|
||||
# :return: the command.
|
||||
# """
|
||||
# from distutils.core import Distribution
|
||||
# from distutils.config import PyPIRCCommand
|
||||
# d = Distribution()
|
||||
# return PyPIRCCommand(d)
|
||||
# """
|
||||
# Get the distutils command for interacting with PyPI configurations.
|
||||
# :return: the command.
|
||||
# """
|
||||
# from distutils.core import Distribution
|
||||
# from distutils.config import PyPIRCCommand
|
||||
# d = Distribution()
|
||||
# return PyPIRCCommand(d)
|
||||
|
||||
|
||||
class PyPIRCFile(object):
|
||||
|
||||
@@ -1763,9 +1814,7 @@ class PyPIRCFile(object):
|
||||
if 'distutils' in sections:
|
||||
# let's get the list of servers
|
||||
index_servers = config.get('distutils', 'index-servers')
|
||||
_servers = [server.strip() for server in
|
||||
index_servers.split('\n')
|
||||
if server.strip() != '']
|
||||
_servers = [server.strip() for server in index_servers.split('\n') if server.strip() != '']
|
||||
if _servers == []:
|
||||
# nothing set, let's try to get the default pypi
|
||||
if 'pypi' in sections:
|
||||
@@ -1776,8 +1825,7 @@ class PyPIRCFile(object):
|
||||
result['username'] = config.get(server, 'username')
|
||||
|
||||
# optional params
|
||||
for key, default in (('repository', self.DEFAULT_REPOSITORY),
|
||||
('realm', self.DEFAULT_REALM),
|
||||
for key, default in (('repository', self.DEFAULT_REPOSITORY), ('realm', self.DEFAULT_REALM),
|
||||
('password', None)):
|
||||
if config.has_option(server, key):
|
||||
result[key] = config.get(server, key)
|
||||
@@ -1787,11 +1835,9 @@ class PyPIRCFile(object):
|
||||
# work around people having "repository" for the "pypi"
|
||||
# section of their config set to the HTTP (rather than
|
||||
# HTTPS) URL
|
||||
if (server == 'pypi' and
|
||||
repository in (self.DEFAULT_REPOSITORY, 'pypi')):
|
||||
if (server == 'pypi' and repository in (self.DEFAULT_REPOSITORY, 'pypi')):
|
||||
result['repository'] = self.DEFAULT_REPOSITORY
|
||||
elif (result['server'] != repository and
|
||||
result['repository'] != repository):
|
||||
elif (result['server'] != repository and result['repository'] != repository):
|
||||
result = {}
|
||||
elif 'server-login' in sections:
|
||||
# old format
|
||||
@@ -1821,20 +1867,24 @@ class PyPIRCFile(object):
|
||||
with open(fn, 'w') as f:
|
||||
config.write(f)
|
||||
|
||||
|
||||
def _load_pypirc(index):
|
||||
"""
|
||||
Read the PyPI access configuration as supported by distutils.
|
||||
"""
|
||||
return PyPIRCFile(url=index.url).read()
|
||||
|
||||
|
||||
def _store_pypirc(index):
|
||||
PyPIRCFile().update(index.username, index.password)
|
||||
|
||||
|
||||
#
|
||||
# get_platform()/get_host_platform() copied from Python 3.10.a0 source, with some minor
|
||||
# tweaks
|
||||
#
|
||||
|
||||
|
||||
def get_host_platform():
|
||||
"""Return a string that identifies the current platform. This is used mainly to
|
||||
distinguish platform-specific build directories and platform-specific built
|
||||
@@ -1886,16 +1936,16 @@ def get_host_platform():
|
||||
# At least on Linux/Intel, 'machine' is the processor --
|
||||
# i386, etc.
|
||||
# XXX what about Alpha, SPARC, etc?
|
||||
return "%s-%s" % (osname, machine)
|
||||
return "%s-%s" % (osname, machine)
|
||||
|
||||
elif osname[:5] == 'sunos':
|
||||
if release[0] >= '5': # SunOS 5 == Solaris 2
|
||||
if release[0] >= '5': # SunOS 5 == Solaris 2
|
||||
osname = 'solaris'
|
||||
release = '%d.%s' % (int(release[0]) - 3, release[2:])
|
||||
# We can't use 'platform.architecture()[0]' because a
|
||||
# bootstrap problem. We use a dict to get an error
|
||||
# if some suspicious happens.
|
||||
bitness = {2147483647:'32bit', 9223372036854775807:'64bit'}
|
||||
bitness = {2147483647: '32bit', 9223372036854775807: '64bit'}
|
||||
machine += '.%s' % bitness[sys.maxsize]
|
||||
# fall through to standard osname-release-machine representation
|
||||
elif osname[:3] == 'aix':
|
||||
@@ -1903,23 +1953,25 @@ def get_host_platform():
|
||||
return aix_platform()
|
||||
elif osname[:6] == 'cygwin':
|
||||
osname = 'cygwin'
|
||||
rel_re = re.compile (r'[\d.]+', re.ASCII)
|
||||
rel_re = re.compile(r'[\d.]+', re.ASCII)
|
||||
m = rel_re.match(release)
|
||||
if m:
|
||||
release = m.group()
|
||||
elif osname[:6] == 'darwin':
|
||||
import _osx_support, distutils.sysconfig
|
||||
osname, release, machine = _osx_support.get_platform_osx(
|
||||
distutils.sysconfig.get_config_vars(),
|
||||
osname, release, machine)
|
||||
import _osx_support
|
||||
try:
|
||||
from distutils import sysconfig
|
||||
except ImportError:
|
||||
import sysconfig
|
||||
osname, release, machine = _osx_support.get_platform_osx(sysconfig.get_config_vars(), osname, release, machine)
|
||||
|
||||
return '%s-%s-%s' % (osname, release, machine)
|
||||
|
||||
|
||||
_TARGET_TO_PLAT = {
|
||||
'x86' : 'win32',
|
||||
'x64' : 'win-amd64',
|
||||
'arm' : 'win-arm32',
|
||||
'x86': 'win32',
|
||||
'x64': 'win-amd64',
|
||||
'arm': 'win-arm32',
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012-2017 The Python Software Foundation.
|
||||
# Copyright (C) 2012-2023 The Python Software Foundation.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
"""
|
||||
@@ -176,9 +176,9 @@ class Matcher(object):
|
||||
return self._string
|
||||
|
||||
|
||||
PEP440_VERSION_RE = re.compile(r'^v?(\d+!)?(\d+(\.\d+)*)((a|b|c|rc)(\d+))?'
|
||||
r'(\.(post)(\d+))?(\.(dev)(\d+))?'
|
||||
r'(\+([a-zA-Z\d]+(\.[a-zA-Z\d]+)?))?$')
|
||||
PEP440_VERSION_RE = re.compile(r'^v?(\d+!)?(\d+(\.\d+)*)((a|alpha|b|beta|c|rc|pre|preview)(\d+)?)?'
|
||||
r'(\.(post|r|rev)(\d+)?)?([._-]?(dev)(\d+)?)?'
|
||||
r'(\+([a-zA-Z\d]+(\.[a-zA-Z\d]+)?))?$', re.I)
|
||||
|
||||
|
||||
def _pep_440_key(s):
|
||||
@@ -202,15 +202,24 @@ def _pep_440_key(s):
|
||||
if pre == (None, None):
|
||||
pre = ()
|
||||
else:
|
||||
pre = pre[0], int(pre[1])
|
||||
if pre[1] is None:
|
||||
pre = pre[0], 0
|
||||
else:
|
||||
pre = pre[0], int(pre[1])
|
||||
if post == (None, None):
|
||||
post = ()
|
||||
else:
|
||||
post = post[0], int(post[1])
|
||||
if post[1] is None:
|
||||
post = post[0], 0
|
||||
else:
|
||||
post = post[0], int(post[1])
|
||||
if dev == (None, None):
|
||||
dev = ()
|
||||
else:
|
||||
dev = dev[0], int(dev[1])
|
||||
if dev[1] is None:
|
||||
dev = dev[0], 0
|
||||
else:
|
||||
dev = dev[0], int(dev[1])
|
||||
if local is None:
|
||||
local = ()
|
||||
else:
|
||||
@@ -238,7 +247,6 @@ def _pep_440_key(s):
|
||||
if not dev:
|
||||
dev = ('final',)
|
||||
|
||||
#print('%s -> %s' % (s, m.groups()))
|
||||
return epoch, nums, pre, post, dev, local
|
||||
|
||||
|
||||
@@ -378,6 +386,7 @@ class NormalizedMatcher(Matcher):
|
||||
pfx = '.'.join([str(i) for i in release_clause])
|
||||
return _match_prefix(version, pfx)
|
||||
|
||||
|
||||
_REPLACEMENTS = (
|
||||
(re.compile('[.+-]$'), ''), # remove trailing puncts
|
||||
(re.compile(r'^[.](\d)'), r'0.\1'), # .N -> 0.N at start
|
||||
@@ -388,7 +397,7 @@ _REPLACEMENTS = (
|
||||
(re.compile('[.]{2,}'), '.'), # multiple runs of '.'
|
||||
(re.compile(r'\b(alfa|apha)\b'), 'alpha'), # misspelt alpha
|
||||
(re.compile(r'\b(pre-alpha|prealpha)\b'),
|
||||
'pre.alpha'), # standardise
|
||||
'pre.alpha'), # standardise
|
||||
(re.compile(r'\(beta\)$'), 'beta'), # remove parentheses
|
||||
)
|
||||
|
||||
@@ -416,7 +425,7 @@ def _suggest_semantic_version(s):
|
||||
|
||||
# Now look for numeric prefix, and separate it out from
|
||||
# the rest.
|
||||
#import pdb; pdb.set_trace()
|
||||
# import pdb; pdb.set_trace()
|
||||
m = _NUMERIC_PREFIX.match(result)
|
||||
if not m:
|
||||
prefix = '0.0.0'
|
||||
@@ -434,7 +443,7 @@ def _suggest_semantic_version(s):
|
||||
prefix = '.'.join([str(i) for i in prefix])
|
||||
suffix = suffix.strip()
|
||||
if suffix:
|
||||
#import pdb; pdb.set_trace()
|
||||
# import pdb; pdb.set_trace()
|
||||
# massage the suffix.
|
||||
for pat, repl in _SUFFIX_REPLACEMENTS:
|
||||
suffix = pat.sub(repl, suffix)
|
||||
@@ -504,7 +513,7 @@ def _suggest_normalized_version(s):
|
||||
rs = rs[1:]
|
||||
|
||||
# Clean leading '0's on numbers.
|
||||
#TODO: unintended side-effect on, e.g., "2003.05.09"
|
||||
# TODO: unintended side-effect on, e.g., "2003.05.09"
|
||||
# PyPI stats: 77 (~2%) better
|
||||
rs = re.sub(r"\b0+(\d+)(?!\d)", r"\1", rs)
|
||||
|
||||
@@ -563,6 +572,7 @@ def _suggest_normalized_version(s):
|
||||
# Legacy version processing (distribute-compatible)
|
||||
#
|
||||
|
||||
|
||||
_VERSION_PART = re.compile(r'([a-z]+|\d+|[\.-])', re.I)
|
||||
_VERSION_REPLACE = {
|
||||
'pre': 'c',
|
||||
@@ -609,8 +619,7 @@ class LegacyVersion(Version):
|
||||
def is_prerelease(self):
|
||||
result = False
|
||||
for x in self._parts:
|
||||
if (isinstance(x, string_types) and x.startswith('*') and
|
||||
x < '*final'):
|
||||
if (isinstance(x, string_types) and x.startswith('*') and x < '*final'):
|
||||
result = True
|
||||
break
|
||||
return result
|
||||
@@ -641,6 +650,7 @@ class LegacyMatcher(Matcher):
|
||||
# Semantic versioning
|
||||
#
|
||||
|
||||
|
||||
_SEMVER_RE = re.compile(r'^(\d+)\.(\d+)\.(\d+)'
|
||||
r'(-[a-z0-9]+(\.[a-z0-9-]+)*)?'
|
||||
r'(\+[a-z0-9]+(\.[a-z0-9-]+)*)?$', re.I)
|
||||
@@ -722,6 +732,7 @@ class VersionScheme(object):
|
||||
result = self.suggester(s)
|
||||
return result
|
||||
|
||||
|
||||
_SCHEMES = {
|
||||
'normalized': VersionScheme(_normalized_key, NormalizedMatcher,
|
||||
_suggest_normalized_version),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2013-2020 Vinay Sajip.
|
||||
# Copyright (C) 2013-2023 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
@@ -24,16 +24,14 @@ import zipfile
|
||||
from . import __version__, DistlibException
|
||||
from .compat import sysconfig, ZipFile, fsdecode, text_type, filter
|
||||
from .database import InstalledDistribution
|
||||
from .metadata import (Metadata, METADATA_FILENAME, WHEEL_METADATA_FILENAME,
|
||||
LEGACY_METADATA_FILENAME)
|
||||
from .util import (FileOperator, convert_path, CSVReader, CSVWriter, Cache,
|
||||
cached_property, get_cache_base, read_exports, tempdir,
|
||||
get_platform)
|
||||
from .metadata import Metadata, WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME
|
||||
from .util import (FileOperator, convert_path, CSVReader, CSVWriter, Cache, cached_property, get_cache_base,
|
||||
read_exports, tempdir, get_platform)
|
||||
from .version import NormalizedVersion, UnsupportedVersionError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
cache = None # created when needed
|
||||
cache = None # created when needed
|
||||
|
||||
if hasattr(sys, 'pypy_version_info'): # pragma: no cover
|
||||
IMP_PREFIX = 'pp'
|
||||
@@ -45,7 +43,7 @@ else:
|
||||
IMP_PREFIX = 'cp'
|
||||
|
||||
VER_SUFFIX = sysconfig.get_config_var('py_version_nodot')
|
||||
if not VER_SUFFIX: # pragma: no cover
|
||||
if not VER_SUFFIX: # pragma: no cover
|
||||
VER_SUFFIX = '%s%s' % sys.version_info[:2]
|
||||
PYVER = 'py' + VER_SUFFIX
|
||||
IMPVER = IMP_PREFIX + VER_SUFFIX
|
||||
@@ -56,6 +54,7 @@ ABI = sysconfig.get_config_var('SOABI')
|
||||
if ABI and ABI.startswith('cpython-'):
|
||||
ABI = ABI.replace('cpython-', 'cp').split('-')[0]
|
||||
else:
|
||||
|
||||
def _derive_abi():
|
||||
parts = ['cp', VER_SUFFIX]
|
||||
if sysconfig.get_config_var('Py_DEBUG'):
|
||||
@@ -73,10 +72,12 @@ else:
|
||||
if us == 4 or (us is None and sys.maxunicode == 0x10FFFF):
|
||||
parts.append('u')
|
||||
return ''.join(parts)
|
||||
|
||||
ABI = _derive_abi()
|
||||
del _derive_abi
|
||||
|
||||
FILENAME_RE = re.compile(r'''
|
||||
FILENAME_RE = re.compile(
|
||||
r'''
|
||||
(?P<nm>[^-]+)
|
||||
-(?P<vn>\d+[^-]*)
|
||||
(-(?P<bn>\d+[^-]*))?
|
||||
@@ -109,12 +110,14 @@ else:
|
||||
import importlib.machinery
|
||||
import importlib.util
|
||||
|
||||
|
||||
def _get_suffixes():
|
||||
if imp:
|
||||
return [s[0] for s in imp.get_suffixes()]
|
||||
else:
|
||||
return importlib.machinery.EXTENSION_SUFFIXES
|
||||
|
||||
|
||||
def _load_dynamic(name, path):
|
||||
# https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
|
||||
if imp:
|
||||
@@ -126,7 +129,9 @@ def _load_dynamic(name, path):
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
class Mounter(object):
|
||||
|
||||
def __init__(self):
|
||||
self.impure_wheels = {}
|
||||
self.libs = {}
|
||||
@@ -161,6 +166,7 @@ class Mounter(object):
|
||||
result.__package__ = parts[0]
|
||||
return result
|
||||
|
||||
|
||||
_hook = Mounter()
|
||||
|
||||
|
||||
@@ -227,8 +233,7 @@ class Wheel(object):
|
||||
arch = '.'.join(self.arch)
|
||||
# replace - with _ as a local version separator
|
||||
version = self.version.replace('-', '_')
|
||||
return '%s-%s%s-%s-%s-%s.whl' % (self.name, version, buildver,
|
||||
pyver, abi, arch)
|
||||
return '%s-%s%s-%s-%s-%s.whl' % (self.name, version, buildver, pyver, abi, arch)
|
||||
|
||||
@property
|
||||
def exists(self):
|
||||
@@ -249,14 +254,14 @@ class Wheel(object):
|
||||
info_dir = '%s.dist-info' % name_ver
|
||||
wrapper = codecs.getreader('utf-8')
|
||||
with ZipFile(pathname, 'r') as zf:
|
||||
wheel_metadata = self.get_wheel_metadata(zf)
|
||||
wv = wheel_metadata['Wheel-Version'].split('.', 1)
|
||||
file_version = tuple([int(i) for i in wv])
|
||||
self.get_wheel_metadata(zf)
|
||||
# wv = wheel_metadata['Wheel-Version'].split('.', 1)
|
||||
# file_version = tuple([int(i) for i in wv])
|
||||
# if file_version < (1, 1):
|
||||
# fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME,
|
||||
# LEGACY_METADATA_FILENAME]
|
||||
# fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME,
|
||||
# LEGACY_METADATA_FILENAME]
|
||||
# else:
|
||||
# fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME]
|
||||
# fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME]
|
||||
fns = [WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME]
|
||||
result = None
|
||||
for fn in fns:
|
||||
@@ -332,7 +337,7 @@ class Wheel(object):
|
||||
return hash_kind, result
|
||||
|
||||
def write_record(self, records, record_path, archive_record_path):
|
||||
records = list(records) # make a copy, as mutated
|
||||
records = list(records) # make a copy, as mutated
|
||||
records.append((archive_record_path, '', ''))
|
||||
with CSVWriter(record_path) as writer:
|
||||
for row in records:
|
||||
@@ -341,7 +346,7 @@ class Wheel(object):
|
||||
def write_records(self, info, libdir, archive_paths):
|
||||
records = []
|
||||
distinfo, info_dir = info
|
||||
hasher = getattr(hashlib, self.hash_kind)
|
||||
# hasher = getattr(hashlib, self.hash_kind)
|
||||
for ap, p in archive_paths:
|
||||
with open(p, 'rb') as f:
|
||||
data = f.read()
|
||||
@@ -466,6 +471,7 @@ class Wheel(object):
|
||||
if '.dist-info' in ap:
|
||||
n += 10000
|
||||
return (n, ap)
|
||||
|
||||
archive_paths = sorted(archive_paths, key=sorter)
|
||||
|
||||
# Now, at last, RECORD.
|
||||
@@ -503,7 +509,7 @@ class Wheel(object):
|
||||
installed, and the headers, scripts, data and dist-info metadata are
|
||||
not written. If kwarg ``bytecode_hashed_invalidation`` is True, written
|
||||
bytecode will try to use file-hash based invalidation (PEP-552) on
|
||||
supported interpreter versions (CPython 2.7+).
|
||||
supported interpreter versions (CPython 3.7+).
|
||||
|
||||
The return value is a :class:`InstalledDistribution` instance unless
|
||||
``options.lib_only`` is True, in which case the return value is ``None``.
|
||||
@@ -553,11 +559,11 @@ class Wheel(object):
|
||||
# make a new instance rather than a copy of maker's,
|
||||
# as we mutate it
|
||||
fileop = FileOperator(dry_run=dry_run)
|
||||
fileop.record = True # so we can rollback if needed
|
||||
fileop.record = True # so we can rollback if needed
|
||||
|
||||
bc = not sys.dont_write_bytecode # Double negatives. Lovely!
|
||||
bc = not sys.dont_write_bytecode # Double negatives. Lovely!
|
||||
|
||||
outfiles = [] # for RECORD writing
|
||||
outfiles = [] # for RECORD writing
|
||||
|
||||
# for script copying/shebang processing
|
||||
workdir = tempfile.mkdtemp()
|
||||
@@ -591,8 +597,7 @@ class Wheel(object):
|
||||
if lib_only and u_arcname.startswith((info_pfx, data_pfx)):
|
||||
logger.debug('lib_only: skipping %s', u_arcname)
|
||||
continue
|
||||
is_script = (u_arcname.startswith(script_pfx)
|
||||
and not u_arcname.endswith('.exe'))
|
||||
is_script = (u_arcname.startswith(script_pfx) and not u_arcname.endswith('.exe'))
|
||||
|
||||
if u_arcname.startswith(data_pfx):
|
||||
_, where, rp = u_arcname.split('/', 2)
|
||||
@@ -624,14 +629,12 @@ class Wheel(object):
|
||||
'%s' % outfile)
|
||||
if bc and outfile.endswith('.py'):
|
||||
try:
|
||||
pyc = fileop.byte_compile(outfile,
|
||||
hashed_invalidation=bc_hashed_invalidation)
|
||||
pyc = fileop.byte_compile(outfile, hashed_invalidation=bc_hashed_invalidation)
|
||||
outfiles.append(pyc)
|
||||
except Exception:
|
||||
# Don't give up if byte-compilation fails,
|
||||
# but log it and perhaps warn the user
|
||||
logger.warning('Byte-compilation failed',
|
||||
exc_info=True)
|
||||
logger.warning('Byte-compilation failed', exc_info=True)
|
||||
else:
|
||||
fn = os.path.basename(convert_path(arcname))
|
||||
workname = os.path.join(workdir, fn)
|
||||
@@ -700,7 +703,7 @@ class Wheel(object):
|
||||
fileop.set_executable_mode(filenames)
|
||||
|
||||
if gui_scripts:
|
||||
options = {'gui': True }
|
||||
options = {'gui': True}
|
||||
for k, v in gui_scripts.items():
|
||||
script = '%s = %s' % (k, v)
|
||||
filenames = maker.make(script, options)
|
||||
@@ -710,7 +713,7 @@ class Wheel(object):
|
||||
dist = InstalledDistribution(p)
|
||||
|
||||
# Write SHARED
|
||||
paths = dict(paths) # don't change passed in dict
|
||||
paths = dict(paths) # don't change passed in dict
|
||||
del paths['purelib']
|
||||
del paths['platlib']
|
||||
paths['lib'] = libdir
|
||||
@@ -719,8 +722,7 @@ class Wheel(object):
|
||||
outfiles.append(p)
|
||||
|
||||
# Write RECORD
|
||||
dist.write_installed_files(outfiles, paths['prefix'],
|
||||
dry_run)
|
||||
dist.write_installed_files(outfiles, paths['prefix'], dry_run)
|
||||
return dist
|
||||
except Exception: # pragma: no cover
|
||||
logger.exception('installation failed.')
|
||||
@@ -733,8 +735,7 @@ class Wheel(object):
|
||||
global cache
|
||||
if cache is None:
|
||||
# Use native string to avoid issues on 2.x: see Python #20140.
|
||||
base = os.path.join(get_cache_base(), str('dylib-cache'),
|
||||
'%s.%s' % sys.version_info[:2])
|
||||
base = os.path.join(get_cache_base(), str('dylib-cache'), '%s.%s' % sys.version_info[:2])
|
||||
cache = Cache(base)
|
||||
return cache
|
||||
|
||||
@@ -751,7 +752,7 @@ class Wheel(object):
|
||||
wf = wrapper(bf)
|
||||
extensions = json.load(wf)
|
||||
cache = self._get_dylib_cache()
|
||||
prefix = cache.prefix_to_dir(pathname)
|
||||
prefix = cache.prefix_to_dir(self.filename, use_abspath=False)
|
||||
cache_base = os.path.join(cache.base, prefix)
|
||||
if not os.path.isdir(cache_base):
|
||||
os.makedirs(cache_base)
|
||||
@@ -782,7 +783,7 @@ class Wheel(object):
|
||||
"""
|
||||
Determine if a wheel is asserted as mountable by its metadata.
|
||||
"""
|
||||
return True # for now - metadata details TBD
|
||||
return True # for now - metadata details TBD
|
||||
|
||||
def mount(self, append=False):
|
||||
pathname = os.path.abspath(os.path.join(self.dirname, self.filename))
|
||||
@@ -820,10 +821,10 @@ class Wheel(object):
|
||||
def verify(self):
|
||||
pathname = os.path.join(self.dirname, self.filename)
|
||||
name_ver = '%s-%s' % (self.name, self.version)
|
||||
data_dir = '%s.data' % name_ver
|
||||
# data_dir = '%s.data' % name_ver
|
||||
info_dir = '%s.dist-info' % name_ver
|
||||
|
||||
metadata_name = posixpath.join(info_dir, LEGACY_METADATA_FILENAME)
|
||||
# metadata_name = posixpath.join(info_dir, LEGACY_METADATA_FILENAME)
|
||||
wheel_metadata_name = posixpath.join(info_dir, 'WHEEL')
|
||||
record_name = posixpath.join(info_dir, 'RECORD')
|
||||
|
||||
@@ -832,9 +833,9 @@ class Wheel(object):
|
||||
with ZipFile(pathname, 'r') as zf:
|
||||
with zf.open(wheel_metadata_name) as bwf:
|
||||
wf = wrapper(bwf)
|
||||
message = message_from_file(wf)
|
||||
wv = message['Wheel-Version'].split('.', 1)
|
||||
file_version = tuple([int(i) for i in wv])
|
||||
message_from_file(wf)
|
||||
# wv = message['Wheel-Version'].split('.', 1)
|
||||
# file_version = tuple([int(i) for i in wv])
|
||||
# TODO version verification
|
||||
|
||||
records = {}
|
||||
@@ -903,15 +904,14 @@ class Wheel(object):
|
||||
def update_version(version, path):
|
||||
updated = None
|
||||
try:
|
||||
v = NormalizedVersion(version)
|
||||
NormalizedVersion(version)
|
||||
i = version.find('-')
|
||||
if i < 0:
|
||||
updated = '%s+1' % version
|
||||
else:
|
||||
parts = [int(s) for s in version[i + 1:].split('.')]
|
||||
parts[-1] += 1
|
||||
updated = '%s+%s' % (version[:i],
|
||||
'.'.join(str(i) for i in parts))
|
||||
updated = '%s+%s' % (version[:i], '.'.join(str(i) for i in parts))
|
||||
except UnsupportedVersionError:
|
||||
logger.debug('Cannot update non-compliant (PEP-440) '
|
||||
'version %r', version)
|
||||
@@ -920,8 +920,7 @@ class Wheel(object):
|
||||
md.version = updated
|
||||
legacy = path.endswith(LEGACY_METADATA_FILENAME)
|
||||
md.write(path=path, legacy=legacy)
|
||||
logger.debug('Version updated from %r to %r', version,
|
||||
updated)
|
||||
logger.debug('Version updated from %r to %r', version, updated)
|
||||
|
||||
pathname = os.path.join(self.dirname, self.filename)
|
||||
name_ver = '%s-%s' % (self.name, self.version)
|
||||
@@ -957,9 +956,7 @@ class Wheel(object):
|
||||
update_version(current_version, path)
|
||||
# Decide where the new wheel goes.
|
||||
if dest_dir is None:
|
||||
fd, newpath = tempfile.mkstemp(suffix='.whl',
|
||||
prefix='wheel-update-',
|
||||
dir=workdir)
|
||||
fd, newpath = tempfile.mkstemp(suffix='.whl', prefix='wheel-update-', dir=workdir)
|
||||
os.close(fd)
|
||||
else:
|
||||
if not os.path.isdir(dest_dir):
|
||||
@@ -974,6 +971,7 @@ class Wheel(object):
|
||||
shutil.copyfile(newpath, pathname)
|
||||
return modified
|
||||
|
||||
|
||||
def _get_glibc_version():
|
||||
import platform
|
||||
ver = platform.libc_ver()
|
||||
@@ -984,15 +982,25 @@ def _get_glibc_version():
|
||||
result = tuple(result)
|
||||
return result
|
||||
|
||||
|
||||
def compatible_tags():
|
||||
"""
|
||||
Return (pyver, abi, arch) tuples compatible with this Python.
|
||||
"""
|
||||
versions = [VER_SUFFIX]
|
||||
major = VER_SUFFIX[0]
|
||||
for minor in range(sys.version_info[1] - 1, - 1, -1):
|
||||
versions.append(''.join([major, str(minor)]))
|
||||
class _Version:
|
||||
def __init__(self, major, minor):
|
||||
self.major = major
|
||||
self.major_minor = (major, minor)
|
||||
self.string = ''.join((str(major), str(minor)))
|
||||
|
||||
def __str__(self):
|
||||
return self.string
|
||||
|
||||
|
||||
versions = [
|
||||
_Version(sys.version_info.major, minor_version)
|
||||
for minor_version in range(sys.version_info.minor, -1, -1)
|
||||
]
|
||||
abis = []
|
||||
for suffix in _get_suffixes():
|
||||
if suffix.startswith('.abi'):
|
||||
@@ -1023,40 +1031,50 @@ def compatible_tags():
|
||||
while minor >= 0:
|
||||
for match in matches:
|
||||
s = '%s_%s_%s_%s' % (name, major, minor, match)
|
||||
if s != ARCH: # already there
|
||||
if s != ARCH: # already there
|
||||
arches.append(s)
|
||||
minor -= 1
|
||||
|
||||
# Most specific - our Python version, ABI and arch
|
||||
for abi in abis:
|
||||
for arch in arches:
|
||||
result.append((''.join((IMP_PREFIX, versions[0])), abi, arch))
|
||||
# manylinux
|
||||
if abi != 'none' and sys.platform.startswith('linux'):
|
||||
arch = arch.replace('linux_', '')
|
||||
parts = _get_glibc_version()
|
||||
if len(parts) == 2:
|
||||
if parts >= (2, 5):
|
||||
result.append((''.join((IMP_PREFIX, versions[0])), abi,
|
||||
'manylinux1_%s' % arch))
|
||||
if parts >= (2, 12):
|
||||
result.append((''.join((IMP_PREFIX, versions[0])), abi,
|
||||
'manylinux2010_%s' % arch))
|
||||
if parts >= (2, 17):
|
||||
result.append((''.join((IMP_PREFIX, versions[0])), abi,
|
||||
'manylinux2014_%s' % arch))
|
||||
result.append((''.join((IMP_PREFIX, versions[0])), abi,
|
||||
'manylinux_%s_%s_%s' % (parts[0], parts[1],
|
||||
arch)))
|
||||
for i, version_object in enumerate(versions):
|
||||
version = str(version_object)
|
||||
add_abis = []
|
||||
|
||||
if i == 0:
|
||||
add_abis = abis
|
||||
|
||||
if IMP_PREFIX == 'cp' and version_object.major_minor >= (3, 2):
|
||||
limited_api_abi = 'abi' + str(version_object.major)
|
||||
if limited_api_abi not in add_abis:
|
||||
add_abis.append(limited_api_abi)
|
||||
|
||||
for abi in add_abis:
|
||||
for arch in arches:
|
||||
result.append((''.join((IMP_PREFIX, version)), abi, arch))
|
||||
# manylinux
|
||||
if abi != 'none' and sys.platform.startswith('linux'):
|
||||
arch = arch.replace('linux_', '')
|
||||
parts = _get_glibc_version()
|
||||
if len(parts) == 2:
|
||||
if parts >= (2, 5):
|
||||
result.append((''.join((IMP_PREFIX, version)), abi, 'manylinux1_%s' % arch))
|
||||
if parts >= (2, 12):
|
||||
result.append((''.join((IMP_PREFIX, version)), abi, 'manylinux2010_%s' % arch))
|
||||
if parts >= (2, 17):
|
||||
result.append((''.join((IMP_PREFIX, version)), abi, 'manylinux2014_%s' % arch))
|
||||
result.append((''.join(
|
||||
(IMP_PREFIX, version)), abi, 'manylinux_%s_%s_%s' % (parts[0], parts[1], arch)))
|
||||
|
||||
# where no ABI / arch dependency, but IMP_PREFIX dependency
|
||||
for i, version in enumerate(versions):
|
||||
for i, version_object in enumerate(versions):
|
||||
version = str(version_object)
|
||||
result.append((''.join((IMP_PREFIX, version)), 'none', 'any'))
|
||||
if i == 0:
|
||||
result.append((''.join((IMP_PREFIX, version[0])), 'none', 'any'))
|
||||
|
||||
# no IMP_PREFIX, ABI or arch dependency
|
||||
for i, version in enumerate(versions):
|
||||
for i, version_object in enumerate(versions):
|
||||
version = str(version_object)
|
||||
result.append((''.join(('py', version)), 'none', 'any'))
|
||||
if i == 0:
|
||||
result.append((''.join(('py', version[0])), 'none', 'any'))
|
||||
@@ -1071,7 +1089,7 @@ del compatible_tags
|
||||
|
||||
def is_compatible(wheel, tags=None):
|
||||
if not isinstance(wheel, Wheel):
|
||||
wheel = Wheel(wheel) # assume it's a filename
|
||||
wheel = Wheel(wheel) # assume it's a filename
|
||||
result = False
|
||||
if tags is None:
|
||||
tags = COMPATIBLE_TAGS
|
||||
|
||||
Reference in New Issue
Block a user