mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
Executable → Regular
+81
-201
@@ -34,11 +34,9 @@ import platform
|
||||
import collections
|
||||
import plistlib
|
||||
import email.parser
|
||||
import errno
|
||||
import tempfile
|
||||
import textwrap
|
||||
import itertools
|
||||
import inspect
|
||||
from pkgutil import get_importer
|
||||
|
||||
try:
|
||||
@@ -47,8 +45,8 @@ except ImportError:
|
||||
# Python 3.2 compatibility
|
||||
import imp as _imp
|
||||
|
||||
from pkg_resources.extern import six
|
||||
from pkg_resources.extern.six.moves import urllib, map, filter
|
||||
from pip._vendor import six
|
||||
from pip._vendor.six.moves import urllib, map, filter
|
||||
|
||||
# capture these to bypass sandboxing
|
||||
from os import utime
|
||||
@@ -69,22 +67,20 @@ try:
|
||||
except ImportError:
|
||||
importlib_machinery = None
|
||||
|
||||
from . import py31compat
|
||||
from pkg_resources.extern import appdirs
|
||||
from pkg_resources.extern import packaging
|
||||
__import__('pkg_resources.extern.packaging.version')
|
||||
__import__('pkg_resources.extern.packaging.specifiers')
|
||||
__import__('pkg_resources.extern.packaging.requirements')
|
||||
__import__('pkg_resources.extern.packaging.markers')
|
||||
from pip._vendor import appdirs
|
||||
from pip._vendor import packaging
|
||||
__import__('pip._vendor.packaging.version')
|
||||
__import__('pip._vendor.packaging.specifiers')
|
||||
__import__('pip._vendor.packaging.requirements')
|
||||
__import__('pip._vendor.packaging.markers')
|
||||
|
||||
|
||||
if (3, 0) < sys.version_info < (3, 3):
|
||||
raise RuntimeError("Python 3.3 or later is required")
|
||||
|
||||
if six.PY2:
|
||||
# Those builtin exceptions are only defined in Python 3
|
||||
PermissionError = None
|
||||
NotADirectoryError = None
|
||||
msg = (
|
||||
"Support for Python 3.0-3.2 has been dropped. Future versions "
|
||||
"will fail here."
|
||||
)
|
||||
warnings.warn(msg)
|
||||
|
||||
# declare some globals that will be defined later to
|
||||
# satisfy the linters.
|
||||
@@ -795,7 +791,7 @@ class WorkingSet(object):
|
||||
self._added_new(dist)
|
||||
|
||||
def resolve(self, requirements, env=None, installer=None,
|
||||
replace_conflicting=False, extras=None):
|
||||
replace_conflicting=False):
|
||||
"""List all distributions needed to (recursively) meet `requirements`
|
||||
|
||||
`requirements` must be a sequence of ``Requirement`` objects. `env`,
|
||||
@@ -811,12 +807,6 @@ class WorkingSet(object):
|
||||
the wrong version. Otherwise, if an `installer` is supplied it will be
|
||||
invoked to obtain the correct version of the requirement and activate
|
||||
it.
|
||||
|
||||
`extras` is a list of the extras to be used with these requirements.
|
||||
This is important because extra requirements may look like `my_req;
|
||||
extra = "my_extra"`, which would otherwise be interpreted as a purely
|
||||
optional requirement. Instead, we want to be able to assert that these
|
||||
requirements are truly required.
|
||||
"""
|
||||
|
||||
# set up the stack
|
||||
@@ -840,7 +830,7 @@ class WorkingSet(object):
|
||||
# Ignore cyclic or redundant dependencies
|
||||
continue
|
||||
|
||||
if not req_extras.markers_pass(req, extras):
|
||||
if not req_extras.markers_pass(req):
|
||||
continue
|
||||
|
||||
dist = best.get(req.key)
|
||||
@@ -858,10 +848,7 @@ class WorkingSet(object):
|
||||
# distribution
|
||||
env = Environment([])
|
||||
ws = WorkingSet([])
|
||||
dist = best[req.key] = env.best_match(
|
||||
req, ws, installer,
|
||||
replace_conflicting=replace_conflicting
|
||||
)
|
||||
dist = best[req.key] = env.best_match(req, ws, installer)
|
||||
if dist is None:
|
||||
requirers = required_by.get(req, None)
|
||||
raise DistributionNotFound(req, requirers)
|
||||
@@ -1022,7 +1009,7 @@ class _ReqExtras(dict):
|
||||
Map each requirement to the extras that demanded it.
|
||||
"""
|
||||
|
||||
def markers_pass(self, req, extras=None):
|
||||
def markers_pass(self, req):
|
||||
"""
|
||||
Evaluate markers for req against each extra that
|
||||
demanded it.
|
||||
@@ -1032,7 +1019,7 @@ class _ReqExtras(dict):
|
||||
"""
|
||||
extra_evals = (
|
||||
req.marker.evaluate({'extra': extra})
|
||||
for extra in self.get(req, ()) + (extras or (None,))
|
||||
for extra in self.get(req, ()) + (None,)
|
||||
)
|
||||
return not req.marker or any(extra_evals)
|
||||
|
||||
@@ -1113,7 +1100,7 @@ class Environment(object):
|
||||
dists.append(dist)
|
||||
dists.sort(key=operator.attrgetter('hashcmp'), reverse=True)
|
||||
|
||||
def best_match(self, req, working_set, installer=None, replace_conflicting=False):
|
||||
def best_match(self, req, working_set, installer=None):
|
||||
"""Find distribution best matching `req` and usable on `working_set`
|
||||
|
||||
This calls the ``find(req)`` method of the `working_set` to see if a
|
||||
@@ -1126,12 +1113,7 @@ class Environment(object):
|
||||
calling the environment's ``obtain(req, installer)`` method will be
|
||||
returned.
|
||||
"""
|
||||
try:
|
||||
dist = working_set.find(req)
|
||||
except VersionConflict:
|
||||
if not replace_conflicting:
|
||||
raise
|
||||
dist = None
|
||||
dist = working_set.find(req)
|
||||
if dist is not None:
|
||||
return dist
|
||||
for dist in self[req.key]:
|
||||
@@ -1567,7 +1549,7 @@ class EggProvider(NullProvider):
|
||||
path = self.module_path
|
||||
old = None
|
||||
while path != old:
|
||||
if _is_egg_path(path):
|
||||
if _is_unpacked_egg(path):
|
||||
self.egg_name = os.path.basename(path)
|
||||
self.egg_info = os.path.join(path, 'EGG-INFO')
|
||||
self.egg_root = path
|
||||
@@ -1970,16 +1952,10 @@ def find_eggs_in_zip(importer, path_item, only=False):
|
||||
# don't yield nested distros
|
||||
return
|
||||
for subitem in metadata.resource_listdir('/'):
|
||||
if _is_egg_path(subitem):
|
||||
if _is_unpacked_egg(subitem):
|
||||
subpath = os.path.join(path_item, subitem)
|
||||
for dist in find_eggs_in_zip(zipimport.zipimporter(subpath), subpath):
|
||||
yield dist
|
||||
elif subitem.lower().endswith('.dist-info'):
|
||||
subpath = os.path.join(path_item, subitem)
|
||||
submeta = EggMetadata(zipimport.zipimporter(subpath))
|
||||
submeta.egg_info = subpath
|
||||
yield Distribution.from_location(path_item, subitem, submeta)
|
||||
|
||||
|
||||
|
||||
register_finder(zipimport.zipimporter, find_eggs_in_zip)
|
||||
@@ -2022,123 +1998,46 @@ def find_on_path(importer, path_item, only=False):
|
||||
"""Yield distributions accessible on a sys.path directory"""
|
||||
path_item = _normalize_cached(path_item)
|
||||
|
||||
if _is_unpacked_egg(path_item):
|
||||
yield Distribution.from_filename(
|
||||
path_item, metadata=PathMetadata(
|
||||
path_item, os.path.join(path_item, 'EGG-INFO')
|
||||
if os.path.isdir(path_item) and os.access(path_item, os.R_OK):
|
||||
if _is_unpacked_egg(path_item):
|
||||
yield Distribution.from_filename(
|
||||
path_item, metadata=PathMetadata(
|
||||
path_item, os.path.join(path_item, 'EGG-INFO')
|
||||
)
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
entries = safe_listdir(path_item)
|
||||
|
||||
# for performance, before sorting by version,
|
||||
# screen entries for only those that will yield
|
||||
# distributions
|
||||
filtered = (
|
||||
entry
|
||||
for entry in entries
|
||||
if dist_factory(path_item, entry, only)
|
||||
)
|
||||
|
||||
# scan for .egg and .egg-info in directory
|
||||
path_item_entries = _by_version_descending(filtered)
|
||||
for entry in path_item_entries:
|
||||
fullpath = os.path.join(path_item, entry)
|
||||
factory = dist_factory(path_item, entry, only)
|
||||
for dist in factory(fullpath):
|
||||
yield dist
|
||||
|
||||
|
||||
def dist_factory(path_item, entry, only):
|
||||
"""
|
||||
Return a dist_factory for a path_item and entry
|
||||
"""
|
||||
lower = entry.lower()
|
||||
is_meta = any(map(lower.endswith, ('.egg-info', '.dist-info')))
|
||||
return (
|
||||
distributions_from_metadata
|
||||
if is_meta else
|
||||
find_distributions
|
||||
if not only and _is_egg_path(entry) else
|
||||
resolve_egg_link
|
||||
if not only and lower.endswith('.egg-link') else
|
||||
NoDists()
|
||||
)
|
||||
|
||||
|
||||
class NoDists:
|
||||
"""
|
||||
>>> bool(NoDists())
|
||||
False
|
||||
|
||||
>>> list(NoDists()('anything'))
|
||||
[]
|
||||
"""
|
||||
def __bool__(self):
|
||||
return False
|
||||
if six.PY2:
|
||||
__nonzero__ = __bool__
|
||||
|
||||
def __call__(self, fullpath):
|
||||
return iter(())
|
||||
|
||||
|
||||
def safe_listdir(path):
|
||||
"""
|
||||
Attempt to list contents of path, but suppress some exceptions.
|
||||
"""
|
||||
try:
|
||||
return os.listdir(path)
|
||||
except (PermissionError, NotADirectoryError):
|
||||
pass
|
||||
except OSError as e:
|
||||
# Ignore the directory if does not exist, not a directory or
|
||||
# permission denied
|
||||
ignorable = (
|
||||
e.errno in (errno.ENOTDIR, errno.EACCES, errno.ENOENT)
|
||||
# Python 2 on Windows needs to be handled this way :(
|
||||
or getattr(e, "winerror", None) == 267
|
||||
)
|
||||
if not ignorable:
|
||||
raise
|
||||
return ()
|
||||
|
||||
|
||||
def distributions_from_metadata(path):
|
||||
root = os.path.dirname(path)
|
||||
if os.path.isdir(path):
|
||||
if len(os.listdir(path)) == 0:
|
||||
# empty metadata dir; skip
|
||||
return
|
||||
metadata = PathMetadata(root, path)
|
||||
else:
|
||||
metadata = FileMetadata(path)
|
||||
entry = os.path.basename(path)
|
||||
yield Distribution.from_location(
|
||||
root, entry, metadata, precedence=DEVELOP_DIST,
|
||||
)
|
||||
|
||||
|
||||
def non_empty_lines(path):
|
||||
"""
|
||||
Yield non-empty lines from file at path
|
||||
"""
|
||||
return (line.rstrip() for line in open(path) if line.strip())
|
||||
|
||||
|
||||
def resolve_egg_link(path):
|
||||
"""
|
||||
Given a path to an .egg-link, resolve distributions
|
||||
present in the referenced path.
|
||||
"""
|
||||
referenced_paths = non_empty_lines(path)
|
||||
resolved_paths = (
|
||||
os.path.join(os.path.dirname(path), ref)
|
||||
for ref in referenced_paths
|
||||
)
|
||||
dist_groups = map(find_distributions, resolved_paths)
|
||||
return next(dist_groups, ())
|
||||
else:
|
||||
# scan for .egg and .egg-info in directory
|
||||
path_item_entries = _by_version_descending(os.listdir(path_item))
|
||||
for entry in path_item_entries:
|
||||
lower = entry.lower()
|
||||
if lower.endswith('.egg-info') or lower.endswith('.dist-info'):
|
||||
fullpath = os.path.join(path_item, entry)
|
||||
if os.path.isdir(fullpath):
|
||||
# egg-info directory, allow getting metadata
|
||||
if len(os.listdir(fullpath)) == 0:
|
||||
# Empty egg directory, skip.
|
||||
continue
|
||||
metadata = PathMetadata(path_item, fullpath)
|
||||
else:
|
||||
metadata = FileMetadata(fullpath)
|
||||
yield Distribution.from_location(
|
||||
path_item, entry, metadata, precedence=DEVELOP_DIST
|
||||
)
|
||||
elif not only and _is_unpacked_egg(entry):
|
||||
dists = find_distributions(os.path.join(path_item, entry))
|
||||
for dist in dists:
|
||||
yield dist
|
||||
elif not only and lower.endswith('.egg-link'):
|
||||
with open(os.path.join(path_item, entry)) as entry_file:
|
||||
entry_lines = entry_file.readlines()
|
||||
for line in entry_lines:
|
||||
if not line.strip():
|
||||
continue
|
||||
path = os.path.join(path_item, line.rstrip())
|
||||
dists = find_distributions(path)
|
||||
for item in dists:
|
||||
yield item
|
||||
break
|
||||
|
||||
|
||||
register_finder(pkgutil.ImpImporter, find_on_path)
|
||||
@@ -2219,10 +2118,6 @@ def _rebuild_mod_path(orig_path, package_name, module):
|
||||
parts = path_parts[:-module_parts]
|
||||
return safe_sys_path_index(_normalize_cached(os.sep.join(parts)))
|
||||
|
||||
if not isinstance(orig_path, list):
|
||||
# Is this behavior useful when module.__path__ is not a list?
|
||||
return
|
||||
|
||||
orig_path.sort(key=position_in_sys_path)
|
||||
module.__path__[:] = [_normalize_cached(p) for p in orig_path]
|
||||
|
||||
@@ -2312,20 +2207,12 @@ def _normalize_cached(filename, _cache={}):
|
||||
return result
|
||||
|
||||
|
||||
def _is_egg_path(path):
|
||||
"""
|
||||
Determine if given path appears to be an egg.
|
||||
"""
|
||||
return path.lower().endswith('.egg')
|
||||
|
||||
|
||||
def _is_unpacked_egg(path):
|
||||
"""
|
||||
Determine if given path appears to be an unpacked egg.
|
||||
"""
|
||||
return (
|
||||
_is_egg_path(path) and
|
||||
os.path.isfile(os.path.join(path, 'EGG-INFO', 'PKG-INFO'))
|
||||
path.lower().endswith('.egg')
|
||||
)
|
||||
|
||||
|
||||
@@ -2417,14 +2304,8 @@ class EntryPoint(object):
|
||||
def require(self, env=None, installer=None):
|
||||
if self.extras and not self.dist:
|
||||
raise UnknownExtra("Can't require() without a distribution", self)
|
||||
|
||||
# Get the requirements for this entry point with all its extras and
|
||||
# then resolve them. We have to pass `extras` along when resolving so
|
||||
# that the working set knows what extras we want. Otherwise, for
|
||||
# dist-info distributions, the working set will assume that the
|
||||
# requirements for that extra are purely optional and skip over them.
|
||||
reqs = self.dist.requires(self.extras)
|
||||
items = working_set.resolve(reqs, env, installer, extras=self.extras)
|
||||
items = working_set.resolve(reqs, env, installer)
|
||||
list(map(working_set.add, items))
|
||||
|
||||
pattern = re.compile(
|
||||
@@ -3039,20 +2920,20 @@ class Requirement(packaging.requirements.Requirement):
|
||||
return req
|
||||
|
||||
|
||||
def _always_object(classes):
|
||||
"""
|
||||
Ensure object appears in the mro even
|
||||
for old-style classes.
|
||||
"""
|
||||
if object not in classes:
|
||||
return classes + (object,)
|
||||
return classes
|
||||
def _get_mro(cls):
|
||||
"""Get an mro for a type or classic class"""
|
||||
if not isinstance(cls, type):
|
||||
|
||||
class cls(cls, object):
|
||||
pass
|
||||
|
||||
return cls.__mro__[1:]
|
||||
return cls.__mro__
|
||||
|
||||
|
||||
def _find_adapter(registry, ob):
|
||||
"""Return an adapter factory for `ob` from `registry`"""
|
||||
types = _always_object(inspect.getmro(getattr(ob, '__class__', type(ob))))
|
||||
for t in types:
|
||||
for t in _get_mro(getattr(ob, '__class__', type(ob))):
|
||||
if t in registry:
|
||||
return registry[t]
|
||||
|
||||
@@ -3060,7 +2941,8 @@ def _find_adapter(registry, ob):
|
||||
def ensure_directory(path):
|
||||
"""Ensure that the parent directory of `path` exists"""
|
||||
dirname = os.path.dirname(path)
|
||||
py31compat.makedirs(dirname, exist_ok=True)
|
||||
if not os.path.isdir(dirname):
|
||||
os.makedirs(dirname)
|
||||
|
||||
|
||||
def _bypass_ensure_directory(path):
|
||||
@@ -3128,11 +3010,9 @@ def _initialize(g=globals()):
|
||||
"Set up global resource manager (deliberately not state-saved)"
|
||||
manager = ResourceManager()
|
||||
g['_manager'] = manager
|
||||
g.update(
|
||||
(name, getattr(manager, name))
|
||||
for name in dir(manager)
|
||||
if not name.startswith('_')
|
||||
)
|
||||
for name in dir(manager):
|
||||
if not name.startswith('_'):
|
||||
g[name] = getattr(manager, name)
|
||||
|
||||
|
||||
@_call_aside
|
||||
@@ -3161,10 +3041,10 @@ def _initialize_master_working_set():
|
||||
# ensure that all distributions added to the working set in the future
|
||||
# (e.g. by calling ``require()``) will get activated as well,
|
||||
# with higher priority (replace=True).
|
||||
tuple(
|
||||
dist = None # ensure dist is defined for del dist below
|
||||
for dist in working_set:
|
||||
dist.activate(replace=False)
|
||||
for dist in working_set
|
||||
)
|
||||
del dist
|
||||
add_activation_listener(lambda dist: dist.activate(replace=True), existing=False)
|
||||
working_set.entries = []
|
||||
# match order
|
||||
|
||||
@@ -1,552 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2005-2010 ActiveState Software Inc.
|
||||
# Copyright (c) 2013 Eddy Petrișor
|
||||
|
||||
"""Utilities for determining application-specific dirs.
|
||||
|
||||
See <http://github.com/ActiveState/appdirs> for details and usage.
|
||||
"""
|
||||
# Dev Notes:
|
||||
# - MSDN on where to store app data files:
|
||||
# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120
|
||||
# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html
|
||||
# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||
|
||||
__version_info__ = (1, 4, 0)
|
||||
__version__ = '.'.join(map(str, __version_info__))
|
||||
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
if PY3:
|
||||
unicode = str
|
||||
|
||||
if sys.platform.startswith('java'):
|
||||
import platform
|
||||
os_name = platform.java_ver()[3][0]
|
||||
if os_name.startswith('Windows'): # "Windows XP", "Windows 7", etc.
|
||||
system = 'win32'
|
||||
elif os_name.startswith('Mac'): # "Mac OS X", etc.
|
||||
system = 'darwin'
|
||||
else: # "Linux", "SunOS", "FreeBSD", etc.
|
||||
# Setting this to "linux2" is not ideal, but only Windows or Mac
|
||||
# are actually checked for and the rest of the module expects
|
||||
# *sys.platform* style strings.
|
||||
system = 'linux2'
|
||||
else:
|
||||
system = sys.platform
|
||||
|
||||
|
||||
|
||||
def user_data_dir(appname=None, appauthor=None, version=None, roaming=False):
|
||||
r"""Return full path to the user-specific data dir for this application.
|
||||
|
||||
"appname" is the name of application.
|
||||
If None, just the system directory is returned.
|
||||
"appauthor" (only used on Windows) is the name of the
|
||||
appauthor or distributing body for this application. Typically
|
||||
it is the owning company name. This falls back to appname. You may
|
||||
pass False to disable it.
|
||||
"version" is an optional version path element to append to the
|
||||
path. You might want to use this if you want multiple versions
|
||||
of your app to be able to run independently. If used, this
|
||||
would typically be "<major>.<minor>".
|
||||
Only applied when appname is present.
|
||||
"roaming" (boolean, default False) can be set True to use the Windows
|
||||
roaming appdata directory. That means that for users on a Windows
|
||||
network setup for roaming profiles, this user data will be
|
||||
sync'd on login. See
|
||||
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
|
||||
for a discussion of issues.
|
||||
|
||||
Typical user data directories are:
|
||||
Mac OS X: ~/Library/Application Support/<AppName>
|
||||
Unix: ~/.local/share/<AppName> # or in $XDG_DATA_HOME, if defined
|
||||
Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName>
|
||||
Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>
|
||||
Win 7 (not roaming): C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>
|
||||
Win 7 (roaming): C:\Users\<username>\AppData\Roaming\<AppAuthor>\<AppName>
|
||||
|
||||
For Unix, we follow the XDG spec and support $XDG_DATA_HOME.
|
||||
That means, by default "~/.local/share/<AppName>".
|
||||
"""
|
||||
if system == "win32":
|
||||
if appauthor is None:
|
||||
appauthor = appname
|
||||
const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA"
|
||||
path = os.path.normpath(_get_win_folder(const))
|
||||
if appname:
|
||||
if appauthor is not False:
|
||||
path = os.path.join(path, appauthor, appname)
|
||||
else:
|
||||
path = os.path.join(path, appname)
|
||||
elif system == 'darwin':
|
||||
path = os.path.expanduser('~/Library/Application Support/')
|
||||
if appname:
|
||||
path = os.path.join(path, appname)
|
||||
else:
|
||||
path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share"))
|
||||
if appname:
|
||||
path = os.path.join(path, appname)
|
||||
if appname and version:
|
||||
path = os.path.join(path, version)
|
||||
return path
|
||||
|
||||
|
||||
def site_data_dir(appname=None, appauthor=None, version=None, multipath=False):
|
||||
"""Return full path to the user-shared data dir for this application.
|
||||
|
||||
"appname" is the name of application.
|
||||
If None, just the system directory is returned.
|
||||
"appauthor" (only used on Windows) is the name of the
|
||||
appauthor or distributing body for this application. Typically
|
||||
it is the owning company name. This falls back to appname. You may
|
||||
pass False to disable it.
|
||||
"version" is an optional version path element to append to the
|
||||
path. You might want to use this if you want multiple versions
|
||||
of your app to be able to run independently. If used, this
|
||||
would typically be "<major>.<minor>".
|
||||
Only applied when appname is present.
|
||||
"multipath" is an optional parameter only applicable to *nix
|
||||
which indicates that the entire list of data dirs should be
|
||||
returned. By default, the first item from XDG_DATA_DIRS is
|
||||
returned, or '/usr/local/share/<AppName>',
|
||||
if XDG_DATA_DIRS is not set
|
||||
|
||||
Typical user data directories are:
|
||||
Mac OS X: /Library/Application Support/<AppName>
|
||||
Unix: /usr/local/share/<AppName> or /usr/share/<AppName>
|
||||
Win XP: C:\Documents and Settings\All Users\Application Data\<AppAuthor>\<AppName>
|
||||
Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
|
||||
Win 7: C:\ProgramData\<AppAuthor>\<AppName> # Hidden, but writeable on Win 7.
|
||||
|
||||
For Unix, this is using the $XDG_DATA_DIRS[0] default.
|
||||
|
||||
WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
|
||||
"""
|
||||
if system == "win32":
|
||||
if appauthor is None:
|
||||
appauthor = appname
|
||||
path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA"))
|
||||
if appname:
|
||||
if appauthor is not False:
|
||||
path = os.path.join(path, appauthor, appname)
|
||||
else:
|
||||
path = os.path.join(path, appname)
|
||||
elif system == 'darwin':
|
||||
path = os.path.expanduser('/Library/Application Support')
|
||||
if appname:
|
||||
path = os.path.join(path, appname)
|
||||
else:
|
||||
# XDG default for $XDG_DATA_DIRS
|
||||
# only first, if multipath is False
|
||||
path = os.getenv('XDG_DATA_DIRS',
|
||||
os.pathsep.join(['/usr/local/share', '/usr/share']))
|
||||
pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)]
|
||||
if appname:
|
||||
if version:
|
||||
appname = os.path.join(appname, version)
|
||||
pathlist = [os.sep.join([x, appname]) for x in pathlist]
|
||||
|
||||
if multipath:
|
||||
path = os.pathsep.join(pathlist)
|
||||
else:
|
||||
path = pathlist[0]
|
||||
return path
|
||||
|
||||
if appname and version:
|
||||
path = os.path.join(path, version)
|
||||
return path
|
||||
|
||||
|
||||
def user_config_dir(appname=None, appauthor=None, version=None, roaming=False):
|
||||
r"""Return full path to the user-specific config dir for this application.
|
||||
|
||||
"appname" is the name of application.
|
||||
If None, just the system directory is returned.
|
||||
"appauthor" (only used on Windows) is the name of the
|
||||
appauthor or distributing body for this application. Typically
|
||||
it is the owning company name. This falls back to appname. You may
|
||||
pass False to disable it.
|
||||
"version" is an optional version path element to append to the
|
||||
path. You might want to use this if you want multiple versions
|
||||
of your app to be able to run independently. If used, this
|
||||
would typically be "<major>.<minor>".
|
||||
Only applied when appname is present.
|
||||
"roaming" (boolean, default False) can be set True to use the Windows
|
||||
roaming appdata directory. That means that for users on a Windows
|
||||
network setup for roaming profiles, this user data will be
|
||||
sync'd on login. See
|
||||
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
|
||||
for a discussion of issues.
|
||||
|
||||
Typical user data directories are:
|
||||
Mac OS X: same as user_data_dir
|
||||
Unix: ~/.config/<AppName> # or in $XDG_CONFIG_HOME, if defined
|
||||
Win *: same as user_data_dir
|
||||
|
||||
For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME.
|
||||
That means, by deafult "~/.config/<AppName>".
|
||||
"""
|
||||
if system in ["win32", "darwin"]:
|
||||
path = user_data_dir(appname, appauthor, None, roaming)
|
||||
else:
|
||||
path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config"))
|
||||
if appname:
|
||||
path = os.path.join(path, appname)
|
||||
if appname and version:
|
||||
path = os.path.join(path, version)
|
||||
return path
|
||||
|
||||
|
||||
def site_config_dir(appname=None, appauthor=None, version=None, multipath=False):
|
||||
"""Return full path to the user-shared data dir for this application.
|
||||
|
||||
"appname" is the name of application.
|
||||
If None, just the system directory is returned.
|
||||
"appauthor" (only used on Windows) is the name of the
|
||||
appauthor or distributing body for this application. Typically
|
||||
it is the owning company name. This falls back to appname. You may
|
||||
pass False to disable it.
|
||||
"version" is an optional version path element to append to the
|
||||
path. You might want to use this if you want multiple versions
|
||||
of your app to be able to run independently. If used, this
|
||||
would typically be "<major>.<minor>".
|
||||
Only applied when appname is present.
|
||||
"multipath" is an optional parameter only applicable to *nix
|
||||
which indicates that the entire list of config dirs should be
|
||||
returned. By default, the first item from XDG_CONFIG_DIRS is
|
||||
returned, or '/etc/xdg/<AppName>', if XDG_CONFIG_DIRS is not set
|
||||
|
||||
Typical user data directories are:
|
||||
Mac OS X: same as site_data_dir
|
||||
Unix: /etc/xdg/<AppName> or $XDG_CONFIG_DIRS[i]/<AppName> for each value in
|
||||
$XDG_CONFIG_DIRS
|
||||
Win *: same as site_data_dir
|
||||
Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
|
||||
|
||||
For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False
|
||||
|
||||
WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
|
||||
"""
|
||||
if system in ["win32", "darwin"]:
|
||||
path = site_data_dir(appname, appauthor)
|
||||
if appname and version:
|
||||
path = os.path.join(path, version)
|
||||
else:
|
||||
# XDG default for $XDG_CONFIG_DIRS
|
||||
# only first, if multipath is False
|
||||
path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg')
|
||||
pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)]
|
||||
if appname:
|
||||
if version:
|
||||
appname = os.path.join(appname, version)
|
||||
pathlist = [os.sep.join([x, appname]) for x in pathlist]
|
||||
|
||||
if multipath:
|
||||
path = os.pathsep.join(pathlist)
|
||||
else:
|
||||
path = pathlist[0]
|
||||
return path
|
||||
|
||||
|
||||
def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True):
|
||||
r"""Return full path to the user-specific cache dir for this application.
|
||||
|
||||
"appname" is the name of application.
|
||||
If None, just the system directory is returned.
|
||||
"appauthor" (only used on Windows) is the name of the
|
||||
appauthor or distributing body for this application. Typically
|
||||
it is the owning company name. This falls back to appname. You may
|
||||
pass False to disable it.
|
||||
"version" is an optional version path element to append to the
|
||||
path. You might want to use this if you want multiple versions
|
||||
of your app to be able to run independently. If used, this
|
||||
would typically be "<major>.<minor>".
|
||||
Only applied when appname is present.
|
||||
"opinion" (boolean) can be False to disable the appending of
|
||||
"Cache" to the base app data dir for Windows. See
|
||||
discussion below.
|
||||
|
||||
Typical user cache directories are:
|
||||
Mac OS X: ~/Library/Caches/<AppName>
|
||||
Unix: ~/.cache/<AppName> (XDG default)
|
||||
Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache
|
||||
Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache
|
||||
|
||||
On Windows the only suggestion in the MSDN docs is that local settings go in
|
||||
the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming
|
||||
app data dir (the default returned by `user_data_dir` above). Apps typically
|
||||
put cache data somewhere *under* the given dir here. Some examples:
|
||||
...\Mozilla\Firefox\Profiles\<ProfileName>\Cache
|
||||
...\Acme\SuperApp\Cache\1.0
|
||||
OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value.
|
||||
This can be disabled with the `opinion=False` option.
|
||||
"""
|
||||
if system == "win32":
|
||||
if appauthor is None:
|
||||
appauthor = appname
|
||||
path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA"))
|
||||
if appname:
|
||||
if appauthor is not False:
|
||||
path = os.path.join(path, appauthor, appname)
|
||||
else:
|
||||
path = os.path.join(path, appname)
|
||||
if opinion:
|
||||
path = os.path.join(path, "Cache")
|
||||
elif system == 'darwin':
|
||||
path = os.path.expanduser('~/Library/Caches')
|
||||
if appname:
|
||||
path = os.path.join(path, appname)
|
||||
else:
|
||||
path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache'))
|
||||
if appname:
|
||||
path = os.path.join(path, appname)
|
||||
if appname and version:
|
||||
path = os.path.join(path, version)
|
||||
return path
|
||||
|
||||
|
||||
def user_log_dir(appname=None, appauthor=None, version=None, opinion=True):
|
||||
r"""Return full path to the user-specific log dir for this application.
|
||||
|
||||
"appname" is the name of application.
|
||||
If None, just the system directory is returned.
|
||||
"appauthor" (only used on Windows) is the name of the
|
||||
appauthor or distributing body for this application. Typically
|
||||
it is the owning company name. This falls back to appname. You may
|
||||
pass False to disable it.
|
||||
"version" is an optional version path element to append to the
|
||||
path. You might want to use this if you want multiple versions
|
||||
of your app to be able to run independently. If used, this
|
||||
would typically be "<major>.<minor>".
|
||||
Only applied when appname is present.
|
||||
"opinion" (boolean) can be False to disable the appending of
|
||||
"Logs" to the base app data dir for Windows, and "log" to the
|
||||
base cache dir for Unix. See discussion below.
|
||||
|
||||
Typical user cache directories are:
|
||||
Mac OS X: ~/Library/Logs/<AppName>
|
||||
Unix: ~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if defined
|
||||
Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs
|
||||
Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs
|
||||
|
||||
On Windows the only suggestion in the MSDN docs is that local settings
|
||||
go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in
|
||||
examples of what some windows apps use for a logs dir.)
|
||||
|
||||
OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA`
|
||||
value for Windows and appends "log" to the user cache dir for Unix.
|
||||
This can be disabled with the `opinion=False` option.
|
||||
"""
|
||||
if system == "darwin":
|
||||
path = os.path.join(
|
||||
os.path.expanduser('~/Library/Logs'),
|
||||
appname)
|
||||
elif system == "win32":
|
||||
path = user_data_dir(appname, appauthor, version)
|
||||
version = False
|
||||
if opinion:
|
||||
path = os.path.join(path, "Logs")
|
||||
else:
|
||||
path = user_cache_dir(appname, appauthor, version)
|
||||
version = False
|
||||
if opinion:
|
||||
path = os.path.join(path, "log")
|
||||
if appname and version:
|
||||
path = os.path.join(path, version)
|
||||
return path
|
||||
|
||||
|
||||
class AppDirs(object):
|
||||
"""Convenience wrapper for getting application dirs."""
|
||||
def __init__(self, appname, appauthor=None, version=None, roaming=False,
|
||||
multipath=False):
|
||||
self.appname = appname
|
||||
self.appauthor = appauthor
|
||||
self.version = version
|
||||
self.roaming = roaming
|
||||
self.multipath = multipath
|
||||
|
||||
@property
|
||||
def user_data_dir(self):
|
||||
return user_data_dir(self.appname, self.appauthor,
|
||||
version=self.version, roaming=self.roaming)
|
||||
|
||||
@property
|
||||
def site_data_dir(self):
|
||||
return site_data_dir(self.appname, self.appauthor,
|
||||
version=self.version, multipath=self.multipath)
|
||||
|
||||
@property
|
||||
def user_config_dir(self):
|
||||
return user_config_dir(self.appname, self.appauthor,
|
||||
version=self.version, roaming=self.roaming)
|
||||
|
||||
@property
|
||||
def site_config_dir(self):
|
||||
return site_config_dir(self.appname, self.appauthor,
|
||||
version=self.version, multipath=self.multipath)
|
||||
|
||||
@property
|
||||
def user_cache_dir(self):
|
||||
return user_cache_dir(self.appname, self.appauthor,
|
||||
version=self.version)
|
||||
|
||||
@property
|
||||
def user_log_dir(self):
|
||||
return user_log_dir(self.appname, self.appauthor,
|
||||
version=self.version)
|
||||
|
||||
|
||||
#---- internal support stuff
|
||||
|
||||
def _get_win_folder_from_registry(csidl_name):
|
||||
"""This is a fallback technique at best. I'm not sure if using the
|
||||
registry for this guarantees us the correct answer for all CSIDL_*
|
||||
names.
|
||||
"""
|
||||
import _winreg
|
||||
|
||||
shell_folder_name = {
|
||||
"CSIDL_APPDATA": "AppData",
|
||||
"CSIDL_COMMON_APPDATA": "Common AppData",
|
||||
"CSIDL_LOCAL_APPDATA": "Local AppData",
|
||||
}[csidl_name]
|
||||
|
||||
key = _winreg.OpenKey(
|
||||
_winreg.HKEY_CURRENT_USER,
|
||||
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
|
||||
)
|
||||
dir, type = _winreg.QueryValueEx(key, shell_folder_name)
|
||||
return dir
|
||||
|
||||
|
||||
def _get_win_folder_with_pywin32(csidl_name):
|
||||
from win32com.shell import shellcon, shell
|
||||
dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0)
|
||||
# Try to make this a unicode path because SHGetFolderPath does
|
||||
# not return unicode strings when there is unicode data in the
|
||||
# path.
|
||||
try:
|
||||
dir = unicode(dir)
|
||||
|
||||
# Downgrade to short path name if have highbit chars. See
|
||||
# <http://bugs.activestate.com/show_bug.cgi?id=85099>.
|
||||
has_high_char = False
|
||||
for c in dir:
|
||||
if ord(c) > 255:
|
||||
has_high_char = True
|
||||
break
|
||||
if has_high_char:
|
||||
try:
|
||||
import win32api
|
||||
dir = win32api.GetShortPathName(dir)
|
||||
except ImportError:
|
||||
pass
|
||||
except UnicodeError:
|
||||
pass
|
||||
return dir
|
||||
|
||||
|
||||
def _get_win_folder_with_ctypes(csidl_name):
|
||||
import ctypes
|
||||
|
||||
csidl_const = {
|
||||
"CSIDL_APPDATA": 26,
|
||||
"CSIDL_COMMON_APPDATA": 35,
|
||||
"CSIDL_LOCAL_APPDATA": 28,
|
||||
}[csidl_name]
|
||||
|
||||
buf = ctypes.create_unicode_buffer(1024)
|
||||
ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
|
||||
|
||||
# Downgrade to short path name if have highbit chars. See
|
||||
# <http://bugs.activestate.com/show_bug.cgi?id=85099>.
|
||||
has_high_char = False
|
||||
for c in buf:
|
||||
if ord(c) > 255:
|
||||
has_high_char = True
|
||||
break
|
||||
if has_high_char:
|
||||
buf2 = ctypes.create_unicode_buffer(1024)
|
||||
if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
|
||||
buf = buf2
|
||||
|
||||
return buf.value
|
||||
|
||||
def _get_win_folder_with_jna(csidl_name):
|
||||
import array
|
||||
from com.sun import jna
|
||||
from com.sun.jna.platform import win32
|
||||
|
||||
buf_size = win32.WinDef.MAX_PATH * 2
|
||||
buf = array.zeros('c', buf_size)
|
||||
shell = win32.Shell32.INSTANCE
|
||||
shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf)
|
||||
dir = jna.Native.toString(buf.tostring()).rstrip("\0")
|
||||
|
||||
# Downgrade to short path name if have highbit chars. See
|
||||
# <http://bugs.activestate.com/show_bug.cgi?id=85099>.
|
||||
has_high_char = False
|
||||
for c in dir:
|
||||
if ord(c) > 255:
|
||||
has_high_char = True
|
||||
break
|
||||
if has_high_char:
|
||||
buf = array.zeros('c', buf_size)
|
||||
kernel = win32.Kernel32.INSTANCE
|
||||
if kernal.GetShortPathName(dir, buf, buf_size):
|
||||
dir = jna.Native.toString(buf.tostring()).rstrip("\0")
|
||||
|
||||
return dir
|
||||
|
||||
if system == "win32":
|
||||
try:
|
||||
import win32com.shell
|
||||
_get_win_folder = _get_win_folder_with_pywin32
|
||||
except ImportError:
|
||||
try:
|
||||
from ctypes import windll
|
||||
_get_win_folder = _get_win_folder_with_ctypes
|
||||
except ImportError:
|
||||
try:
|
||||
import com.sun.jna
|
||||
_get_win_folder = _get_win_folder_with_jna
|
||||
except ImportError:
|
||||
_get_win_folder = _get_win_folder_from_registry
|
||||
|
||||
|
||||
#---- self test code
|
||||
|
||||
if __name__ == "__main__":
|
||||
appname = "MyApp"
|
||||
appauthor = "MyCompany"
|
||||
|
||||
props = ("user_data_dir", "site_data_dir",
|
||||
"user_config_dir", "site_config_dir",
|
||||
"user_cache_dir", "user_log_dir")
|
||||
|
||||
print("-- app dirs (with optional 'version')")
|
||||
dirs = AppDirs(appname, appauthor, version="1.0")
|
||||
for prop in props:
|
||||
print("%s: %s" % (prop, getattr(dirs, prop)))
|
||||
|
||||
print("\n-- app dirs (without optional 'version')")
|
||||
dirs = AppDirs(appname, appauthor)
|
||||
for prop in props:
|
||||
print("%s: %s" % (prop, getattr(dirs, prop)))
|
||||
|
||||
print("\n-- app dirs (without optional 'appauthor')")
|
||||
dirs = AppDirs(appname)
|
||||
for prop in props:
|
||||
print("%s: %s" % (prop, getattr(dirs, prop)))
|
||||
|
||||
print("\n-- app dirs (with disabled 'appauthor')")
|
||||
dirs = AppDirs(appname, appauthor=False)
|
||||
for prop in props:
|
||||
print("%s: %s" % (prop, getattr(dirs, prop)))
|
||||
@@ -1,21 +0,0 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__all__ = [
|
||||
"__title__", "__summary__", "__uri__", "__version__", "__author__",
|
||||
"__email__", "__license__", "__copyright__",
|
||||
]
|
||||
|
||||
__title__ = "packaging"
|
||||
__summary__ = "Core utilities for Python packages"
|
||||
__uri__ = "https://github.com/pypa/packaging"
|
||||
|
||||
__version__ = "16.8"
|
||||
|
||||
__author__ = "Donald Stufft and individual contributors"
|
||||
__email__ = "donald@stufft.io"
|
||||
|
||||
__license__ = "BSD or Apache License, Version 2.0"
|
||||
__copyright__ = "Copyright 2014-2016 %s" % __author__
|
||||
@@ -1,14 +0,0 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
from .__about__ import (
|
||||
__author__, __copyright__, __email__, __license__, __summary__, __title__,
|
||||
__uri__, __version__
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"__title__", "__summary__", "__uri__", "__version__", "__author__",
|
||||
"__email__", "__license__", "__copyright__",
|
||||
]
|
||||
@@ -1,30 +0,0 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
# flake8: noqa
|
||||
|
||||
if PY3:
|
||||
string_types = str,
|
||||
else:
|
||||
string_types = basestring,
|
||||
|
||||
|
||||
def with_metaclass(meta, *bases):
|
||||
"""
|
||||
Create a base class with a metaclass.
|
||||
"""
|
||||
# This requires a bit of explanation: the basic idea is to make a dummy
|
||||
# metaclass for one level of class instantiation that replaces itself with
|
||||
# the actual metaclass.
|
||||
class metaclass(meta):
|
||||
def __new__(cls, name, this_bases, d):
|
||||
return meta(name, bases, d)
|
||||
return type.__new__(metaclass, 'temporary_class', (), {})
|
||||
@@ -1,68 +0,0 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
|
||||
class Infinity(object):
|
||||
|
||||
def __repr__(self):
|
||||
return "Infinity"
|
||||
|
||||
def __hash__(self):
|
||||
return hash(repr(self))
|
||||
|
||||
def __lt__(self, other):
|
||||
return False
|
||||
|
||||
def __le__(self, other):
|
||||
return False
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, self.__class__)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not isinstance(other, self.__class__)
|
||||
|
||||
def __gt__(self, other):
|
||||
return True
|
||||
|
||||
def __ge__(self, other):
|
||||
return True
|
||||
|
||||
def __neg__(self):
|
||||
return NegativeInfinity
|
||||
|
||||
Infinity = Infinity()
|
||||
|
||||
|
||||
class NegativeInfinity(object):
|
||||
|
||||
def __repr__(self):
|
||||
return "-Infinity"
|
||||
|
||||
def __hash__(self):
|
||||
return hash(repr(self))
|
||||
|
||||
def __lt__(self, other):
|
||||
return True
|
||||
|
||||
def __le__(self, other):
|
||||
return True
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, self.__class__)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not isinstance(other, self.__class__)
|
||||
|
||||
def __gt__(self, other):
|
||||
return False
|
||||
|
||||
def __ge__(self, other):
|
||||
return False
|
||||
|
||||
def __neg__(self):
|
||||
return Infinity
|
||||
|
||||
NegativeInfinity = NegativeInfinity()
|
||||
@@ -1,301 +0,0 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import operator
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
|
||||
from pkg_resources.extern.pyparsing import ParseException, ParseResults, stringStart, stringEnd
|
||||
from pkg_resources.extern.pyparsing import ZeroOrMore, Group, Forward, QuotedString
|
||||
from pkg_resources.extern.pyparsing import Literal as L # noqa
|
||||
|
||||
from ._compat import string_types
|
||||
from .specifiers import Specifier, InvalidSpecifier
|
||||
|
||||
|
||||
__all__ = [
|
||||
"InvalidMarker", "UndefinedComparison", "UndefinedEnvironmentName",
|
||||
"Marker", "default_environment",
|
||||
]
|
||||
|
||||
|
||||
class InvalidMarker(ValueError):
|
||||
"""
|
||||
An invalid marker was found, users should refer to PEP 508.
|
||||
"""
|
||||
|
||||
|
||||
class UndefinedComparison(ValueError):
|
||||
"""
|
||||
An invalid operation was attempted on a value that doesn't support it.
|
||||
"""
|
||||
|
||||
|
||||
class UndefinedEnvironmentName(ValueError):
|
||||
"""
|
||||
A name was attempted to be used that does not exist inside of the
|
||||
environment.
|
||||
"""
|
||||
|
||||
|
||||
class Node(object):
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return str(self.value)
|
||||
|
||||
def __repr__(self):
|
||||
return "<{0}({1!r})>".format(self.__class__.__name__, str(self))
|
||||
|
||||
def serialize(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Variable(Node):
|
||||
|
||||
def serialize(self):
|
||||
return str(self)
|
||||
|
||||
|
||||
class Value(Node):
|
||||
|
||||
def serialize(self):
|
||||
return '"{0}"'.format(self)
|
||||
|
||||
|
||||
class Op(Node):
|
||||
|
||||
def serialize(self):
|
||||
return str(self)
|
||||
|
||||
|
||||
VARIABLE = (
|
||||
L("implementation_version") |
|
||||
L("platform_python_implementation") |
|
||||
L("implementation_name") |
|
||||
L("python_full_version") |
|
||||
L("platform_release") |
|
||||
L("platform_version") |
|
||||
L("platform_machine") |
|
||||
L("platform_system") |
|
||||
L("python_version") |
|
||||
L("sys_platform") |
|
||||
L("os_name") |
|
||||
L("os.name") | # PEP-345
|
||||
L("sys.platform") | # PEP-345
|
||||
L("platform.version") | # PEP-345
|
||||
L("platform.machine") | # PEP-345
|
||||
L("platform.python_implementation") | # PEP-345
|
||||
L("python_implementation") | # undocumented setuptools legacy
|
||||
L("extra")
|
||||
)
|
||||
ALIASES = {
|
||||
'os.name': 'os_name',
|
||||
'sys.platform': 'sys_platform',
|
||||
'platform.version': 'platform_version',
|
||||
'platform.machine': 'platform_machine',
|
||||
'platform.python_implementation': 'platform_python_implementation',
|
||||
'python_implementation': 'platform_python_implementation'
|
||||
}
|
||||
VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0])))
|
||||
|
||||
VERSION_CMP = (
|
||||
L("===") |
|
||||
L("==") |
|
||||
L(">=") |
|
||||
L("<=") |
|
||||
L("!=") |
|
||||
L("~=") |
|
||||
L(">") |
|
||||
L("<")
|
||||
)
|
||||
|
||||
MARKER_OP = VERSION_CMP | L("not in") | L("in")
|
||||
MARKER_OP.setParseAction(lambda s, l, t: Op(t[0]))
|
||||
|
||||
MARKER_VALUE = QuotedString("'") | QuotedString('"')
|
||||
MARKER_VALUE.setParseAction(lambda s, l, t: Value(t[0]))
|
||||
|
||||
BOOLOP = L("and") | L("or")
|
||||
|
||||
MARKER_VAR = VARIABLE | MARKER_VALUE
|
||||
|
||||
MARKER_ITEM = Group(MARKER_VAR + MARKER_OP + MARKER_VAR)
|
||||
MARKER_ITEM.setParseAction(lambda s, l, t: tuple(t[0]))
|
||||
|
||||
LPAREN = L("(").suppress()
|
||||
RPAREN = L(")").suppress()
|
||||
|
||||
MARKER_EXPR = Forward()
|
||||
MARKER_ATOM = MARKER_ITEM | Group(LPAREN + MARKER_EXPR + RPAREN)
|
||||
MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR)
|
||||
|
||||
MARKER = stringStart + MARKER_EXPR + stringEnd
|
||||
|
||||
|
||||
def _coerce_parse_result(results):
|
||||
if isinstance(results, ParseResults):
|
||||
return [_coerce_parse_result(i) for i in results]
|
||||
else:
|
||||
return results
|
||||
|
||||
|
||||
def _format_marker(marker, first=True):
|
||||
assert isinstance(marker, (list, tuple, string_types))
|
||||
|
||||
# Sometimes we have a structure like [[...]] which is a single item list
|
||||
# where the single item is itself it's own list. In that case we want skip
|
||||
# the rest of this function so that we don't get extraneous () on the
|
||||
# outside.
|
||||
if (isinstance(marker, list) and len(marker) == 1 and
|
||||
isinstance(marker[0], (list, tuple))):
|
||||
return _format_marker(marker[0])
|
||||
|
||||
if isinstance(marker, list):
|
||||
inner = (_format_marker(m, first=False) for m in marker)
|
||||
if first:
|
||||
return " ".join(inner)
|
||||
else:
|
||||
return "(" + " ".join(inner) + ")"
|
||||
elif isinstance(marker, tuple):
|
||||
return " ".join([m.serialize() for m in marker])
|
||||
else:
|
||||
return marker
|
||||
|
||||
|
||||
_operators = {
|
||||
"in": lambda lhs, rhs: lhs in rhs,
|
||||
"not in": lambda lhs, rhs: lhs not in rhs,
|
||||
"<": operator.lt,
|
||||
"<=": operator.le,
|
||||
"==": operator.eq,
|
||||
"!=": operator.ne,
|
||||
">=": operator.ge,
|
||||
">": operator.gt,
|
||||
}
|
||||
|
||||
|
||||
def _eval_op(lhs, op, rhs):
|
||||
try:
|
||||
spec = Specifier("".join([op.serialize(), rhs]))
|
||||
except InvalidSpecifier:
|
||||
pass
|
||||
else:
|
||||
return spec.contains(lhs)
|
||||
|
||||
oper = _operators.get(op.serialize())
|
||||
if oper is None:
|
||||
raise UndefinedComparison(
|
||||
"Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs)
|
||||
)
|
||||
|
||||
return oper(lhs, rhs)
|
||||
|
||||
|
||||
_undefined = object()
|
||||
|
||||
|
||||
def _get_env(environment, name):
|
||||
value = environment.get(name, _undefined)
|
||||
|
||||
if value is _undefined:
|
||||
raise UndefinedEnvironmentName(
|
||||
"{0!r} does not exist in evaluation environment.".format(name)
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def _evaluate_markers(markers, environment):
|
||||
groups = [[]]
|
||||
|
||||
for marker in markers:
|
||||
assert isinstance(marker, (list, tuple, string_types))
|
||||
|
||||
if isinstance(marker, list):
|
||||
groups[-1].append(_evaluate_markers(marker, environment))
|
||||
elif isinstance(marker, tuple):
|
||||
lhs, op, rhs = marker
|
||||
|
||||
if isinstance(lhs, Variable):
|
||||
lhs_value = _get_env(environment, lhs.value)
|
||||
rhs_value = rhs.value
|
||||
else:
|
||||
lhs_value = lhs.value
|
||||
rhs_value = _get_env(environment, rhs.value)
|
||||
|
||||
groups[-1].append(_eval_op(lhs_value, op, rhs_value))
|
||||
else:
|
||||
assert marker in ["and", "or"]
|
||||
if marker == "or":
|
||||
groups.append([])
|
||||
|
||||
return any(all(item) for item in groups)
|
||||
|
||||
|
||||
def format_full_version(info):
|
||||
version = '{0.major}.{0.minor}.{0.micro}'.format(info)
|
||||
kind = info.releaselevel
|
||||
if kind != 'final':
|
||||
version += kind[0] + str(info.serial)
|
||||
return version
|
||||
|
||||
|
||||
def default_environment():
|
||||
if hasattr(sys, 'implementation'):
|
||||
iver = format_full_version(sys.implementation.version)
|
||||
implementation_name = sys.implementation.name
|
||||
else:
|
||||
iver = '0'
|
||||
implementation_name = ''
|
||||
|
||||
return {
|
||||
"implementation_name": implementation_name,
|
||||
"implementation_version": iver,
|
||||
"os_name": os.name,
|
||||
"platform_machine": platform.machine(),
|
||||
"platform_release": platform.release(),
|
||||
"platform_system": platform.system(),
|
||||
"platform_version": platform.version(),
|
||||
"python_full_version": platform.python_version(),
|
||||
"platform_python_implementation": platform.python_implementation(),
|
||||
"python_version": platform.python_version()[:3],
|
||||
"sys_platform": sys.platform,
|
||||
}
|
||||
|
||||
|
||||
class Marker(object):
|
||||
|
||||
def __init__(self, marker):
|
||||
try:
|
||||
self._markers = _coerce_parse_result(MARKER.parseString(marker))
|
||||
except ParseException as e:
|
||||
err_str = "Invalid marker: {0!r}, parse error at {1!r}".format(
|
||||
marker, marker[e.loc:e.loc + 8])
|
||||
raise InvalidMarker(err_str)
|
||||
|
||||
def __str__(self):
|
||||
return _format_marker(self._markers)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Marker({0!r})>".format(str(self))
|
||||
|
||||
def evaluate(self, environment=None):
|
||||
"""Evaluate a marker.
|
||||
|
||||
Return the boolean from evaluating the given marker against the
|
||||
environment. environment is an optional argument to override all or
|
||||
part of the determined environment.
|
||||
|
||||
The environment is determined from the current Python process.
|
||||
"""
|
||||
current_environment = default_environment()
|
||||
if environment is not None:
|
||||
current_environment.update(environment)
|
||||
|
||||
return _evaluate_markers(self._markers, current_environment)
|
||||
@@ -1,127 +0,0 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import string
|
||||
import re
|
||||
|
||||
from pkg_resources.extern.pyparsing import stringStart, stringEnd, originalTextFor, ParseException
|
||||
from pkg_resources.extern.pyparsing import ZeroOrMore, Word, Optional, Regex, Combine
|
||||
from pkg_resources.extern.pyparsing import Literal as L # noqa
|
||||
from pkg_resources.extern.six.moves.urllib import parse as urlparse
|
||||
|
||||
from .markers import MARKER_EXPR, Marker
|
||||
from .specifiers import LegacySpecifier, Specifier, SpecifierSet
|
||||
|
||||
|
||||
class InvalidRequirement(ValueError):
|
||||
"""
|
||||
An invalid requirement was found, users should refer to PEP 508.
|
||||
"""
|
||||
|
||||
|
||||
ALPHANUM = Word(string.ascii_letters + string.digits)
|
||||
|
||||
LBRACKET = L("[").suppress()
|
||||
RBRACKET = L("]").suppress()
|
||||
LPAREN = L("(").suppress()
|
||||
RPAREN = L(")").suppress()
|
||||
COMMA = L(",").suppress()
|
||||
SEMICOLON = L(";").suppress()
|
||||
AT = L("@").suppress()
|
||||
|
||||
PUNCTUATION = Word("-_.")
|
||||
IDENTIFIER_END = ALPHANUM | (ZeroOrMore(PUNCTUATION) + ALPHANUM)
|
||||
IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END))
|
||||
|
||||
NAME = IDENTIFIER("name")
|
||||
EXTRA = IDENTIFIER
|
||||
|
||||
URI = Regex(r'[^ ]+')("url")
|
||||
URL = (AT + URI)
|
||||
|
||||
EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA)
|
||||
EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras")
|
||||
|
||||
VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE)
|
||||
VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE)
|
||||
|
||||
VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY
|
||||
VERSION_MANY = Combine(VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE),
|
||||
joinString=",", adjacent=False)("_raw_spec")
|
||||
_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY))
|
||||
_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or '')
|
||||
|
||||
VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier")
|
||||
VERSION_SPEC.setParseAction(lambda s, l, t: t[1])
|
||||
|
||||
MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker")
|
||||
MARKER_EXPR.setParseAction(
|
||||
lambda s, l, t: Marker(s[t._original_start:t._original_end])
|
||||
)
|
||||
MARKER_SEPERATOR = SEMICOLON
|
||||
MARKER = MARKER_SEPERATOR + MARKER_EXPR
|
||||
|
||||
VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER)
|
||||
URL_AND_MARKER = URL + Optional(MARKER)
|
||||
|
||||
NAMED_REQUIREMENT = \
|
||||
NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER)
|
||||
|
||||
REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd
|
||||
|
||||
|
||||
class Requirement(object):
|
||||
"""Parse a requirement.
|
||||
|
||||
Parse a given requirement string into its parts, such as name, specifier,
|
||||
URL, and extras. Raises InvalidRequirement on a badly-formed requirement
|
||||
string.
|
||||
"""
|
||||
|
||||
# TODO: Can we test whether something is contained within a requirement?
|
||||
# If so how do we do that? Do we need to test against the _name_ of
|
||||
# the thing as well as the version? What about the markers?
|
||||
# TODO: Can we normalize the name and extra name?
|
||||
|
||||
def __init__(self, requirement_string):
|
||||
try:
|
||||
req = REQUIREMENT.parseString(requirement_string)
|
||||
except ParseException as e:
|
||||
raise InvalidRequirement(
|
||||
"Invalid requirement, parse error at \"{0!r}\"".format(
|
||||
requirement_string[e.loc:e.loc + 8]))
|
||||
|
||||
self.name = req.name
|
||||
if req.url:
|
||||
parsed_url = urlparse.urlparse(req.url)
|
||||
if not (parsed_url.scheme and parsed_url.netloc) or (
|
||||
not parsed_url.scheme and not parsed_url.netloc):
|
||||
raise InvalidRequirement("Invalid URL given")
|
||||
self.url = req.url
|
||||
else:
|
||||
self.url = None
|
||||
self.extras = set(req.extras.asList() if req.extras else [])
|
||||
self.specifier = SpecifierSet(req.specifier)
|
||||
self.marker = req.marker if req.marker else None
|
||||
|
||||
def __str__(self):
|
||||
parts = [self.name]
|
||||
|
||||
if self.extras:
|
||||
parts.append("[{0}]".format(",".join(sorted(self.extras))))
|
||||
|
||||
if self.specifier:
|
||||
parts.append(str(self.specifier))
|
||||
|
||||
if self.url:
|
||||
parts.append("@ {0}".format(self.url))
|
||||
|
||||
if self.marker:
|
||||
parts.append("; {0}".format(self.marker))
|
||||
|
||||
return "".join(parts)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Requirement({0!r})>".format(str(self))
|
||||
@@ -1,774 +0,0 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import abc
|
||||
import functools
|
||||
import itertools
|
||||
import re
|
||||
|
||||
from ._compat import string_types, with_metaclass
|
||||
from .version import Version, LegacyVersion, parse
|
||||
|
||||
|
||||
class InvalidSpecifier(ValueError):
|
||||
"""
|
||||
An invalid specifier was found, users should refer to PEP 440.
|
||||
"""
|
||||
|
||||
|
||||
class BaseSpecifier(with_metaclass(abc.ABCMeta, object)):
|
||||
|
||||
@abc.abstractmethod
|
||||
def __str__(self):
|
||||
"""
|
||||
Returns the str representation of this Specifier like object. This
|
||||
should be representative of the Specifier itself.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def __hash__(self):
|
||||
"""
|
||||
Returns a hash value for this Specifier like object.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Returns a boolean representing whether or not the two Specifier like
|
||||
objects are equal.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
Returns a boolean representing whether or not the two Specifier like
|
||||
objects are not equal.
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def prereleases(self):
|
||||
"""
|
||||
Returns whether or not pre-releases as a whole are allowed by this
|
||||
specifier.
|
||||
"""
|
||||
|
||||
@prereleases.setter
|
||||
def prereleases(self, value):
|
||||
"""
|
||||
Sets whether or not pre-releases as a whole are allowed by this
|
||||
specifier.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def contains(self, item, prereleases=None):
|
||||
"""
|
||||
Determines if the given item is contained within this specifier.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def filter(self, iterable, prereleases=None):
|
||||
"""
|
||||
Takes an iterable of items and filters them so that only items which
|
||||
are contained within this specifier are allowed in it.
|
||||
"""
|
||||
|
||||
|
||||
class _IndividualSpecifier(BaseSpecifier):
|
||||
|
||||
_operators = {}
|
||||
|
||||
def __init__(self, spec="", prereleases=None):
|
||||
match = self._regex.search(spec)
|
||||
if not match:
|
||||
raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec))
|
||||
|
||||
self._spec = (
|
||||
match.group("operator").strip(),
|
||||
match.group("version").strip(),
|
||||
)
|
||||
|
||||
# Store whether or not this Specifier should accept prereleases
|
||||
self._prereleases = prereleases
|
||||
|
||||
def __repr__(self):
|
||||
pre = (
|
||||
", prereleases={0!r}".format(self.prereleases)
|
||||
if self._prereleases is not None
|
||||
else ""
|
||||
)
|
||||
|
||||
return "<{0}({1!r}{2})>".format(
|
||||
self.__class__.__name__,
|
||||
str(self),
|
||||
pre,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return "{0}{1}".format(*self._spec)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._spec)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, string_types):
|
||||
try:
|
||||
other = self.__class__(other)
|
||||
except InvalidSpecifier:
|
||||
return NotImplemented
|
||||
elif not isinstance(other, self.__class__):
|
||||
return NotImplemented
|
||||
|
||||
return self._spec == other._spec
|
||||
|
||||
def __ne__(self, other):
|
||||
if isinstance(other, string_types):
|
||||
try:
|
||||
other = self.__class__(other)
|
||||
except InvalidSpecifier:
|
||||
return NotImplemented
|
||||
elif not isinstance(other, self.__class__):
|
||||
return NotImplemented
|
||||
|
||||
return self._spec != other._spec
|
||||
|
||||
def _get_operator(self, op):
|
||||
return getattr(self, "_compare_{0}".format(self._operators[op]))
|
||||
|
||||
def _coerce_version(self, version):
|
||||
if not isinstance(version, (LegacyVersion, Version)):
|
||||
version = parse(version)
|
||||
return version
|
||||
|
||||
@property
|
||||
def operator(self):
|
||||
return self._spec[0]
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self._spec[1]
|
||||
|
||||
@property
|
||||
def prereleases(self):
|
||||
return self._prereleases
|
||||
|
||||
@prereleases.setter
|
||||
def prereleases(self, value):
|
||||
self._prereleases = value
|
||||
|
||||
def __contains__(self, item):
|
||||
return self.contains(item)
|
||||
|
||||
def contains(self, item, prereleases=None):
|
||||
# Determine if prereleases are to be allowed or not.
|
||||
if prereleases is None:
|
||||
prereleases = self.prereleases
|
||||
|
||||
# Normalize item to a Version or LegacyVersion, this allows us to have
|
||||
# a shortcut for ``"2.0" in Specifier(">=2")
|
||||
item = self._coerce_version(item)
|
||||
|
||||
# Determine if we should be supporting prereleases in this specifier
|
||||
# or not, if we do not support prereleases than we can short circuit
|
||||
# logic if this version is a prereleases.
|
||||
if item.is_prerelease and not prereleases:
|
||||
return False
|
||||
|
||||
# Actually do the comparison to determine if this item is contained
|
||||
# within this Specifier or not.
|
||||
return self._get_operator(self.operator)(item, self.version)
|
||||
|
||||
def filter(self, iterable, prereleases=None):
|
||||
yielded = False
|
||||
found_prereleases = []
|
||||
|
||||
kw = {"prereleases": prereleases if prereleases is not None else True}
|
||||
|
||||
# Attempt to iterate over all the values in the iterable and if any of
|
||||
# them match, yield them.
|
||||
for version in iterable:
|
||||
parsed_version = self._coerce_version(version)
|
||||
|
||||
if self.contains(parsed_version, **kw):
|
||||
# If our version is a prerelease, and we were not set to allow
|
||||
# prereleases, then we'll store it for later incase nothing
|
||||
# else matches this specifier.
|
||||
if (parsed_version.is_prerelease and not
|
||||
(prereleases or self.prereleases)):
|
||||
found_prereleases.append(version)
|
||||
# Either this is not a prerelease, or we should have been
|
||||
# accepting prereleases from the begining.
|
||||
else:
|
||||
yielded = True
|
||||
yield version
|
||||
|
||||
# Now that we've iterated over everything, determine if we've yielded
|
||||
# any values, and if we have not and we have any prereleases stored up
|
||||
# then we will go ahead and yield the prereleases.
|
||||
if not yielded and found_prereleases:
|
||||
for version in found_prereleases:
|
||||
yield version
|
||||
|
||||
|
||||
class LegacySpecifier(_IndividualSpecifier):
|
||||
|
||||
_regex_str = (
|
||||
r"""
|
||||
(?P<operator>(==|!=|<=|>=|<|>))
|
||||
\s*
|
||||
(?P<version>
|
||||
[^,;\s)]* # Since this is a "legacy" specifier, and the version
|
||||
# string can be just about anything, we match everything
|
||||
# except for whitespace, a semi-colon for marker support,
|
||||
# a closing paren since versions can be enclosed in
|
||||
# them, and a comma since it's a version separator.
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
_regex = re.compile(
|
||||
r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
|
||||
|
||||
_operators = {
|
||||
"==": "equal",
|
||||
"!=": "not_equal",
|
||||
"<=": "less_than_equal",
|
||||
">=": "greater_than_equal",
|
||||
"<": "less_than",
|
||||
">": "greater_than",
|
||||
}
|
||||
|
||||
def _coerce_version(self, version):
|
||||
if not isinstance(version, LegacyVersion):
|
||||
version = LegacyVersion(str(version))
|
||||
return version
|
||||
|
||||
def _compare_equal(self, prospective, spec):
|
||||
return prospective == self._coerce_version(spec)
|
||||
|
||||
def _compare_not_equal(self, prospective, spec):
|
||||
return prospective != self._coerce_version(spec)
|
||||
|
||||
def _compare_less_than_equal(self, prospective, spec):
|
||||
return prospective <= self._coerce_version(spec)
|
||||
|
||||
def _compare_greater_than_equal(self, prospective, spec):
|
||||
return prospective >= self._coerce_version(spec)
|
||||
|
||||
def _compare_less_than(self, prospective, spec):
|
||||
return prospective < self._coerce_version(spec)
|
||||
|
||||
def _compare_greater_than(self, prospective, spec):
|
||||
return prospective > self._coerce_version(spec)
|
||||
|
||||
|
||||
def _require_version_compare(fn):
|
||||
@functools.wraps(fn)
|
||||
def wrapped(self, prospective, spec):
|
||||
if not isinstance(prospective, Version):
|
||||
return False
|
||||
return fn(self, prospective, spec)
|
||||
return wrapped
|
||||
|
||||
|
||||
class Specifier(_IndividualSpecifier):
|
||||
|
||||
_regex_str = (
|
||||
r"""
|
||||
(?P<operator>(~=|==|!=|<=|>=|<|>|===))
|
||||
(?P<version>
|
||||
(?:
|
||||
# The identity operators allow for an escape hatch that will
|
||||
# do an exact string match of the version you wish to install.
|
||||
# This will not be parsed by PEP 440 and we cannot determine
|
||||
# any semantic meaning from it. This operator is discouraged
|
||||
# but included entirely as an escape hatch.
|
||||
(?<====) # Only match for the identity operator
|
||||
\s*
|
||||
[^\s]* # We just match everything, except for whitespace
|
||||
# since we are only testing for strict identity.
|
||||
)
|
||||
|
|
||||
(?:
|
||||
# The (non)equality operators allow for wild card and local
|
||||
# versions to be specified so we have to define these two
|
||||
# operators separately to enable that.
|
||||
(?<===|!=) # Only match for equals and not equals
|
||||
|
||||
\s*
|
||||
v?
|
||||
(?:[0-9]+!)? # epoch
|
||||
[0-9]+(?:\.[0-9]+)* # release
|
||||
(?: # pre release
|
||||
[-_\.]?
|
||||
(a|b|c|rc|alpha|beta|pre|preview)
|
||||
[-_\.]?
|
||||
[0-9]*
|
||||
)?
|
||||
(?: # post release
|
||||
(?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
|
||||
)?
|
||||
|
||||
# You cannot use a wild card and a dev or local version
|
||||
# together so group them with a | and make them optional.
|
||||
(?:
|
||||
(?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
|
||||
(?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local
|
||||
|
|
||||
\.\* # Wild card syntax of .*
|
||||
)?
|
||||
)
|
||||
|
|
||||
(?:
|
||||
# The compatible operator requires at least two digits in the
|
||||
# release segment.
|
||||
(?<=~=) # Only match for the compatible operator
|
||||
|
||||
\s*
|
||||
v?
|
||||
(?:[0-9]+!)? # epoch
|
||||
[0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *)
|
||||
(?: # pre release
|
||||
[-_\.]?
|
||||
(a|b|c|rc|alpha|beta|pre|preview)
|
||||
[-_\.]?
|
||||
[0-9]*
|
||||
)?
|
||||
(?: # post release
|
||||
(?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
|
||||
)?
|
||||
(?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
|
||||
)
|
||||
|
|
||||
(?:
|
||||
# All other operators only allow a sub set of what the
|
||||
# (non)equality operators do. Specifically they do not allow
|
||||
# local versions to be specified nor do they allow the prefix
|
||||
# matching wild cards.
|
||||
(?<!==|!=|~=) # We have special cases for these
|
||||
# operators so we want to make sure they
|
||||
# don't match here.
|
||||
|
||||
\s*
|
||||
v?
|
||||
(?:[0-9]+!)? # epoch
|
||||
[0-9]+(?:\.[0-9]+)* # release
|
||||
(?: # pre release
|
||||
[-_\.]?
|
||||
(a|b|c|rc|alpha|beta|pre|preview)
|
||||
[-_\.]?
|
||||
[0-9]*
|
||||
)?
|
||||
(?: # post release
|
||||
(?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
|
||||
)?
|
||||
(?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
|
||||
)
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
_regex = re.compile(
|
||||
r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
|
||||
|
||||
_operators = {
|
||||
"~=": "compatible",
|
||||
"==": "equal",
|
||||
"!=": "not_equal",
|
||||
"<=": "less_than_equal",
|
||||
">=": "greater_than_equal",
|
||||
"<": "less_than",
|
||||
">": "greater_than",
|
||||
"===": "arbitrary",
|
||||
}
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_compatible(self, prospective, spec):
|
||||
# Compatible releases have an equivalent combination of >= and ==. That
|
||||
# is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
|
||||
# implement this in terms of the other specifiers instead of
|
||||
# implementing it ourselves. The only thing we need to do is construct
|
||||
# the other specifiers.
|
||||
|
||||
# We want everything but the last item in the version, but we want to
|
||||
# ignore post and dev releases and we want to treat the pre-release as
|
||||
# it's own separate segment.
|
||||
prefix = ".".join(
|
||||
list(
|
||||
itertools.takewhile(
|
||||
lambda x: (not x.startswith("post") and not
|
||||
x.startswith("dev")),
|
||||
_version_split(spec),
|
||||
)
|
||||
)[:-1]
|
||||
)
|
||||
|
||||
# Add the prefix notation to the end of our string
|
||||
prefix += ".*"
|
||||
|
||||
return (self._get_operator(">=")(prospective, spec) and
|
||||
self._get_operator("==")(prospective, prefix))
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_equal(self, prospective, spec):
|
||||
# We need special logic to handle prefix matching
|
||||
if spec.endswith(".*"):
|
||||
# In the case of prefix matching we want to ignore local segment.
|
||||
prospective = Version(prospective.public)
|
||||
# Split the spec out by dots, and pretend that there is an implicit
|
||||
# dot in between a release segment and a pre-release segment.
|
||||
spec = _version_split(spec[:-2]) # Remove the trailing .*
|
||||
|
||||
# Split the prospective version out by dots, and pretend that there
|
||||
# is an implicit dot in between a release segment and a pre-release
|
||||
# segment.
|
||||
prospective = _version_split(str(prospective))
|
||||
|
||||
# Shorten the prospective version to be the same length as the spec
|
||||
# so that we can determine if the specifier is a prefix of the
|
||||
# prospective version or not.
|
||||
prospective = prospective[:len(spec)]
|
||||
|
||||
# Pad out our two sides with zeros so that they both equal the same
|
||||
# length.
|
||||
spec, prospective = _pad_version(spec, prospective)
|
||||
else:
|
||||
# Convert our spec string into a Version
|
||||
spec = Version(spec)
|
||||
|
||||
# If the specifier does not have a local segment, then we want to
|
||||
# act as if the prospective version also does not have a local
|
||||
# segment.
|
||||
if not spec.local:
|
||||
prospective = Version(prospective.public)
|
||||
|
||||
return prospective == spec
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_not_equal(self, prospective, spec):
|
||||
return not self._compare_equal(prospective, spec)
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_less_than_equal(self, prospective, spec):
|
||||
return prospective <= Version(spec)
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_greater_than_equal(self, prospective, spec):
|
||||
return prospective >= Version(spec)
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_less_than(self, prospective, spec):
|
||||
# Convert our spec to a Version instance, since we'll want to work with
|
||||
# it as a version.
|
||||
spec = Version(spec)
|
||||
|
||||
# Check to see if the prospective version is less than the spec
|
||||
# version. If it's not we can short circuit and just return False now
|
||||
# instead of doing extra unneeded work.
|
||||
if not prospective < spec:
|
||||
return False
|
||||
|
||||
# This special case is here so that, unless the specifier itself
|
||||
# includes is a pre-release version, that we do not accept pre-release
|
||||
# versions for the version mentioned in the specifier (e.g. <3.1 should
|
||||
# not match 3.1.dev0, but should match 3.0.dev0).
|
||||
if not spec.is_prerelease and prospective.is_prerelease:
|
||||
if Version(prospective.base_version) == Version(spec.base_version):
|
||||
return False
|
||||
|
||||
# If we've gotten to here, it means that prospective version is both
|
||||
# less than the spec version *and* it's not a pre-release of the same
|
||||
# version in the spec.
|
||||
return True
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_greater_than(self, prospective, spec):
|
||||
# Convert our spec to a Version instance, since we'll want to work with
|
||||
# it as a version.
|
||||
spec = Version(spec)
|
||||
|
||||
# Check to see if the prospective version is greater than the spec
|
||||
# version. If it's not we can short circuit and just return False now
|
||||
# instead of doing extra unneeded work.
|
||||
if not prospective > spec:
|
||||
return False
|
||||
|
||||
# This special case is here so that, unless the specifier itself
|
||||
# includes is a post-release version, that we do not accept
|
||||
# post-release versions for the version mentioned in the specifier
|
||||
# (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0).
|
||||
if not spec.is_postrelease and prospective.is_postrelease:
|
||||
if Version(prospective.base_version) == Version(spec.base_version):
|
||||
return False
|
||||
|
||||
# Ensure that we do not allow a local version of the version mentioned
|
||||
# in the specifier, which is techincally greater than, to match.
|
||||
if prospective.local is not None:
|
||||
if Version(prospective.base_version) == Version(spec.base_version):
|
||||
return False
|
||||
|
||||
# If we've gotten to here, it means that prospective version is both
|
||||
# greater than the spec version *and* it's not a pre-release of the
|
||||
# same version in the spec.
|
||||
return True
|
||||
|
||||
def _compare_arbitrary(self, prospective, spec):
|
||||
return str(prospective).lower() == str(spec).lower()
|
||||
|
||||
@property
|
||||
def prereleases(self):
|
||||
# If there is an explicit prereleases set for this, then we'll just
|
||||
# blindly use that.
|
||||
if self._prereleases is not None:
|
||||
return self._prereleases
|
||||
|
||||
# Look at all of our specifiers and determine if they are inclusive
|
||||
# operators, and if they are if they are including an explicit
|
||||
# prerelease.
|
||||
operator, version = self._spec
|
||||
if operator in ["==", ">=", "<=", "~=", "==="]:
|
||||
# The == specifier can include a trailing .*, if it does we
|
||||
# want to remove before parsing.
|
||||
if operator == "==" and version.endswith(".*"):
|
||||
version = version[:-2]
|
||||
|
||||
# Parse the version, and if it is a pre-release than this
|
||||
# specifier allows pre-releases.
|
||||
if parse(version).is_prerelease:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@prereleases.setter
|
||||
def prereleases(self, value):
|
||||
self._prereleases = value
|
||||
|
||||
|
||||
_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$")
|
||||
|
||||
|
||||
def _version_split(version):
|
||||
result = []
|
||||
for item in version.split("."):
|
||||
match = _prefix_regex.search(item)
|
||||
if match:
|
||||
result.extend(match.groups())
|
||||
else:
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
|
||||
def _pad_version(left, right):
|
||||
left_split, right_split = [], []
|
||||
|
||||
# Get the release segment of our versions
|
||||
left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left)))
|
||||
right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
|
||||
|
||||
# Get the rest of our versions
|
||||
left_split.append(left[len(left_split[0]):])
|
||||
right_split.append(right[len(right_split[0]):])
|
||||
|
||||
# Insert our padding
|
||||
left_split.insert(
|
||||
1,
|
||||
["0"] * max(0, len(right_split[0]) - len(left_split[0])),
|
||||
)
|
||||
right_split.insert(
|
||||
1,
|
||||
["0"] * max(0, len(left_split[0]) - len(right_split[0])),
|
||||
)
|
||||
|
||||
return (
|
||||
list(itertools.chain(*left_split)),
|
||||
list(itertools.chain(*right_split)),
|
||||
)
|
||||
|
||||
|
||||
class SpecifierSet(BaseSpecifier):
|
||||
|
||||
def __init__(self, specifiers="", prereleases=None):
|
||||
# Split on , to break each indidivual specifier into it's own item, and
|
||||
# strip each item to remove leading/trailing whitespace.
|
||||
specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
|
||||
|
||||
# Parsed each individual specifier, attempting first to make it a
|
||||
# Specifier and falling back to a LegacySpecifier.
|
||||
parsed = set()
|
||||
for specifier in specifiers:
|
||||
try:
|
||||
parsed.add(Specifier(specifier))
|
||||
except InvalidSpecifier:
|
||||
parsed.add(LegacySpecifier(specifier))
|
||||
|
||||
# Turn our parsed specifiers into a frozen set and save them for later.
|
||||
self._specs = frozenset(parsed)
|
||||
|
||||
# Store our prereleases value so we can use it later to determine if
|
||||
# we accept prereleases or not.
|
||||
self._prereleases = prereleases
|
||||
|
||||
def __repr__(self):
|
||||
pre = (
|
||||
", prereleases={0!r}".format(self.prereleases)
|
||||
if self._prereleases is not None
|
||||
else ""
|
||||
)
|
||||
|
||||
return "<SpecifierSet({0!r}{1})>".format(str(self), pre)
|
||||
|
||||
def __str__(self):
|
||||
return ",".join(sorted(str(s) for s in self._specs))
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._specs)
|
||||
|
||||
def __and__(self, other):
|
||||
if isinstance(other, string_types):
|
||||
other = SpecifierSet(other)
|
||||
elif not isinstance(other, SpecifierSet):
|
||||
return NotImplemented
|
||||
|
||||
specifier = SpecifierSet()
|
||||
specifier._specs = frozenset(self._specs | other._specs)
|
||||
|
||||
if self._prereleases is None and other._prereleases is not None:
|
||||
specifier._prereleases = other._prereleases
|
||||
elif self._prereleases is not None and other._prereleases is None:
|
||||
specifier._prereleases = self._prereleases
|
||||
elif self._prereleases == other._prereleases:
|
||||
specifier._prereleases = self._prereleases
|
||||
else:
|
||||
raise ValueError(
|
||||
"Cannot combine SpecifierSets with True and False prerelease "
|
||||
"overrides."
|
||||
)
|
||||
|
||||
return specifier
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, string_types):
|
||||
other = SpecifierSet(other)
|
||||
elif isinstance(other, _IndividualSpecifier):
|
||||
other = SpecifierSet(str(other))
|
||||
elif not isinstance(other, SpecifierSet):
|
||||
return NotImplemented
|
||||
|
||||
return self._specs == other._specs
|
||||
|
||||
def __ne__(self, other):
|
||||
if isinstance(other, string_types):
|
||||
other = SpecifierSet(other)
|
||||
elif isinstance(other, _IndividualSpecifier):
|
||||
other = SpecifierSet(str(other))
|
||||
elif not isinstance(other, SpecifierSet):
|
||||
return NotImplemented
|
||||
|
||||
return self._specs != other._specs
|
||||
|
||||
def __len__(self):
|
||||
return len(self._specs)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._specs)
|
||||
|
||||
@property
|
||||
def prereleases(self):
|
||||
# If we have been given an explicit prerelease modifier, then we'll
|
||||
# pass that through here.
|
||||
if self._prereleases is not None:
|
||||
return self._prereleases
|
||||
|
||||
# If we don't have any specifiers, and we don't have a forced value,
|
||||
# then we'll just return None since we don't know if this should have
|
||||
# pre-releases or not.
|
||||
if not self._specs:
|
||||
return None
|
||||
|
||||
# Otherwise we'll see if any of the given specifiers accept
|
||||
# prereleases, if any of them do we'll return True, otherwise False.
|
||||
return any(s.prereleases for s in self._specs)
|
||||
|
||||
@prereleases.setter
|
||||
def prereleases(self, value):
|
||||
self._prereleases = value
|
||||
|
||||
def __contains__(self, item):
|
||||
return self.contains(item)
|
||||
|
||||
def contains(self, item, prereleases=None):
|
||||
# Ensure that our item is a Version or LegacyVersion instance.
|
||||
if not isinstance(item, (LegacyVersion, Version)):
|
||||
item = parse(item)
|
||||
|
||||
# Determine if we're forcing a prerelease or not, if we're not forcing
|
||||
# one for this particular filter call, then we'll use whatever the
|
||||
# SpecifierSet thinks for whether or not we should support prereleases.
|
||||
if prereleases is None:
|
||||
prereleases = self.prereleases
|
||||
|
||||
# We can determine if we're going to allow pre-releases by looking to
|
||||
# see if any of the underlying items supports them. If none of them do
|
||||
# and this item is a pre-release then we do not allow it and we can
|
||||
# short circuit that here.
|
||||
# Note: This means that 1.0.dev1 would not be contained in something
|
||||
# like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0
|
||||
if not prereleases and item.is_prerelease:
|
||||
return False
|
||||
|
||||
# We simply dispatch to the underlying specs here to make sure that the
|
||||
# given version is contained within all of them.
|
||||
# Note: This use of all() here means that an empty set of specifiers
|
||||
# will always return True, this is an explicit design decision.
|
||||
return all(
|
||||
s.contains(item, prereleases=prereleases)
|
||||
for s in self._specs
|
||||
)
|
||||
|
||||
def filter(self, iterable, prereleases=None):
|
||||
# Determine if we're forcing a prerelease or not, if we're not forcing
|
||||
# one for this particular filter call, then we'll use whatever the
|
||||
# SpecifierSet thinks for whether or not we should support prereleases.
|
||||
if prereleases is None:
|
||||
prereleases = self.prereleases
|
||||
|
||||
# If we have any specifiers, then we want to wrap our iterable in the
|
||||
# filter method for each one, this will act as a logical AND amongst
|
||||
# each specifier.
|
||||
if self._specs:
|
||||
for spec in self._specs:
|
||||
iterable = spec.filter(iterable, prereleases=bool(prereleases))
|
||||
return iterable
|
||||
# If we do not have any specifiers, then we need to have a rough filter
|
||||
# which will filter out any pre-releases, unless there are no final
|
||||
# releases, and which will filter out LegacyVersion in general.
|
||||
else:
|
||||
filtered = []
|
||||
found_prereleases = []
|
||||
|
||||
for item in iterable:
|
||||
# Ensure that we some kind of Version class for this item.
|
||||
if not isinstance(item, (LegacyVersion, Version)):
|
||||
parsed_version = parse(item)
|
||||
else:
|
||||
parsed_version = item
|
||||
|
||||
# Filter out any item which is parsed as a LegacyVersion
|
||||
if isinstance(parsed_version, LegacyVersion):
|
||||
continue
|
||||
|
||||
# Store any item which is a pre-release for later unless we've
|
||||
# already found a final version or we are accepting prereleases
|
||||
if parsed_version.is_prerelease and not prereleases:
|
||||
if not filtered:
|
||||
found_prereleases.append(item)
|
||||
else:
|
||||
filtered.append(item)
|
||||
|
||||
# If we've found no items except for pre-releases, then we'll go
|
||||
# ahead and use the pre-releases
|
||||
if not filtered and found_prereleases and prereleases is None:
|
||||
return found_prereleases
|
||||
|
||||
return filtered
|
||||
@@ -1,14 +0,0 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import re
|
||||
|
||||
|
||||
_canonicalize_regex = re.compile(r"[-_.]+")
|
||||
|
||||
|
||||
def canonicalize_name(name):
|
||||
# This is taken from PEP 503.
|
||||
return _canonicalize_regex.sub("-", name).lower()
|
||||
@@ -1,393 +0,0 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import collections
|
||||
import itertools
|
||||
import re
|
||||
|
||||
from ._structures import Infinity
|
||||
|
||||
|
||||
__all__ = [
|
||||
"parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"
|
||||
]
|
||||
|
||||
|
||||
_Version = collections.namedtuple(
|
||||
"_Version",
|
||||
["epoch", "release", "dev", "pre", "post", "local"],
|
||||
)
|
||||
|
||||
|
||||
def parse(version):
|
||||
"""
|
||||
Parse the given version string and return either a :class:`Version` object
|
||||
or a :class:`LegacyVersion` object depending on if the given version is
|
||||
a valid PEP 440 version or a legacy version.
|
||||
"""
|
||||
try:
|
||||
return Version(version)
|
||||
except InvalidVersion:
|
||||
return LegacyVersion(version)
|
||||
|
||||
|
||||
class InvalidVersion(ValueError):
|
||||
"""
|
||||
An invalid version was found, users should refer to PEP 440.
|
||||
"""
|
||||
|
||||
|
||||
class _BaseVersion(object):
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._key)
|
||||
|
||||
def __lt__(self, other):
|
||||
return self._compare(other, lambda s, o: s < o)
|
||||
|
||||
def __le__(self, other):
|
||||
return self._compare(other, lambda s, o: s <= o)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._compare(other, lambda s, o: s == o)
|
||||
|
||||
def __ge__(self, other):
|
||||
return self._compare(other, lambda s, o: s >= o)
|
||||
|
||||
def __gt__(self, other):
|
||||
return self._compare(other, lambda s, o: s > o)
|
||||
|
||||
def __ne__(self, other):
|
||||
return self._compare(other, lambda s, o: s != o)
|
||||
|
||||
def _compare(self, other, method):
|
||||
if not isinstance(other, _BaseVersion):
|
||||
return NotImplemented
|
||||
|
||||
return method(self._key, other._key)
|
||||
|
||||
|
||||
class LegacyVersion(_BaseVersion):
|
||||
|
||||
def __init__(self, version):
|
||||
self._version = str(version)
|
||||
self._key = _legacy_cmpkey(self._version)
|
||||
|
||||
def __str__(self):
|
||||
return self._version
|
||||
|
||||
def __repr__(self):
|
||||
return "<LegacyVersion({0})>".format(repr(str(self)))
|
||||
|
||||
@property
|
||||
def public(self):
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def base_version(self):
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def local(self):
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_prerelease(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_postrelease(self):
|
||||
return False
|
||||
|
||||
|
||||
_legacy_version_component_re = re.compile(
|
||||
r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE,
|
||||
)
|
||||
|
||||
_legacy_version_replacement_map = {
|
||||
"pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@",
|
||||
}
|
||||
|
||||
|
||||
def _parse_version_parts(s):
|
||||
for part in _legacy_version_component_re.split(s):
|
||||
part = _legacy_version_replacement_map.get(part, part)
|
||||
|
||||
if not part or part == ".":
|
||||
continue
|
||||
|
||||
if part[:1] in "0123456789":
|
||||
# pad for numeric comparison
|
||||
yield part.zfill(8)
|
||||
else:
|
||||
yield "*" + part
|
||||
|
||||
# ensure that alpha/beta/candidate are before final
|
||||
yield "*final"
|
||||
|
||||
|
||||
def _legacy_cmpkey(version):
|
||||
# We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch
|
||||
# greater than or equal to 0. This will effectively put the LegacyVersion,
|
||||
# which uses the defacto standard originally implemented by setuptools,
|
||||
# as before all PEP 440 versions.
|
||||
epoch = -1
|
||||
|
||||
# This scheme is taken from pkg_resources.parse_version setuptools prior to
|
||||
# it's adoption of the packaging library.
|
||||
parts = []
|
||||
for part in _parse_version_parts(version.lower()):
|
||||
if part.startswith("*"):
|
||||
# remove "-" before a prerelease tag
|
||||
if part < "*final":
|
||||
while parts and parts[-1] == "*final-":
|
||||
parts.pop()
|
||||
|
||||
# remove trailing zeros from each series of numeric parts
|
||||
while parts and parts[-1] == "00000000":
|
||||
parts.pop()
|
||||
|
||||
parts.append(part)
|
||||
parts = tuple(parts)
|
||||
|
||||
return epoch, parts
|
||||
|
||||
# Deliberately not anchored to the start and end of the string, to make it
|
||||
# easier for 3rd party code to reuse
|
||||
VERSION_PATTERN = r"""
|
||||
v?
|
||||
(?:
|
||||
(?:(?P<epoch>[0-9]+)!)? # epoch
|
||||
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
|
||||
(?P<pre> # pre-release
|
||||
[-_\.]?
|
||||
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
|
||||
[-_\.]?
|
||||
(?P<pre_n>[0-9]+)?
|
||||
)?
|
||||
(?P<post> # post release
|
||||
(?:-(?P<post_n1>[0-9]+))
|
||||
|
|
||||
(?:
|
||||
[-_\.]?
|
||||
(?P<post_l>post|rev|r)
|
||||
[-_\.]?
|
||||
(?P<post_n2>[0-9]+)?
|
||||
)
|
||||
)?
|
||||
(?P<dev> # dev release
|
||||
[-_\.]?
|
||||
(?P<dev_l>dev)
|
||||
[-_\.]?
|
||||
(?P<dev_n>[0-9]+)?
|
||||
)?
|
||||
)
|
||||
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
|
||||
"""
|
||||
|
||||
|
||||
class Version(_BaseVersion):
|
||||
|
||||
_regex = re.compile(
|
||||
r"^\s*" + VERSION_PATTERN + r"\s*$",
|
||||
re.VERBOSE | re.IGNORECASE,
|
||||
)
|
||||
|
||||
def __init__(self, version):
|
||||
# Validate the version and parse it into pieces
|
||||
match = self._regex.search(version)
|
||||
if not match:
|
||||
raise InvalidVersion("Invalid version: '{0}'".format(version))
|
||||
|
||||
# Store the parsed out pieces of the version
|
||||
self._version = _Version(
|
||||
epoch=int(match.group("epoch")) if match.group("epoch") else 0,
|
||||
release=tuple(int(i) for i in match.group("release").split(".")),
|
||||
pre=_parse_letter_version(
|
||||
match.group("pre_l"),
|
||||
match.group("pre_n"),
|
||||
),
|
||||
post=_parse_letter_version(
|
||||
match.group("post_l"),
|
||||
match.group("post_n1") or match.group("post_n2"),
|
||||
),
|
||||
dev=_parse_letter_version(
|
||||
match.group("dev_l"),
|
||||
match.group("dev_n"),
|
||||
),
|
||||
local=_parse_local_version(match.group("local")),
|
||||
)
|
||||
|
||||
# Generate a key which will be used for sorting
|
||||
self._key = _cmpkey(
|
||||
self._version.epoch,
|
||||
self._version.release,
|
||||
self._version.pre,
|
||||
self._version.post,
|
||||
self._version.dev,
|
||||
self._version.local,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Version({0})>".format(repr(str(self)))
|
||||
|
||||
def __str__(self):
|
||||
parts = []
|
||||
|
||||
# Epoch
|
||||
if self._version.epoch != 0:
|
||||
parts.append("{0}!".format(self._version.epoch))
|
||||
|
||||
# Release segment
|
||||
parts.append(".".join(str(x) for x in self._version.release))
|
||||
|
||||
# Pre-release
|
||||
if self._version.pre is not None:
|
||||
parts.append("".join(str(x) for x in self._version.pre))
|
||||
|
||||
# Post-release
|
||||
if self._version.post is not None:
|
||||
parts.append(".post{0}".format(self._version.post[1]))
|
||||
|
||||
# Development release
|
||||
if self._version.dev is not None:
|
||||
parts.append(".dev{0}".format(self._version.dev[1]))
|
||||
|
||||
# Local version segment
|
||||
if self._version.local is not None:
|
||||
parts.append(
|
||||
"+{0}".format(".".join(str(x) for x in self._version.local))
|
||||
)
|
||||
|
||||
return "".join(parts)
|
||||
|
||||
@property
|
||||
def public(self):
|
||||
return str(self).split("+", 1)[0]
|
||||
|
||||
@property
|
||||
def base_version(self):
|
||||
parts = []
|
||||
|
||||
# Epoch
|
||||
if self._version.epoch != 0:
|
||||
parts.append("{0}!".format(self._version.epoch))
|
||||
|
||||
# Release segment
|
||||
parts.append(".".join(str(x) for x in self._version.release))
|
||||
|
||||
return "".join(parts)
|
||||
|
||||
@property
|
||||
def local(self):
|
||||
version_string = str(self)
|
||||
if "+" in version_string:
|
||||
return version_string.split("+", 1)[1]
|
||||
|
||||
@property
|
||||
def is_prerelease(self):
|
||||
return bool(self._version.dev or self._version.pre)
|
||||
|
||||
@property
|
||||
def is_postrelease(self):
|
||||
return bool(self._version.post)
|
||||
|
||||
|
||||
def _parse_letter_version(letter, number):
|
||||
if letter:
|
||||
# We consider there to be an implicit 0 in a pre-release if there is
|
||||
# not a numeral associated with it.
|
||||
if number is None:
|
||||
number = 0
|
||||
|
||||
# We normalize any letters to their lower case form
|
||||
letter = letter.lower()
|
||||
|
||||
# We consider some words to be alternate spellings of other words and
|
||||
# in those cases we want to normalize the spellings to our preferred
|
||||
# spelling.
|
||||
if letter == "alpha":
|
||||
letter = "a"
|
||||
elif letter == "beta":
|
||||
letter = "b"
|
||||
elif letter in ["c", "pre", "preview"]:
|
||||
letter = "rc"
|
||||
elif letter in ["rev", "r"]:
|
||||
letter = "post"
|
||||
|
||||
return letter, int(number)
|
||||
if not letter and number:
|
||||
# We assume if we are given a number, but we are not given a letter
|
||||
# then this is using the implicit post release syntax (e.g. 1.0-1)
|
||||
letter = "post"
|
||||
|
||||
return letter, int(number)
|
||||
|
||||
|
||||
_local_version_seperators = re.compile(r"[\._-]")
|
||||
|
||||
|
||||
def _parse_local_version(local):
|
||||
"""
|
||||
Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
|
||||
"""
|
||||
if local is not None:
|
||||
return tuple(
|
||||
part.lower() if not part.isdigit() else int(part)
|
||||
for part in _local_version_seperators.split(local)
|
||||
)
|
||||
|
||||
|
||||
def _cmpkey(epoch, release, pre, post, dev, local):
|
||||
# When we compare a release version, we want to compare it with all of the
|
||||
# trailing zeros removed. So we'll use a reverse the list, drop all the now
|
||||
# leading zeros until we come to something non zero, then take the rest
|
||||
# re-reverse it back into the correct order and make it a tuple and use
|
||||
# that for our sorting key.
|
||||
release = tuple(
|
||||
reversed(list(
|
||||
itertools.dropwhile(
|
||||
lambda x: x == 0,
|
||||
reversed(release),
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
# We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
|
||||
# We'll do this by abusing the pre segment, but we _only_ want to do this
|
||||
# if there is not a pre or a post segment. If we have one of those then
|
||||
# the normal sorting rules will handle this case correctly.
|
||||
if pre is None and post is None and dev is not None:
|
||||
pre = -Infinity
|
||||
# Versions without a pre-release (except as noted above) should sort after
|
||||
# those with one.
|
||||
elif pre is None:
|
||||
pre = Infinity
|
||||
|
||||
# Versions without a post segment should sort before those with one.
|
||||
if post is None:
|
||||
post = -Infinity
|
||||
|
||||
# Versions without a development segment should sort after those with one.
|
||||
if dev is None:
|
||||
dev = Infinity
|
||||
|
||||
if local is None:
|
||||
# Versions without a local segment should sort before those with one.
|
||||
local = -Infinity
|
||||
else:
|
||||
# Versions with a local segment need that segment parsed to implement
|
||||
# the sorting rules in PEP440.
|
||||
# - Alpha numeric segments sort before numeric segments
|
||||
# - Alpha numeric segments sort lexicographically
|
||||
# - Numeric segments sort numerically
|
||||
# - Shorter versions sort before longer versions when the prefixes
|
||||
# match exactly
|
||||
local = tuple(
|
||||
(i, "") if isinstance(i, int) else (-Infinity, i)
|
||||
for i in local
|
||||
)
|
||||
|
||||
return epoch, release, pre, post, dev, local
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,868 +0,0 @@
|
||||
"""Utilities for writing code that runs on Python 2 and 3"""
|
||||
|
||||
# Copyright (c) 2010-2015 Benjamin Peterson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import functools
|
||||
import itertools
|
||||
import operator
|
||||
import sys
|
||||
import types
|
||||
|
||||
__author__ = "Benjamin Peterson <benjamin@python.org>"
|
||||
__version__ = "1.10.0"
|
||||
|
||||
|
||||
# Useful for very coarse version differentiation.
|
||||
PY2 = sys.version_info[0] == 2
|
||||
PY3 = sys.version_info[0] == 3
|
||||
PY34 = sys.version_info[0:2] >= (3, 4)
|
||||
|
||||
if PY3:
|
||||
string_types = str,
|
||||
integer_types = int,
|
||||
class_types = type,
|
||||
text_type = str
|
||||
binary_type = bytes
|
||||
|
||||
MAXSIZE = sys.maxsize
|
||||
else:
|
||||
string_types = basestring,
|
||||
integer_types = (int, long)
|
||||
class_types = (type, types.ClassType)
|
||||
text_type = unicode
|
||||
binary_type = str
|
||||
|
||||
if sys.platform.startswith("java"):
|
||||
# Jython always uses 32 bits.
|
||||
MAXSIZE = int((1 << 31) - 1)
|
||||
else:
|
||||
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
|
||||
class X(object):
|
||||
|
||||
def __len__(self):
|
||||
return 1 << 31
|
||||
try:
|
||||
len(X())
|
||||
except OverflowError:
|
||||
# 32-bit
|
||||
MAXSIZE = int((1 << 31) - 1)
|
||||
else:
|
||||
# 64-bit
|
||||
MAXSIZE = int((1 << 63) - 1)
|
||||
del X
|
||||
|
||||
|
||||
def _add_doc(func, doc):
|
||||
"""Add documentation to a function."""
|
||||
func.__doc__ = doc
|
||||
|
||||
|
||||
def _import_module(name):
|
||||
"""Import module, returning the module after the last dot."""
|
||||
__import__(name)
|
||||
return sys.modules[name]
|
||||
|
||||
|
||||
class _LazyDescr(object):
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __get__(self, obj, tp):
|
||||
result = self._resolve()
|
||||
setattr(obj, self.name, result) # Invokes __set__.
|
||||
try:
|
||||
# This is a bit ugly, but it avoids running this again by
|
||||
# removing this descriptor.
|
||||
delattr(obj.__class__, self.name)
|
||||
except AttributeError:
|
||||
pass
|
||||
return result
|
||||
|
||||
|
||||
class MovedModule(_LazyDescr):
|
||||
|
||||
def __init__(self, name, old, new=None):
|
||||
super(MovedModule, self).__init__(name)
|
||||
if PY3:
|
||||
if new is None:
|
||||
new = name
|
||||
self.mod = new
|
||||
else:
|
||||
self.mod = old
|
||||
|
||||
def _resolve(self):
|
||||
return _import_module(self.mod)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
_module = self._resolve()
|
||||
value = getattr(_module, attr)
|
||||
setattr(self, attr, value)
|
||||
return value
|
||||
|
||||
|
||||
class _LazyModule(types.ModuleType):
|
||||
|
||||
def __init__(self, name):
|
||||
super(_LazyModule, self).__init__(name)
|
||||
self.__doc__ = self.__class__.__doc__
|
||||
|
||||
def __dir__(self):
|
||||
attrs = ["__doc__", "__name__"]
|
||||
attrs += [attr.name for attr in self._moved_attributes]
|
||||
return attrs
|
||||
|
||||
# Subclasses should override this
|
||||
_moved_attributes = []
|
||||
|
||||
|
||||
class MovedAttribute(_LazyDescr):
|
||||
|
||||
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
|
||||
super(MovedAttribute, self).__init__(name)
|
||||
if PY3:
|
||||
if new_mod is None:
|
||||
new_mod = name
|
||||
self.mod = new_mod
|
||||
if new_attr is None:
|
||||
if old_attr is None:
|
||||
new_attr = name
|
||||
else:
|
||||
new_attr = old_attr
|
||||
self.attr = new_attr
|
||||
else:
|
||||
self.mod = old_mod
|
||||
if old_attr is None:
|
||||
old_attr = name
|
||||
self.attr = old_attr
|
||||
|
||||
def _resolve(self):
|
||||
module = _import_module(self.mod)
|
||||
return getattr(module, self.attr)
|
||||
|
||||
|
||||
class _SixMetaPathImporter(object):
|
||||
|
||||
"""
|
||||
A meta path importer to import six.moves and its submodules.
|
||||
|
||||
This class implements a PEP302 finder and loader. It should be compatible
|
||||
with Python 2.5 and all existing versions of Python3
|
||||
"""
|
||||
|
||||
def __init__(self, six_module_name):
|
||||
self.name = six_module_name
|
||||
self.known_modules = {}
|
||||
|
||||
def _add_module(self, mod, *fullnames):
|
||||
for fullname in fullnames:
|
||||
self.known_modules[self.name + "." + fullname] = mod
|
||||
|
||||
def _get_module(self, fullname):
|
||||
return self.known_modules[self.name + "." + fullname]
|
||||
|
||||
def find_module(self, fullname, path=None):
|
||||
if fullname in self.known_modules:
|
||||
return self
|
||||
return None
|
||||
|
||||
def __get_module(self, fullname):
|
||||
try:
|
||||
return self.known_modules[fullname]
|
||||
except KeyError:
|
||||
raise ImportError("This loader does not know module " + fullname)
|
||||
|
||||
def load_module(self, fullname):
|
||||
try:
|
||||
# in case of a reload
|
||||
return sys.modules[fullname]
|
||||
except KeyError:
|
||||
pass
|
||||
mod = self.__get_module(fullname)
|
||||
if isinstance(mod, MovedModule):
|
||||
mod = mod._resolve()
|
||||
else:
|
||||
mod.__loader__ = self
|
||||
sys.modules[fullname] = mod
|
||||
return mod
|
||||
|
||||
def is_package(self, fullname):
|
||||
"""
|
||||
Return true, if the named module is a package.
|
||||
|
||||
We need this method to get correct spec objects with
|
||||
Python 3.4 (see PEP451)
|
||||
"""
|
||||
return hasattr(self.__get_module(fullname), "__path__")
|
||||
|
||||
def get_code(self, fullname):
|
||||
"""Return None
|
||||
|
||||
Required, if is_package is implemented"""
|
||||
self.__get_module(fullname) # eventually raises ImportError
|
||||
return None
|
||||
get_source = get_code # same as get_code
|
||||
|
||||
_importer = _SixMetaPathImporter(__name__)
|
||||
|
||||
|
||||
class _MovedItems(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects"""
|
||||
__path__ = [] # mark as package
|
||||
|
||||
|
||||
_moved_attributes = [
|
||||
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
|
||||
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
|
||||
MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
|
||||
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
|
||||
MovedAttribute("intern", "__builtin__", "sys"),
|
||||
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
|
||||
MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
|
||||
MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
|
||||
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
|
||||
MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
|
||||
MovedAttribute("reduce", "__builtin__", "functools"),
|
||||
MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
|
||||
MovedAttribute("StringIO", "StringIO", "io"),
|
||||
MovedAttribute("UserDict", "UserDict", "collections"),
|
||||
MovedAttribute("UserList", "UserList", "collections"),
|
||||
MovedAttribute("UserString", "UserString", "collections"),
|
||||
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
|
||||
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
|
||||
MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
|
||||
MovedModule("builtins", "__builtin__"),
|
||||
MovedModule("configparser", "ConfigParser"),
|
||||
MovedModule("copyreg", "copy_reg"),
|
||||
MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
|
||||
MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
|
||||
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
|
||||
MovedModule("http_cookies", "Cookie", "http.cookies"),
|
||||
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
|
||||
MovedModule("html_parser", "HTMLParser", "html.parser"),
|
||||
MovedModule("http_client", "httplib", "http.client"),
|
||||
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
|
||||
MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
|
||||
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
|
||||
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
|
||||
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
|
||||
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
|
||||
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
|
||||
MovedModule("cPickle", "cPickle", "pickle"),
|
||||
MovedModule("queue", "Queue"),
|
||||
MovedModule("reprlib", "repr"),
|
||||
MovedModule("socketserver", "SocketServer"),
|
||||
MovedModule("_thread", "thread", "_thread"),
|
||||
MovedModule("tkinter", "Tkinter"),
|
||||
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
|
||||
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
|
||||
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
|
||||
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
|
||||
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
|
||||
MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
|
||||
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
|
||||
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
|
||||
MovedModule("tkinter_colorchooser", "tkColorChooser",
|
||||
"tkinter.colorchooser"),
|
||||
MovedModule("tkinter_commondialog", "tkCommonDialog",
|
||||
"tkinter.commondialog"),
|
||||
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
|
||||
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
|
||||
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
|
||||
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
|
||||
"tkinter.simpledialog"),
|
||||
MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
|
||||
MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
|
||||
MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
|
||||
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
|
||||
MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
|
||||
MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
|
||||
]
|
||||
# Add windows specific modules.
|
||||
if sys.platform == "win32":
|
||||
_moved_attributes += [
|
||||
MovedModule("winreg", "_winreg"),
|
||||
]
|
||||
|
||||
for attr in _moved_attributes:
|
||||
setattr(_MovedItems, attr.name, attr)
|
||||
if isinstance(attr, MovedModule):
|
||||
_importer._add_module(attr, "moves." + attr.name)
|
||||
del attr
|
||||
|
||||
_MovedItems._moved_attributes = _moved_attributes
|
||||
|
||||
moves = _MovedItems(__name__ + ".moves")
|
||||
_importer._add_module(moves, "moves")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_parse(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_parse"""
|
||||
|
||||
|
||||
_urllib_parse_moved_attributes = [
|
||||
MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urljoin", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlparse", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("quote", "urllib", "urllib.parse"),
|
||||
MovedAttribute("quote_plus", "urllib", "urllib.parse"),
|
||||
MovedAttribute("unquote", "urllib", "urllib.parse"),
|
||||
MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
|
||||
MovedAttribute("urlencode", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splitquery", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splittag", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splituser", "urllib", "urllib.parse"),
|
||||
MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_params", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_query", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
|
||||
]
|
||||
for attr in _urllib_parse_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_parse, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
|
||||
"moves.urllib_parse", "moves.urllib.parse")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_error(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_error"""
|
||||
|
||||
|
||||
_urllib_error_moved_attributes = [
|
||||
MovedAttribute("URLError", "urllib2", "urllib.error"),
|
||||
MovedAttribute("HTTPError", "urllib2", "urllib.error"),
|
||||
MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
|
||||
]
|
||||
for attr in _urllib_error_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_error, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
|
||||
"moves.urllib_error", "moves.urllib.error")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_request(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_request"""
|
||||
|
||||
|
||||
_urllib_request_moved_attributes = [
|
||||
MovedAttribute("urlopen", "urllib2", "urllib.request"),
|
||||
MovedAttribute("install_opener", "urllib2", "urllib.request"),
|
||||
MovedAttribute("build_opener", "urllib2", "urllib.request"),
|
||||
MovedAttribute("pathname2url", "urllib", "urllib.request"),
|
||||
MovedAttribute("url2pathname", "urllib", "urllib.request"),
|
||||
MovedAttribute("getproxies", "urllib", "urllib.request"),
|
||||
MovedAttribute("Request", "urllib2", "urllib.request"),
|
||||
MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
|
||||
MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
|
||||
MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("FileHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
|
||||
MovedAttribute("urlretrieve", "urllib", "urllib.request"),
|
||||
MovedAttribute("urlcleanup", "urllib", "urllib.request"),
|
||||
MovedAttribute("URLopener", "urllib", "urllib.request"),
|
||||
MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
|
||||
MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
|
||||
]
|
||||
for attr in _urllib_request_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_request, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
|
||||
"moves.urllib_request", "moves.urllib.request")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_response(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_response"""
|
||||
|
||||
|
||||
_urllib_response_moved_attributes = [
|
||||
MovedAttribute("addbase", "urllib", "urllib.response"),
|
||||
MovedAttribute("addclosehook", "urllib", "urllib.response"),
|
||||
MovedAttribute("addinfo", "urllib", "urllib.response"),
|
||||
MovedAttribute("addinfourl", "urllib", "urllib.response"),
|
||||
]
|
||||
for attr in _urllib_response_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_response, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
|
||||
"moves.urllib_response", "moves.urllib.response")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_robotparser(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_robotparser"""
|
||||
|
||||
|
||||
_urllib_robotparser_moved_attributes = [
|
||||
MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
|
||||
]
|
||||
for attr in _urllib_robotparser_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
|
||||
"moves.urllib_robotparser", "moves.urllib.robotparser")
|
||||
|
||||
|
||||
class Module_six_moves_urllib(types.ModuleType):
|
||||
|
||||
"""Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
|
||||
__path__ = [] # mark as package
|
||||
parse = _importer._get_module("moves.urllib_parse")
|
||||
error = _importer._get_module("moves.urllib_error")
|
||||
request = _importer._get_module("moves.urllib_request")
|
||||
response = _importer._get_module("moves.urllib_response")
|
||||
robotparser = _importer._get_module("moves.urllib_robotparser")
|
||||
|
||||
def __dir__(self):
|
||||
return ['parse', 'error', 'request', 'response', 'robotparser']
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
|
||||
"moves.urllib")
|
||||
|
||||
|
||||
def add_move(move):
|
||||
"""Add an item to six.moves."""
|
||||
setattr(_MovedItems, move.name, move)
|
||||
|
||||
|
||||
def remove_move(name):
|
||||
"""Remove item from six.moves."""
|
||||
try:
|
||||
delattr(_MovedItems, name)
|
||||
except AttributeError:
|
||||
try:
|
||||
del moves.__dict__[name]
|
||||
except KeyError:
|
||||
raise AttributeError("no such move, %r" % (name,))
|
||||
|
||||
|
||||
if PY3:
|
||||
_meth_func = "__func__"
|
||||
_meth_self = "__self__"
|
||||
|
||||
_func_closure = "__closure__"
|
||||
_func_code = "__code__"
|
||||
_func_defaults = "__defaults__"
|
||||
_func_globals = "__globals__"
|
||||
else:
|
||||
_meth_func = "im_func"
|
||||
_meth_self = "im_self"
|
||||
|
||||
_func_closure = "func_closure"
|
||||
_func_code = "func_code"
|
||||
_func_defaults = "func_defaults"
|
||||
_func_globals = "func_globals"
|
||||
|
||||
|
||||
try:
|
||||
advance_iterator = next
|
||||
except NameError:
|
||||
def advance_iterator(it):
|
||||
return it.next()
|
||||
next = advance_iterator
|
||||
|
||||
|
||||
try:
|
||||
callable = callable
|
||||
except NameError:
|
||||
def callable(obj):
|
||||
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
|
||||
|
||||
|
||||
if PY3:
|
||||
def get_unbound_function(unbound):
|
||||
return unbound
|
||||
|
||||
create_bound_method = types.MethodType
|
||||
|
||||
def create_unbound_method(func, cls):
|
||||
return func
|
||||
|
||||
Iterator = object
|
||||
else:
|
||||
def get_unbound_function(unbound):
|
||||
return unbound.im_func
|
||||
|
||||
def create_bound_method(func, obj):
|
||||
return types.MethodType(func, obj, obj.__class__)
|
||||
|
||||
def create_unbound_method(func, cls):
|
||||
return types.MethodType(func, None, cls)
|
||||
|
||||
class Iterator(object):
|
||||
|
||||
def next(self):
|
||||
return type(self).__next__(self)
|
||||
|
||||
callable = callable
|
||||
_add_doc(get_unbound_function,
|
||||
"""Get the function out of a possibly unbound function""")
|
||||
|
||||
|
||||
get_method_function = operator.attrgetter(_meth_func)
|
||||
get_method_self = operator.attrgetter(_meth_self)
|
||||
get_function_closure = operator.attrgetter(_func_closure)
|
||||
get_function_code = operator.attrgetter(_func_code)
|
||||
get_function_defaults = operator.attrgetter(_func_defaults)
|
||||
get_function_globals = operator.attrgetter(_func_globals)
|
||||
|
||||
|
||||
if PY3:
|
||||
def iterkeys(d, **kw):
|
||||
return iter(d.keys(**kw))
|
||||
|
||||
def itervalues(d, **kw):
|
||||
return iter(d.values(**kw))
|
||||
|
||||
def iteritems(d, **kw):
|
||||
return iter(d.items(**kw))
|
||||
|
||||
def iterlists(d, **kw):
|
||||
return iter(d.lists(**kw))
|
||||
|
||||
viewkeys = operator.methodcaller("keys")
|
||||
|
||||
viewvalues = operator.methodcaller("values")
|
||||
|
||||
viewitems = operator.methodcaller("items")
|
||||
else:
|
||||
def iterkeys(d, **kw):
|
||||
return d.iterkeys(**kw)
|
||||
|
||||
def itervalues(d, **kw):
|
||||
return d.itervalues(**kw)
|
||||
|
||||
def iteritems(d, **kw):
|
||||
return d.iteritems(**kw)
|
||||
|
||||
def iterlists(d, **kw):
|
||||
return d.iterlists(**kw)
|
||||
|
||||
viewkeys = operator.methodcaller("viewkeys")
|
||||
|
||||
viewvalues = operator.methodcaller("viewvalues")
|
||||
|
||||
viewitems = operator.methodcaller("viewitems")
|
||||
|
||||
_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
|
||||
_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
|
||||
_add_doc(iteritems,
|
||||
"Return an iterator over the (key, value) pairs of a dictionary.")
|
||||
_add_doc(iterlists,
|
||||
"Return an iterator over the (key, [values]) pairs of a dictionary.")
|
||||
|
||||
|
||||
if PY3:
|
||||
def b(s):
|
||||
return s.encode("latin-1")
|
||||
|
||||
def u(s):
|
||||
return s
|
||||
unichr = chr
|
||||
import struct
|
||||
int2byte = struct.Struct(">B").pack
|
||||
del struct
|
||||
byte2int = operator.itemgetter(0)
|
||||
indexbytes = operator.getitem
|
||||
iterbytes = iter
|
||||
import io
|
||||
StringIO = io.StringIO
|
||||
BytesIO = io.BytesIO
|
||||
_assertCountEqual = "assertCountEqual"
|
||||
if sys.version_info[1] <= 1:
|
||||
_assertRaisesRegex = "assertRaisesRegexp"
|
||||
_assertRegex = "assertRegexpMatches"
|
||||
else:
|
||||
_assertRaisesRegex = "assertRaisesRegex"
|
||||
_assertRegex = "assertRegex"
|
||||
else:
|
||||
def b(s):
|
||||
return s
|
||||
# Workaround for standalone backslash
|
||||
|
||||
def u(s):
|
||||
return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
|
||||
unichr = unichr
|
||||
int2byte = chr
|
||||
|
||||
def byte2int(bs):
|
||||
return ord(bs[0])
|
||||
|
||||
def indexbytes(buf, i):
|
||||
return ord(buf[i])
|
||||
iterbytes = functools.partial(itertools.imap, ord)
|
||||
import StringIO
|
||||
StringIO = BytesIO = StringIO.StringIO
|
||||
_assertCountEqual = "assertItemsEqual"
|
||||
_assertRaisesRegex = "assertRaisesRegexp"
|
||||
_assertRegex = "assertRegexpMatches"
|
||||
_add_doc(b, """Byte literal""")
|
||||
_add_doc(u, """Text literal""")
|
||||
|
||||
|
||||
def assertCountEqual(self, *args, **kwargs):
|
||||
return getattr(self, _assertCountEqual)(*args, **kwargs)
|
||||
|
||||
|
||||
def assertRaisesRegex(self, *args, **kwargs):
|
||||
return getattr(self, _assertRaisesRegex)(*args, **kwargs)
|
||||
|
||||
|
||||
def assertRegex(self, *args, **kwargs):
|
||||
return getattr(self, _assertRegex)(*args, **kwargs)
|
||||
|
||||
|
||||
if PY3:
|
||||
exec_ = getattr(moves.builtins, "exec")
|
||||
|
||||
def reraise(tp, value, tb=None):
|
||||
if value is None:
|
||||
value = tp()
|
||||
if value.__traceback__ is not tb:
|
||||
raise value.with_traceback(tb)
|
||||
raise value
|
||||
|
||||
else:
|
||||
def exec_(_code_, _globs_=None, _locs_=None):
|
||||
"""Execute code in a namespace."""
|
||||
if _globs_ is None:
|
||||
frame = sys._getframe(1)
|
||||
_globs_ = frame.f_globals
|
||||
if _locs_ is None:
|
||||
_locs_ = frame.f_locals
|
||||
del frame
|
||||
elif _locs_ is None:
|
||||
_locs_ = _globs_
|
||||
exec("""exec _code_ in _globs_, _locs_""")
|
||||
|
||||
exec_("""def reraise(tp, value, tb=None):
|
||||
raise tp, value, tb
|
||||
""")
|
||||
|
||||
|
||||
if sys.version_info[:2] == (3, 2):
|
||||
exec_("""def raise_from(value, from_value):
|
||||
if from_value is None:
|
||||
raise value
|
||||
raise value from from_value
|
||||
""")
|
||||
elif sys.version_info[:2] > (3, 2):
|
||||
exec_("""def raise_from(value, from_value):
|
||||
raise value from from_value
|
||||
""")
|
||||
else:
|
||||
def raise_from(value, from_value):
|
||||
raise value
|
||||
|
||||
|
||||
print_ = getattr(moves.builtins, "print", None)
|
||||
if print_ is None:
|
||||
def print_(*args, **kwargs):
|
||||
"""The new-style print function for Python 2.4 and 2.5."""
|
||||
fp = kwargs.pop("file", sys.stdout)
|
||||
if fp is None:
|
||||
return
|
||||
|
||||
def write(data):
|
||||
if not isinstance(data, basestring):
|
||||
data = str(data)
|
||||
# If the file has an encoding, encode unicode with it.
|
||||
if (isinstance(fp, file) and
|
||||
isinstance(data, unicode) and
|
||||
fp.encoding is not None):
|
||||
errors = getattr(fp, "errors", None)
|
||||
if errors is None:
|
||||
errors = "strict"
|
||||
data = data.encode(fp.encoding, errors)
|
||||
fp.write(data)
|
||||
want_unicode = False
|
||||
sep = kwargs.pop("sep", None)
|
||||
if sep is not None:
|
||||
if isinstance(sep, unicode):
|
||||
want_unicode = True
|
||||
elif not isinstance(sep, str):
|
||||
raise TypeError("sep must be None or a string")
|
||||
end = kwargs.pop("end", None)
|
||||
if end is not None:
|
||||
if isinstance(end, unicode):
|
||||
want_unicode = True
|
||||
elif not isinstance(end, str):
|
||||
raise TypeError("end must be None or a string")
|
||||
if kwargs:
|
||||
raise TypeError("invalid keyword arguments to print()")
|
||||
if not want_unicode:
|
||||
for arg in args:
|
||||
if isinstance(arg, unicode):
|
||||
want_unicode = True
|
||||
break
|
||||
if want_unicode:
|
||||
newline = unicode("\n")
|
||||
space = unicode(" ")
|
||||
else:
|
||||
newline = "\n"
|
||||
space = " "
|
||||
if sep is None:
|
||||
sep = space
|
||||
if end is None:
|
||||
end = newline
|
||||
for i, arg in enumerate(args):
|
||||
if i:
|
||||
write(sep)
|
||||
write(arg)
|
||||
write(end)
|
||||
if sys.version_info[:2] < (3, 3):
|
||||
_print = print_
|
||||
|
||||
def print_(*args, **kwargs):
|
||||
fp = kwargs.get("file", sys.stdout)
|
||||
flush = kwargs.pop("flush", False)
|
||||
_print(*args, **kwargs)
|
||||
if flush and fp is not None:
|
||||
fp.flush()
|
||||
|
||||
_add_doc(reraise, """Reraise an exception.""")
|
||||
|
||||
if sys.version_info[0:2] < (3, 4):
|
||||
def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
|
||||
updated=functools.WRAPPER_UPDATES):
|
||||
def wrapper(f):
|
||||
f = functools.wraps(wrapped, assigned, updated)(f)
|
||||
f.__wrapped__ = wrapped
|
||||
return f
|
||||
return wrapper
|
||||
else:
|
||||
wraps = functools.wraps
|
||||
|
||||
|
||||
def with_metaclass(meta, *bases):
|
||||
"""Create a base class with a metaclass."""
|
||||
# This requires a bit of explanation: the basic idea is to make a dummy
|
||||
# metaclass for one level of class instantiation that replaces itself with
|
||||
# the actual metaclass.
|
||||
class metaclass(meta):
|
||||
|
||||
def __new__(cls, name, this_bases, d):
|
||||
return meta(name, bases, d)
|
||||
return type.__new__(metaclass, 'temporary_class', (), {})
|
||||
|
||||
|
||||
def add_metaclass(metaclass):
|
||||
"""Class decorator for creating a class with a metaclass."""
|
||||
def wrapper(cls):
|
||||
orig_vars = cls.__dict__.copy()
|
||||
slots = orig_vars.get('__slots__')
|
||||
if slots is not None:
|
||||
if isinstance(slots, str):
|
||||
slots = [slots]
|
||||
for slots_var in slots:
|
||||
orig_vars.pop(slots_var)
|
||||
orig_vars.pop('__dict__', None)
|
||||
orig_vars.pop('__weakref__', None)
|
||||
return metaclass(cls.__name__, cls.__bases__, orig_vars)
|
||||
return wrapper
|
||||
|
||||
|
||||
def python_2_unicode_compatible(klass):
|
||||
"""
|
||||
A decorator that defines __unicode__ and __str__ methods under Python 2.
|
||||
Under Python 3 it does nothing.
|
||||
|
||||
To support Python 2 and 3 with a single code base, define a __str__ method
|
||||
returning text and apply this decorator to the class.
|
||||
"""
|
||||
if PY2:
|
||||
if '__str__' not in klass.__dict__:
|
||||
raise ValueError("@python_2_unicode_compatible cannot be applied "
|
||||
"to %s because it doesn't define __str__()." %
|
||||
klass.__name__)
|
||||
klass.__unicode__ = klass.__str__
|
||||
klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
|
||||
return klass
|
||||
|
||||
|
||||
# Complete the moves implementation.
|
||||
# This code is at the end of this module to speed up module loading.
|
||||
# Turn this module into a package.
|
||||
__path__ = [] # required for PEP 302 and PEP 451
|
||||
__package__ = __name__ # see PEP 366 @ReservedAssignment
|
||||
if globals().get("__spec__") is not None:
|
||||
__spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
|
||||
# Remove other six meta path importers, since they cause problems. This can
|
||||
# happen if six is removed from sys.modules and then reloaded. (Setuptools does
|
||||
# this for some reason.)
|
||||
if sys.meta_path:
|
||||
for i, importer in enumerate(sys.meta_path):
|
||||
# Here's some real nastiness: Another "instance" of the six module might
|
||||
# be floating around. Therefore, we can't use isinstance() to check for
|
||||
# the six meta path importer, since the other six instance will have
|
||||
# inserted an importer with different class.
|
||||
if (type(importer).__name__ == "_SixMetaPathImporter" and
|
||||
importer.name == __name__):
|
||||
del sys.meta_path[i]
|
||||
break
|
||||
del i, importer
|
||||
# Finally, add the importer to the meta path import hook.
|
||||
sys.meta_path.append(_importer)
|
||||
@@ -1,4 +0,0 @@
|
||||
packaging==16.8
|
||||
pyparsing==2.1.10
|
||||
six==1.10.0
|
||||
appdirs==1.4.0
|
||||
@@ -1,401 +0,0 @@
|
||||
Pluggable Distributions of Python Software
|
||||
==========================================
|
||||
|
||||
Distributions
|
||||
-------------
|
||||
|
||||
A "Distribution" is a collection of files that represent a "Release" of a
|
||||
"Project" as of a particular point in time, denoted by a
|
||||
"Version"::
|
||||
|
||||
>>> import sys, pkg_resources
|
||||
>>> from pkg_resources import Distribution
|
||||
>>> Distribution(project_name="Foo", version="1.2")
|
||||
Foo 1.2
|
||||
|
||||
Distributions have a location, which can be a filename, URL, or really anything
|
||||
else you care to use::
|
||||
|
||||
>>> dist = Distribution(
|
||||
... location="http://example.com/something",
|
||||
... project_name="Bar", version="0.9"
|
||||
... )
|
||||
|
||||
>>> dist
|
||||
Bar 0.9 (http://example.com/something)
|
||||
|
||||
|
||||
Distributions have various introspectable attributes::
|
||||
|
||||
>>> dist.location
|
||||
'http://example.com/something'
|
||||
|
||||
>>> dist.project_name
|
||||
'Bar'
|
||||
|
||||
>>> dist.version
|
||||
'0.9'
|
||||
|
||||
>>> dist.py_version == sys.version[:3]
|
||||
True
|
||||
|
||||
>>> print(dist.platform)
|
||||
None
|
||||
|
||||
Including various computed attributes::
|
||||
|
||||
>>> from pkg_resources import parse_version
|
||||
>>> dist.parsed_version == parse_version(dist.version)
|
||||
True
|
||||
|
||||
>>> dist.key # case-insensitive form of the project name
|
||||
'bar'
|
||||
|
||||
Distributions are compared (and hashed) by version first::
|
||||
|
||||
>>> Distribution(version='1.0') == Distribution(version='1.0')
|
||||
True
|
||||
>>> Distribution(version='1.0') == Distribution(version='1.1')
|
||||
False
|
||||
>>> Distribution(version='1.0') < Distribution(version='1.1')
|
||||
True
|
||||
|
||||
but also by project name (case-insensitive), platform, Python version,
|
||||
location, etc.::
|
||||
|
||||
>>> Distribution(project_name="Foo",version="1.0") == \
|
||||
... Distribution(project_name="Foo",version="1.0")
|
||||
True
|
||||
|
||||
>>> Distribution(project_name="Foo",version="1.0") == \
|
||||
... Distribution(project_name="foo",version="1.0")
|
||||
True
|
||||
|
||||
>>> Distribution(project_name="Foo",version="1.0") == \
|
||||
... Distribution(project_name="Foo",version="1.1")
|
||||
False
|
||||
|
||||
>>> Distribution(project_name="Foo",py_version="2.3",version="1.0") == \
|
||||
... Distribution(project_name="Foo",py_version="2.4",version="1.0")
|
||||
False
|
||||
|
||||
>>> Distribution(location="spam",version="1.0") == \
|
||||
... Distribution(location="spam",version="1.0")
|
||||
True
|
||||
|
||||
>>> Distribution(location="spam",version="1.0") == \
|
||||
... Distribution(location="baz",version="1.0")
|
||||
False
|
||||
|
||||
|
||||
|
||||
Hash and compare distribution by prio/plat
|
||||
|
||||
Get version from metadata
|
||||
provider capabilities
|
||||
egg_name()
|
||||
as_requirement()
|
||||
from_location, from_filename (w/path normalization)
|
||||
|
||||
Releases may have zero or more "Requirements", which indicate
|
||||
what releases of another project the release requires in order to
|
||||
function. A Requirement names the other project, expresses some criteria
|
||||
as to what releases of that project are acceptable, and lists any "Extras"
|
||||
that the requiring release may need from that project. (An Extra is an
|
||||
optional feature of a Release, that can only be used if its additional
|
||||
Requirements are satisfied.)
|
||||
|
||||
|
||||
|
||||
The Working Set
|
||||
---------------
|
||||
|
||||
A collection of active distributions is called a Working Set. Note that a
|
||||
Working Set can contain any importable distribution, not just pluggable ones.
|
||||
For example, the Python standard library is an importable distribution that
|
||||
will usually be part of the Working Set, even though it is not pluggable.
|
||||
Similarly, when you are doing development work on a project, the files you are
|
||||
editing are also a Distribution. (And, with a little attention to the
|
||||
directory names used, and including some additional metadata, such a
|
||||
"development distribution" can be made pluggable as well.)
|
||||
|
||||
>>> from pkg_resources import WorkingSet
|
||||
|
||||
A working set's entries are the sys.path entries that correspond to the active
|
||||
distributions. By default, the working set's entries are the items on
|
||||
``sys.path``::
|
||||
|
||||
>>> ws = WorkingSet()
|
||||
>>> ws.entries == sys.path
|
||||
True
|
||||
|
||||
But you can also create an empty working set explicitly, and add distributions
|
||||
to it::
|
||||
|
||||
>>> ws = WorkingSet([])
|
||||
>>> ws.add(dist)
|
||||
>>> ws.entries
|
||||
['http://example.com/something']
|
||||
>>> dist in ws
|
||||
True
|
||||
>>> Distribution('foo',version="") in ws
|
||||
False
|
||||
|
||||
And you can iterate over its distributions::
|
||||
|
||||
>>> list(ws)
|
||||
[Bar 0.9 (http://example.com/something)]
|
||||
|
||||
Adding the same distribution more than once is a no-op::
|
||||
|
||||
>>> ws.add(dist)
|
||||
>>> list(ws)
|
||||
[Bar 0.9 (http://example.com/something)]
|
||||
|
||||
For that matter, adding multiple distributions for the same project also does
|
||||
nothing, because a working set can only hold one active distribution per
|
||||
project -- the first one added to it::
|
||||
|
||||
>>> ws.add(
|
||||
... Distribution(
|
||||
... 'http://example.com/something', project_name="Bar",
|
||||
... version="7.2"
|
||||
... )
|
||||
... )
|
||||
>>> list(ws)
|
||||
[Bar 0.9 (http://example.com/something)]
|
||||
|
||||
You can append a path entry to a working set using ``add_entry()``::
|
||||
|
||||
>>> ws.entries
|
||||
['http://example.com/something']
|
||||
>>> ws.add_entry(pkg_resources.__file__)
|
||||
>>> ws.entries
|
||||
['http://example.com/something', '...pkg_resources...']
|
||||
|
||||
Multiple additions result in multiple entries, even if the entry is already in
|
||||
the working set (because ``sys.path`` can contain the same entry more than
|
||||
once)::
|
||||
|
||||
>>> ws.add_entry(pkg_resources.__file__)
|
||||
>>> ws.entries
|
||||
['...example.com...', '...pkg_resources...', '...pkg_resources...']
|
||||
|
||||
And you can specify the path entry a distribution was found under, using the
|
||||
optional second parameter to ``add()``::
|
||||
|
||||
>>> ws = WorkingSet([])
|
||||
>>> ws.add(dist,"foo")
|
||||
>>> ws.entries
|
||||
['foo']
|
||||
|
||||
But even if a distribution is found under multiple path entries, it still only
|
||||
shows up once when iterating the working set:
|
||||
|
||||
>>> ws.add_entry(ws.entries[0])
|
||||
>>> list(ws)
|
||||
[Bar 0.9 (http://example.com/something)]
|
||||
|
||||
You can ask a WorkingSet to ``find()`` a distribution matching a requirement::
|
||||
|
||||
>>> from pkg_resources import Requirement
|
||||
>>> print(ws.find(Requirement.parse("Foo==1.0"))) # no match, return None
|
||||
None
|
||||
|
||||
>>> ws.find(Requirement.parse("Bar==0.9")) # match, return distribution
|
||||
Bar 0.9 (http://example.com/something)
|
||||
|
||||
Note that asking for a conflicting version of a distribution already in a
|
||||
working set triggers a ``pkg_resources.VersionConflict`` error:
|
||||
|
||||
>>> try:
|
||||
... ws.find(Requirement.parse("Bar==1.0"))
|
||||
... except pkg_resources.VersionConflict as exc:
|
||||
... print(str(exc))
|
||||
... else:
|
||||
... raise AssertionError("VersionConflict was not raised")
|
||||
(Bar 0.9 (http://example.com/something), Requirement.parse('Bar==1.0'))
|
||||
|
||||
You can subscribe a callback function to receive notifications whenever a new
|
||||
distribution is added to a working set. The callback is immediately invoked
|
||||
once for each existing distribution in the working set, and then is called
|
||||
again for new distributions added thereafter::
|
||||
|
||||
>>> def added(dist): print("Added %s" % dist)
|
||||
>>> ws.subscribe(added)
|
||||
Added Bar 0.9
|
||||
>>> foo12 = Distribution(project_name="Foo", version="1.2", location="f12")
|
||||
>>> ws.add(foo12)
|
||||
Added Foo 1.2
|
||||
|
||||
Note, however, that only the first distribution added for a given project name
|
||||
will trigger a callback, even during the initial ``subscribe()`` callback::
|
||||
|
||||
>>> foo14 = Distribution(project_name="Foo", version="1.4", location="f14")
|
||||
>>> ws.add(foo14) # no callback, because Foo 1.2 is already active
|
||||
|
||||
>>> ws = WorkingSet([])
|
||||
>>> ws.add(foo12)
|
||||
>>> ws.add(foo14)
|
||||
>>> ws.subscribe(added)
|
||||
Added Foo 1.2
|
||||
|
||||
And adding a callback more than once has no effect, either::
|
||||
|
||||
>>> ws.subscribe(added) # no callbacks
|
||||
|
||||
# and no double-callbacks on subsequent additions, either
|
||||
>>> just_a_test = Distribution(project_name="JustATest", version="0.99")
|
||||
>>> ws.add(just_a_test)
|
||||
Added JustATest 0.99
|
||||
|
||||
|
||||
Finding Plugins
|
||||
---------------
|
||||
|
||||
``WorkingSet`` objects can be used to figure out what plugins in an
|
||||
``Environment`` can be loaded without any resolution errors::
|
||||
|
||||
>>> from pkg_resources import Environment
|
||||
|
||||
>>> plugins = Environment([]) # normally, a list of plugin directories
|
||||
>>> plugins.add(foo12)
|
||||
>>> plugins.add(foo14)
|
||||
>>> plugins.add(just_a_test)
|
||||
|
||||
In the simplest case, we just get the newest version of each distribution in
|
||||
the plugin environment::
|
||||
|
||||
>>> ws = WorkingSet([])
|
||||
>>> ws.find_plugins(plugins)
|
||||
([JustATest 0.99, Foo 1.4 (f14)], {})
|
||||
|
||||
But if there's a problem with a version conflict or missing requirements, the
|
||||
method falls back to older versions, and the error info dict will contain an
|
||||
exception instance for each unloadable plugin::
|
||||
|
||||
>>> ws.add(foo12) # this will conflict with Foo 1.4
|
||||
>>> ws.find_plugins(plugins)
|
||||
([JustATest 0.99, Foo 1.2 (f12)], {Foo 1.4 (f14): VersionConflict(...)})
|
||||
|
||||
But if you disallow fallbacks, the failed plugin will be skipped instead of
|
||||
trying older versions::
|
||||
|
||||
>>> ws.find_plugins(plugins, fallback=False)
|
||||
([JustATest 0.99], {Foo 1.4 (f14): VersionConflict(...)})
|
||||
|
||||
|
||||
|
||||
Platform Compatibility Rules
|
||||
----------------------------
|
||||
|
||||
On the Mac, there are potential compatibility issues for modules compiled
|
||||
on newer versions of Mac OS X than what the user is running. Additionally,
|
||||
Mac OS X will soon have two platforms to contend with: Intel and PowerPC.
|
||||
|
||||
Basic equality works as on other platforms::
|
||||
|
||||
>>> from pkg_resources import compatible_platforms as cp
|
||||
>>> reqd = 'macosx-10.4-ppc'
|
||||
>>> cp(reqd, reqd)
|
||||
True
|
||||
>>> cp("win32", reqd)
|
||||
False
|
||||
|
||||
Distributions made on other machine types are not compatible::
|
||||
|
||||
>>> cp("macosx-10.4-i386", reqd)
|
||||
False
|
||||
|
||||
Distributions made on earlier versions of the OS are compatible, as
|
||||
long as they are from the same top-level version. The patchlevel version
|
||||
number does not matter::
|
||||
|
||||
>>> cp("macosx-10.4-ppc", reqd)
|
||||
True
|
||||
>>> cp("macosx-10.3-ppc", reqd)
|
||||
True
|
||||
>>> cp("macosx-10.5-ppc", reqd)
|
||||
False
|
||||
>>> cp("macosx-9.5-ppc", reqd)
|
||||
False
|
||||
|
||||
Backwards compatibility for packages made via earlier versions of
|
||||
setuptools is provided as well::
|
||||
|
||||
>>> cp("darwin-8.2.0-Power_Macintosh", reqd)
|
||||
True
|
||||
>>> cp("darwin-7.2.0-Power_Macintosh", reqd)
|
||||
True
|
||||
>>> cp("darwin-8.2.0-Power_Macintosh", "macosx-10.3-ppc")
|
||||
False
|
||||
|
||||
|
||||
Environment Markers
|
||||
-------------------
|
||||
|
||||
>>> from pkg_resources import invalid_marker as im, evaluate_marker as em
|
||||
>>> import os
|
||||
|
||||
>>> print(im("sys_platform"))
|
||||
Invalid marker: 'sys_platform', parse error at ''
|
||||
|
||||
>>> print(im("sys_platform=="))
|
||||
Invalid marker: 'sys_platform==', parse error at ''
|
||||
|
||||
>>> print(im("sys_platform=='win32'"))
|
||||
False
|
||||
|
||||
>>> print(im("sys=='x'"))
|
||||
Invalid marker: "sys=='x'", parse error at "sys=='x'"
|
||||
|
||||
>>> print(im("(extra)"))
|
||||
Invalid marker: '(extra)', parse error at ')'
|
||||
|
||||
>>> print(im("(extra"))
|
||||
Invalid marker: '(extra', parse error at ''
|
||||
|
||||
>>> print(im("os.open('foo')=='y'"))
|
||||
Invalid marker: "os.open('foo')=='y'", parse error at 'os.open('
|
||||
|
||||
>>> print(im("'x'=='y' and os.open('foo')=='y'")) # no short-circuit!
|
||||
Invalid marker: "'x'=='y' and os.open('foo')=='y'", parse error at 'and os.o'
|
||||
|
||||
>>> print(im("'x'=='x' or os.open('foo')=='y'")) # no short-circuit!
|
||||
Invalid marker: "'x'=='x' or os.open('foo')=='y'", parse error at 'or os.op'
|
||||
|
||||
>>> print(im("'x' < 'y' < 'z'"))
|
||||
Invalid marker: "'x' < 'y' < 'z'", parse error at "< 'z'"
|
||||
|
||||
>>> print(im("r'x'=='x'"))
|
||||
Invalid marker: "r'x'=='x'", parse error at "r'x'=='x"
|
||||
|
||||
>>> print(im("'''x'''=='x'"))
|
||||
Invalid marker: "'''x'''=='x'", parse error at "'x'''=='"
|
||||
|
||||
>>> print(im('"""x"""=="x"'))
|
||||
Invalid marker: '"""x"""=="x"', parse error at '"x"""=="'
|
||||
|
||||
>>> print(im(r"x\n=='x'"))
|
||||
Invalid marker: "x\\n=='x'", parse error at "x\\n=='x'"
|
||||
|
||||
>>> print(im("os.open=='y'"))
|
||||
Invalid marker: "os.open=='y'", parse error at 'os.open='
|
||||
|
||||
>>> em("sys_platform=='win32'") == (sys.platform=='win32')
|
||||
True
|
||||
|
||||
>>> em("python_version >= '2.6'")
|
||||
True
|
||||
|
||||
>>> em("python_version > '2.5'")
|
||||
True
|
||||
|
||||
>>> im("implementation_name=='cpython'")
|
||||
False
|
||||
|
||||
>>> im("platform_python_implementation=='CPython'")
|
||||
False
|
||||
|
||||
>>> im("implementation_version=='3.5.1'")
|
||||
False
|
||||
@@ -1,73 +0,0 @@
|
||||
import sys
|
||||
|
||||
|
||||
class VendorImporter:
|
||||
"""
|
||||
A PEP 302 meta path importer for finding optionally-vendored
|
||||
or otherwise naturally-installed packages from root_name.
|
||||
"""
|
||||
|
||||
def __init__(self, root_name, vendored_names=(), vendor_pkg=None):
|
||||
self.root_name = root_name
|
||||
self.vendored_names = set(vendored_names)
|
||||
self.vendor_pkg = vendor_pkg or root_name.replace('extern', '_vendor')
|
||||
|
||||
@property
|
||||
def search_path(self):
|
||||
"""
|
||||
Search first the vendor package then as a natural package.
|
||||
"""
|
||||
yield self.vendor_pkg + '.'
|
||||
yield ''
|
||||
|
||||
def find_module(self, fullname, path=None):
|
||||
"""
|
||||
Return self when fullname starts with root_name and the
|
||||
target module is one vendored through this importer.
|
||||
"""
|
||||
root, base, target = fullname.partition(self.root_name + '.')
|
||||
if root:
|
||||
return
|
||||
if not any(map(target.startswith, self.vendored_names)):
|
||||
return
|
||||
return self
|
||||
|
||||
def load_module(self, fullname):
|
||||
"""
|
||||
Iterate over the search path to locate and load fullname.
|
||||
"""
|
||||
root, base, target = fullname.partition(self.root_name + '.')
|
||||
for prefix in self.search_path:
|
||||
try:
|
||||
extant = prefix + target
|
||||
__import__(extant)
|
||||
mod = sys.modules[extant]
|
||||
sys.modules[fullname] = mod
|
||||
# mysterious hack:
|
||||
# Remove the reference to the extant package/module
|
||||
# on later Python versions to cause relative imports
|
||||
# in the vendor package to resolve the same modules
|
||||
# as those going through this importer.
|
||||
if sys.version_info > (3, 3):
|
||||
del sys.modules[extant]
|
||||
return mod
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
raise ImportError(
|
||||
"The '{target}' package is required; "
|
||||
"normally this is bundled with this package so if you get "
|
||||
"this warning, consult the packager of your "
|
||||
"distribution.".format(**locals())
|
||||
)
|
||||
|
||||
def install(self):
|
||||
"""
|
||||
Install this importer into sys.meta_path if not already present.
|
||||
"""
|
||||
if self not in sys.meta_path:
|
||||
sys.meta_path.append(self)
|
||||
|
||||
|
||||
names = 'packaging', 'pyparsing', 'six', 'appdirs'
|
||||
VendorImporter(__name__, names).install()
|
||||
@@ -1,22 +0,0 @@
|
||||
import os
|
||||
import errno
|
||||
import sys
|
||||
|
||||
|
||||
def _makedirs_31(path, exist_ok=False):
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except OSError as exc:
|
||||
if not exist_ok or exc.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
|
||||
# rely on compatibility behavior until mode considerations
|
||||
# and exists_ok considerations are disentangled.
|
||||
# See https://github.com/pypa/setuptools/pull/1083#issuecomment-315168663
|
||||
needs_makedirs = (
|
||||
sys.version_info < (3, 2, 5) or
|
||||
(3, 3) <= sys.version_info < (3, 3, 6) or
|
||||
(3, 4) <= sys.version_info < (3, 4, 1)
|
||||
)
|
||||
makedirs = _makedirs_31 if needs_makedirs else os.makedirs
|
||||
@@ -1,65 +0,0 @@
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
import pkg_resources
|
||||
|
||||
SETUP_TEMPLATE = """
|
||||
import setuptools
|
||||
setuptools.setup(
|
||||
name="my-test-package",
|
||||
version="1.0",
|
||||
zip_safe=True,
|
||||
)
|
||||
""".lstrip()
|
||||
|
||||
class TestFindDistributions:
|
||||
|
||||
@pytest.fixture
|
||||
def target_dir(self, tmpdir):
|
||||
target_dir = tmpdir.mkdir('target')
|
||||
# place a .egg named directory in the target that is not an egg:
|
||||
target_dir.mkdir('not.an.egg')
|
||||
return str(target_dir)
|
||||
|
||||
@pytest.fixture
|
||||
def project_dir(self, tmpdir):
|
||||
project_dir = tmpdir.mkdir('my-test-package')
|
||||
(project_dir / "setup.py").write(SETUP_TEMPLATE)
|
||||
return str(project_dir)
|
||||
|
||||
def test_non_egg_dir_named_egg(self, target_dir):
|
||||
dists = pkg_resources.find_distributions(target_dir)
|
||||
assert not list(dists)
|
||||
|
||||
def test_standalone_egg_directory(self, project_dir, target_dir):
|
||||
# install this distro as an unpacked egg:
|
||||
args = [
|
||||
sys.executable,
|
||||
'-c', 'from setuptools.command.easy_install import main; main()',
|
||||
'-mNx',
|
||||
'-d', target_dir,
|
||||
'--always-unzip',
|
||||
project_dir,
|
||||
]
|
||||
subprocess.check_call(args)
|
||||
dists = pkg_resources.find_distributions(target_dir)
|
||||
assert [dist.project_name for dist in dists] == ['my-test-package']
|
||||
dists = pkg_resources.find_distributions(target_dir, only=True)
|
||||
assert not list(dists)
|
||||
|
||||
def test_zipped_egg(self, project_dir, target_dir):
|
||||
# install this distro as an unpacked egg:
|
||||
args = [
|
||||
sys.executable,
|
||||
'-c', 'from setuptools.command.easy_install import main; main()',
|
||||
'-mNx',
|
||||
'-d', target_dir,
|
||||
'--zip-ok',
|
||||
project_dir,
|
||||
]
|
||||
subprocess.check_call(args)
|
||||
dists = pkg_resources.find_distributions(target_dir)
|
||||
assert [dist.project_name for dist in dists] == ['my-test-package']
|
||||
dists = pkg_resources.find_distributions(target_dir, only=True)
|
||||
assert not list(dists)
|
||||
@@ -1,8 +0,0 @@
|
||||
import mock
|
||||
|
||||
from pkg_resources import evaluate_marker
|
||||
|
||||
|
||||
@mock.patch('platform.python_version', return_value='2.7.10')
|
||||
def test_ordering(python_version_mock):
|
||||
assert evaluate_marker("python_full_version > '2.7.3'") is True
|
||||
@@ -1,172 +0,0 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
import tempfile
|
||||
import os
|
||||
import zipfile
|
||||
import datetime
|
||||
import time
|
||||
import subprocess
|
||||
import stat
|
||||
import distutils.dist
|
||||
import distutils.command.install_egg_info
|
||||
|
||||
from pkg_resources.extern.six.moves import map
|
||||
|
||||
import pytest
|
||||
|
||||
import pkg_resources
|
||||
|
||||
try:
|
||||
unicode
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
|
||||
def timestamp(dt):
|
||||
"""
|
||||
Return a timestamp for a local, naive datetime instance.
|
||||
"""
|
||||
try:
|
||||
return dt.timestamp()
|
||||
except AttributeError:
|
||||
# Python 3.2 and earlier
|
||||
return time.mktime(dt.timetuple())
|
||||
|
||||
|
||||
class EggRemover(unicode):
|
||||
def __call__(self):
|
||||
if self in sys.path:
|
||||
sys.path.remove(self)
|
||||
if os.path.exists(self):
|
||||
os.remove(self)
|
||||
|
||||
|
||||
class TestZipProvider(object):
|
||||
finalizers = []
|
||||
|
||||
ref_time = datetime.datetime(2013, 5, 12, 13, 25, 0)
|
||||
"A reference time for a file modification"
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
"create a zip egg and add it to sys.path"
|
||||
egg = tempfile.NamedTemporaryFile(suffix='.egg', delete=False)
|
||||
zip_egg = zipfile.ZipFile(egg, 'w')
|
||||
zip_info = zipfile.ZipInfo()
|
||||
zip_info.filename = 'mod.py'
|
||||
zip_info.date_time = cls.ref_time.timetuple()
|
||||
zip_egg.writestr(zip_info, 'x = 3\n')
|
||||
zip_info = zipfile.ZipInfo()
|
||||
zip_info.filename = 'data.dat'
|
||||
zip_info.date_time = cls.ref_time.timetuple()
|
||||
zip_egg.writestr(zip_info, 'hello, world!')
|
||||
zip_egg.close()
|
||||
egg.close()
|
||||
|
||||
sys.path.append(egg.name)
|
||||
cls.finalizers.append(EggRemover(egg.name))
|
||||
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
for finalizer in cls.finalizers:
|
||||
finalizer()
|
||||
|
||||
def test_resource_filename_rewrites_on_change(self):
|
||||
"""
|
||||
If a previous call to get_resource_filename has saved the file, but
|
||||
the file has been subsequently mutated with different file of the
|
||||
same size and modification time, it should not be overwritten on a
|
||||
subsequent call to get_resource_filename.
|
||||
"""
|
||||
import mod
|
||||
manager = pkg_resources.ResourceManager()
|
||||
zp = pkg_resources.ZipProvider(mod)
|
||||
filename = zp.get_resource_filename(manager, 'data.dat')
|
||||
actual = datetime.datetime.fromtimestamp(os.stat(filename).st_mtime)
|
||||
assert actual == self.ref_time
|
||||
f = open(filename, 'w')
|
||||
f.write('hello, world?')
|
||||
f.close()
|
||||
ts = timestamp(self.ref_time)
|
||||
os.utime(filename, (ts, ts))
|
||||
filename = zp.get_resource_filename(manager, 'data.dat')
|
||||
f = open(filename)
|
||||
assert f.read() == 'hello, world!'
|
||||
manager.cleanup_resources()
|
||||
|
||||
|
||||
class TestResourceManager(object):
|
||||
def test_get_cache_path(self):
|
||||
mgr = pkg_resources.ResourceManager()
|
||||
path = mgr.get_cache_path('foo')
|
||||
type_ = str(type(path))
|
||||
message = "Unexpected type from get_cache_path: " + type_
|
||||
assert isinstance(path, (unicode, str)), message
|
||||
|
||||
|
||||
class TestIndependence:
|
||||
"""
|
||||
Tests to ensure that pkg_resources runs independently from setuptools.
|
||||
"""
|
||||
|
||||
def test_setuptools_not_imported(self):
|
||||
"""
|
||||
In a separate Python environment, import pkg_resources and assert
|
||||
that action doesn't cause setuptools to be imported.
|
||||
"""
|
||||
lines = (
|
||||
'import pkg_resources',
|
||||
'import sys',
|
||||
'assert "setuptools" not in sys.modules, '
|
||||
'"setuptools was imported"',
|
||||
)
|
||||
cmd = [sys.executable, '-c', '; '.join(lines)]
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
class TestDeepVersionLookupDistutils(object):
|
||||
@pytest.fixture
|
||||
def env(self, tmpdir):
|
||||
"""
|
||||
Create a package environment, similar to a virtualenv,
|
||||
in which packages are installed.
|
||||
"""
|
||||
|
||||
class Environment(str):
|
||||
pass
|
||||
|
||||
env = Environment(tmpdir)
|
||||
tmpdir.chmod(stat.S_IRWXU)
|
||||
subs = 'home', 'lib', 'scripts', 'data', 'egg-base'
|
||||
env.paths = dict(
|
||||
(dirname, str(tmpdir / dirname))
|
||||
for dirname in subs
|
||||
)
|
||||
list(map(os.mkdir, env.paths.values()))
|
||||
return env
|
||||
|
||||
def create_foo_pkg(self, env, version):
|
||||
"""
|
||||
Create a foo package installed (distutils-style) to env.paths['lib']
|
||||
as version.
|
||||
"""
|
||||
ld = "This package has unicode metadata! ❄"
|
||||
attrs = dict(name='foo', version=version, long_description=ld)
|
||||
dist = distutils.dist.Distribution(attrs)
|
||||
iei_cmd = distutils.command.install_egg_info.install_egg_info(dist)
|
||||
iei_cmd.initialize_options()
|
||||
iei_cmd.install_dir = env.paths['lib']
|
||||
iei_cmd.finalize_options()
|
||||
iei_cmd.run()
|
||||
|
||||
def test_version_resolved_from_egg_info(self, env):
|
||||
version = '1.11.0.dev0+2329eae'
|
||||
self.create_foo_pkg(env, version)
|
||||
|
||||
# this requirement parsing will raise a VersionConflict unless the
|
||||
# .egg-info file is parsed (see #419 on BitBucket)
|
||||
req = pkg_resources.Requirement.parse('foo>=1.9')
|
||||
dist = pkg_resources.WorkingSet([env.paths['lib']]).find(req)
|
||||
assert dist.version == version
|
||||
@@ -1,858 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import sys
|
||||
import string
|
||||
import platform
|
||||
|
||||
from pkg_resources.extern.six.moves import map
|
||||
|
||||
import pytest
|
||||
from pkg_resources.extern import packaging
|
||||
|
||||
import pkg_resources
|
||||
from pkg_resources import (parse_requirements, VersionConflict, parse_version,
|
||||
Distribution, EntryPoint, Requirement, safe_version, safe_name,
|
||||
WorkingSet)
|
||||
|
||||
|
||||
class Metadata(pkg_resources.EmptyProvider):
|
||||
"""Mock object to return metadata as if from an on-disk distribution"""
|
||||
|
||||
def __init__(self, *pairs):
|
||||
self.metadata = dict(pairs)
|
||||
|
||||
def has_metadata(self, name):
|
||||
return name in self.metadata
|
||||
|
||||
def get_metadata(self, name):
|
||||
return self.metadata[name]
|
||||
|
||||
def get_metadata_lines(self, name):
|
||||
return pkg_resources.yield_lines(self.get_metadata(name))
|
||||
|
||||
|
||||
dist_from_fn = pkg_resources.Distribution.from_filename
|
||||
|
||||
|
||||
class TestDistro:
|
||||
def testCollection(self):
|
||||
# empty path should produce no distributions
|
||||
ad = pkg_resources.Environment([], platform=None, python=None)
|
||||
assert list(ad) == []
|
||||
assert ad['FooPkg'] == []
|
||||
ad.add(dist_from_fn("FooPkg-1.3_1.egg"))
|
||||
ad.add(dist_from_fn("FooPkg-1.4-py2.4-win32.egg"))
|
||||
ad.add(dist_from_fn("FooPkg-1.2-py2.4.egg"))
|
||||
|
||||
# Name is in there now
|
||||
assert ad['FooPkg']
|
||||
# But only 1 package
|
||||
assert list(ad) == ['foopkg']
|
||||
|
||||
# Distributions sort by version
|
||||
assert [dist.version for dist in ad['FooPkg']] == ['1.4', '1.3-1', '1.2']
|
||||
|
||||
# Removing a distribution leaves sequence alone
|
||||
ad.remove(ad['FooPkg'][1])
|
||||
assert [dist.version for dist in ad['FooPkg']] == ['1.4', '1.2']
|
||||
|
||||
# And inserting adds them in order
|
||||
ad.add(dist_from_fn("FooPkg-1.9.egg"))
|
||||
assert [dist.version for dist in ad['FooPkg']] == ['1.9', '1.4', '1.2']
|
||||
|
||||
ws = WorkingSet([])
|
||||
foo12 = dist_from_fn("FooPkg-1.2-py2.4.egg")
|
||||
foo14 = dist_from_fn("FooPkg-1.4-py2.4-win32.egg")
|
||||
req, = parse_requirements("FooPkg>=1.3")
|
||||
|
||||
# Nominal case: no distros on path, should yield all applicable
|
||||
assert ad.best_match(req, ws).version == '1.9'
|
||||
# If a matching distro is already installed, should return only that
|
||||
ws.add(foo14)
|
||||
assert ad.best_match(req, ws).version == '1.4'
|
||||
|
||||
# If the first matching distro is unsuitable, it's a version conflict
|
||||
ws = WorkingSet([])
|
||||
ws.add(foo12)
|
||||
ws.add(foo14)
|
||||
with pytest.raises(VersionConflict):
|
||||
ad.best_match(req, ws)
|
||||
|
||||
# If more than one match on the path, the first one takes precedence
|
||||
ws = WorkingSet([])
|
||||
ws.add(foo14)
|
||||
ws.add(foo12)
|
||||
ws.add(foo14)
|
||||
assert ad.best_match(req, ws).version == '1.4'
|
||||
|
||||
def checkFooPkg(self, d):
|
||||
assert d.project_name == "FooPkg"
|
||||
assert d.key == "foopkg"
|
||||
assert d.version == "1.3.post1"
|
||||
assert d.py_version == "2.4"
|
||||
assert d.platform == "win32"
|
||||
assert d.parsed_version == parse_version("1.3-1")
|
||||
|
||||
def testDistroBasics(self):
|
||||
d = Distribution(
|
||||
"/some/path",
|
||||
project_name="FooPkg", version="1.3-1", py_version="2.4", platform="win32"
|
||||
)
|
||||
self.checkFooPkg(d)
|
||||
|
||||
d = Distribution("/some/path")
|
||||
assert d.py_version == sys.version[:3]
|
||||
assert d.platform is None
|
||||
|
||||
def testDistroParse(self):
|
||||
d = dist_from_fn("FooPkg-1.3.post1-py2.4-win32.egg")
|
||||
self.checkFooPkg(d)
|
||||
d = dist_from_fn("FooPkg-1.3.post1-py2.4-win32.egg-info")
|
||||
self.checkFooPkg(d)
|
||||
|
||||
def testDistroMetadata(self):
|
||||
d = Distribution(
|
||||
"/some/path", project_name="FooPkg", py_version="2.4", platform="win32",
|
||||
metadata=Metadata(
|
||||
('PKG-INFO', "Metadata-Version: 1.0\nVersion: 1.3-1\n")
|
||||
)
|
||||
)
|
||||
self.checkFooPkg(d)
|
||||
|
||||
def distRequires(self, txt):
|
||||
return Distribution("/foo", metadata=Metadata(('depends.txt', txt)))
|
||||
|
||||
def checkRequires(self, dist, txt, extras=()):
|
||||
assert list(dist.requires(extras)) == list(parse_requirements(txt))
|
||||
|
||||
def testDistroDependsSimple(self):
|
||||
for v in "Twisted>=1.5", "Twisted>=1.5\nZConfig>=2.0":
|
||||
self.checkRequires(self.distRequires(v), v)
|
||||
|
||||
def testResolve(self):
|
||||
ad = pkg_resources.Environment([])
|
||||
ws = WorkingSet([])
|
||||
# Resolving no requirements -> nothing to install
|
||||
assert list(ws.resolve([], ad)) == []
|
||||
# Request something not in the collection -> DistributionNotFound
|
||||
with pytest.raises(pkg_resources.DistributionNotFound):
|
||||
ws.resolve(parse_requirements("Foo"), ad)
|
||||
|
||||
Foo = Distribution.from_filename(
|
||||
"/foo_dir/Foo-1.2.egg",
|
||||
metadata=Metadata(('depends.txt', "[bar]\nBaz>=2.0"))
|
||||
)
|
||||
ad.add(Foo)
|
||||
ad.add(Distribution.from_filename("Foo-0.9.egg"))
|
||||
|
||||
# Request thing(s) that are available -> list to activate
|
||||
for i in range(3):
|
||||
targets = list(ws.resolve(parse_requirements("Foo"), ad))
|
||||
assert targets == [Foo]
|
||||
list(map(ws.add, targets))
|
||||
with pytest.raises(VersionConflict):
|
||||
ws.resolve(parse_requirements("Foo==0.9"), ad)
|
||||
ws = WorkingSet([]) # reset
|
||||
|
||||
# Request an extra that causes an unresolved dependency for "Baz"
|
||||
with pytest.raises(pkg_resources.DistributionNotFound):
|
||||
ws.resolve(parse_requirements("Foo[bar]"), ad)
|
||||
Baz = Distribution.from_filename(
|
||||
"/foo_dir/Baz-2.1.egg", metadata=Metadata(('depends.txt', "Foo"))
|
||||
)
|
||||
ad.add(Baz)
|
||||
|
||||
# Activation list now includes resolved dependency
|
||||
assert list(ws.resolve(parse_requirements("Foo[bar]"), ad)) == [Foo, Baz]
|
||||
# Requests for conflicting versions produce VersionConflict
|
||||
with pytest.raises(VersionConflict) as vc:
|
||||
ws.resolve(parse_requirements("Foo==1.2\nFoo!=1.2"), ad)
|
||||
|
||||
msg = 'Foo 0.9 is installed but Foo==1.2 is required'
|
||||
assert vc.value.report() == msg
|
||||
|
||||
def test_environment_marker_evaluation_negative(self):
|
||||
"""Environment markers are evaluated at resolution time."""
|
||||
ad = pkg_resources.Environment([])
|
||||
ws = WorkingSet([])
|
||||
res = ws.resolve(parse_requirements("Foo;python_version<'2'"), ad)
|
||||
assert list(res) == []
|
||||
|
||||
def test_environment_marker_evaluation_positive(self):
|
||||
ad = pkg_resources.Environment([])
|
||||
ws = WorkingSet([])
|
||||
Foo = Distribution.from_filename("/foo_dir/Foo-1.2.dist-info")
|
||||
ad.add(Foo)
|
||||
res = ws.resolve(parse_requirements("Foo;python_version>='2'"), ad)
|
||||
assert list(res) == [Foo]
|
||||
|
||||
def test_environment_marker_evaluation_called(self):
|
||||
"""
|
||||
If one package foo requires bar without any extras,
|
||||
markers should pass for bar without extras.
|
||||
"""
|
||||
parent_req, = parse_requirements("foo")
|
||||
req, = parse_requirements("bar;python_version>='2'")
|
||||
req_extras = pkg_resources._ReqExtras({req: parent_req.extras})
|
||||
assert req_extras.markers_pass(req)
|
||||
|
||||
parent_req, = parse_requirements("foo[]")
|
||||
req, = parse_requirements("bar;python_version>='2'")
|
||||
req_extras = pkg_resources._ReqExtras({req: parent_req.extras})
|
||||
assert req_extras.markers_pass(req)
|
||||
|
||||
def test_marker_evaluation_with_extras(self):
|
||||
"""Extras are also evaluated as markers at resolution time."""
|
||||
ad = pkg_resources.Environment([])
|
||||
ws = WorkingSet([])
|
||||
# Metadata needs to be native strings due to cStringIO behaviour in
|
||||
# 2.6, so use str().
|
||||
Foo = Distribution.from_filename(
|
||||
"/foo_dir/Foo-1.2.dist-info",
|
||||
metadata=Metadata(("METADATA", str("Provides-Extra: baz\n"
|
||||
"Requires-Dist: quux; extra=='baz'")))
|
||||
)
|
||||
ad.add(Foo)
|
||||
assert list(ws.resolve(parse_requirements("Foo"), ad)) == [Foo]
|
||||
quux = Distribution.from_filename("/foo_dir/quux-1.0.dist-info")
|
||||
ad.add(quux)
|
||||
res = list(ws.resolve(parse_requirements("Foo[baz]"), ad))
|
||||
assert res == [Foo, quux]
|
||||
|
||||
def test_marker_evaluation_with_extras_normlized(self):
|
||||
"""Extras are also evaluated as markers at resolution time."""
|
||||
ad = pkg_resources.Environment([])
|
||||
ws = WorkingSet([])
|
||||
# Metadata needs to be native strings due to cStringIO behaviour in
|
||||
# 2.6, so use str().
|
||||
Foo = Distribution.from_filename(
|
||||
"/foo_dir/Foo-1.2.dist-info",
|
||||
metadata=Metadata(("METADATA", str("Provides-Extra: baz-lightyear\n"
|
||||
"Requires-Dist: quux; extra=='baz-lightyear'")))
|
||||
)
|
||||
ad.add(Foo)
|
||||
assert list(ws.resolve(parse_requirements("Foo"), ad)) == [Foo]
|
||||
quux = Distribution.from_filename("/foo_dir/quux-1.0.dist-info")
|
||||
ad.add(quux)
|
||||
res = list(ws.resolve(parse_requirements("Foo[baz-lightyear]"), ad))
|
||||
assert res == [Foo, quux]
|
||||
|
||||
def test_marker_evaluation_with_multiple_extras(self):
|
||||
ad = pkg_resources.Environment([])
|
||||
ws = WorkingSet([])
|
||||
# Metadata needs to be native strings due to cStringIO behaviour in
|
||||
# 2.6, so use str().
|
||||
Foo = Distribution.from_filename(
|
||||
"/foo_dir/Foo-1.2.dist-info",
|
||||
metadata=Metadata(("METADATA", str("Provides-Extra: baz\n"
|
||||
"Requires-Dist: quux; extra=='baz'\n"
|
||||
"Provides-Extra: bar\n"
|
||||
"Requires-Dist: fred; extra=='bar'\n")))
|
||||
)
|
||||
ad.add(Foo)
|
||||
quux = Distribution.from_filename("/foo_dir/quux-1.0.dist-info")
|
||||
ad.add(quux)
|
||||
fred = Distribution.from_filename("/foo_dir/fred-0.1.dist-info")
|
||||
ad.add(fred)
|
||||
res = list(ws.resolve(parse_requirements("Foo[baz,bar]"), ad))
|
||||
assert sorted(res) == [fred, quux, Foo]
|
||||
|
||||
def test_marker_evaluation_with_extras_loop(self):
|
||||
ad = pkg_resources.Environment([])
|
||||
ws = WorkingSet([])
|
||||
# Metadata needs to be native strings due to cStringIO behaviour in
|
||||
# 2.6, so use str().
|
||||
a = Distribution.from_filename(
|
||||
"/foo_dir/a-0.2.dist-info",
|
||||
metadata=Metadata(("METADATA", str("Requires-Dist: c[a]")))
|
||||
)
|
||||
b = Distribution.from_filename(
|
||||
"/foo_dir/b-0.3.dist-info",
|
||||
metadata=Metadata(("METADATA", str("Requires-Dist: c[b]")))
|
||||
)
|
||||
c = Distribution.from_filename(
|
||||
"/foo_dir/c-1.0.dist-info",
|
||||
metadata=Metadata(("METADATA", str("Provides-Extra: a\n"
|
||||
"Requires-Dist: b;extra=='a'\n"
|
||||
"Provides-Extra: b\n"
|
||||
"Requires-Dist: foo;extra=='b'")))
|
||||
)
|
||||
foo = Distribution.from_filename("/foo_dir/foo-0.1.dist-info")
|
||||
for dist in (a, b, c, foo):
|
||||
ad.add(dist)
|
||||
res = list(ws.resolve(parse_requirements("a"), ad))
|
||||
assert res == [a, c, b, foo]
|
||||
|
||||
def testDistroDependsOptions(self):
|
||||
d = self.distRequires("""
|
||||
Twisted>=1.5
|
||||
[docgen]
|
||||
ZConfig>=2.0
|
||||
docutils>=0.3
|
||||
[fastcgi]
|
||||
fcgiapp>=0.1""")
|
||||
self.checkRequires(d, "Twisted>=1.5")
|
||||
self.checkRequires(
|
||||
d, "Twisted>=1.5 ZConfig>=2.0 docutils>=0.3".split(), ["docgen"]
|
||||
)
|
||||
self.checkRequires(
|
||||
d, "Twisted>=1.5 fcgiapp>=0.1".split(), ["fastcgi"]
|
||||
)
|
||||
self.checkRequires(
|
||||
d, "Twisted>=1.5 ZConfig>=2.0 docutils>=0.3 fcgiapp>=0.1".split(),
|
||||
["docgen", "fastcgi"]
|
||||
)
|
||||
self.checkRequires(
|
||||
d, "Twisted>=1.5 fcgiapp>=0.1 ZConfig>=2.0 docutils>=0.3".split(),
|
||||
["fastcgi", "docgen"]
|
||||
)
|
||||
with pytest.raises(pkg_resources.UnknownExtra):
|
||||
d.requires(["foo"])
|
||||
|
||||
|
||||
class TestWorkingSet:
|
||||
def test_find_conflicting(self):
|
||||
ws = WorkingSet([])
|
||||
Foo = Distribution.from_filename("/foo_dir/Foo-1.2.egg")
|
||||
ws.add(Foo)
|
||||
|
||||
# create a requirement that conflicts with Foo 1.2
|
||||
req = next(parse_requirements("Foo<1.2"))
|
||||
|
||||
with pytest.raises(VersionConflict) as vc:
|
||||
ws.find(req)
|
||||
|
||||
msg = 'Foo 1.2 is installed but Foo<1.2 is required'
|
||||
assert vc.value.report() == msg
|
||||
|
||||
def test_resolve_conflicts_with_prior(self):
|
||||
"""
|
||||
A ContextualVersionConflict should be raised when a requirement
|
||||
conflicts with a prior requirement for a different package.
|
||||
"""
|
||||
# Create installation where Foo depends on Baz 1.0 and Bar depends on
|
||||
# Baz 2.0.
|
||||
ws = WorkingSet([])
|
||||
md = Metadata(('depends.txt', "Baz==1.0"))
|
||||
Foo = Distribution.from_filename("/foo_dir/Foo-1.0.egg", metadata=md)
|
||||
ws.add(Foo)
|
||||
md = Metadata(('depends.txt', "Baz==2.0"))
|
||||
Bar = Distribution.from_filename("/foo_dir/Bar-1.0.egg", metadata=md)
|
||||
ws.add(Bar)
|
||||
Baz = Distribution.from_filename("/foo_dir/Baz-1.0.egg")
|
||||
ws.add(Baz)
|
||||
Baz = Distribution.from_filename("/foo_dir/Baz-2.0.egg")
|
||||
ws.add(Baz)
|
||||
|
||||
with pytest.raises(VersionConflict) as vc:
|
||||
ws.resolve(parse_requirements("Foo\nBar\n"))
|
||||
|
||||
msg = "Baz 1.0 is installed but Baz==2.0 is required by "
|
||||
msg += repr(set(['Bar']))
|
||||
assert vc.value.report() == msg
|
||||
|
||||
|
||||
class TestEntryPoints:
|
||||
def assertfields(self, ep):
|
||||
assert ep.name == "foo"
|
||||
assert ep.module_name == "pkg_resources.tests.test_resources"
|
||||
assert ep.attrs == ("TestEntryPoints",)
|
||||
assert ep.extras == ("x",)
|
||||
assert ep.load() is TestEntryPoints
|
||||
expect = "foo = pkg_resources.tests.test_resources:TestEntryPoints [x]"
|
||||
assert str(ep) == expect
|
||||
|
||||
def setup_method(self, method):
|
||||
self.dist = Distribution.from_filename(
|
||||
"FooPkg-1.2-py2.4.egg", metadata=Metadata(('requires.txt', '[x]')))
|
||||
|
||||
def testBasics(self):
|
||||
ep = EntryPoint(
|
||||
"foo", "pkg_resources.tests.test_resources", ["TestEntryPoints"],
|
||||
["x"], self.dist
|
||||
)
|
||||
self.assertfields(ep)
|
||||
|
||||
def testParse(self):
|
||||
s = "foo = pkg_resources.tests.test_resources:TestEntryPoints [x]"
|
||||
ep = EntryPoint.parse(s, self.dist)
|
||||
self.assertfields(ep)
|
||||
|
||||
ep = EntryPoint.parse("bar baz= spammity[PING]")
|
||||
assert ep.name == "bar baz"
|
||||
assert ep.module_name == "spammity"
|
||||
assert ep.attrs == ()
|
||||
assert ep.extras == ("ping",)
|
||||
|
||||
ep = EntryPoint.parse(" fizzly = wocka:foo")
|
||||
assert ep.name == "fizzly"
|
||||
assert ep.module_name == "wocka"
|
||||
assert ep.attrs == ("foo",)
|
||||
assert ep.extras == ()
|
||||
|
||||
# plus in the name
|
||||
spec = "html+mako = mako.ext.pygmentplugin:MakoHtmlLexer"
|
||||
ep = EntryPoint.parse(spec)
|
||||
assert ep.name == 'html+mako'
|
||||
|
||||
reject_specs = "foo", "x=a:b:c", "q=x/na", "fez=pish:tush-z", "x=f[a]>2"
|
||||
|
||||
@pytest.mark.parametrize("reject_spec", reject_specs)
|
||||
def test_reject_spec(self, reject_spec):
|
||||
with pytest.raises(ValueError):
|
||||
EntryPoint.parse(reject_spec)
|
||||
|
||||
def test_printable_name(self):
|
||||
"""
|
||||
Allow any printable character in the name.
|
||||
"""
|
||||
# Create a name with all printable characters; strip the whitespace.
|
||||
name = string.printable.strip()
|
||||
spec = "{name} = module:attr".format(**locals())
|
||||
ep = EntryPoint.parse(spec)
|
||||
assert ep.name == name
|
||||
|
||||
def checkSubMap(self, m):
|
||||
assert len(m) == len(self.submap_expect)
|
||||
for key, ep in self.submap_expect.items():
|
||||
assert m.get(key).name == ep.name
|
||||
assert m.get(key).module_name == ep.module_name
|
||||
assert sorted(m.get(key).attrs) == sorted(ep.attrs)
|
||||
assert sorted(m.get(key).extras) == sorted(ep.extras)
|
||||
|
||||
submap_expect = dict(
|
||||
feature1=EntryPoint('feature1', 'somemodule', ['somefunction']),
|
||||
feature2=EntryPoint('feature2', 'another.module', ['SomeClass'], ['extra1', 'extra2']),
|
||||
feature3=EntryPoint('feature3', 'this.module', extras=['something'])
|
||||
)
|
||||
submap_str = """
|
||||
# define features for blah blah
|
||||
feature1 = somemodule:somefunction
|
||||
feature2 = another.module:SomeClass [extra1,extra2]
|
||||
feature3 = this.module [something]
|
||||
"""
|
||||
|
||||
def testParseList(self):
|
||||
self.checkSubMap(EntryPoint.parse_group("xyz", self.submap_str))
|
||||
with pytest.raises(ValueError):
|
||||
EntryPoint.parse_group("x a", "foo=bar")
|
||||
with pytest.raises(ValueError):
|
||||
EntryPoint.parse_group("x", ["foo=baz", "foo=bar"])
|
||||
|
||||
def testParseMap(self):
|
||||
m = EntryPoint.parse_map({'xyz': self.submap_str})
|
||||
self.checkSubMap(m['xyz'])
|
||||
assert list(m.keys()) == ['xyz']
|
||||
m = EntryPoint.parse_map("[xyz]\n" + self.submap_str)
|
||||
self.checkSubMap(m['xyz'])
|
||||
assert list(m.keys()) == ['xyz']
|
||||
with pytest.raises(ValueError):
|
||||
EntryPoint.parse_map(["[xyz]", "[xyz]"])
|
||||
with pytest.raises(ValueError):
|
||||
EntryPoint.parse_map(self.submap_str)
|
||||
|
||||
|
||||
class TestRequirements:
|
||||
def testBasics(self):
|
||||
r = Requirement.parse("Twisted>=1.2")
|
||||
assert str(r) == "Twisted>=1.2"
|
||||
assert repr(r) == "Requirement.parse('Twisted>=1.2')"
|
||||
assert r == Requirement("Twisted>=1.2")
|
||||
assert r == Requirement("twisTed>=1.2")
|
||||
assert r != Requirement("Twisted>=2.0")
|
||||
assert r != Requirement("Zope>=1.2")
|
||||
assert r != Requirement("Zope>=3.0")
|
||||
assert r != Requirement("Twisted[extras]>=1.2")
|
||||
|
||||
def testOrdering(self):
|
||||
r1 = Requirement("Twisted==1.2c1,>=1.2")
|
||||
r2 = Requirement("Twisted>=1.2,==1.2c1")
|
||||
assert r1 == r2
|
||||
assert str(r1) == str(r2)
|
||||
assert str(r2) == "Twisted==1.2c1,>=1.2"
|
||||
|
||||
def testBasicContains(self):
|
||||
r = Requirement("Twisted>=1.2")
|
||||
foo_dist = Distribution.from_filename("FooPkg-1.3_1.egg")
|
||||
twist11 = Distribution.from_filename("Twisted-1.1.egg")
|
||||
twist12 = Distribution.from_filename("Twisted-1.2.egg")
|
||||
assert parse_version('1.2') in r
|
||||
assert parse_version('1.1') not in r
|
||||
assert '1.2' in r
|
||||
assert '1.1' not in r
|
||||
assert foo_dist not in r
|
||||
assert twist11 not in r
|
||||
assert twist12 in r
|
||||
|
||||
def testOptionsAndHashing(self):
|
||||
r1 = Requirement.parse("Twisted[foo,bar]>=1.2")
|
||||
r2 = Requirement.parse("Twisted[bar,FOO]>=1.2")
|
||||
assert r1 == r2
|
||||
assert set(r1.extras) == set(("foo", "bar"))
|
||||
assert set(r2.extras) == set(("foo", "bar"))
|
||||
assert hash(r1) == hash(r2)
|
||||
assert (
|
||||
hash(r1)
|
||||
==
|
||||
hash((
|
||||
"twisted",
|
||||
packaging.specifiers.SpecifierSet(">=1.2"),
|
||||
frozenset(["foo", "bar"]),
|
||||
None
|
||||
))
|
||||
)
|
||||
|
||||
def testVersionEquality(self):
|
||||
r1 = Requirement.parse("foo==0.3a2")
|
||||
r2 = Requirement.parse("foo!=0.3a4")
|
||||
d = Distribution.from_filename
|
||||
|
||||
assert d("foo-0.3a4.egg") not in r1
|
||||
assert d("foo-0.3a1.egg") not in r1
|
||||
assert d("foo-0.3a4.egg") not in r2
|
||||
|
||||
assert d("foo-0.3a2.egg") in r1
|
||||
assert d("foo-0.3a2.egg") in r2
|
||||
assert d("foo-0.3a3.egg") in r2
|
||||
assert d("foo-0.3a5.egg") in r2
|
||||
|
||||
def testSetuptoolsProjectName(self):
|
||||
"""
|
||||
The setuptools project should implement the setuptools package.
|
||||
"""
|
||||
|
||||
assert (
|
||||
Requirement.parse('setuptools').project_name == 'setuptools')
|
||||
# setuptools 0.7 and higher means setuptools.
|
||||
assert (
|
||||
Requirement.parse('setuptools == 0.7').project_name == 'setuptools')
|
||||
assert (
|
||||
Requirement.parse('setuptools == 0.7a1').project_name == 'setuptools')
|
||||
assert (
|
||||
Requirement.parse('setuptools >= 0.7').project_name == 'setuptools')
|
||||
|
||||
|
||||
class TestParsing:
|
||||
def testEmptyParse(self):
|
||||
assert list(parse_requirements('')) == []
|
||||
|
||||
def testYielding(self):
|
||||
for inp, out in [
|
||||
([], []), ('x', ['x']), ([[]], []), (' x\n y', ['x', 'y']),
|
||||
(['x\n\n', 'y'], ['x', 'y']),
|
||||
]:
|
||||
assert list(pkg_resources.yield_lines(inp)) == out
|
||||
|
||||
def testSplitting(self):
|
||||
sample = """
|
||||
x
|
||||
[Y]
|
||||
z
|
||||
|
||||
a
|
||||
[b ]
|
||||
# foo
|
||||
c
|
||||
[ d]
|
||||
[q]
|
||||
v
|
||||
"""
|
||||
assert (
|
||||
list(pkg_resources.split_sections(sample))
|
||||
==
|
||||
[
|
||||
(None, ["x"]),
|
||||
("Y", ["z", "a"]),
|
||||
("b", ["c"]),
|
||||
("d", []),
|
||||
("q", ["v"]),
|
||||
]
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
list(pkg_resources.split_sections("[foo"))
|
||||
|
||||
def testSafeName(self):
|
||||
assert safe_name("adns-python") == "adns-python"
|
||||
assert safe_name("WSGI Utils") == "WSGI-Utils"
|
||||
assert safe_name("WSGI Utils") == "WSGI-Utils"
|
||||
assert safe_name("Money$$$Maker") == "Money-Maker"
|
||||
assert safe_name("peak.web") != "peak-web"
|
||||
|
||||
def testSafeVersion(self):
|
||||
assert safe_version("1.2-1") == "1.2.post1"
|
||||
assert safe_version("1.2 alpha") == "1.2.alpha"
|
||||
assert safe_version("2.3.4 20050521") == "2.3.4.20050521"
|
||||
assert safe_version("Money$$$Maker") == "Money-Maker"
|
||||
assert safe_version("peak.web") == "peak.web"
|
||||
|
||||
def testSimpleRequirements(self):
|
||||
assert (
|
||||
list(parse_requirements('Twis-Ted>=1.2-1'))
|
||||
==
|
||||
[Requirement('Twis-Ted>=1.2-1')]
|
||||
)
|
||||
assert (
|
||||
list(parse_requirements('Twisted >=1.2, \\ # more\n<2.0'))
|
||||
==
|
||||
[Requirement('Twisted>=1.2,<2.0')]
|
||||
)
|
||||
assert (
|
||||
Requirement.parse("FooBar==1.99a3")
|
||||
==
|
||||
Requirement("FooBar==1.99a3")
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
Requirement.parse(">=2.3")
|
||||
with pytest.raises(ValueError):
|
||||
Requirement.parse("x\\")
|
||||
with pytest.raises(ValueError):
|
||||
Requirement.parse("x==2 q")
|
||||
with pytest.raises(ValueError):
|
||||
Requirement.parse("X==1\nY==2")
|
||||
with pytest.raises(ValueError):
|
||||
Requirement.parse("#")
|
||||
|
||||
def test_requirements_with_markers(self):
|
||||
assert (
|
||||
Requirement.parse("foobar;os_name=='a'")
|
||||
==
|
||||
Requirement.parse("foobar;os_name=='a'")
|
||||
)
|
||||
assert (
|
||||
Requirement.parse("name==1.1;python_version=='2.7'")
|
||||
!=
|
||||
Requirement.parse("name==1.1;python_version=='3.3'")
|
||||
)
|
||||
assert (
|
||||
Requirement.parse("name==1.0;python_version=='2.7'")
|
||||
!=
|
||||
Requirement.parse("name==1.2;python_version=='2.7'")
|
||||
)
|
||||
assert (
|
||||
Requirement.parse("name[foo]==1.0;python_version=='3.3'")
|
||||
!=
|
||||
Requirement.parse("name[foo,bar]==1.0;python_version=='3.3'")
|
||||
)
|
||||
|
||||
def test_local_version(self):
|
||||
req, = parse_requirements('foo==1.0.org1')
|
||||
|
||||
def test_spaces_between_multiple_versions(self):
|
||||
req, = parse_requirements('foo>=1.0, <3')
|
||||
req, = parse_requirements('foo >= 1.0, < 3')
|
||||
|
||||
def testVersionEquality(self):
|
||||
def c(s1, s2):
|
||||
p1, p2 = parse_version(s1), parse_version(s2)
|
||||
assert p1 == p2, (s1, s2, p1, p2)
|
||||
|
||||
c('1.2-rc1', '1.2rc1')
|
||||
c('0.4', '0.4.0')
|
||||
c('0.4.0.0', '0.4.0')
|
||||
c('0.4.0-0', '0.4-0')
|
||||
c('0post1', '0.0post1')
|
||||
c('0pre1', '0.0c1')
|
||||
c('0.0.0preview1', '0c1')
|
||||
c('0.0c1', '0-rc1')
|
||||
c('1.2a1', '1.2.a.1')
|
||||
c('1.2.a', '1.2a')
|
||||
|
||||
def testVersionOrdering(self):
|
||||
def c(s1, s2):
|
||||
p1, p2 = parse_version(s1), parse_version(s2)
|
||||
assert p1 < p2, (s1, s2, p1, p2)
|
||||
|
||||
c('2.1', '2.1.1')
|
||||
c('2a1', '2b0')
|
||||
c('2a1', '2.1')
|
||||
c('2.3a1', '2.3')
|
||||
c('2.1-1', '2.1-2')
|
||||
c('2.1-1', '2.1.1')
|
||||
c('2.1', '2.1post4')
|
||||
c('2.1a0-20040501', '2.1')
|
||||
c('1.1', '02.1')
|
||||
c('3.2', '3.2.post0')
|
||||
c('3.2post1', '3.2post2')
|
||||
c('0.4', '4.0')
|
||||
c('0.0.4', '0.4.0')
|
||||
c('0post1', '0.4post1')
|
||||
c('2.1.0-rc1', '2.1.0')
|
||||
c('2.1dev', '2.1a0')
|
||||
|
||||
torture = """
|
||||
0.80.1-3 0.80.1-2 0.80.1-1 0.79.9999+0.80.0pre4-1
|
||||
0.79.9999+0.80.0pre2-3 0.79.9999+0.80.0pre2-2
|
||||
0.77.2-1 0.77.1-1 0.77.0-1
|
||||
""".split()
|
||||
|
||||
for p, v1 in enumerate(torture):
|
||||
for v2 in torture[p + 1:]:
|
||||
c(v2, v1)
|
||||
|
||||
def testVersionBuildout(self):
|
||||
"""
|
||||
Buildout has a function in it's bootstrap.py that inspected the return
|
||||
value of parse_version. The new parse_version returns a Version class
|
||||
which needs to support this behavior, at least for now.
|
||||
"""
|
||||
|
||||
def buildout(parsed_version):
|
||||
_final_parts = '*final-', '*final'
|
||||
|
||||
def _final_version(parsed_version):
|
||||
for part in parsed_version:
|
||||
if (part[:1] == '*') and (part not in _final_parts):
|
||||
return False
|
||||
return True
|
||||
|
||||
return _final_version(parsed_version)
|
||||
|
||||
assert buildout(parse_version("1.0"))
|
||||
assert not buildout(parse_version("1.0a1"))
|
||||
|
||||
def testVersionIndexable(self):
|
||||
"""
|
||||
Some projects were doing things like parse_version("v")[0], so we'll
|
||||
support indexing the same as we support iterating.
|
||||
"""
|
||||
assert parse_version("1.0")[0] == "00000001"
|
||||
|
||||
def testVersionTupleSort(self):
|
||||
"""
|
||||
Some projects expected to be able to sort tuples against the return
|
||||
value of parse_version. So again we'll add a warning enabled shim to
|
||||
make this possible.
|
||||
"""
|
||||
assert parse_version("1.0") < tuple(parse_version("2.0"))
|
||||
assert parse_version("1.0") <= tuple(parse_version("2.0"))
|
||||
assert parse_version("1.0") == tuple(parse_version("1.0"))
|
||||
assert parse_version("3.0") > tuple(parse_version("2.0"))
|
||||
assert parse_version("3.0") >= tuple(parse_version("2.0"))
|
||||
assert parse_version("3.0") != tuple(parse_version("2.0"))
|
||||
assert not (parse_version("3.0") != tuple(parse_version("3.0")))
|
||||
|
||||
def testVersionHashable(self):
|
||||
"""
|
||||
Ensure that our versions stay hashable even though we've subclassed
|
||||
them and added some shim code to them.
|
||||
"""
|
||||
assert (
|
||||
hash(parse_version("1.0"))
|
||||
==
|
||||
hash(parse_version("1.0"))
|
||||
)
|
||||
|
||||
|
||||
class TestNamespaces:
|
||||
|
||||
ns_str = "__import__('pkg_resources').declare_namespace(__name__)\n"
|
||||
|
||||
@pytest.yield_fixture
|
||||
def symlinked_tmpdir(self, tmpdir):
|
||||
"""
|
||||
Where available, return the tempdir as a symlink,
|
||||
which as revealed in #231 is more fragile than
|
||||
a natural tempdir.
|
||||
"""
|
||||
if not hasattr(os, 'symlink'):
|
||||
yield str(tmpdir)
|
||||
return
|
||||
|
||||
link_name = str(tmpdir) + '-linked'
|
||||
os.symlink(str(tmpdir), link_name)
|
||||
try:
|
||||
yield type(tmpdir)(link_name)
|
||||
finally:
|
||||
os.unlink(link_name)
|
||||
|
||||
@pytest.yield_fixture(autouse=True)
|
||||
def patched_path(self, tmpdir):
|
||||
"""
|
||||
Patch sys.path to include the 'site-pkgs' dir. Also
|
||||
restore pkg_resources._namespace_packages to its
|
||||
former state.
|
||||
"""
|
||||
saved_ns_pkgs = pkg_resources._namespace_packages.copy()
|
||||
saved_sys_path = sys.path[:]
|
||||
site_pkgs = tmpdir.mkdir('site-pkgs')
|
||||
sys.path.append(str(site_pkgs))
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
pkg_resources._namespace_packages = saved_ns_pkgs
|
||||
sys.path = saved_sys_path
|
||||
|
||||
issue591 = pytest.mark.xfail(platform.system() == 'Windows', reason="#591")
|
||||
|
||||
@issue591
|
||||
def test_two_levels_deep(self, symlinked_tmpdir):
|
||||
"""
|
||||
Test nested namespace packages
|
||||
Create namespace packages in the following tree :
|
||||
site-packages-1/pkg1/pkg2
|
||||
site-packages-2/pkg1/pkg2
|
||||
Check both are in the _namespace_packages dict and that their __path__
|
||||
is correct
|
||||
"""
|
||||
real_tmpdir = symlinked_tmpdir.realpath()
|
||||
tmpdir = symlinked_tmpdir
|
||||
sys.path.append(str(tmpdir / 'site-pkgs2'))
|
||||
site_dirs = tmpdir / 'site-pkgs', tmpdir / 'site-pkgs2'
|
||||
for site in site_dirs:
|
||||
pkg1 = site / 'pkg1'
|
||||
pkg2 = pkg1 / 'pkg2'
|
||||
pkg2.ensure_dir()
|
||||
(pkg1 / '__init__.py').write_text(self.ns_str, encoding='utf-8')
|
||||
(pkg2 / '__init__.py').write_text(self.ns_str, encoding='utf-8')
|
||||
import pkg1
|
||||
assert "pkg1" in pkg_resources._namespace_packages
|
||||
# attempt to import pkg2 from site-pkgs2
|
||||
import pkg1.pkg2
|
||||
# check the _namespace_packages dict
|
||||
assert "pkg1.pkg2" in pkg_resources._namespace_packages
|
||||
assert pkg_resources._namespace_packages["pkg1"] == ["pkg1.pkg2"]
|
||||
# check the __path__ attribute contains both paths
|
||||
expected = [
|
||||
str(real_tmpdir / "site-pkgs" / "pkg1" / "pkg2"),
|
||||
str(real_tmpdir / "site-pkgs2" / "pkg1" / "pkg2"),
|
||||
]
|
||||
assert pkg1.pkg2.__path__ == expected
|
||||
|
||||
@issue591
|
||||
def test_path_order(self, symlinked_tmpdir):
|
||||
"""
|
||||
Test that if multiple versions of the same namespace package subpackage
|
||||
are on different sys.path entries, that only the one earliest on
|
||||
sys.path is imported, and that the namespace package's __path__ is in
|
||||
the correct order.
|
||||
|
||||
Regression test for https://github.com/pypa/setuptools/issues/207
|
||||
"""
|
||||
|
||||
tmpdir = symlinked_tmpdir
|
||||
site_dirs = (
|
||||
tmpdir / "site-pkgs",
|
||||
tmpdir / "site-pkgs2",
|
||||
tmpdir / "site-pkgs3",
|
||||
)
|
||||
|
||||
vers_str = "__version__ = %r"
|
||||
|
||||
for number, site in enumerate(site_dirs, 1):
|
||||
if number > 1:
|
||||
sys.path.append(str(site))
|
||||
nspkg = site / 'nspkg'
|
||||
subpkg = nspkg / 'subpkg'
|
||||
subpkg.ensure_dir()
|
||||
(nspkg / '__init__.py').write_text(self.ns_str, encoding='utf-8')
|
||||
(subpkg / '__init__.py').write_text(vers_str % number, encoding='utf-8')
|
||||
|
||||
import nspkg.subpkg
|
||||
import nspkg
|
||||
expected = [
|
||||
str(site.realpath() / 'nspkg')
|
||||
for site in site_dirs
|
||||
]
|
||||
assert nspkg.__path__ == expected
|
||||
assert nspkg.subpkg.__version__ == 1
|
||||
@@ -1,478 +0,0 @@
|
||||
import inspect
|
||||
import re
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
|
||||
import pkg_resources
|
||||
|
||||
from .test_resources import Metadata
|
||||
|
||||
|
||||
def strip_comments(s):
|
||||
return '\n'.join(
|
||||
l for l in s.split('\n')
|
||||
if l.strip() and not l.strip().startswith('#')
|
||||
)
|
||||
|
||||
def parse_distributions(s):
|
||||
'''
|
||||
Parse a series of distribution specs of the form:
|
||||
{project_name}-{version}
|
||||
[optional, indented requirements specification]
|
||||
|
||||
Example:
|
||||
|
||||
foo-0.2
|
||||
bar-1.0
|
||||
foo>=3.0
|
||||
[feature]
|
||||
baz
|
||||
|
||||
yield 2 distributions:
|
||||
- project_name=foo, version=0.2
|
||||
- project_name=bar, version=1.0, requires=['foo>=3.0', 'baz; extra=="feature"']
|
||||
'''
|
||||
s = s.strip()
|
||||
for spec in re.split('\n(?=[^\s])', s):
|
||||
if not spec:
|
||||
continue
|
||||
fields = spec.split('\n', 1)
|
||||
assert 1 <= len(fields) <= 2
|
||||
name, version = fields.pop(0).split('-')
|
||||
if fields:
|
||||
requires = textwrap.dedent(fields.pop(0))
|
||||
metadata=Metadata(('requires.txt', requires))
|
||||
else:
|
||||
metadata = None
|
||||
dist = pkg_resources.Distribution(project_name=name,
|
||||
version=version,
|
||||
metadata=metadata)
|
||||
yield dist
|
||||
|
||||
|
||||
class FakeInstaller(object):
|
||||
|
||||
def __init__(self, installable_dists):
|
||||
self._installable_dists = installable_dists
|
||||
|
||||
def __call__(self, req):
|
||||
return next(iter(filter(lambda dist: dist in req,
|
||||
self._installable_dists)), None)
|
||||
|
||||
|
||||
def parametrize_test_working_set_resolve(*test_list):
|
||||
idlist = []
|
||||
argvalues = []
|
||||
for test in test_list:
|
||||
(
|
||||
name,
|
||||
installed_dists,
|
||||
installable_dists,
|
||||
requirements,
|
||||
expected1, expected2
|
||||
) = [
|
||||
strip_comments(s.lstrip()) for s in
|
||||
textwrap.dedent(test).lstrip().split('\n\n', 5)
|
||||
]
|
||||
installed_dists = list(parse_distributions(installed_dists))
|
||||
installable_dists = list(parse_distributions(installable_dists))
|
||||
requirements = list(pkg_resources.parse_requirements(requirements))
|
||||
for id_, replace_conflicting, expected in (
|
||||
(name, False, expected1),
|
||||
(name + '_replace_conflicting', True, expected2),
|
||||
):
|
||||
idlist.append(id_)
|
||||
expected = strip_comments(expected.strip())
|
||||
if re.match('\w+$', expected):
|
||||
expected = getattr(pkg_resources, expected)
|
||||
assert issubclass(expected, Exception)
|
||||
else:
|
||||
expected = list(parse_distributions(expected))
|
||||
argvalues.append(pytest.param(installed_dists, installable_dists,
|
||||
requirements, replace_conflicting,
|
||||
expected))
|
||||
return pytest.mark.parametrize('installed_dists,installable_dists,'
|
||||
'requirements,replace_conflicting,'
|
||||
'resolved_dists_or_exception',
|
||||
argvalues, ids=idlist)
|
||||
|
||||
|
||||
@parametrize_test_working_set_resolve(
|
||||
'''
|
||||
# id
|
||||
noop
|
||||
|
||||
# installed
|
||||
|
||||
# installable
|
||||
|
||||
# wanted
|
||||
|
||||
# resolved
|
||||
|
||||
# resolved [replace conflicting]
|
||||
''',
|
||||
|
||||
'''
|
||||
# id
|
||||
already_installed
|
||||
|
||||
# installed
|
||||
foo-3.0
|
||||
|
||||
# installable
|
||||
|
||||
# wanted
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# resolved
|
||||
foo-3.0
|
||||
|
||||
# resolved [replace conflicting]
|
||||
foo-3.0
|
||||
''',
|
||||
|
||||
'''
|
||||
# id
|
||||
installable_not_installed
|
||||
|
||||
# installed
|
||||
|
||||
# installable
|
||||
foo-3.0
|
||||
foo-4.0
|
||||
|
||||
# wanted
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# resolved
|
||||
foo-3.0
|
||||
|
||||
# resolved [replace conflicting]
|
||||
foo-3.0
|
||||
''',
|
||||
|
||||
'''
|
||||
# id
|
||||
not_installable
|
||||
|
||||
# installed
|
||||
|
||||
# installable
|
||||
|
||||
# wanted
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# resolved
|
||||
DistributionNotFound
|
||||
|
||||
# resolved [replace conflicting]
|
||||
DistributionNotFound
|
||||
''',
|
||||
|
||||
'''
|
||||
# id
|
||||
no_matching_version
|
||||
|
||||
# installed
|
||||
|
||||
# installable
|
||||
foo-3.1
|
||||
|
||||
# wanted
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# resolved
|
||||
DistributionNotFound
|
||||
|
||||
# resolved [replace conflicting]
|
||||
DistributionNotFound
|
||||
''',
|
||||
|
||||
'''
|
||||
# id
|
||||
installable_with_installed_conflict
|
||||
|
||||
# installed
|
||||
foo-3.1
|
||||
|
||||
# installable
|
||||
foo-3.5
|
||||
|
||||
# wanted
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# resolved
|
||||
VersionConflict
|
||||
|
||||
# resolved [replace conflicting]
|
||||
foo-3.5
|
||||
''',
|
||||
|
||||
'''
|
||||
# id
|
||||
not_installable_with_installed_conflict
|
||||
|
||||
# installed
|
||||
foo-3.1
|
||||
|
||||
# installable
|
||||
|
||||
# wanted
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# resolved
|
||||
VersionConflict
|
||||
|
||||
# resolved [replace conflicting]
|
||||
DistributionNotFound
|
||||
''',
|
||||
|
||||
'''
|
||||
# id
|
||||
installed_with_installed_require
|
||||
|
||||
# installed
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# installable
|
||||
|
||||
# wanted
|
||||
baz
|
||||
|
||||
# resolved
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
|
||||
# resolved [replace conflicting]
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
''',
|
||||
|
||||
'''
|
||||
# id
|
||||
installed_with_conflicting_installed_require
|
||||
|
||||
# installed
|
||||
foo-5
|
||||
baz-0.1
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# installable
|
||||
|
||||
# wanted
|
||||
baz
|
||||
|
||||
# resolved
|
||||
VersionConflict
|
||||
|
||||
# resolved [replace conflicting]
|
||||
DistributionNotFound
|
||||
''',
|
||||
|
||||
'''
|
||||
# id
|
||||
installed_with_installable_conflicting_require
|
||||
|
||||
# installed
|
||||
foo-5
|
||||
baz-0.1
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# installable
|
||||
foo-2.9
|
||||
|
||||
# wanted
|
||||
baz
|
||||
|
||||
# resolved
|
||||
VersionConflict
|
||||
|
||||
# resolved [replace conflicting]
|
||||
baz-0.1
|
||||
foo-2.9
|
||||
''',
|
||||
|
||||
'''
|
||||
# id
|
||||
installed_with_installable_require
|
||||
|
||||
# installed
|
||||
baz-0.1
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# installable
|
||||
foo-3.9
|
||||
|
||||
# wanted
|
||||
baz
|
||||
|
||||
# resolved
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
|
||||
# resolved [replace conflicting]
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
''',
|
||||
|
||||
'''
|
||||
# id
|
||||
installable_with_installed_require
|
||||
|
||||
# installed
|
||||
foo-3.9
|
||||
|
||||
# installable
|
||||
baz-0.1
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# wanted
|
||||
baz
|
||||
|
||||
# resolved
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
|
||||
# resolved [replace conflicting]
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
''',
|
||||
|
||||
'''
|
||||
# id
|
||||
installable_with_installable_require
|
||||
|
||||
# installed
|
||||
|
||||
# installable
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# wanted
|
||||
baz
|
||||
|
||||
# resolved
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
|
||||
# resolved [replace conflicting]
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
''',
|
||||
|
||||
'''
|
||||
# id
|
||||
installable_with_conflicting_installable_require
|
||||
|
||||
# installed
|
||||
foo-5
|
||||
|
||||
# installable
|
||||
foo-2.9
|
||||
baz-0.1
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# wanted
|
||||
baz
|
||||
|
||||
# resolved
|
||||
VersionConflict
|
||||
|
||||
# resolved [replace conflicting]
|
||||
baz-0.1
|
||||
foo-2.9
|
||||
''',
|
||||
|
||||
'''
|
||||
# id
|
||||
conflicting_installables
|
||||
|
||||
# installed
|
||||
|
||||
# installable
|
||||
foo-2.9
|
||||
foo-5.0
|
||||
|
||||
# wanted
|
||||
foo>=2.1,!=3.1,<4
|
||||
foo>=4
|
||||
|
||||
# resolved
|
||||
VersionConflict
|
||||
|
||||
# resolved [replace conflicting]
|
||||
VersionConflict
|
||||
''',
|
||||
|
||||
'''
|
||||
# id
|
||||
installables_with_conflicting_requires
|
||||
|
||||
# installed
|
||||
|
||||
# installable
|
||||
foo-2.9
|
||||
dep==1.0
|
||||
baz-5.0
|
||||
dep==2.0
|
||||
dep-1.0
|
||||
dep-2.0
|
||||
|
||||
# wanted
|
||||
foo
|
||||
baz
|
||||
|
||||
# resolved
|
||||
VersionConflict
|
||||
|
||||
# resolved [replace conflicting]
|
||||
VersionConflict
|
||||
''',
|
||||
|
||||
'''
|
||||
# id
|
||||
installables_with_conflicting_nested_requires
|
||||
|
||||
# installed
|
||||
|
||||
# installable
|
||||
foo-2.9
|
||||
dep1
|
||||
dep1-1.0
|
||||
subdep<1.0
|
||||
baz-5.0
|
||||
dep2
|
||||
dep2-1.0
|
||||
subdep>1.0
|
||||
subdep-0.9
|
||||
subdep-1.1
|
||||
|
||||
# wanted
|
||||
foo
|
||||
baz
|
||||
|
||||
# resolved
|
||||
VersionConflict
|
||||
|
||||
# resolved [replace conflicting]
|
||||
VersionConflict
|
||||
''',
|
||||
)
|
||||
def test_working_set_resolve(installed_dists, installable_dists, requirements,
|
||||
replace_conflicting, resolved_dists_or_exception):
|
||||
ws = pkg_resources.WorkingSet([])
|
||||
list(map(ws.add, installed_dists))
|
||||
resolve_call = lambda: ws.resolve(
|
||||
requirements, installer=FakeInstaller(installable_dists),
|
||||
replace_conflicting=replace_conflicting,
|
||||
)
|
||||
if inspect.isclass(resolved_dists_or_exception):
|
||||
with pytest.raises(resolved_dists_or_exception):
|
||||
resolve_call()
|
||||
else:
|
||||
assert sorted(resolve_call()) == sorted(resolved_dists_or_exception)
|
||||
Reference in New Issue
Block a user