diff --git a/.travis.yml b/.travis.yml index f4c6ecbae8e..a388581bd04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,8 +7,6 @@ matrix: - env: TOXENV=pep8 - env: TOXENV=py3pep8 - env: TOXENV=packaging - - python: 2.6 # these are just to make travis's UI a bit prettier - env: TOXENV=py26 - python: 2.7 env: TOXENV=py27 - python: 3.3 diff --git a/CHANGES.txt b/CHANGES.txt index 20a8de5b7be..2f4c0295c4e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,6 @@ -**9.1.0 (UNRELEASED)** +**10.0.0 (UNRELEASED)** + +* **BACKWARD INCOMPATIBLE** Dropped support for Python 2.6. * **WARNING** pip 9.1 cache format has changed. Old versions of pip cannot read cache entries written by 9.1 but will handle this gracefully by diff --git a/docs/installing.rst b/docs/installing.rst index 0ab378940d0..d911f36aab4 100644 --- a/docs/installing.rst +++ b/docs/installing.rst @@ -46,9 +46,7 @@ speed), although neither are required to install pre-built :term:`wheels .. note:: The get-pip.py script is supported on the same python version as pip. - For the now unsupported Python 3.2, an alternate script is available - `here `__. For Python 2.6 - which will be unsupported in pip 10.0, an alternative script is available + For the now unsupported Python 2.6, alternate script is available `here `__. @@ -110,11 +108,10 @@ On Windows [5]_: Python and OS Compatibility --------------------------- -pip works with CPython versions 2.6, 2.7, 3.3, 3.4, 3.5 and also pypy. +pip works with CPython versions 2.7, 3.3, 3.4, 3.5, 3.6 and also pypy. -This means pip works on the latest patch version of each of these minor versions -(i.e. 2.6.9 for 2.6, etc). -Previous patch versions are supported on a best effort approach. +This means pip works on the latest patch version of each of these minor +versions. Previous patch versions are supported on a best effort approach. pip works on Unix/Linux, macOS, and Windows. diff --git a/pip/_vendor/html5lib/filters/alphabeticalattributes.py b/pip/_vendor/html5lib/filters/alphabeticalattributes.py index 3509afb9341..4795baecc99 100644 --- a/pip/_vendor/html5lib/filters/alphabeticalattributes.py +++ b/pip/_vendor/html5lib/filters/alphabeticalattributes.py @@ -5,7 +5,7 @@ try: from collections import OrderedDict except ImportError: - from pip._vendor.ordereddict import OrderedDict + from ordereddict import OrderedDict class Filter(base.Filter): diff --git a/pip/_vendor/html5lib/html5parser.py b/pip/_vendor/html5lib/html5parser.py index f7043cb1989..0cd77032277 100644 --- a/pip/_vendor/html5lib/html5parser.py +++ b/pip/_vendor/html5lib/html5parser.py @@ -6,7 +6,7 @@ try: from collections import OrderedDict except ImportError: - from pip._vendor.ordereddict import OrderedDict + from ordereddict import OrderedDict from . import _inputstream from . import _tokenizer diff --git a/pip/_vendor/html5lib/treewalkers/etree.py b/pip/_vendor/html5lib/treewalkers/etree.py index ffcff1da8c3..bcf17d170cb 100644 --- a/pip/_vendor/html5lib/treewalkers/etree.py +++ b/pip/_vendor/html5lib/treewalkers/etree.py @@ -4,7 +4,7 @@ from collections import OrderedDict except ImportError: try: - from pip._vendor.ordereddict import OrderedDict + from ordereddict import OrderedDict except ImportError: OrderedDict = dict diff --git a/pip/_vendor/ordereddict.py b/pip/_vendor/ordereddict.py deleted file mode 100644 index 7242b5060de..00000000000 --- a/pip/_vendor/ordereddict.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright (c) 2009 Raymond Hettinger -# -# 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 UserDict import DictMixin - -class OrderedDict(dict, DictMixin): - - def __init__(self, *args, **kwds): - if len(args) > 1: - raise TypeError('expected at most 1 arguments, got %d' % len(args)) - try: - self.__end - except AttributeError: - self.clear() - self.update(*args, **kwds) - - def clear(self): - self.__end = end = [] - end += [None, end, end] # sentinel node for doubly linked list - self.__map = {} # key --> [key, prev, next] - dict.clear(self) - - def __setitem__(self, key, value): - if key not in self: - end = self.__end - curr = end[1] - curr[2] = end[1] = self.__map[key] = [key, curr, end] - dict.__setitem__(self, key, value) - - def __delitem__(self, key): - dict.__delitem__(self, key) - key, prev, next = self.__map.pop(key) - prev[2] = next - next[1] = prev - - def __iter__(self): - end = self.__end - curr = end[2] - while curr is not end: - yield curr[0] - curr = curr[2] - - def __reversed__(self): - end = self.__end - curr = end[1] - while curr is not end: - yield curr[0] - curr = curr[1] - - def popitem(self, last=True): - if not self: - raise KeyError('dictionary is empty') - if last: - key = reversed(self).next() - else: - key = iter(self).next() - value = self.pop(key) - return key, value - - def __reduce__(self): - items = [[k, self[k]] for k in self] - tmp = self.__map, self.__end - del self.__map, self.__end - inst_dict = vars(self).copy() - self.__map, self.__end = tmp - if inst_dict: - return (self.__class__, (items,), inst_dict) - return self.__class__, (items,) - - def keys(self): - return list(self) - - setdefault = DictMixin.setdefault - update = DictMixin.update - pop = DictMixin.pop - values = DictMixin.values - items = DictMixin.items - iterkeys = DictMixin.iterkeys - itervalues = DictMixin.itervalues - iteritems = DictMixin.iteritems - - def __repr__(self): - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, self.items()) - - def copy(self): - return self.__class__(self) - - @classmethod - def fromkeys(cls, iterable, value=None): - d = cls() - for key in iterable: - d[key] = value - return d - - def __eq__(self, other): - if isinstance(other, OrderedDict): - if len(self) != len(other): - return False - for p, q in zip(self.items(), other.items()): - if p != q: - return False - return True - return dict.__eq__(self, other) - - def __ne__(self, other): - return not self == other diff --git a/pip/_vendor/pyparsing.py b/pip/_vendor/pyparsing.py index 06784804776..ba2619c23ea 100644 --- a/pip/_vendor/pyparsing.py +++ b/pip/_vendor/pyparsing.py @@ -86,7 +86,7 @@ class names, and the use of '+', '|' and '^' operators. from collections import OrderedDict as _OrderedDict except ImportError: try: - from pip._vendor.ordereddict import OrderedDict as _OrderedDict + from ordereddict import OrderedDict as _OrderedDict except ImportError: _OrderedDict = None diff --git a/pip/_vendor/vendor.txt b/pip/_vendor/vendor.txt index 533d6d517b7..29f70ebbe5e 100644 --- a/pip/_vendor/vendor.txt +++ b/pip/_vendor/vendor.txt @@ -7,7 +7,6 @@ colorama==0.3.7 CacheControl==0.12.1 msgpack-python==0.4.8 lockfile==0.12.2 -ordereddict==1.1 # Only needed on 2.6 progress==1.2 ipaddress==1.0.18 # Only needed on 2.6 and 2.7 packaging==16.8 diff --git a/pip/basecommand.py b/pip/basecommand.py index c33a2d77ac3..ef2c14ec664 100644 --- a/pip/basecommand.py +++ b/pip/basecommand.py @@ -2,10 +2,10 @@ from __future__ import absolute_import import logging +import logging.config import os import sys import optparse -import warnings from pip import cmdoptions from pip.index import PackageFinder @@ -14,14 +14,13 @@ from pip.exceptions import (BadCommand, InstallationError, UninstallationError, CommandError, PreviousBuildDirError) -from pip.compat import logging_dictConfig from pip.baseparser import ConfigOptionParser, UpdatingDefaultsHelpFormatter from pip.req import InstallRequirement, parse_requirements from pip.status_codes import ( SUCCESS, ERROR, UNKNOWN_ERROR, VIRTUALENV_NOT_FOUND, PREVIOUS_BUILD_DIR_ERROR, ) -from pip.utils import deprecation, get_prog, normalize_path +from pip.utils import get_prog, normalize_path from pip.utils.logging import IndentingFormatter from pip.utils.outdated import pip_version_check @@ -123,7 +122,7 @@ def main(self, args): if options.log: root_level = "DEBUG" - logging_dictConfig({ + logging.config.dictConfig({ "version": 1, "disable_existing_loggers": False, "filters": { @@ -186,14 +185,6 @@ def main(self, args): ), }) - if sys.version_info[:2] == (2, 6): - warnings.warn( - "Python 2.6 is no longer supported by the Python core team, " - "please upgrade your Python. A future version of pip will " - "drop support for Python 2.6", - deprecation.Python26DeprecationWarning - ) - # TODO: try to get these passing down from the command? # without resorting to os.environ to hold these. diff --git a/pip/commands/download.py b/pip/commands/download.py index 7fe7ef05900..8da5452f371 100644 --- a/pip/commands/download.py +++ b/pip/commands/download.py @@ -135,7 +135,7 @@ def run(self, options, args): options.abi, options.implementation, ]) - binary_only = FormatControl(set(), set([':all:'])) + binary_only = FormatControl(set(), {':all:'}) if dist_restriction_set and options.format_control != binary_only: raise CommandError( "--only-binary=:all: must be set and --no-binary must not " diff --git a/pip/commands/freeze.py b/pip/commands/freeze.py index 5b330a39e44..133ee397fd6 100644 --- a/pip/commands/freeze.py +++ b/pip/commands/freeze.py @@ -9,7 +9,7 @@ from pip.wheel import WheelCache -DEV_PKGS = ('pip', 'setuptools', 'distribute', 'wheel') +DEV_PKGS = {'pip', 'setuptools', 'distribute', 'wheel'} class FreezeCommand(Command): diff --git a/pip/commands/search.py b/pip/commands/search.py index bd2ea8ad3e5..a9cf8c93218 100644 --- a/pip/commands/search.py +++ b/pip/commands/search.py @@ -4,8 +4,9 @@ import sys import textwrap +from collections import OrderedDict + from pip.basecommand import Command, SUCCESS -from pip.compat import OrderedDict from pip.download import PipXmlrpcTransport from pip.models import PyPI from pip.utils import get_terminal_size diff --git a/pip/compat/__init__.py b/pip/compat.py similarity index 73% rename from pip/compat/__init__.py rename to pip/compat.py index 099672cd1ad..3682fd451a4 100644 --- a/pip/compat/__init__.py +++ b/pip/compat.py @@ -7,16 +7,6 @@ from pip._vendor.six import text_type -try: - from logging.config import dictConfig as logging_dictConfig -except ImportError: - from pip.compat.dictconfig import dictConfig as logging_dictConfig - -try: - from collections import OrderedDict -except ImportError: - from pip._vendor.ordereddict import OrderedDict - try: import ipaddress except ImportError: @@ -28,30 +18,9 @@ ipaddress.ip_network = ipaddress.IPNetwork -try: - import sysconfig - - def get_stdlib(): - paths = [ - sysconfig.get_path("stdlib"), - sysconfig.get_path("platstdlib"), - ] - return set(filter(bool, paths)) -except ImportError: - from distutils import sysconfig - - def get_stdlib(): - paths = [ - sysconfig.get_python_lib(standard_lib=True), - sysconfig.get_python_lib(standard_lib=True, plat_specific=True), - ] - return set(filter(bool, paths)) - - __all__ = [ - "logging_dictConfig", "ipaddress", "uses_pycache", "console_to_str", - "native_str", "get_path_uid", "stdlib_pkgs", "WINDOWS", "samefile", - "OrderedDict", + "ipaddress", "uses_pycache", "console_to_str", "native_str", + "get_path_uid", "stdlib_pkgs", "WINDOWS", "samefile", ] @@ -90,14 +59,6 @@ def native_str(s, replace=False): return s -def total_seconds(td): - if hasattr(td, "total_seconds"): - return td.total_seconds() - else: - val = td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6 - return val / 10 ** 6 - - def get_path_uid(path): """ Return path's uid. @@ -144,9 +105,7 @@ def expanduser(path): # dist.location (py27:`sysconfig.get_paths()['stdlib']`, # py26:sysconfig.get_config_vars('LIBDEST')), but fear platform variation may # make this ineffective, so hard-coding -stdlib_pkgs = ('python', 'wsgiref') -if sys.version_info >= (2, 7): - stdlib_pkgs += ('argparse',) +stdlib_pkgs = {"python", "wsgiref", "argparse"} # windows detection, covers cpython and ironpython diff --git a/pip/compat/dictconfig.py b/pip/compat/dictconfig.py deleted file mode 100644 index ec684aac203..00000000000 --- a/pip/compat/dictconfig.py +++ /dev/null @@ -1,565 +0,0 @@ -# This is a copy of the Python logging.config.dictconfig module, -# reproduced with permission. It is provided here for backwards -# compatibility for Python versions prior to 2.7. -# -# Copyright 2009-2010 by Vinay Sajip. All Rights Reserved. -# -# Permission to use, copy, modify, and distribute this software and its -# documentation for any purpose and without fee is hereby granted, -# provided that the above copyright notice appear in all copies and that -# both that copyright notice and this permission notice appear in -# supporting documentation, and that the name of Vinay Sajip -# not be used in advertising or publicity pertaining to distribution -# of the software without specific, written prior permission. -# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING -# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL -# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR -# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER -# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT -# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -from __future__ import absolute_import - -import logging.handlers -import re -import sys -import types - -from pip._vendor import six - -# flake8: noqa - -IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I) - - -def valid_ident(s): - m = IDENTIFIER.match(s) - if not m: - raise ValueError('Not a valid Python identifier: %r' % s) - return True - -# -# This function is defined in logging only in recent versions of Python -# -try: - from logging import _checkLevel -except ImportError: - def _checkLevel(level): - if isinstance(level, int): - rv = level - elif str(level) == level: - if level not in logging._levelNames: - raise ValueError('Unknown level: %r' % level) - rv = logging._levelNames[level] - else: - raise TypeError('Level not an integer or a ' - 'valid string: %r' % level) - return rv - -# The ConvertingXXX classes are wrappers around standard Python containers, -# and they serve to convert any suitable values in the container. The -# conversion converts base dicts, lists and tuples to their wrapped -# equivalents, whereas strings which match a conversion format are converted -# appropriately. -# -# Each wrapper should have a configurator attribute holding the actual -# configurator to use for conversion. - - -class ConvertingDict(dict): - """A converting dictionary wrapper.""" - - def __getitem__(self, key): - value = dict.__getitem__(self, key) - result = self.configurator.convert(value) - # If the converted value is different, save for next time - if value is not result: - self[key] = result - if type(result) in (ConvertingDict, ConvertingList, - ConvertingTuple): - result.parent = self - result.key = key - return result - - def get(self, key, default=None): - value = dict.get(self, key, default) - result = self.configurator.convert(value) - # If the converted value is different, save for next time - if value is not result: - self[key] = result - if type(result) in (ConvertingDict, ConvertingList, - ConvertingTuple): - result.parent = self - result.key = key - return result - - def pop(self, key, default=None): - value = dict.pop(self, key, default) - result = self.configurator.convert(value) - if value is not result: - if type(result) in (ConvertingDict, ConvertingList, - ConvertingTuple): - result.parent = self - result.key = key - return result - - -class ConvertingList(list): - """A converting list wrapper.""" - def __getitem__(self, key): - value = list.__getitem__(self, key) - result = self.configurator.convert(value) - # If the converted value is different, save for next time - if value is not result: - self[key] = result - if type(result) in (ConvertingDict, ConvertingList, - ConvertingTuple): - result.parent = self - result.key = key - return result - - def pop(self, idx=-1): - value = list.pop(self, idx) - result = self.configurator.convert(value) - if value is not result: - if type(result) in (ConvertingDict, ConvertingList, - ConvertingTuple): - result.parent = self - return result - - -class ConvertingTuple(tuple): - """A converting tuple wrapper.""" - def __getitem__(self, key): - value = tuple.__getitem__(self, key) - result = self.configurator.convert(value) - if value is not result: - if type(result) in (ConvertingDict, ConvertingList, - ConvertingTuple): - result.parent = self - result.key = key - return result - - -class BaseConfigurator(object): - """ - The configurator base class which defines some useful defaults. - """ - - CONVERT_PATTERN = re.compile(r'^(?P[a-z]+)://(?P.*)$') - - WORD_PATTERN = re.compile(r'^\s*(\w+)\s*') - DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*') - INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*') - DIGIT_PATTERN = re.compile(r'^\d+$') - - value_converters = { - 'ext' : 'ext_convert', - 'cfg' : 'cfg_convert', - } - - # We might want to use a different one, e.g. importlib - importer = __import__ - - def __init__(self, config): - self.config = ConvertingDict(config) - self.config.configurator = self - - def resolve(self, s): - """ - Resolve strings to objects using standard import and attribute - syntax. - """ - name = s.split('.') - used = name.pop(0) - try: - found = self.importer(used) - for frag in name: - used += '.' + frag - try: - found = getattr(found, frag) - except AttributeError: - self.importer(used) - found = getattr(found, frag) - return found - except ImportError: - e, tb = sys.exc_info()[1:] - v = ValueError('Cannot resolve %r: %s' % (s, e)) - v.__cause__, v.__traceback__ = e, tb - raise v - - def ext_convert(self, value): - """Default converter for the ext:// protocol.""" - return self.resolve(value) - - def cfg_convert(self, value): - """Default converter for the cfg:// protocol.""" - rest = value - m = self.WORD_PATTERN.match(rest) - if m is None: - raise ValueError("Unable to convert %r" % value) - else: - rest = rest[m.end():] - d = self.config[m.groups()[0]] - # print d, rest - while rest: - m = self.DOT_PATTERN.match(rest) - if m: - d = d[m.groups()[0]] - else: - m = self.INDEX_PATTERN.match(rest) - if m: - idx = m.groups()[0] - if not self.DIGIT_PATTERN.match(idx): - d = d[idx] - else: - try: - n = int(idx) # try as number first (most likely) - d = d[n] - except TypeError: - d = d[idx] - if m: - rest = rest[m.end():] - else: - raise ValueError('Unable to convert ' - '%r at %r' % (value, rest)) - # rest should be empty - return d - - def convert(self, value): - """ - Convert values to an appropriate type. dicts, lists and tuples are - replaced by their converting alternatives. Strings are checked to - see if they have a conversion format and are converted if they do. - """ - if not isinstance(value, ConvertingDict) and isinstance(value, dict): - value = ConvertingDict(value) - value.configurator = self - elif not isinstance(value, ConvertingList) and isinstance(value, list): - value = ConvertingList(value) - value.configurator = self - elif not isinstance(value, ConvertingTuple) and\ - isinstance(value, tuple): - value = ConvertingTuple(value) - value.configurator = self - elif isinstance(value, six.string_types): # str for py3k - m = self.CONVERT_PATTERN.match(value) - if m: - d = m.groupdict() - prefix = d['prefix'] - converter = self.value_converters.get(prefix, None) - if converter: - suffix = d['suffix'] - converter = getattr(self, converter) - value = converter(suffix) - return value - - def configure_custom(self, config): - """Configure an object with a user-supplied factory.""" - c = config.pop('()') - if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType: - c = self.resolve(c) - props = config.pop('.', None) - # Check for valid identifiers - kwargs = dict((k, config[k]) for k in config if valid_ident(k)) - result = c(**kwargs) - if props: - for name, value in props.items(): - setattr(result, name, value) - return result - - def as_tuple(self, value): - """Utility function which converts lists to tuples.""" - if isinstance(value, list): - value = tuple(value) - return value - - -class DictConfigurator(BaseConfigurator): - """ - Configure logging using a dictionary-like object to describe the - configuration. - """ - - def configure(self): - """Do the configuration.""" - - config = self.config - if 'version' not in config: - raise ValueError("dictionary doesn't specify a version") - if config['version'] != 1: - raise ValueError("Unsupported version: %s" % config['version']) - incremental = config.pop('incremental', False) - EMPTY_DICT = {} - logging._acquireLock() - try: - if incremental: - handlers = config.get('handlers', EMPTY_DICT) - # incremental handler config only if handler name - # ties in to logging._handlers (Python 2.7) - if sys.version_info[:2] == (2, 7): - for name in handlers: - if name not in logging._handlers: - raise ValueError('No handler found with ' - 'name %r' % name) - else: - try: - handler = logging._handlers[name] - handler_config = handlers[name] - level = handler_config.get('level', None) - if level: - handler.setLevel(_checkLevel(level)) - except StandardError as e: - raise ValueError('Unable to configure handler ' - '%r: %s' % (name, e)) - loggers = config.get('loggers', EMPTY_DICT) - for name in loggers: - try: - self.configure_logger(name, loggers[name], True) - except StandardError as e: - raise ValueError('Unable to configure logger ' - '%r: %s' % (name, e)) - root = config.get('root', None) - if root: - try: - self.configure_root(root, True) - except StandardError as e: - raise ValueError('Unable to configure root ' - 'logger: %s' % e) - else: - disable_existing = config.pop('disable_existing_loggers', True) - - logging._handlers.clear() - del logging._handlerList[:] - - # Do formatters first - they don't refer to anything else - formatters = config.get('formatters', EMPTY_DICT) - for name in formatters: - try: - formatters[name] = self.configure_formatter( - formatters[name]) - except StandardError as e: - raise ValueError('Unable to configure ' - 'formatter %r: %s' % (name, e)) - # Next, do filters - they don't refer to anything else, either - filters = config.get('filters', EMPTY_DICT) - for name in filters: - try: - filters[name] = self.configure_filter(filters[name]) - except StandardError as e: - raise ValueError('Unable to configure ' - 'filter %r: %s' % (name, e)) - - # Next, do handlers - they refer to formatters and filters - # As handlers can refer to other handlers, sort the keys - # to allow a deterministic order of configuration - handlers = config.get('handlers', EMPTY_DICT) - for name in sorted(handlers): - try: - handler = self.configure_handler(handlers[name]) - handler.name = name - handlers[name] = handler - except StandardError as e: - raise ValueError('Unable to configure handler ' - '%r: %s' % (name, e)) - # Next, do loggers - they refer to handlers and filters - - # we don't want to lose the existing loggers, - # since other threads may have pointers to them. - # existing is set to contain all existing loggers, - # and as we go through the new configuration we - # remove any which are configured. At the end, - # what's left in existing is the set of loggers - # which were in the previous configuration but - # which are not in the new configuration. - root = logging.root - existing = list(root.manager.loggerDict) - # The list needs to be sorted so that we can - # avoid disabling child loggers of explicitly - # named loggers. With a sorted list it is easier - # to find the child loggers. - existing.sort() - # We'll keep the list of existing loggers - # which are children of named loggers here... - child_loggers = [] - # now set up the new ones... - loggers = config.get('loggers', EMPTY_DICT) - for name in loggers: - if name in existing: - i = existing.index(name) - prefixed = name + "." - pflen = len(prefixed) - num_existing = len(existing) - i = i + 1 # look at the entry after name - while (i < num_existing) and\ - (existing[i][:pflen] == prefixed): - child_loggers.append(existing[i]) - i = i + 1 - existing.remove(name) - try: - self.configure_logger(name, loggers[name]) - except StandardError as e: - raise ValueError('Unable to configure logger ' - '%r: %s' % (name, e)) - - # Disable any old loggers. There's no point deleting - # them as other threads may continue to hold references - # and by disabling them, you stop them doing any logging. - # However, don't disable children of named loggers, as that's - # probably not what was intended by the user. - for log in existing: - logger = root.manager.loggerDict[log] - if log in child_loggers: - logger.level = logging.NOTSET - logger.handlers = [] - logger.propagate = True - elif disable_existing: - logger.disabled = True - - # And finally, do the root logger - root = config.get('root', None) - if root: - try: - self.configure_root(root) - except StandardError as e: - raise ValueError('Unable to configure root ' - 'logger: %s' % e) - finally: - logging._releaseLock() - - def configure_formatter(self, config): - """Configure a formatter from a dictionary.""" - if '()' in config: - factory = config['()'] # for use in exception handler - try: - result = self.configure_custom(config) - except TypeError as te: - if "'format'" not in str(te): - raise - # Name of parameter changed from fmt to format. - # Retry with old name. - # This is so that code can be used with older Python versions - #(e.g. by Django) - config['fmt'] = config.pop('format') - config['()'] = factory - result = self.configure_custom(config) - else: - fmt = config.get('format', None) - dfmt = config.get('datefmt', None) - result = logging.Formatter(fmt, dfmt) - return result - - def configure_filter(self, config): - """Configure a filter from a dictionary.""" - if '()' in config: - result = self.configure_custom(config) - else: - name = config.get('name', '') - result = logging.Filter(name) - return result - - def add_filters(self, filterer, filters): - """Add filters to a filterer from a list of names.""" - for f in filters: - try: - filterer.addFilter(self.config['filters'][f]) - except StandardError as e: - raise ValueError('Unable to add filter %r: %s' % (f, e)) - - def configure_handler(self, config): - """Configure a handler from a dictionary.""" - formatter = config.pop('formatter', None) - if formatter: - try: - formatter = self.config['formatters'][formatter] - except StandardError as e: - raise ValueError('Unable to set formatter ' - '%r: %s' % (formatter, e)) - level = config.pop('level', None) - filters = config.pop('filters', None) - if '()' in config: - c = config.pop('()') - if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType: - c = self.resolve(c) - factory = c - else: - klass = self.resolve(config.pop('class')) - # Special case for handler which refers to another handler - if issubclass(klass, logging.handlers.MemoryHandler) and\ - 'target' in config: - try: - config['target'] = self.config['handlers'][config['target']] - except StandardError as e: - raise ValueError('Unable to set target handler ' - '%r: %s' % (config['target'], e)) - elif issubclass(klass, logging.handlers.SMTPHandler) and\ - 'mailhost' in config: - config['mailhost'] = self.as_tuple(config['mailhost']) - elif issubclass(klass, logging.handlers.SysLogHandler) and\ - 'address' in config: - config['address'] = self.as_tuple(config['address']) - factory = klass - kwargs = dict((k, config[k]) for k in config if valid_ident(k)) - try: - result = factory(**kwargs) - except TypeError as te: - if "'stream'" not in str(te): - raise - # The argument name changed from strm to stream - # Retry with old name. - # This is so that code can be used with older Python versions - #(e.g. by Django) - kwargs['strm'] = kwargs.pop('stream') - result = factory(**kwargs) - if formatter: - result.setFormatter(formatter) - if level is not None: - result.setLevel(_checkLevel(level)) - if filters: - self.add_filters(result, filters) - return result - - def add_handlers(self, logger, handlers): - """Add handlers to a logger from a list of names.""" - for h in handlers: - try: - logger.addHandler(self.config['handlers'][h]) - except StandardError as e: - raise ValueError('Unable to add handler %r: %s' % (h, e)) - - def common_logger_config(self, logger, config, incremental=False): - """ - Perform configuration which is common to root and non-root loggers. - """ - level = config.get('level', None) - if level is not None: - logger.setLevel(_checkLevel(level)) - if not incremental: - # Remove any existing handlers - for h in logger.handlers[:]: - logger.removeHandler(h) - handlers = config.get('handlers', None) - if handlers: - self.add_handlers(logger, handlers) - filters = config.get('filters', None) - if filters: - self.add_filters(logger, filters) - - def configure_logger(self, name, config, incremental=False): - """Configure a non-root logger from a dictionary.""" - logger = logging.getLogger(name) - self.common_logger_config(logger, config, incremental) - propagate = config.get('propagate', None) - if propagate is not None: - logger.propagate = propagate - - def configure_root(self, config, incremental=False): - """Configure a root logger from a dictionary.""" - root = logging.getLogger() - self.common_logger_config(root, config, incremental) - -dictConfigClass = DictConfigurator - - -def dictConfig(config): - """Configure logging using a dictionary.""" - dictConfigClass(config).configure() diff --git a/pip/download.py b/pip/download.py index 10790b5a1cc..6080e31b3df 100644 --- a/pip/download.py +++ b/pip/download.py @@ -116,8 +116,7 @@ def user_agent(): if platform.machine(): data["cpu"] = platform.machine() - # Python 2.6 doesn't have ssl.OPENSSL_VERSION. - if HAS_TLS and sys.version_info[:2] > (2, 6): + if HAS_TLS: data["openssl_version"] = ssl.OPENSSL_VERSION return "{data[installer][name]}/{data[installer][version]} {json}".format( diff --git a/pip/index.py b/pip/index.py index f653f6e6a6e..0e7b94b4326 100644 --- a/pip/index.py +++ b/pip/index.py @@ -833,7 +833,7 @@ def _handle_fail(link, reason, url, meth=None): def _get_content_type(url, session): """Get the Content-Type of the given url, using a HEAD request""" scheme, netloc, path, query, fragment = urllib_parse.urlsplit(url) - if scheme not in ('http', 'https'): + if scheme not in {'http', 'https'}: # FIXME: some warning or something? # assertion error? return '' @@ -1067,7 +1067,7 @@ def fmt_ctl_handle_mutual_exclude(value, target, other): def fmt_ctl_formats(fmt_ctl, canonical_name): - result = set(["binary", "source"]) + result = {"binary", "source"} if canonical_name in fmt_ctl.only_binary: result.discard('source') elif canonical_name in fmt_ctl.no_binary: diff --git a/pip/locations.py b/pip/locations.py index e598ef105a4..90902bde5f9 100644 --- a/pip/locations.py +++ b/pip/locations.py @@ -3,10 +3,12 @@ import os import os.path +import platform import site import sys +import sysconfig -from distutils import sysconfig +from distutils import sysconfig as distutils_sysconfig from distutils.command.install import install, SCHEME_KEYS # noqa from pip.compat import WINDOWS, expanduser @@ -80,7 +82,12 @@ def virtualenv_no_global(): # FIXME doesn't account for venv linked to global site-packages -site_packages = sysconfig.get_python_lib() +site_packages = sysconfig.get_path("purelib") +# This is because of a bug in PyPy's sysconfig module, see +# https://bitbucket.org/pypy/pypy/issues/2506/sysconfig-returns-incorrect-paths +# for more information. +if platform.python_implementation().lower() == "pypy": + site_packages = distutils_sysconfig.get_python_lib() user_site = site.USER_SITE user_dir = expanduser('~') if WINDOWS: diff --git a/pip/operations/check.py b/pip/operations/check.py index ebadc2fd374..eef0630fc0a 100644 --- a/pip/operations/check.py +++ b/pip/operations/check.py @@ -22,7 +22,7 @@ def get_missing_reqs(dist, installed_dists): `installed_dists`. """ - installed_names = set(d.project_name.lower() for d in installed_dists) + installed_names = {d.project_name.lower() for d in installed_dists} missing_requirements = set() for requirement in dist.requires(): diff --git a/pip/pep425tags.py b/pip/pep425tags.py index ad202ef313f..1278e0f638e 100644 --- a/pip/pep425tags.py +++ b/pip/pep425tags.py @@ -1,20 +1,16 @@ """Generate and work with PEP 425 Compatibility Tags.""" from __future__ import absolute_import +import distutils.util import re import sys +import sysconfig import warnings import platform import logging -try: - import sysconfig -except ImportError: # pragma nocover - # Python < 2.7 - import distutils.sysconfig as sysconfig -import distutils.util +from collections import OrderedDict -from pip.compat import OrderedDict import pip.utils.glibc logger = logging.getLogger(__name__) @@ -26,7 +22,7 @@ def get_config_var(var): try: return sysconfig.get_config_var(var) except IOError as e: # Issue #1074 - warnings.warn("{0}".format(e), RuntimeWarning) + warnings.warn("{}".format(e), RuntimeWarning) return None @@ -66,7 +62,7 @@ def get_impl_tag(): """ Returns the Tag for this specific implementation. """ - return "{0}{1}".format(get_abbr_impl(), get_impl_ver()) + return "{}{}".format(get_abbr_impl(), get_impl_ver()) def get_flag(var, fallback, expected=True, warn=True): @@ -86,7 +82,7 @@ def get_abi_tag(): (CPython 2, PyPy).""" soabi = get_config_var('SOABI') impl = get_abbr_impl() - if not soabi and impl in ('cp', 'pp') and hasattr(sys, 'maxunicode'): + if not soabi and impl in {'cp', 'pp'} and hasattr(sys, 'maxunicode'): d = '' m = '' u = '' @@ -133,7 +129,7 @@ def get_platform(): elif machine == "ppc64" and _is_running_32bit(): machine = "ppc" - return 'macosx_{0}_{1}_{2}'.format(split_ver[0], split_ver[1], machine) + return 'macosx_{}_{}_{}'.format(split_ver[0], split_ver[1], machine) # XXX remove distutils dependency result = distutils.util.get_platform().replace('.', '_').replace('-', '_') @@ -147,7 +143,7 @@ def get_platform(): def is_manylinux1_compatible(): # Only Linux, and only x86-64 / i686 - if get_platform() not in ("linux_x86_64", "linux_i686"): + if get_platform() not in {"linux_x86_64", "linux_i686"}: return False # Check for presence of _manylinux module @@ -273,7 +269,7 @@ def get_supported(versions=None, noarch=False, platform=None, match = _osx_arch_pat.match(arch) if match: name, major, minor, actual_arch = match.groups() - tpl = '{0}_{1}_%i_%s'.format(name, major) + tpl = '{}_{}_%i_%s'.format(name, major) arches = [] for m in reversed(range(int(minor) + 1)): for a in get_darwin_arches(int(major), m, actual_arch): @@ -294,7 +290,7 @@ def get_supported(versions=None, noarch=False, platform=None, # abi3 modules compatible with older version of Python for version in versions[1:]: # abi3 was introduced in Python 3.2 - if version in ('31', '30'): + if version in {'31', '30'}: break for abi in abi3s: # empty set if not Python 3 for arch in arches: @@ -318,6 +314,7 @@ def get_supported(versions=None, noarch=False, platform=None, return supported + supported_tags = get_supported() supported_tags_noarch = get_supported(noarch=True) diff --git a/pip/req/req_install.py b/pip/req/req_install.py index df4b0621d6a..0498450bbfe 100644 --- a/pip/req/req_install.py +++ b/pip/req/req_install.py @@ -5,12 +5,12 @@ import re import shutil import sys +import sysconfig import tempfile import traceback import warnings import zipfile -from distutils import sysconfig from distutils.util import change_root from email.parser import FeedParser @@ -320,7 +320,7 @@ def is_pinned(self): """ specifiers = self.specifier return (len(specifiers) == 1 and - next(iter(specifiers)).operator in ('==', '===')) + next(iter(specifiers)).operator in {'==', '==='}) def from_path(self): if self.req is None: diff --git a/pip/req/req_uninstall.py b/pip/req/req_uninstall.py index d22717c0c9c..6a98b1952ec 100644 --- a/pip/req/req_uninstall.py +++ b/pip/req/req_uninstall.py @@ -5,13 +5,13 @@ import logging import os import sys +import sysconfig import tempfile import warnings from pip._vendor import pkg_resources from pip.compat import uses_pycache, WINDOWS, cache_from_source -from pip.compat import get_stdlib from pip.exceptions import UninstallationError from pip.locations import ( bin_py, bin_user, @@ -223,7 +223,9 @@ def from_dist(cls, dist): ) return cls(dist) - if dist_path in get_stdlib(): + if dist_path in {p for p in {sysconfig.get_path("stdlib"), + sysconfig.get_path("platstdlib")} + if p}: logger.info( "Not uninstalling %s at %s, as it is in the standard library.", dist.key, diff --git a/pip/utils/__init__.py b/pip/utils/__init__.py index a471ab8aefb..7567404ded3 100644 --- a/pip/utils/__init__.py +++ b/pip/utils/__init__.py @@ -308,7 +308,7 @@ def dist_in_usersite(dist): def dist_in_site_packages(dist): """ Return True if given Distribution is installed in - distutils.sysconfig.get_python_lib(). + sysconfig.get_python_lib(). """ return normalize_path( dist_location(dist) diff --git a/pip/utils/deprecation.py b/pip/utils/deprecation.py index c3f799e64a8..71ec1ad0a24 100644 --- a/pip/utils/deprecation.py +++ b/pip/utils/deprecation.py @@ -23,10 +23,6 @@ class RemovedInPip11Warning(PipDeprecationWarning, Pending): pass -class Python26DeprecationWarning(PipDeprecationWarning): - pass - - # Warnings <-> Logging Integration diff --git a/pip/utils/outdated.py b/pip/utils/outdated.py index 2164cc3cc2f..78ca2bbeb44 100644 --- a/pip/utils/outdated.py +++ b/pip/utils/outdated.py @@ -9,7 +9,7 @@ from pip._vendor import lockfile from pip._vendor.packaging import version as packaging_version -from pip.compat import total_seconds, WINDOWS +from pip.compat import WINDOWS from pip.models import PyPI from pip.locations import USER_CACHE_DIR, running_under_virtualenv from pip.utils import ensure_dir, get_installed_version @@ -116,7 +116,7 @@ def pip_version_check(session): state.state["last_check"], SELFCHECK_DATE_FMT ) - if total_seconds(current_time - last_check) < 7 * 24 * 60 * 60: + if (current_time - last_check).total_seconds() < 7 * 24 * 60 * 60: pypi_version = state.state["pypi_version"] # Refresh the version if we need to or just see if we need to warn diff --git a/pip/wheel.py b/pip/wheel.py index 3ea04d953ac..56887d1cb67 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -126,7 +126,7 @@ def cached_wheel(cache_dir, link, format_control, package_name): try: wheel_names = os.listdir(root) except OSError as e: - if e.errno in (errno.ENOENT, errno.ENOTDIR): + if e.errno in {errno.ENOENT, errno.ENOTDIR}: return link raise candidates = [] diff --git a/setup.py b/setup.py index 20f9fee9f19..71c6b15938c 100644 --- a/setup.py +++ b/setup.py @@ -39,6 +39,7 @@ def find_version(*file_paths): return version_match.group(1) raise RuntimeError("Unable to find version string.") + long_description = read('README.rst') tests_require = ['pytest', 'virtualenv>=1.10', 'scripttest>=1.3', 'mock', @@ -56,7 +57,6 @@ def find_version(*file_paths): "License :: OSI Approved :: MIT License", "Topic :: Software Development :: Build Tools", "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", @@ -86,7 +86,7 @@ def find_version(*file_paths): }, tests_require=tests_require, zip_safe=False, - python_requires='>=2.6,!=3.0.*,!=3.1.*,!=3.2.*', + python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*', extras_require={ 'testing': tests_require, }, diff --git a/tests/data/src/sample/setup.py b/tests/data/src/sample/setup.py index fe2c2864b59..8dc8a066055 100644 --- a/tests/data/src/sample/setup.py +++ b/tests/data/src/sample/setup.py @@ -60,7 +60,6 @@ def find_version(*file_paths): # Specify the Python versions you support here. In particular, ensure # that you indicate whether you support Python 2, Python 3 or both. 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.1', diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py index 51b1da82f65..2852bed358a 100644 --- a/tests/functional/test_install.py +++ b/tests/functional/test_install.py @@ -394,7 +394,7 @@ def test_editable_install_from_local_directory_with_no_setup_py(script, data): assert "is not installable. File 'setup.py' not found." in result.stderr -@pytest.mark.skipif("sys.version_info < (2,7) or sys.version_info >= (3,4)") +@pytest.mark.skipif("sys.version_info >= (3,4)") @pytest.mark.xfail def test_install_argparse_shadowed(script, data): # When argparse is in the stdlib, we support installing it diff --git a/tests/functional/test_install_upgrade.py b/tests/functional/test_install_upgrade.py index 10c95717a31..0c8293f39c4 100644 --- a/tests/functional/test_install_upgrade.py +++ b/tests/functional/test_install_upgrade.py @@ -461,14 +461,12 @@ def test_from_distribute_6_to_setuptools_7( result = self.script.run( self.ve_bin / 'pip', 'install', '--no-index', '--find-links=%s' % data.find_links, '-U', 'distribute', - expect_stderr=True if sys.version_info[:2] == (2, 6) else False, ) assert ( "Found existing installation: distribute 0.6.34" in result.stdout ) result = self.script.run( self.ve_bin / 'pip', 'list', '--format=legacy', - expect_stderr=True if sys.version_info[:2] == (2, 6) else False, ) assert "setuptools (0.9.8)" in result.stdout assert "distribute (0.7.3)" in result.stdout diff --git a/tests/functional/test_warning.py b/tests/functional/test_warning.py index ba445b95ba1..56e2b27ed7d 100644 --- a/tests/functional/test_warning.py +++ b/tests/functional/test_warning.py @@ -1,19 +1,4 @@ -import pytest -PY26_WARNING = "Python 2.6 is no longer supported" - - -@pytest.mark.skipif("sys.version_info >= (2,7)") -def test_python26_options(script): - result = script.run( - 'python', '-m', 'pip.__main__', 'list', expect_stderr=True, - ) - assert PY26_WARNING in result.stderr - result = script.run('python', '-W', 'ignore', '-m', 'pip.__main__', 'list') - assert result.stderr == '' - - -@pytest.mark.skipif("sys.version_info < (2,7)") def test_environ(script, tmpdir): """$PYTHONWARNINGS was added in python2.7""" demo = tmpdir.join('warnings_demo.py') diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py index c05fd494b36..a4e9e816623 100644 --- a/tests/lib/__init__.py +++ b/tests/lib/__init__.py @@ -334,9 +334,6 @@ def pip(self, *args, **kwargs): if (pyversion_tuple < (2, 7, 9) and args and args[0] in ('search', 'install', 'download')): kwargs['expect_stderr'] = True - # Python 2.6 is deprecated and we emit a warning on it. - if pyversion_tuple[:2] == (2, 6): - kwargs['expect_stderr'] = True return self.run("pip", *args, **kwargs) diff --git a/tests/unit/test_finder.py b/tests/unit/test_finder.py index d6758f0ede2..50c342c42b6 100644 --- a/tests/unit/test_finder.py +++ b/tests/unit/test_finder.py @@ -383,9 +383,7 @@ def test_finder_only_installs_data_require(data): links = finder.find_all_candidates("fakepackage") expected = ['1.0.0', '9.9.9'] - if sys.version_info < (2, 7): - expected.append('2.6.0') - elif (2, 7) < sys.version_info < (3,): + if (2, 7) < sys.version_info < (3,): expected.append('2.7.0') elif sys.version_info > (3, 3): expected.append('3.3.0') diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 3aa785531ee..e29d00020b9 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -238,19 +238,6 @@ def test_user_only(self, mock_dist_is_editable, assert len(dists) == 1 assert dists[0].test_name == "user" - @pytest.mark.skipif("sys.version_info >= (2,7)") - @patch('pip._vendor.pkg_resources.working_set', workingset_stdlib) - def test_py26_excludes(self, mock_dist_is_editable, - mock_dist_is_local, - mock_dist_in_usersite): - mock_dist_is_editable.side_effect = self.dist_is_editable - mock_dist_is_local.side_effect = self.dist_is_local - mock_dist_in_usersite.side_effect = self.dist_in_usersite - dists = get_installed_distributions() - assert len(dists) == 1 - assert dists[0].key == 'argparse' - - @pytest.mark.skipif("sys.version_info < (2,7)") @patch('pip._vendor.pkg_resources.working_set', workingset_stdlib) def test_gte_py27_excludes(self, mock_dist_is_editable, mock_dist_is_local, diff --git a/tox.ini b/tox.ini index c5e596381f4..64637d52601 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - docs, packaging, pep8, py3pep8, py26, py27, py33, py34, py35, py36, py37, pypy + docs, packaging, pep8, py3pep8, py27, py33, py34, py35, py36, py37, pypy [testenv] setenv = @@ -11,9 +11,6 @@ deps = -r{toxinidir}/dev-requirements.txt commands = py.test --timeout 300 [] install_command = python -m pip install {opts} {packages} -[testenv:py26] -install_command = pip install {opts} {packages} - [testenv:docs] deps = sphinx basepython = python2.7