diff options
Diffstat (limited to 'setuptools')
42 files changed, 632 insertions, 490 deletions
diff --git a/setuptools/__init__.py b/setuptools/__init__.py index b0a5401f..b8cec4c3 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -8,11 +8,12 @@ from distutils.core import Command as _Command from distutils.util import convert_path from fnmatch import fnmatchcase +from setuptools.extern.six.moves import filterfalse, map + import pkg_resources from setuptools.extension import Extension from setuptools.dist import Distribution, Feature, _get_unpatched from setuptools.depends import Require -from setuptools.compat import filterfalse __all__ = [ 'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require', diff --git a/setuptools/command/__init__.py b/setuptools/command/__init__.py index f6dbc39c..3fb2f6df 100644 --- a/setuptools/command/__init__.py +++ b/setuptools/command/__init__.py @@ -2,7 +2,7 @@ __all__ = [ 'alias', 'bdist_egg', 'bdist_rpm', 'build_ext', 'build_py', 'develop', 'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts', 'sdist', 'setopt', 'test', 'install_egg_info', 'install_scripts', - 'register', 'bdist_wininst', 'upload_docs', + 'register', 'bdist_wininst', 'upload_docs', 'upload', ] from distutils.command.bdist import bdist diff --git a/setuptools/command/alias.py b/setuptools/command/alias.py index 452a9244..4532b1cc 100755 --- a/setuptools/command/alias.py +++ b/setuptools/command/alias.py @@ -1,5 +1,7 @@ from distutils.errors import DistutilsOptionError +from setuptools.extern.six.moves import map + from setuptools.command.setopt import edit_config, option_base, config_file diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py index 87dce882..9cebd7fa 100644 --- a/setuptools/command/bdist_egg.py +++ b/setuptools/command/bdist_egg.py @@ -11,9 +11,10 @@ import os import marshal import textwrap +from setuptools.extern import six + from pkg_resources import get_build_platform, Distribution, ensure_directory from pkg_resources import EntryPoint -from setuptools.compat import basestring from setuptools.extension import Library from setuptools import Command @@ -413,7 +414,7 @@ def iter_symbols(code): for name in code.co_names: yield name for const in code.co_consts: - if isinstance(const, basestring): + if isinstance(const, six.string_types): yield const elif isinstance(const, CodeType): for name in iter_symbols(const): diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py index 8a50f032..8623c777 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -9,6 +9,7 @@ import distutils.errors import collections import itertools +from setuptools.extern.six.moves import map try: from setuptools.lib2to3_ex import Mixin2to3 @@ -59,9 +60,10 @@ class build_py(orig.build_py, Mixin2to3): self.byte_compile(orig.build_py.get_outputs(self, include_bytecode=0)) def __getattr__(self, attr): - if attr == 'data_files': # lazily compute data files - self.data_files = files = self._get_data_files() - return files + "lazily compute data files" + if attr == 'data_files': + self.data_files = self._get_data_files() + return self.data_files return orig.build_py.__getattr__(self, attr) def build_module(self, module, module_file, package): @@ -74,23 +76,21 @@ class build_py(orig.build_py, Mixin2to3): def _get_data_files(self): """Generate list of '(package,src_dir,build_dir,filenames)' tuples""" self.analyze_manifest() - data = [] - for package in self.packages or (): - # Locate package source directory - src_dir = self.get_package_dir(package) + return list(map(self._get_pkg_data_files, self.packages or ())) - # Compute package build directory - build_dir = os.path.join(*([self.build_lib] + package.split('.'))) + def _get_pkg_data_files(self, package): + # Locate package source directory + src_dir = self.get_package_dir(package) - # Length of path to strip from found files - plen = len(src_dir) + 1 + # Compute package build directory + build_dir = os.path.join(*([self.build_lib] + package.split('.'))) - # Strip directory from globbed filenames - filenames = [ - file[plen:] for file in self.find_data_files(package, src_dir) - ] - data.append((package, src_dir, build_dir, filenames)) - return data + # Strip directory from globbed filenames + filenames = [ + os.path.relpath(file, src_dir) + for file in self.find_data_files(package, src_dir) + ] + return package, src_dir, build_dir, filenames def find_data_files(self, package, src_dir): """Return filenames for package's data files in 'src_dir'""" diff --git a/setuptools/command/develop.py b/setuptools/command/develop.py index 07b66ccb..11b5df10 100755 --- a/setuptools/command/develop.py +++ b/setuptools/command/develop.py @@ -5,9 +5,10 @@ import os import glob import io +from setuptools.extern import six + from pkg_resources import Distribution, PathMetadata, normalize_path from setuptools.command.easy_install import easy_install -from setuptools.compat import PY3 import setuptools @@ -87,7 +88,7 @@ class develop(easy_install): " installation directory", p, normalize_path(os.curdir)) def install_for_development(self): - if PY3 and getattr(self.distribution, 'use_2to3', False): + if six.PY3 and getattr(self.distribution, 'use_2to3', False): # If we run 2to3 we can not do this inplace: # Ensure metadata is up-to-date diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 9ca3b515..ea5cb028 100755 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -40,6 +40,9 @@ import subprocess import shlex import io +from setuptools.extern import six +from setuptools.extern.six.moves import configparser, map + from setuptools import Command from setuptools.sandbox import run_setup from setuptools.py31compat import get_path, get_config_vars @@ -48,8 +51,6 @@ from setuptools.archive_util import unpack_archive from setuptools.package_index import PackageIndex from setuptools.package_index import URL_SCHEME from setuptools.command import bdist_egg, egg_info -from setuptools.compat import (iteritems, maxsize, basestring, unicode, - reraise, PY2, PY3) from pkg_resources import ( yield_lines, normalize_path, resource_string, ensure_directory, get_distribution, find_distributions, Environment, Requirement, @@ -82,13 +83,13 @@ def samefile(p1, p2): return norm_p1 == norm_p2 -if PY2: +if six.PY2: def _to_ascii(s): return s def isascii(s): try: - unicode(s, 'ascii') + six.text_type(s, 'ascii') return True except UnicodeError: return False @@ -320,7 +321,7 @@ class easy_install(Command): self.local_index = Environment(self.shadow_path + sys.path) if self.find_links is not None: - if isinstance(self.find_links, basestring): + if isinstance(self.find_links, six.string_types): self.find_links = self.find_links.split() else: self.find_links = [] @@ -413,7 +414,7 @@ class easy_install(Command): try: pid = os.getpid() except: - pid = random.randint(0, maxsize) + pid = random.randint(0, sys.maxsize) return os.path.join(self.install_dir, "test-easy-install-%s" % pid) def warn_deprecated_options(self): @@ -709,10 +710,7 @@ class easy_install(Command): elif requirement is None or dist not in requirement: # if we wound up with a different version, resolve what we've got distreq = dist.as_requirement() - requirement = requirement or distreq - requirement = Requirement( - distreq.project_name, distreq.specs, requirement.extras - ) + requirement = Requirement(str(distreq)) log.info("Processing dependencies for %s", requirement) try: distros = WorkingSet([]).resolve( @@ -782,7 +780,7 @@ class easy_install(Command): There are a couple of template scripts in the package. This function loads one of them and prepares it for use. """ - # See https://bitbucket.org/pypa/setuptools/issue/134 for info + # See https://github.com/pypa/setuptools/issues/134 for info # on script file naming and downstream issues with SVR4 name = 'script.tmpl' if dev_path: @@ -1238,17 +1236,14 @@ class easy_install(Command): sitepy = os.path.join(self.install_dir, "site.py") source = resource_string("setuptools", "site-patch.py") + source = source.decode('utf-8') current = "" if os.path.exists(sitepy): log.debug("Checking existing site.py in %s", self.install_dir) - f = open(sitepy, 'rb') - current = f.read() - # we want str, not bytes - if PY3: - current = current.decode() + with io.open(sitepy) as strm: + current = strm.read() - f.close() if not current.startswith('def __boot():'): raise DistutilsError( "%s is not a setuptools-generated site.py; please" @@ -1259,9 +1254,8 @@ class easy_install(Command): log.info("Creating %s", sitepy) if not self.dry_run: ensure_directory(sitepy) - f = open(sitepy, 'wb') - f.write(source) - f.close() + with io.open(sitepy, 'w', encoding='utf-8') as strm: + strm.write(source) self.byte_compile([sitepy]) self.sitepy_installed = True @@ -1271,7 +1265,7 @@ class easy_install(Command): if not self.user: return home = convert_path(os.path.expanduser("~")) - for name, path in iteritems(self.config_vars): + for name, path in six.iteritems(self.config_vars): if path.startswith(home) and not os.path.isdir(path): self.debug_print("os.makedirs('%s', 0o700)" % path) os.makedirs(path, 0o700) @@ -1415,9 +1409,6 @@ def extract_wininst_cfg(dist_filename): return None f.seek(prepended - 12) - from setuptools.compat import StringIO, configparser - import struct - tag, cfglen, bmlen = struct.unpack("<iii", f.read(12)) if tag not in (0x1234567A, 0x1234567B): return None # not a valid tag @@ -1432,7 +1423,7 @@ def extract_wininst_cfg(dist_filename): # Now the config is in bytes, but for RawConfigParser, it should # be text, so decode it. config = config.decode(sys.getfilesystemencoding()) - cfg.readfp(StringIO(config)) + cfg.readfp(six.StringIO(config)) except configparser.Error: return None if not cfg.has_section('metadata') or not cfg.has_section('Setup'): @@ -1467,7 +1458,7 @@ def get_exe_prefixes(exe_filename): continue if parts[0].upper() in ('PURELIB', 'PLATLIB'): contents = z.read(name) - if PY3: + if six.PY3: contents = contents.decode() for pth in yield_lines(contents): pth = pth.strip().replace('\\', '/') @@ -1643,7 +1634,7 @@ def auto_chmod(func, arg, exc): chmod(arg, stat.S_IWRITE) return func(arg) et, ev, _ = sys.exc_info() - reraise(et, (ev[0], ev[1] + (" %s %s" % (func, arg)))) + six.reraise(et, (ev[0], ev[1] + (" %s %s" % (func, arg)))) def update_dist_caches(dist_path, fix_zipimporter_caches): @@ -1771,7 +1762,7 @@ def _update_zipimporter_cache(normalized_path, cache, updater=None): # * Does not support the dict.pop() method, forcing us to use the # get/del patterns instead. For more detailed information see the # following links: - # https://bitbucket.org/pypa/setuptools/issue/202/more-robust-zipimporter-cache-invalidation#comment-10495960 + # https://github.com/pypa/setuptools/issues/202#issuecomment-202913420 # https://bitbucket.org/pypy/pypy/src/dd07756a34a41f674c0cacfbc8ae1d4cc9ea2ae4/pypy/module/zipimport/interp_zipimport.py#cl-99 old_entry = cache[p] del cache[p] @@ -1878,17 +1869,6 @@ def chmod(path, mode): log.debug("chmod failed: %s", e) -def fix_jython_executable(executable, options): - warnings.warn("Use JythonCommandSpec", DeprecationWarning, stacklevel=2) - - if not JythonCommandSpec.relevant(): - return executable - - cmd = CommandSpec.best().from_param(executable) - cmd.install_options(options) - return cmd.as_header().lstrip('#!').rstrip('\n') - - class CommandSpec(list): """ A command spec for a #! header, specified as a list of arguments akin to @@ -1903,7 +1883,7 @@ class CommandSpec(list): """ Choose the best CommandSpec class based on environmental conditions. """ - return cls if not JythonCommandSpec.relevant() else JythonCommandSpec + return cls @classmethod def _sys_executable(cls): @@ -1970,36 +1950,6 @@ class WindowsCommandSpec(CommandSpec): split_args = dict(posix=False) -class JythonCommandSpec(CommandSpec): - @classmethod - def relevant(cls): - return ( - sys.platform.startswith('java') - and - __import__('java').lang.System.getProperty('os.name') != 'Linux' - ) - - def as_header(self): - """ - Workaround Jython's sys.executable being a .sh (an invalid - shebang line interpreter) - """ - if not is_sh(self[0]): - return super(JythonCommandSpec, self).as_header() - - if self.options: - # Can't apply the workaround, leave it broken - log.warn( - "WARNING: Unable to adapt shebang line for Jython," - " the following script is NOT executable\n" - " see http://bugs.jython.org/issue1112 for" - " more information.") - return super(JythonCommandSpec, self).as_header() - - items = ['/usr/bin/env'] + self + list(self.options) - return self._render(items) - - class ScriptWriter(object): """ Encapsulates behavior around writing entry point scripts for console and @@ -2076,7 +2026,10 @@ class ScriptWriter(object): """ Select the best ScriptWriter for this environment. """ - return WindowsScriptWriter.best() if sys.platform == 'win32' else cls + if sys.platform == 'win32' or (os.name == 'java' and os._name == 'nt'): + return WindowsScriptWriter.best() + else: + return cls @classmethod def _get_script_args(cls, type_, name, header, script_text): @@ -2209,7 +2162,7 @@ def get_win_launcher(type): def load_launcher_manifest(name): manifest = pkg_resources.resource_string(__name__, 'launcher manifest.xml') - if PY2: + if six.PY2: return manifest % vars() else: return manifest.decode('utf-8') % vars() diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index 1301bd84..d1bd9b04 100755 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -14,7 +14,8 @@ import io import warnings import time -from setuptools.compat import basestring, PY3, StringIO +from setuptools.extern import six +from setuptools.extern.six.moves import map from setuptools import Command from setuptools.command.sdist import sdist @@ -26,7 +27,7 @@ from pkg_resources import ( safe_version, yield_lines, EntryPoint, iter_entry_points, to_filename) import setuptools.unicode_utils as unicode_utils -from pkg_resources import packaging +from pkg_resources.extern import packaging try: from setuptools_svn import svn_utils @@ -148,7 +149,7 @@ class egg_info(Command): to the file. """ log.info("writing %s to %s", what, filename) - if PY3: + if six.PY3: data = data.encode("utf-8") if not self.dry_run: f = open(filename, 'wb') @@ -412,7 +413,7 @@ def _write_requirements(stream, reqs): def write_requirements(cmd, basename, filename): dist = cmd.distribution - data = StringIO() + data = six.StringIO() _write_requirements(data, dist.install_requires) extras_require = dist.extras_require or {} for extra in sorted(extras_require): @@ -452,12 +453,12 @@ def write_arg(cmd, basename, filename, force=False): def write_entries(cmd, basename, filename): ep = cmd.distribution.entry_points - if isinstance(ep, basestring) or ep is None: + if isinstance(ep, six.string_types) or ep is None: data = ep elif ep is not None: data = [] for section, contents in sorted(ep.items()): - if not isinstance(contents, basestring): + if not isinstance(contents, six.string_types): contents = EntryPoint.parse_group(section, contents) contents = '\n'.join(sorted(map(str, contents.values()))) data.append('[%s]\n%s\n\n' % (section, contents)) diff --git a/setuptools/command/install.py b/setuptools/command/install.py index d2bca2ec..31a5ddb5 100644 --- a/setuptools/command/install.py +++ b/setuptools/command/install.py @@ -8,7 +8,7 @@ import distutils.command.install as orig import setuptools # Prior to numpy 1.9, NumPy relies on the '_install' name, so provide it for -# now. See https://bitbucket.org/pypa/setuptools/issue/199/ +# now. See https://github.com/pypa/setuptools/issues/199/ _install = orig.install diff --git a/setuptools/command/install_egg_info.py b/setuptools/command/install_egg_info.py index fd0f118b..60b615d2 100755 --- a/setuptools/command/install_egg_info.py +++ b/setuptools/command/install_egg_info.py @@ -1,6 +1,8 @@ from distutils import log, dir_util import os +from setuptools.extern.six.moves import map + from setuptools import Command from setuptools.archive_util import unpack_archive import pkg_resources @@ -27,7 +29,7 @@ class install_egg_info(Command): ).egg_name() + '.egg-info' self.source = ei_cmd.egg_info self.target = os.path.join(self.install_dir, basename) - self.outputs = [self.target] + self.outputs = [] def run(self): self.run_command('egg_info') diff --git a/setuptools/command/rotate.py b/setuptools/command/rotate.py index 1b073620..804f962a 100755 --- a/setuptools/command/rotate.py +++ b/setuptools/command/rotate.py @@ -3,8 +3,9 @@ from distutils import log from distutils.errors import DistutilsOptionError import os +from setuptools.extern import six + from setuptools import Command -from setuptools.compat import basestring class rotate(Command): @@ -36,7 +37,7 @@ class rotate(Command): self.keep = int(self.keep) except ValueError: raise DistutilsOptionError("--keep must be an integer") - if isinstance(self.match, basestring): + if isinstance(self.match, six.string_types): self.match = [ convert_path(p.strip()) for p in self.match.split(',') ] diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index 71196512..6640d4e3 100755 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -5,7 +5,8 @@ import os import sys import io -from setuptools.compat import PY3 +from setuptools.extern import six + from setuptools.utils import cs_path_exists import pkg_resources @@ -181,7 +182,7 @@ class sdist(orig.sdist): manifest = open(self.manifest, 'rbU') for line in manifest: # The manifest must contain UTF-8. See #303. - if PY3: + if six.PY3: try: line = line.decode('UTF-8') except UnicodeDecodeError: diff --git a/setuptools/command/setopt.py b/setuptools/command/setopt.py index 74c7cad8..7f332be5 100755 --- a/setuptools/command/setopt.py +++ b/setuptools/command/setopt.py @@ -4,6 +4,8 @@ from distutils.errors import DistutilsOptionError import distutils import os +from setuptools.extern.six.moves import configparser + from setuptools import Command @@ -37,8 +39,6 @@ def edit_config(filename, settings, dry_run=False): while a dictionary lists settings to be changed or deleted in that section. A setting of ``None`` means to delete that setting. """ - from setuptools.compat import configparser - log.debug("Reading configuration from %s", filename) opts = configparser.RawConfigParser() opts.read([filename]) diff --git a/setuptools/command/test.py b/setuptools/command/test.py index c26f5fc9..371e913b 100644 --- a/setuptools/command/test.py +++ b/setuptools/command/test.py @@ -2,11 +2,13 @@ from distutils.errors import DistutilsOptionError from unittest import TestLoader import sys +from setuptools.extern import six +from setuptools.extern.six.moves import map + from pkg_resources import (resource_listdir, resource_exists, normalize_path, working_set, _namespace_packages, add_activation_listener, require, EntryPoint) from setuptools import Command -from setuptools.compat import PY3 from setuptools.py31compat import unittest_main @@ -100,7 +102,7 @@ class test(Command): yield self.test_suite def with_project_on_sys_path(self, func): - with_2to3 = PY3 and getattr(self.distribution, 'use_2to3', False) + with_2to3 = six.PY3 and getattr(self.distribution, 'use_2to3', False) if with_2to3: # If we run 2to3 we can not do this inplace: @@ -160,7 +162,7 @@ class test(Command): # Purge modules under test from sys.modules. The test loader will # re-import them from the build location. Required when 2to3 is used # with namespace packages. - if PY3 and getattr(self.distribution, 'use_2to3', False): + if six.PY3 and getattr(self.distribution, 'use_2to3', False): module = self.test_suite.split('.')[0] if module in _namespace_packages: del_modules = [] diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py new file mode 100644 index 00000000..08c20ba8 --- /dev/null +++ b/setuptools/command/upload.py @@ -0,0 +1,23 @@ +from distutils.command import upload as orig + + +class upload(orig.upload): + """ + Override default upload behavior to look up password + in the keyring if available. + """ + + def finalize_options(self): + orig.upload.finalize_options(self) + self.password or self._load_password_from_keyring() + + def _load_password_from_keyring(self): + """ + Attempt to load password from keyring. Suppress Exceptions. + """ + try: + keyring = __import__('keyring') + self.password = keyring.get_password(self.repository, + self.username) + except Exception: + pass diff --git a/setuptools/command/upload_docs.py b/setuptools/command/upload_docs.py index 001ee936..f887b47e 100644 --- a/setuptools/command/upload_docs.py +++ b/setuptools/command/upload_docs.py @@ -8,25 +8,26 @@ PyPI's pythonhosted.org). from base64 import standard_b64encode from distutils import log from distutils.errors import DistutilsOptionError -from distutils.command.upload import upload import os import socket import zipfile import tempfile -import sys import shutil -from setuptools.compat import httplib, urlparse, unicode, iteritems, PY3 +from setuptools.extern import six +from setuptools.extern.six.moves import http_client, urllib + from pkg_resources import iter_entry_points +from .upload import upload -errors = 'surrogateescape' if PY3 else 'strict' +errors = 'surrogateescape' if six.PY3 else 'strict' # This is not just a replacement for byte literals # but works as a general purpose encoder def b(s, encoding='utf-8'): - if isinstance(s, unicode): + if isinstance(s, six.text_type): return s.encode(encoding, errors) return s @@ -113,7 +114,7 @@ class upload_docs(upload): # set up the authentication credentials = b(self.username + ':' + self.password) credentials = standard_b64encode(credentials) - if PY3: + if six.PY3: credentials = credentials.decode('ascii') auth = "Basic " + credentials @@ -122,7 +123,7 @@ class upload_docs(upload): sep_boundary = b('\n--') + b(boundary) end_boundary = sep_boundary + b('--') body = [] - for key, values in iteritems(data): + for key, values in six.iteritems(data): title = '\nContent-Disposition: form-data; name="%s"' % key # handle multiple entries for the same name if not isinstance(values, list): @@ -150,12 +151,12 @@ class upload_docs(upload): # We can't use urllib2 since we need to send the Basic # auth right with the first request schema, netloc, url, params, query, fragments = \ - urlparse(self.repository) + urllib.parse.urlparse(self.repository) assert not params and not query and not fragments if schema == 'http': - conn = httplib.HTTPConnection(netloc) + conn = http_client.HTTPConnection(netloc) elif schema == 'https': - conn = httplib.HTTPSConnection(netloc) + conn = http_client.HTTPSConnection(netloc) else: raise AssertionError("unsupported schema " + schema) diff --git a/setuptools/compat.py b/setuptools/compat.py deleted file mode 100644 index f0175a5d..00000000 --- a/setuptools/compat.py +++ /dev/null @@ -1,70 +0,0 @@ -import sys -import itertools - -PY3 = sys.version_info >= (3,) -PY2 = not PY3 - -if PY2: - basestring = basestring - import __builtin__ as builtins - import ConfigParser as configparser - from StringIO import StringIO - BytesIO = StringIO - func_code = lambda o: o.func_code - func_globals = lambda o: o.func_globals - im_func = lambda o: o.im_func - from htmlentitydefs import name2codepoint - import httplib - from BaseHTTPServer import HTTPServer - from SimpleHTTPServer import SimpleHTTPRequestHandler - from BaseHTTPServer import BaseHTTPRequestHandler - iteritems = lambda o: o.iteritems() - long_type = long - maxsize = sys.maxint - unichr = unichr - unicode = unicode - bytes = str - from urllib import url2pathname, splittag, pathname2url - import urllib2 - from urllib2 import urlopen, HTTPError, URLError, unquote, splituser - from urlparse import urlparse, urlunparse, urljoin, urlsplit, urlunsplit - filterfalse = itertools.ifilterfalse - filter = itertools.ifilter - map = itertools.imap - - exec("""def reraise(tp, value, tb=None): - raise tp, value, tb""") - -if PY3: - basestring = str - import builtins - import configparser - from io import StringIO, BytesIO - func_code = lambda o: o.__code__ - func_globals = lambda o: o.__globals__ - im_func = lambda o: o.__func__ - from html.entities import name2codepoint - import http.client as httplib - from http.server import HTTPServer, SimpleHTTPRequestHandler - from http.server import BaseHTTPRequestHandler - iteritems = lambda o: o.items() - long_type = int - maxsize = sys.maxsize - unichr = chr - unicode = str - bytes = bytes - from urllib.error import HTTPError, URLError - import urllib.request as urllib2 - from urllib.request import urlopen, url2pathname, pathname2url - from urllib.parse import ( - urlparse, urlunparse, unquote, splituser, urljoin, urlsplit, - urlunsplit, splittag, - ) - filterfalse = itertools.filterfalse - filter = filter - map = map - - def reraise(tp, value, tb=None): - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value diff --git a/setuptools/depends.py b/setuptools/depends.py index e87ef3f3..9f7c9a35 100644 --- a/setuptools/depends.py +++ b/setuptools/depends.py @@ -3,7 +3,8 @@ import imp import marshal from imp import PKG_DIRECTORY, PY_COMPILED, PY_SOURCE, PY_FROZEN from distutils.version import StrictVersion -from setuptools import compat + +from setuptools.extern import six __all__ = [ 'Require', 'find_module', 'get_module_constant', 'extract_constant' @@ -99,7 +100,8 @@ def _iter_code(code): ptr += 3 if op==EXTENDED_ARG: - extended_arg = arg * compat.long_type(65536) + long_type = six.integer_types[-1] + extended_arg = arg * long_type(65536) continue else: diff --git a/setuptools/dist.py b/setuptools/dist.py index b0c52838..086e0a58 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -13,13 +13,14 @@ from distutils.core import Distribution as _Distribution from distutils.errors import (DistutilsOptionError, DistutilsPlatformError, DistutilsSetupError) +from setuptools.extern import six +from setuptools.extern.six.moves import map +from pkg_resources.extern import packaging + from setuptools.depends import Require -from setuptools.compat import basestring, PY2 from setuptools import windows_support import pkg_resources -packaging = pkg_resources.packaging - def _get_unpatched(cls): """Protect against re-patching the distutils if reloaded @@ -138,7 +139,7 @@ def check_entry_points(dist, attr, value): raise DistutilsSetupError(e) def check_test_suite(dist, attr, value): - if not isinstance(value,basestring): + if not isinstance(value, six.string_types): raise DistutilsSetupError("test_suite must be a string") def check_package_data(dist, attr, value): @@ -160,7 +161,7 @@ def check_packages(dist, attr, value): for pkgname in value: if not re.match(r'\w+(\.\w+)*', pkgname): distutils.log.warn( - "WARNING: %r not a valid package name; please use only" + "WARNING: %r not a valid package name; please use only " ".-separated package names in setup.py", pkgname ) @@ -439,6 +440,14 @@ class Distribution(_Distribution): self.cmdclass[ep.name] = cmdclass return _Distribution.print_commands(self) + def get_command_list(self): + for ep in pkg_resources.iter_entry_points('distutils.commands'): + if ep.name not in self.cmdclass: + # don't require extras as the commands won't be invoked + cmdclass = ep.resolve() + self.cmdclass[ep.name] = cmdclass + return _Distribution.get_command_list(self) + def _set_feature(self,name,status): """Set feature's inclusion status""" setattr(self,self._feature_attrname(name),status) @@ -674,7 +683,7 @@ class Distribution(_Distribution): """ import sys - if PY2 or self.help_commands: + if six.PY2 or self.help_commands: return _Distribution.handle_display_options(self, option_order) # Stdout may be StringIO (e.g. in tests) @@ -711,7 +720,7 @@ class Feature: """ **deprecated** -- The `Feature` facility was never completely implemented or supported, `has reported issues - <https://bitbucket.org/pypa/setuptools/issue/58>`_ and will be removed in + <https://github.com/pypa/setuptools/issues/58>`_ and will be removed in a future version. A subset of the distribution that can be excluded if unneeded/wanted @@ -768,7 +777,7 @@ class Feature: def warn_deprecated(): warnings.warn( "Features are deprecated and will be removed in a future " - "version. See http://bitbucket.org/pypa/setuptools/65.", + "version. See https://github.com/pypa/setuptools/issues/65.", DeprecationWarning, stacklevel=3, ) @@ -817,7 +826,7 @@ class Feature: if not self.available: raise DistutilsPlatformError( - self.description+" is required," + self.description+" is required, " "but is not available on this platform" ) diff --git a/setuptools/extension.py b/setuptools/extension.py index 35eb7c7c..d10609b6 100644 --- a/setuptools/extension.py +++ b/setuptools/extension.py @@ -5,6 +5,8 @@ import distutils.core import distutils.errors import distutils.extension +from setuptools.extern.six.moves import map + from .dist import _get_unpatched from . import msvc9_support diff --git a/setuptools/extern/__init__.py b/setuptools/extern/__init__.py new file mode 100644 index 00000000..6859aa5b --- /dev/null +++ b/setuptools/extern/__init__.py @@ -0,0 +1,5 @@ +from pkg_resources.extern import VendorImporter + + +names = 'six', +VendorImporter(__name__, names, 'pkg_resources._vendor').install() diff --git a/setuptools/launch.py b/setuptools/launch.py new file mode 100644 index 00000000..06e15e1e --- /dev/null +++ b/setuptools/launch.py @@ -0,0 +1,35 @@ +""" +Launch the Python script on the command line after +setuptools is bootstrapped via import. +""" + +# Note that setuptools gets imported implicitly by the +# invocation of this script using python -m setuptools.launch + +import tokenize +import sys + + +def run(): + """ + Run the script in sys.argv[1] as if it had + been invoked naturally. + """ + __builtins__ + script_name = sys.argv[1] + namespace = dict( + __file__ = script_name, + __name__ = '__main__', + __doc__ = None, + ) + sys.argv[:] = sys.argv[1:] + + open_ = getattr(tokenize, 'open', open) + script = open_(script_name).read() + norm_script = script.replace('\\r\\n', '\\n') + code = compile(norm_script, script_name, 'exec') + exec(code, namespace) + + +if __name__ == '__main__': + run() diff --git a/setuptools/package_index.py b/setuptools/package_index.py index 2c565e88..c53343e4 100755 --- a/setuptools/package_index.py +++ b/setuptools/package_index.py @@ -9,6 +9,14 @@ import hashlib import itertools from functools import wraps +try: + from urllib.parse import splituser +except ImportError: + from urllib2 import splituser + +from setuptools.extern import six +from setuptools.extern.six.moves import urllib, http_client, configparser, map + from pkg_resources import ( CHECKOUT_DIST, Distribution, BINARY_DIST, normalize_path, SOURCE_DIST, require, Environment, find_distributions, safe_name, safe_version, @@ -17,12 +25,6 @@ from pkg_resources import ( from setuptools import ssl_support from distutils import log from distutils.errors import DistutilsError -from setuptools.compat import ( - urllib2, httplib, StringIO, HTTPError, urlparse, urlunparse, unquote, - splituser, url2pathname, name2codepoint, unichr, urljoin, urlsplit, - urlunsplit, configparser, filter, map, -) -from setuptools.compat import filterfalse from fnmatch import translate from setuptools.py26compat import strip_fragment from setuptools.py27compat import get_all_headers @@ -69,10 +71,11 @@ def parse_bdist_wininst(name): def egg_info_for_url(url): - scheme, server, path, parameters, query, fragment = urlparse(url) - base = unquote(path.split('/')[-1]) + parts = urllib.parse.urlparse(url) + scheme, server, path, parameters, query, fragment = parts + base = urllib.parse.unquote(path.split('/')[-1]) if server=='sourceforge.net' and base=='download': # XXX Yuck - base = unquote(path.split('/')[-2]) + base = urllib.parse.unquote(path.split('/')[-2]) if '#' in base: base, fragment = base.split('#',1) return base,fragment @@ -158,7 +161,7 @@ def unique_everseen(iterable, key=None): seen = set() seen_add = seen.add if key is None: - for element in filterfalse(seen.__contains__, iterable): + for element in six.moves.filterfalse(seen.__contains__, iterable): seen_add(element) yield element else: @@ -190,14 +193,14 @@ def find_external_links(url, page): rels = set(map(str.strip, rel.lower().split(','))) if 'homepage' in rels or 'download' in rels: for match in HREF.finditer(tag): - yield urljoin(url, htmldecode(match.group(1))) + yield urllib.parse.urljoin(url, htmldecode(match.group(1))) for tag in ("<th>Home Page", "<th>Download URL"): pos = page.find(tag) if pos!=-1: match = HREF.search(page,pos) if match: - yield urljoin(url, htmldecode(match.group(1))) + yield urllib.parse.urljoin(url, htmldecode(match.group(1))) user_agent = "Python-urllib/%s setuptools/%s" % ( sys.version[:3], require('setuptools')[0].version @@ -240,7 +243,7 @@ class HashChecker(ContentChecker): @classmethod def from_url(cls, url): "Construct a (possibly null) ContentChecker from a URL" - fragment = urlparse(url)[-1] + fragment = urllib.parse.urlparse(url)[-1] if not fragment: return ContentChecker() match = cls.pattern.search(fragment) @@ -275,7 +278,7 @@ class PackageIndex(Environment): self.to_scan = [] if verify_ssl and ssl_support.is_available and (ca_bundle or ssl_support.find_ca_bundle()): self.opener = ssl_support.opener_for(ca_bundle) - else: self.opener = urllib2.urlopen + else: self.opener = urllib.request.urlopen def process_url(self, url, retrieve=False): """Evaluate a URL as a possible download, and maybe retrieve it""" @@ -312,7 +315,7 @@ class PackageIndex(Environment): base = f.url # handle redirects page = f.read() if not isinstance(page, str): # We are in Python 3 and got bytes. We want str. - if isinstance(f, HTTPError): + if isinstance(f, urllib.error.HTTPError): # Errors have no charset, assume latin1: charset = 'latin-1' else: @@ -320,7 +323,7 @@ class PackageIndex(Environment): page = page.decode(charset, "ignore") f.close() for match in HREF.finditer(page): - link = urljoin(base, htmldecode(match.group(1))) + link = urllib.parse.urljoin(base, htmldecode(match.group(1))) self.process_url(link) if url.startswith(self.index_url) and getattr(f,'code',None)!=404: page = self.process_index(url, page) @@ -343,7 +346,7 @@ class PackageIndex(Environment): def url_ok(self, url, fatal=False): s = URL_SCHEME(url) - if (s and s.group(1).lower()=='file') or self.allows(urlparse(url)[1]): + if (s and s.group(1).lower()=='file') or self.allows(urllib.parse.urlparse(url)[1]): return True msg = ("\nNote: Bypassing %s (disallowed host; see " "http://bit.ly/1dg9ijs for details).\n") @@ -384,7 +387,7 @@ class PackageIndex(Environment): # Process a URL to see if it's for a package page if link.startswith(self.index_url): parts = list(map( - unquote, link[len(self.index_url):].split('/') + urllib.parse.unquote, link[len(self.index_url):].split('/') )) if len(parts)==2 and '#' not in parts[1]: # it's a package page, sanitize and index it @@ -397,7 +400,7 @@ class PackageIndex(Environment): # process an index page into the package-page index for match in HREF.finditer(page): try: - scan(urljoin(url, htmldecode(match.group(1)))) + scan(urllib.parse.urljoin(url, htmldecode(match.group(1)))) except ValueError: pass @@ -673,7 +676,7 @@ class PackageIndex(Environment): try: checker = HashChecker.from_url(url) fp = self.open_url(strip_fragment(url)) - if isinstance(fp, HTTPError): + if isinstance(fp, urllib.error.HTTPError): raise DistutilsError( "Can't download %s: %s %s" % (url, fp.code,fp.msg) ) @@ -709,21 +712,21 @@ class PackageIndex(Environment): return local_open(url) try: return open_with_auth(url, self.opener) - except (ValueError, httplib.InvalidURL) as v: + except (ValueError, http_client.InvalidURL) as v: msg = ' '.join([str(arg) for arg in v.args]) if warning: self.warn(warning, msg) else: raise DistutilsError('%s %s' % (url, msg)) - except urllib2.HTTPError as v: + except urllib.error.HTTPError as v: return v - except urllib2.URLError as v: + except urllib.error.URLError as v: if warning: self.warn(warning, v.reason) else: raise DistutilsError("Download error for %s: %s" % (url, v.reason)) - except httplib.BadStatusLine as v: + except http_client.BadStatusLine as v: if warning: self.warn(warning, v.line) else: @@ -732,7 +735,7 @@ class PackageIndex(Environment): 'down, %s' % (url, v.line) ) - except httplib.HTTPException as v: + except http_client.HTTPException as v: if warning: self.warn(warning, v) else: @@ -763,7 +766,7 @@ class PackageIndex(Environment): elif scheme.startswith('hg+'): return self._download_hg(url, filename) elif scheme=='file': - return url2pathname(urlparse(url)[2]) + return urllib.request.url2pathname(urllib.parse.urlparse(url)[2]) else: self.url_ok(url, True) # raises error if not allowed return self._attempt_download(url, filename) @@ -797,7 +800,7 @@ class PackageIndex(Environment): url = url.split('#',1)[0] # remove any fragment for svn's sake creds = '' if url.lower().startswith('svn:') and '@' in url: - scheme, netloc, path, p, q, f = urlparse(url) + scheme, netloc, path, p, q, f = urllib.parse.urlparse(url) if not netloc and path.startswith('//') and '/' in path[2:]: netloc, path = path[2:].split('/',1) auth, host = splituser(netloc) @@ -808,14 +811,15 @@ class PackageIndex(Environment): else: creds = " --username="+auth netloc = host - url = urlunparse((scheme, netloc, url, p, q, f)) + parts = scheme, netloc, url, p, q, f + url = urllib.parse.urlunparse(parts) self.info("Doing subversion checkout from %s to %s", url, filename) os.system("svn checkout%s -q %s %s" % (creds, url, filename)) return filename @staticmethod def _vcs_split_rev_from_url(url, pop_prefix=False): - scheme, netloc, path, query, frag = urlsplit(url) + scheme, netloc, path, query, frag = urllib.parse.urlsplit(url) scheme = scheme.split('+', 1)[-1] @@ -827,7 +831,7 @@ class PackageIndex(Environment): path, rev = path.rsplit('@', 1) # Also, discard fragment - url = urlunsplit((scheme, netloc, path, query, '')) + url = urllib.parse.urlunsplit((scheme, netloc, path, query, '')) return url, rev @@ -879,7 +883,7 @@ entity_sub = re.compile(r'&(#(\d+|x[\da-fA-F]+)|[\w.:-]+);?').sub def uchr(c): if not isinstance(c, int): return c - if c>255: return unichr(c) + if c>255: return six.unichr(c) return chr(c) def decode_entity(match): @@ -889,7 +893,7 @@ def decode_entity(match): elif what.startswith('#'): what = int(what[1:]) else: - what = name2codepoint.get(what, match.group(0)) + what = six.moves.html_entities.name2codepoint.get(what, match.group(0)) return uchr(what) def htmldecode(text): @@ -920,7 +924,7 @@ def _encode_auth(auth): >>> chr(10) in str(_encode_auth(long_auth)) False """ - auth_s = unquote(auth) + auth_s = urllib.parse.unquote(auth) # convert to bytes auth_bytes = auth_s.encode() # use the legacy interface for Python 2.3 support @@ -984,15 +988,15 @@ class PyPIConfig(configparser.RawConfigParser): return cred -def open_with_auth(url, opener=urllib2.urlopen): +def open_with_auth(url, opener=urllib.request.urlopen): """Open a urllib2 request, handling HTTP authentication""" - scheme, netloc, path, params, query, frag = urlparse(url) + scheme, netloc, path, params, query, frag = urllib.parse.urlparse(url) # Double scheme does not raise on Mac OS X as revealed by a # failing test. We would expect "nonnumeric port". Refs #20. if netloc.endswith(':'): - raise httplib.InvalidURL("nonnumeric port: ''") + raise http_client.InvalidURL("nonnumeric port: ''") if scheme in ('http', 'https'): auth, host = splituser(netloc) @@ -1008,11 +1012,12 @@ def open_with_auth(url, opener=urllib2.urlopen): if auth: auth = "Basic " + _encode_auth(auth) - new_url = urlunparse((scheme,host,path,params,query,frag)) - request = urllib2.Request(new_url) + parts = scheme, host, path, params, query, frag + new_url = urllib.parse.urlunparse(parts) + request = urllib.request.Request(new_url) request.add_header("Authorization", auth) else: - request = urllib2.Request(url) + request = urllib.request.Request(url) request.add_header('User-Agent', user_agent) fp = opener(request) @@ -1020,9 +1025,10 @@ def open_with_auth(url, opener=urllib2.urlopen): if auth: # Put authentication info back into request URL if same host, # so that links found on the page will work - s2, h2, path2, param2, query2, frag2 = urlparse(fp.url) + s2, h2, path2, param2, query2, frag2 = urllib.parse.urlparse(fp.url) if s2==scheme and h2==host: - fp.url = urlunparse((s2,netloc,path2,param2,query2,frag2)) + parts = s2, netloc, path2, param2, query2, frag2 + fp.url = urllib.parse.urlunparse(parts) return fp @@ -1035,10 +1041,10 @@ def fix_sf_url(url): def local_open(url): """Read a local path, with special support for directories""" - scheme, server, path, param, query, frag = urlparse(url) - filename = url2pathname(path) + scheme, server, path, param, query, frag = urllib.parse.urlparse(url) + filename = urllib.request.url2pathname(path) if os.path.isfile(filename): - return urllib2.urlopen(url) + return urllib.request.urlopen(url) elif path.endswith('/') and os.path.isdir(filename): files = [] for f in os.listdir(filename): @@ -1059,4 +1065,5 @@ def local_open(url): status, message, body = 404, "Path not found", "Not found" headers = {'content-type': 'text/html'} - return HTTPError(url, status, message, headers, StringIO(body)) + body_stream = six.StringIO(body) + return urllib.error.HTTPError(url, status, message, headers, body_stream) diff --git a/setuptools/py26compat.py b/setuptools/py26compat.py index 738b0cc4..e52bd85b 100644 --- a/setuptools/py26compat.py +++ b/setuptools/py26compat.py @@ -4,7 +4,10 @@ Compatibility Support for Python 2.6 and earlier import sys -from setuptools.compat import splittag +try: + from urllib.parse import splittag +except ImportError: + from urllib import splittag def strip_fragment(url): """ diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py index 85de85ff..23e296b1 100755 --- a/setuptools/sandbox.py +++ b/setuptools/sandbox.py @@ -8,6 +8,9 @@ import re import contextlib import pickle +from setuptools.extern import six +from setuptools.extern.six.moves import builtins, map + import pkg_resources if sys.platform.startswith('java'): @@ -22,9 +25,6 @@ _open = open from distutils.errors import DistutilsError from pkg_resources import working_set -from setuptools import compat -from setuptools.compat import builtins - __all__ = [ "AbstractSandbox", "DirectorySandbox", "SandboxViolation", "run_setup", ] @@ -138,7 +138,7 @@ class ExceptionSaver: return type, exc = map(pickle.loads, self._saved) - compat.reraise(type, exc, self._tb) + six.reraise(type, exc, self._tb) @contextlib.contextmanager @@ -207,8 +207,12 @@ def _needs_hiding(mod_name): True >>> _needs_hiding('distutils') True + >>> _needs_hiding('os') + False + >>> _needs_hiding('Cython') + True """ - pattern = re.compile('(setuptools|pkg_resources|distutils)(\.|$)') + pattern = re.compile('(setuptools|pkg_resources|distutils|Cython)(\.|$)') return bool(pattern.match(mod_name)) diff --git a/setuptools/ssl_support.py b/setuptools/ssl_support.py index cc7db067..657197cf 100644 --- a/setuptools/ssl_support.py +++ b/setuptools/ssl_support.py @@ -3,9 +3,10 @@ import socket import atexit import re +from setuptools.extern.six.moves import urllib, http_client, map + import pkg_resources from pkg_resources import ResolutionError, ExtractionError -from setuptools.compat import urllib2 try: import ssl @@ -24,20 +25,15 @@ cert_paths = """ /usr/local/share/certs/ca-root.crt /etc/ssl/cert.pem /System/Library/OpenSSL/certs/cert.pem +/usr/local/share/certs/ca-root-nss.crt """.strip().split() -HTTPSHandler = HTTPSConnection = object - -for what, where in ( - ('HTTPSHandler', ['urllib2','urllib.request']), - ('HTTPSConnection', ['httplib', 'http.client']), -): - for module in where: - try: - exec("from %s import %s" % (module, what)) - except ImportError: - pass +try: + HTTPSHandler = urllib.request.HTTPSHandler + HTTPSConnection = http_client.HTTPSConnection +except AttributeError: + HTTPSHandler = HTTPSConnection = object is_available = ssl is not None and object not in (HTTPSHandler, HTTPSConnection) @@ -198,7 +194,7 @@ class VerifyingHTTPSConn(HTTPSConnection): def opener_for(ca_bundle=None): """Get a urlopen() replacement that uses ca_bundle for verification""" - return urllib2.build_opener( + return urllib.request.build_opener( VerifyingHTTPSHandler(ca_bundle or find_ca_bundle()) ).open @@ -223,6 +219,12 @@ def get_win_certfile(): self.addcerts(certs) atexit.register(self.close) + def close(self): + try: + super(MyCertFile, self).close() + except OSError: + pass + _wincerts = MyCertFile(stores=['CA', 'ROOT']) return _wincerts.name diff --git a/setuptools/tests/__init__.py b/setuptools/tests/__init__.py index f985a6e4..32447356 100644 --- a/setuptools/tests/__init__.py +++ b/setuptools/tests/__init__.py @@ -7,8 +7,8 @@ from distutils.errors import DistutilsOptionError, DistutilsPlatformError from distutils.errors import DistutilsSetupError from distutils.core import Extension from distutils.version import LooseVersion -from setuptools.compat import func_code +from setuptools.extern import six import pytest import setuptools.dist @@ -52,7 +52,7 @@ class TestDepends: x = "test" y = z - fc = func_code(f1) + fc = six.get_function_code(f1) # unrecognized name assert dep.extract_constant(fc,'q', -1) is None diff --git a/setuptools/tests/contexts.py b/setuptools/tests/contexts.py index 1d29284b..ae28c7c3 100644 --- a/setuptools/tests/contexts.py +++ b/setuptools/tests/contexts.py @@ -5,7 +5,8 @@ import sys import contextlib import site -from ..compat import StringIO +from setuptools.extern import six +import pkg_resources @contextlib.contextmanager @@ -57,8 +58,8 @@ def quiet(): old_stdout = sys.stdout old_stderr = sys.stderr - new_stdout = sys.stdout = StringIO() - new_stderr = sys.stderr = StringIO() + new_stdout = sys.stdout = six.StringIO() + new_stderr = sys.stderr = six.StringIO() try: yield new_stdout, new_stderr finally: @@ -78,6 +79,18 @@ def save_user_site_setting(): @contextlib.contextmanager +def save_pkg_resources_state(): + pr_state = pkg_resources.__getstate__() + # also save sys.path + sys_path = sys.path[:] + try: + yield pr_state, sys_path + finally: + sys.path[:] = sys_path + pkg_resources.__setstate__(pr_state) + + +@contextlib.contextmanager def suppress_exceptions(*excs): try: yield diff --git a/setuptools/tests/files.py b/setuptools/tests/files.py new file mode 100644 index 00000000..4364241b --- /dev/null +++ b/setuptools/tests/files.py @@ -0,0 +1,32 @@ +import os + + +def build_files(file_defs, prefix=""): + """ + Build a set of files/directories, as described by the file_defs dictionary. + + Each key/value pair in the dictionary is interpreted as a filename/contents + pair. If the contents value is a dictionary, a directory is created, and the + dictionary interpreted as the files within it, recursively. + + For example: + + {"README.txt": "A README file", + "foo": { + "__init__.py": "", + "bar": { + "__init__.py": "", + }, + "baz.py": "# Some code", + } + } + """ + for name, contents in file_defs.items(): + full_name = os.path.join(prefix, name) + if isinstance(contents, dict): + if not os.path.exists(full_name): + os.makedirs(full_name) + build_files(contents, prefix=full_name) + else: + with open(full_name, 'w') as f: + f.write(contents) diff --git a/setuptools/tests/server.py b/setuptools/tests/server.py index 6b214279..6a687937 100644 --- a/setuptools/tests/server.py +++ b/setuptools/tests/server.py @@ -3,10 +3,11 @@ import time import threading -from setuptools.compat import BaseHTTPRequestHandler -from setuptools.compat import HTTPServer, SimpleHTTPRequestHandler -class IndexServer(HTTPServer): +from setuptools.extern.six.moves import BaseHTTPServer, SimpleHTTPServer + + +class IndexServer(BaseHTTPServer.HTTPServer): """Basic single-threaded http server simulating a package index You can use this server in unittest like this:: @@ -18,8 +19,9 @@ class IndexServer(HTTPServer): s.stop() """ def __init__(self, server_address=('', 0), - RequestHandlerClass=SimpleHTTPRequestHandler): - HTTPServer.__init__(self, server_address, RequestHandlerClass) + RequestHandlerClass=SimpleHTTPServer.SimpleHTTPRequestHandler): + BaseHTTPServer.HTTPServer.__init__(self, server_address, + RequestHandlerClass) self._run = True def start(self): @@ -40,19 +42,20 @@ class IndexServer(HTTPServer): port = self.server_port return 'http://127.0.0.1:%s/setuptools/tests/indexes/' % port -class RequestRecorder(BaseHTTPRequestHandler): +class RequestRecorder(BaseHTTPServer.BaseHTTPRequestHandler): def do_GET(self): requests = vars(self.server).setdefault('requests', []) requests.append(self) self.send_response(200, 'OK') -class MockServer(HTTPServer, threading.Thread): +class MockServer(BaseHTTPServer.HTTPServer, threading.Thread): """ A simple HTTP Server that records the requests made to it. """ def __init__(self, server_address=('', 0), RequestHandlerClass=RequestRecorder): - HTTPServer.__init__(self, server_address, RequestHandlerClass) + BaseHTTPServer.HTTPServer.__init__(self, server_address, + RequestHandlerClass) threading.Thread.__init__(self) self.setDaemon(True) self.requests = [] diff --git a/setuptools/tests/test_develop.py b/setuptools/tests/test_develop.py index ab5da00e..1b844499 100644 --- a/setuptools/tests/test_develop.py +++ b/setuptools/tests/test_develop.py @@ -5,12 +5,13 @@ import site import sys import io +from setuptools.extern import six + import pytest from setuptools.command.develop import develop from setuptools.dist import Distribution from . import contexts -from setuptools.compat import PY3 SETUP_PY = """\ @@ -85,7 +86,7 @@ class TestDevelop: with io.open(fn) as init_file: init = init_file.read().strip() - expected = 'print("foo")' if PY3 else 'print "foo"' + expected = 'print("foo")' if six.PY3 else 'print "foo"' assert init == expected def test_console_scripts(self, tmpdir): diff --git a/setuptools/tests/test_dist_info.py b/setuptools/tests/test_dist_info.py index 6d0ab587..9f226a55 100644 --- a/setuptools/tests/test_dist_info.py +++ b/setuptools/tests/test_dist_info.py @@ -4,6 +4,8 @@ import os import shutil import tempfile +from setuptools.extern.six.moves import map + import pytest import pkg_resources @@ -26,14 +28,15 @@ class TestDistInfo: assert versioned.version == '2.718' # from filename assert unversioned.version == '0.3' # from METADATA - @pytest.mark.importorskip('ast') def test_conditional_dependencies(self): specs = 'splort==4', 'quux>=1.1' requires = list(map(pkg_resources.Requirement.parse, specs)) for d in pkg_resources.find_distributions(self.tmpdir): assert d.requires() == requires[:1] - assert d.requires(extras=('baz',)) == requires + assert d.requires(extras=('baz',)) == [ + requires[0], + pkg_resources.Requirement.parse('quux>=1.1;extra=="baz"')] assert d.extras == ['baz'] metadata_template = DALS(""" diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index 00e16b63..55b8b05a 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -14,6 +14,10 @@ import tarfile import logging import itertools import distutils.errors +import io + +from setuptools.extern.six.moves import urllib +import time import pytest try: @@ -22,8 +26,6 @@ except ImportError: import mock from setuptools import sandbox -from setuptools import compat -from setuptools.compat import StringIO, BytesIO, urlparse from setuptools.sandbox import run_setup import setuptools.command.easy_install as ei from setuptools.command.easy_install import PthDistributions @@ -35,7 +37,7 @@ import setuptools.tests.server import pkg_resources from .py26compat import tarfile_open -from . import contexts, is_ascii +from . import contexts from .textwrap import DALS @@ -56,17 +58,13 @@ SETUP_PY = DALS(""" class TestEasyInstallTest: - def test_install_site_py(self): + def test_install_site_py(self, tmpdir): dist = Distribution() cmd = ei.easy_install(dist) cmd.sitepy_installed = False - cmd.install_dir = tempfile.mkdtemp() - try: - cmd.install_site_py() - sitepy = os.path.join(cmd.install_dir, 'site.py') - assert os.path.exists(sitepy) - finally: - shutil.rmtree(cmd.install_dir) + cmd.install_dir = str(tmpdir) + cmd.install_site_py() + assert (tmpdir / 'site.py').exists() def test_get_script_args(self): header = ei.CommandSpec.best().from_environment().as_header() @@ -272,7 +270,7 @@ class TestSetupRequires: p_index = setuptools.tests.server.MockServer() p_index.start() netloc = 1 - p_index_loc = urlparse(p_index.url)[netloc] + p_index_loc = urllib.parse.urlparse(p_index.url)[netloc] if p_index_loc.endswith(':0'): # Some platforms (Jython) don't find a port to which to bind, # so skip this test for them. @@ -308,32 +306,32 @@ class TestSetupRequires: """ with contexts.tempdir() as dir: dist_path = os.path.join(dir, 'setuptools-test-fetcher-1.0.tar.gz') - script = DALS(""" - import setuptools - setuptools.setup( - name="setuptools-test-fetcher", - version="1.0", - setup_requires = ['does-not-exist'], - ) - """) - make_trivial_sdist(dist_path, script) + make_sdist(dist_path, [ + ('setup.py', DALS(""" + import setuptools + setuptools.setup( + name="setuptools-test-fetcher", + version="1.0", + setup_requires = ['does-not-exist'], + ) + """))]) yield dist_path def test_setup_requires_overrides_version_conflict(self): """ - Regression test for issue #323. + Regression test for distribution issue 323: + https://bitbucket.org/tarek/distribute/issues/323 Ensures that a distribution's setup_requires requirements can still be installed and used locally even if a conflicting version of that requirement is already on the path. """ - pr_state = pkg_resources.__getstate__() fake_dist = PRDistribution('does-not-matter', project_name='foobar', version='0.0') working_set.add(fake_dist) - try: + with contexts.save_pkg_resources_state(): with contexts.tempdir() as temp_dir: test_pkg = create_setup_requires_package(temp_dir) test_setup_py = os.path.join(test_pkg, 'setup.py') @@ -345,19 +343,144 @@ class TestSetupRequires: lines = stdout.readlines() assert len(lines) > 0 assert lines[-1].strip(), 'test_pkg' - finally: - pkg_resources.__setstate__(pr_state) + def test_setup_requires_override_nspkg(self): + """ + Like ``test_setup_requires_overrides_version_conflict`` but where the + ``setup_requires`` package is part of a namespace package that has + *already* been imported. + """ + + with contexts.save_pkg_resources_state(): + with contexts.tempdir() as temp_dir: + foobar_1_archive = os.path.join(temp_dir, 'foo.bar-0.1.tar.gz') + make_nspkg_sdist(foobar_1_archive, 'foo.bar', '0.1') + # Now actually go ahead an extract to the temp dir and add the + # extracted path to sys.path so foo.bar v0.1 is importable + foobar_1_dir = os.path.join(temp_dir, 'foo.bar-0.1') + os.mkdir(foobar_1_dir) + with tarfile_open(foobar_1_archive) as tf: + tf.extractall(foobar_1_dir) + sys.path.insert(1, foobar_1_dir) + + dist = PRDistribution(foobar_1_dir, project_name='foo.bar', + version='0.1') + working_set.add(dist) + + template = DALS("""\ + import foo # Even with foo imported first the + # setup_requires package should override + import setuptools + setuptools.setup(**%r) + + if not (hasattr(foo, '__path__') and + len(foo.__path__) == 2): + print('FAIL') + + if 'foo.bar-0.2' not in foo.__path__[0]: + print('FAIL') + """) + + test_pkg = create_setup_requires_package( + temp_dir, 'foo.bar', '0.2', make_nspkg_sdist, template) + + test_setup_py = os.path.join(test_pkg, 'setup.py') + + with contexts.quiet() as (stdout, stderr): + try: + # Don't even need to install the package, just + # running the setup.py at all is sufficient + run_setup(test_setup_py, ['--name']) + except pkg_resources.VersionConflict: + self.fail('Installing setup.py requirements ' + 'caused a VersionConflict') + + assert 'FAIL' not in stdout.getvalue() + lines = stdout.readlines() + assert len(lines) > 0 + assert lines[-1].strip() == 'test_pkg' + + +def make_trivial_sdist(dist_path, distname, version): + """ + Create a simple sdist tarball at dist_path, containing just a simple + setup.py. + """ + + make_sdist(dist_path, [ + ('setup.py', + DALS("""\ + import setuptools + setuptools.setup( + name=%r, + version=%r + ) + """ % (distname, version)))]) + + +def make_nspkg_sdist(dist_path, distname, version): + """ + Make an sdist tarball with distname and version which also contains one + package with the same name as distname. The top-level package is + designated a namespace package). + """ + + parts = distname.split('.') + nspackage = parts[0] + + packages = ['.'.join(parts[:idx]) for idx in range(1, len(parts) + 1)] + + setup_py = DALS("""\ + import setuptools + setuptools.setup( + name=%r, + version=%r, + packages=%r, + namespace_packages=[%r] + ) + """ % (distname, version, packages, nspackage)) + + init = "__import__('pkg_resources').declare_namespace(__name__)" + + files = [('setup.py', setup_py), + (os.path.join(nspackage, '__init__.py'), init)] + for package in packages[1:]: + filename = os.path.join(*(package.split('.') + ['__init__.py'])) + files.append((filename, '')) + + make_sdist(dist_path, files) + + +def make_sdist(dist_path, files): + """ + Create a simple sdist tarball at dist_path, containing the files + listed in ``files`` as ``(filename, content)`` tuples. + """ -def create_setup_requires_package(path): + with tarfile_open(dist_path, 'w:gz') as dist: + for filename, content in files: + file_bytes = io.BytesIO(content.encode('utf-8')) + file_info = tarfile.TarInfo(name=filename) + file_info.size = len(file_bytes.getvalue()) + file_info.mtime = int(time.time()) + dist.addfile(file_info, fileobj=file_bytes) + + +def create_setup_requires_package(path, distname='foobar', version='0.1', + make_package=make_trivial_sdist, + setup_py_template=None): """Creates a source tree under path for a trivial test package that has a single requirement in setup_requires--a tarball for that requirement is also created and added to the dependency_links argument. + + ``distname`` and ``version`` refer to the name/version of the package that + the test package requires via ``setup_requires``. The name of the test + package itself is just 'test_pkg'. """ test_setup_attrs = { 'name': 'test_pkg', 'version': '0.0', - 'setup_requires': ['foobar==0.1'], + 'setup_requires': ['%s==%s' % (distname, version)], 'dependency_links': [os.path.abspath(path)] } @@ -365,22 +488,17 @@ def create_setup_requires_package(path): test_setup_py = os.path.join(test_pkg, 'setup.py') os.mkdir(test_pkg) - with open(test_setup_py, 'w') as f: - f.write(DALS(""" + if setup_py_template is None: + setup_py_template = DALS("""\ import setuptools setuptools.setup(**%r) - """ % test_setup_attrs)) + """) - foobar_path = os.path.join(path, 'foobar-0.1.tar.gz') - make_trivial_sdist( - foobar_path, - DALS(""" - import setuptools - setuptools.setup( - name='foobar', - version='0.1' - ) - """)) + with open(test_setup_py, 'w') as f: + f.write(setup_py_template % test_setup_attrs) + + foobar_path = os.path.join(path, '%s-%s.tar.gz' % (distname, version)) + make_package(foobar_path, distname, version) return test_pkg @@ -391,12 +509,7 @@ def make_trivial_sdist(dist_path, setup_py): """ setup_py_file = tarfile.TarInfo(name='setup.py') - try: - # Python 3 (StringIO gets converted to io module) - MemFile = BytesIO - except AttributeError: - MemFile = StringIO - setup_py_bytes = MemFile(setup_py.encode('utf-8')) + setup_py_bytes = io.BytesIO(setup_py.encode('utf-8')) setup_py_file.size = len(setup_py_bytes.getvalue()) with tarfile_open(dist_path, 'w:gz') as dist: dist.addfile(setup_py_file, fileobj=setup_py_bytes) @@ -430,46 +543,6 @@ class TestScriptHeader: expected = '#!"%s"\n' % self.exe_with_spaces assert actual == expected - @pytest.mark.xfail( - compat.PY3 and is_ascii, - reason="Test fails in this locale on Python 3" - ) - @mock.patch.dict(sys.modules, java=mock.Mock(lang=mock.Mock(System= - mock.Mock(getProperty=mock.Mock(return_value=""))))) - @mock.patch('sys.platform', 'java1.5.0_13') - def test_get_script_header_jython_workaround(self, tmpdir): - # Create a mock sys.executable that uses a shebang line - header = DALS(""" - #!/usr/bin/python - # -*- coding: utf-8 -*- - """) - exe = tmpdir / 'exe.py' - with exe.open('w') as f: - f.write(header) - exe = str(exe) - - header = ei.ScriptWriter.get_script_header('#!/usr/local/bin/python', - executable=exe) - assert header == '#!/usr/bin/env %s\n' % exe - - expect_out = 'stdout' if sys.version_info < (2,7) else 'stderr' - - with contexts.quiet() as (stdout, stderr): - # When options are included, generate a broken shebang line - # with a warning emitted - candidate = ei.ScriptWriter.get_script_header('#!/usr/bin/python -x', - executable=exe) - assert candidate == '#!%s -x\n' % exe - output = locals()[expect_out] - assert 'Unable to adapt shebang line' in output.getvalue() - - with contexts.quiet() as (stdout, stderr): - candidate = ei.ScriptWriter.get_script_header('#!/usr/bin/python', - executable=self.non_ascii_exe) - assert candidate == '#!%s -x\n' % self.non_ascii_exe - output = locals()[expect_out] - assert 'Unable to adapt shebang line' in output.getvalue() - class TestCommandSpec: def test_custom_launch_command(self): diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py index 645c379c..d37567b4 100644 --- a/setuptools/tests/test_egg_info.py +++ b/setuptools/tests/test_egg_info.py @@ -1,9 +1,13 @@ import os +import glob import stat +from setuptools.extern.six.moves import map + import pytest from . import environment +from .files import build_files from .textwrap import DALS from . import contexts @@ -26,14 +30,13 @@ class TestEggInfo(object): """) def _create_project(self): - with open('setup.py', 'w') as f: - f.write(self.setup_script) - - with open('hello.py', 'w') as f: - f.write(DALS(""" + build_files({ + 'setup.py': self.setup_script, + 'hello.py': DALS(""" def run(): print('hello') - """)) + """) + }) @pytest.yield_fixture def env(self): @@ -46,36 +49,20 @@ class TestEggInfo(object): for dirname in subs ) list(map(os.mkdir, env.paths.values())) - config = os.path.join(env.paths['home'], '.pydistutils.cfg') - with open(config, 'w') as f: - f.write(DALS(""" + build_files({ + env.paths['home']: { + '.pydistutils.cfg': DALS(""" [egg_info] egg-base = %(egg-base)s - """ % env.paths)) + """ % env.paths) + } + }) yield env def test_egg_base_installed_egg_info(self, tmpdir_cwd, env): self._create_project() - environ = os.environ.copy().update( - HOME=env.paths['home'], - ) - cmd = [ - 'install', - '--home', env.paths['home'], - '--install-lib', env.paths['lib'], - '--install-scripts', env.paths['scripts'], - '--install-data', env.paths['data'], - ] - code, data = environment.run_setup_py( - cmd=cmd, - pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]), - data_stream=1, - env=environ, - ) - if code: - raise AssertionError(data) - + self._run_install_command(tmpdir_cwd, env) actual = self._find_egg_info_files(env.paths['lib']) expected = [ @@ -88,9 +75,98 @@ class TestEggInfo(object): ] assert sorted(actual) == expected + def test_manifest_template_is_read(self, tmpdir_cwd, env): + self._create_project() + build_files({ + 'MANIFEST.in': DALS(""" + recursive-include docs *.rst + """), + 'docs': { + 'usage.rst': "Run 'hi'", + } + }) + self._run_install_command(tmpdir_cwd, env) + egg_info_dir = self._find_egg_info_files(env.paths['lib']).base + sources_txt = os.path.join(egg_info_dir, 'SOURCES.txt') + assert 'docs/usage.rst' in open(sources_txt).read().split('\n') + + def _setup_script_with_requires(self, requires_line): + setup_script = DALS(""" + from setuptools import setup + + setup( + name='foo', + %s + zip_safe=False, + ) + """ % requires_line) + build_files({ + 'setup.py': setup_script, + }) + + @pytest.mark.xfail(reason="Functionality disabled; see #523") + def test_install_requires_with_markers(self, tmpdir_cwd, env): + self._setup_script_with_requires( + """install_requires=["barbazquux;python_version<'2'"],""") + self._run_install_command(tmpdir_cwd, env) + egg_info_dir = self._find_egg_info_files(env.paths['lib']).base + requires_txt = os.path.join(egg_info_dir, 'requires.txt') + assert "barbazquux;python_version<'2'" in open( + requires_txt).read().split('\n') + assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == [] + + @pytest.mark.xfail(reason="Functionality disabled; see #523") + def test_setup_requires_with_markers(self, tmpdir_cwd, env): + self._setup_script_with_requires( + """setup_requires=["barbazquux;python_version<'2'"],""") + self._run_install_command(tmpdir_cwd, env) + assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == [] + + @pytest.mark.xfail(reason="Functionality disabled; see #523") + def test_tests_require_with_markers(self, tmpdir_cwd, env): + self._setup_script_with_requires( + """tests_require=["barbazquux;python_version<'2'"],""") + self._run_install_command( + tmpdir_cwd, env, cmd=['test'], output="Ran 0 tests in") + assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == [] + + def test_extra_requires_with_markers(self, tmpdir_cwd, env): + self._setup_script_with_requires( + """extra_requires={":python_version<'2'": ["barbazquux"]},""") + self._run_install_command(tmpdir_cwd, env) + assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == [] + + def _run_install_command(self, tmpdir_cwd, env, cmd=None, output=None): + environ = os.environ.copy().update( + HOME=env.paths['home'], + ) + if cmd is None: + cmd = [ + 'install', + '--home', env.paths['home'], + '--install-lib', env.paths['lib'], + '--install-scripts', env.paths['scripts'], + '--install-data', env.paths['data'], + ] + code, data = environment.run_setup_py( + cmd=cmd, + pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]), + data_stream=1, + env=environ, + ) + if code: + raise AssertionError(data) + if output: + assert output in data + def _find_egg_info_files(self, root): + class DirList(list): + def __init__(self, files, base): + super(DirList, self).__init__(files) + self.base = base + results = ( - filenames + DirList(filenames, dirpath) for dirpath, dirnames, filenames in os.walk(root) if os.path.basename(dirpath) == 'EGG-INFO' ) diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py index 07f06db2..04772ba5 100644 --- a/setuptools/tests/test_integration.py +++ b/setuptools/tests/test_integration.py @@ -7,12 +7,12 @@ import glob import os import sys +from setuptools.extern.six.moves import urllib import pytest from setuptools.command.easy_install import easy_install from setuptools.command import easy_install as easy_install_pkg from setuptools.dist import Distribution -from setuptools.compat import urlopen def setup_module(module): @@ -26,7 +26,7 @@ def setup_module(module): pass try: - urlopen('https://pypi.python.org/pypi') + urllib.request.urlopen('https://pypi.python.org/pypi') except Exception as exc: pytest.skip(str(exc)) diff --git a/setuptools/tests/test_markerlib.py b/setuptools/tests/test_markerlib.py deleted file mode 100644 index 8197b49d..00000000 --- a/setuptools/tests/test_markerlib.py +++ /dev/null @@ -1,63 +0,0 @@ -import os - -import pytest - - -class TestMarkerlib: - - @pytest.mark.importorskip('ast') - def test_markers(self): - from _markerlib import interpret, default_environment, compile - - os_name = os.name - - assert interpret("") - - assert interpret("os.name != 'buuuu'") - assert interpret("os_name != 'buuuu'") - assert interpret("python_version > '1.0'") - assert interpret("python_version < '5.0'") - assert interpret("python_version <= '5.0'") - assert interpret("python_version >= '1.0'") - assert interpret("'%s' in os.name" % os_name) - assert interpret("'%s' in os_name" % os_name) - assert interpret("'buuuu' not in os.name") - - assert not interpret("os.name == 'buuuu'") - assert not interpret("os_name == 'buuuu'") - assert not interpret("python_version < '1.0'") - assert not interpret("python_version > '5.0'") - assert not interpret("python_version >= '5.0'") - assert not interpret("python_version <= '1.0'") - assert not interpret("'%s' not in os.name" % os_name) - assert not interpret("'buuuu' in os.name and python_version >= '5.0'") - assert not interpret("'buuuu' in os_name and python_version >= '5.0'") - - environment = default_environment() - environment['extra'] = 'test' - assert interpret("extra == 'test'", environment) - assert not interpret("extra == 'doc'", environment) - - def raises_nameError(): - try: - interpret("python.version == '42'") - except NameError: - pass - else: - raise Exception("Expected NameError") - - raises_nameError() - - def raises_syntaxError(): - try: - interpret("(x for x in (4,))") - except SyntaxError: - pass - else: - raise Exception("Expected SyntaxError") - - raises_syntaxError() - - statement = "python_version == '5'" - assert compile(statement).__doc__ == statement - diff --git a/setuptools/tests/test_packageindex.py b/setuptools/tests/test_packageindex.py index 746860d5..6a76b5fc 100644 --- a/setuptools/tests/test_packageindex.py +++ b/setuptools/tests/test_packageindex.py @@ -4,9 +4,10 @@ import sys import os import distutils.errors -from setuptools.compat import httplib, HTTPError, unicode, pathname2url -from .textwrap import DALS +from setuptools.extern import six +from setuptools.extern.six.moves import urllib, http_client +from .textwrap import DALS import pkg_resources import setuptools.package_index from setuptools.tests.server import IndexServer @@ -22,7 +23,7 @@ class TestPackageIndex: except Exception as v: assert url in str(v) else: - assert isinstance(v, HTTPError) + assert isinstance(v, urllib.error.HTTPError) def test_bad_url_typo(self): # issue 16 @@ -38,7 +39,7 @@ class TestPackageIndex: except Exception as v: assert url in str(v) else: - assert isinstance(v, HTTPError) + assert isinstance(v, urllib.error.HTTPError) def test_bad_url_bad_status_line(self): index = setuptools.package_index.PackageIndex( @@ -46,7 +47,7 @@ class TestPackageIndex: ) def _urlopen(*args): - raise httplib.BadStatusLine('line') + raise http_client.BadStatusLine('line') index.opener = _urlopen url = 'http://example.com' @@ -70,7 +71,7 @@ class TestPackageIndex: try: index.open_url(url) except distutils.errors.DistutilsError as error: - msg = unicode(error) + msg = six.text_type(error) assert 'nonnumeric port' in msg or 'getaddrinfo failed' in msg or 'Name or service not known' in msg return raise RuntimeError("Did not raise") @@ -167,7 +168,7 @@ class TestPackageIndex: index_file = tmpdir / 'index.html' with index_file.open('w') as f: f.write('<div>content</div>') - url = 'file:' + pathname2url(str(tmpdir)) + '/' + url = 'file:' + urllib.request.pathname2url(str(tmpdir)) + '/' res = setuptools.package_index.local_open(url) assert 'content' in res.read() diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py index 8ec9a4cb..d2a1f1bb 100644 --- a/setuptools/tests/test_sdist.py +++ b/setuptools/tests/test_sdist.py @@ -9,17 +9,19 @@ import unicodedata import contextlib import io +from setuptools.extern import six +from setuptools.extern.six.moves import map + import pytest import pkg_resources -from setuptools.compat import StringIO, unicode, PY3, PY2 from setuptools.command.sdist import sdist from setuptools.command.egg_info import manifest_maker from setuptools.dist import Distribution from setuptools.tests import fail_on_ascii -py3_only = pytest.mark.xfail(PY2, reason="Test runs on Python 3 only") +py3_only = pytest.mark.xfail(six.PY2, reason="Test runs on Python 3 only") SETUP_ATTRS = { @@ -37,7 +39,7 @@ setup(**%r) """ % SETUP_ATTRS -if PY3: +if six.PY3: LATIN1_FILENAME = 'smörbröd.py'.encode('latin-1') else: LATIN1_FILENAME = 'sm\xf6rbr\xf6d.py' @@ -47,7 +49,7 @@ else: @contextlib.contextmanager def quiet(): old_stdout, old_stderr = sys.stdout, sys.stderr - sys.stdout, sys.stderr = StringIO(), StringIO() + sys.stdout, sys.stderr = six.StringIO(), six.StringIO() try: yield finally: @@ -56,14 +58,14 @@ def quiet(): # Fake byte literals for Python <= 2.5 def b(s, encoding='utf-8'): - if PY3: + if six.PY3: return s.encode(encoding) return s # Convert to POSIX path def posix(path): - if PY3 and not isinstance(path, str): + if six.PY3 and not isinstance(path, str): return path.replace(os.sep.encode('ascii'), b('/')) else: return path.replace(os.sep, '/') @@ -71,7 +73,7 @@ def posix(path): # HFS Plus uses decomposed UTF-8 def decompose(path): - if isinstance(path, unicode): + if isinstance(path, six.text_type): return unicodedata.normalize('NFD', path) try: path = path.decode('utf-8') @@ -184,7 +186,7 @@ class TestSdistTest: u_contents = contents.decode('UTF-8') # The manifest should contain the UTF-8 filename - if PY2: + if six.PY2: fs_enc = sys.getfilesystemencoding() filename = filename.decode(fs_enc) @@ -289,7 +291,7 @@ class TestSdistTest: cmd.read_manifest() # The filelist should contain the UTF-8 filename - if PY3: + if six.PY3: filename = filename.decode('utf-8') assert filename in cmd.filelist.files @@ -342,7 +344,7 @@ class TestSdistTest: if sys.platform == 'darwin': filename = decompose(filename) - if PY3: + if six.PY3: fs_enc = sys.getfilesystemencoding() if sys.platform == 'win32': @@ -374,7 +376,7 @@ class TestSdistTest: with quiet(): cmd.run() - if PY3: + if six.PY3: # not all windows systems have a default FS encoding of cp1252 if sys.platform == 'win32': # Latin-1 is similar to Windows-1252 however diff --git a/setuptools/tests/test_setuptools.py b/setuptools/tests/test_setuptools.py index 8aca593a..e59800d2 100644 --- a/setuptools/tests/test_setuptools.py +++ b/setuptools/tests/test_setuptools.py @@ -23,7 +23,7 @@ def test_findall(example_source): def test_findall_curdir(example_source): with example_source.as_cwd(): found = list(setuptools.findall()) - expected = ['readme.txt', 'foo/bar.py'] + expected = ['readme.txt', os.path.join('foo', 'bar.py')] assert found == expected diff --git a/setuptools/tests/test_test.py b/setuptools/tests/test_test.py index a66294c9..4155a5b1 100644 --- a/setuptools/tests/test_test.py +++ b/setuptools/tests/test_test.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import os import site +from distutils.errors import DistutilsError import pytest diff --git a/setuptools/tests/test_unicode_utils.py b/setuptools/tests/test_unicode_utils.py new file mode 100644 index 00000000..a24a9bd5 --- /dev/null +++ b/setuptools/tests/test_unicode_utils.py @@ -0,0 +1,10 @@ +from setuptools import unicode_utils + + +def test_filesys_decode_fs_encoding_is_None(monkeypatch): + """ + Test filesys_decode does not raise TypeError when + getfilesystemencoding returns None. + """ + monkeypatch.setattr('sys.getfilesystemencoding', lambda: None) + unicode_utils.filesys_decode(b'test') diff --git a/setuptools/unicode_utils.py b/setuptools/unicode_utils.py index d2de941a..ffab3e24 100644 --- a/setuptools/unicode_utils.py +++ b/setuptools/unicode_utils.py @@ -1,11 +1,11 @@ import unicodedata import sys -from setuptools.compat import unicode as decoded_string +from setuptools.extern import six # HFS Plus uses decomposed UTF-8 def decompose(path): - if isinstance(path, decoded_string): + if isinstance(path, six.text_type): return unicodedata.normalize('NFD', path) try: path = path.decode('utf-8') @@ -22,11 +22,13 @@ def filesys_decode(path): NONE when no expected encoding works """ - fs_enc = sys.getfilesystemencoding() - if isinstance(path, decoded_string): + if isinstance(path, six.text_type): return path - for enc in (fs_enc, "utf-8"): + fs_enc = sys.getfilesystemencoding() or 'utf-8' + candidates = fs_enc, 'utf-8' + + for enc in candidates: try: return path.decode(enc) except UnicodeDecodeError: |
