summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.hgtags5
-rw-r--r--.travis.yml4
-rw-r--r--CHANGES.txt48
-rw-r--r--ez_setup.py32
-rw-r--r--pkg_resources/__init__.py6
-rw-r--r--release.py32
-rw-r--r--setuptools/command/build_py.py45
-rwxr-xr-xsetuptools/command/develop.py15
-rwxr-xr-xsetuptools/command/easy_install.py17
-rwxr-xr-xsetuptools/command/egg_info.py45
-rwxr-xr-xsetuptools/command/sdist.py6
-rwxr-xr-xsetuptools/command/setopt.py4
-rw-r--r--setuptools/compat.py8
-rw-r--r--setuptools/dist.py3
-rwxr-xr-xsetuptools/package_index.py65
-rwxr-xr-xsetuptools/sandbox.py7
-rw-r--r--setuptools/ssl_support.py6
-rw-r--r--setuptools/tests/test_easy_install.py11
-rw-r--r--setuptools/tests/test_packageindex.py21
-rw-r--r--setuptools/tests/test_sandbox.py39
-rw-r--r--setuptools/tests/test_sdist.py18
-rw-r--r--setuptools/tests/test_setuptools.py2
-rw-r--r--setuptools/version.py2
23 files changed, 292 insertions, 149 deletions
diff --git a/.hgtags b/.hgtags
index 508adf5b..eb98b62a 100644
--- a/.hgtags
+++ b/.hgtags
@@ -227,3 +227,8 @@ dfe190b09908f6b953209d13573063809de451b8 18.6
804f87045a901f1dc121cf9149143d654228dc13 18.6.1
67d07805606aead09349d5b91d7d26c68ddad2fc 18.7
3041e1fc409be90e885968b90faba405420fc161 18.7.1
+c811801ffa1de758cf01fbf6a86e4c04ff0c0935 18.8
+fbf06fa35f93a43f044b1645a7e4ff470edb462c 18.8.1
+cc41477ecf92f221c113736fac2830bf8079d40c 19.0
+834782ce49154e9744e499e00eb392c347f9e034 19.1
+0a2a3d89416e1642cf6f41d22dbc07b3d3c15a4d 19.1.1
diff --git a/.travis.yml b/.travis.yml
index 0bfb11bb..54d9c395 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,4 +20,6 @@ script:
- python bootstrap.py
- python setup.py test --addopts='-rs'
- - python ez_setup.py --version 18.6.1
+
+ # test the bootstrap script
+ - python ez_setup.py
diff --git a/CHANGES.txt b/CHANGES.txt
index 7f7ab9bd..48fc18a9 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -4,6 +4,54 @@ CHANGES
------
+19.1.1
+------
+
+* Issue #476: Cast version to string (using default encoding)
+ to avoid creating Unicode types on Python 2 clients.
+* Issue #477: In Powershell downloader, use explicit rendering
+ of strings, rather than rely on ``repr``, which can be
+ incorrect (especially on Python 2).
+
+----
+19.1
+----
+
+* Issue #215: The bootstrap script ``ez_setup.py`` now
+ automatically detects
+ the latest version of setuptools (using PyPI JSON API) rather
+ than hard-coding a particular value.
+* Issue #475: Fix incorrect usage in _translate_metadata2.
+
+----
+19.0
+----
+
+* Issue #442: Use RawConfigParser for parsing .pypirc file.
+ Interpolated values are no longer honored in .pypirc files.
+
+------
+18.8.1
+------
+
+* Issue #440: Prevent infinite recursion when a SandboxViolation
+ or other UnpickleableException occurs in a sandbox context
+ with setuptools hidden. Fixes regression introduced in Setuptools
+ 12.0.
+
+----
+18.8
+----
+
+* Deprecated ``egg_info.get_pkg_info_revision``.
+* Issue #471: Don't rely on repr for an HTML attribute value in
+ package_index.
+* Issue #419: Avoid errors in FileMetadata when the metadata directory
+ is broken.
+* Issue #472: Remove deprecated use of 'U' in mode parameter
+ when opening files.
+
+------
18.7.1
------
diff --git a/ez_setup.py b/ez_setup.py
index 975b45c5..9715bdc7 100644
--- a/ez_setup.py
+++ b/ez_setup.py
@@ -16,7 +16,8 @@ import subprocess
import platform
import textwrap
import contextlib
-import warnings
+import json
+import codecs
from distutils import log
@@ -30,7 +31,8 @@ try:
except ImportError:
USER_SITE = None
-DEFAULT_VERSION = "18.7.2"
+LATEST = object()
+DEFAULT_VERSION = LATEST
DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/"
DEFAULT_SAVE_DIR = os.curdir
@@ -140,6 +142,7 @@ def use_setuptools(
Return None. Raise SystemExit if the requested version
or later cannot be installed.
"""
+ version = _resolve_version(version)
to_dir = os.path.abspath(to_dir)
# prior to importing, capture the module state for
@@ -222,8 +225,8 @@ def download_file_powershell(url, target):
ps_cmd = (
"[System.Net.WebRequest]::DefaultWebProxy.Credentials = "
"[System.Net.CredentialCache]::DefaultCredentials; "
- "(new-object System.Net.WebClient).DownloadFile(%(url)r, %(target)r)"
- % vars()
+ '(new-object System.Net.WebClient).DownloadFile("%(url)s", "%(target)s")'
+ % locals()
)
cmd = [
'powershell',
@@ -321,6 +324,7 @@ def download_setuptools(
``downloader_factory`` should be a function taking no arguments and
returning a function for downloading a URL to a target.
"""
+ version = _resolve_version(version)
# making sure we use the absolute path
to_dir = os.path.abspath(to_dir)
zip_name = "setuptools-%s.zip" % version
@@ -333,6 +337,26 @@ def download_setuptools(
return os.path.realpath(saveto)
+def _resolve_version(version):
+ """
+ Resolve LATEST version
+ """
+ if version is not LATEST:
+ return version
+
+ resp = urlopen('https://pypi.python.org/pypi/setuptools/json')
+ with contextlib.closing(resp):
+ try:
+ charset = resp.info().get_content_charset()
+ except Exception:
+ # Python 2 compat; assume UTF-8
+ charset = 'UTF-8'
+ reader = codecs.getreader(charset)
+ doc = json.load(reader(resp))
+
+ return str(doc['info']['version'])
+
+
def _build_install_args(options):
"""
Build the arguments to 'python setup.py install' on the setuptools package.
diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py
index 3cd67fa0..b55e4127 100644
--- a/pkg_resources/__init__.py
+++ b/pkg_resources/__init__.py
@@ -1537,7 +1537,7 @@ class MarkerEvaluation(object):
"""
return dict(
(key.replace('.', '_'), value)
- for key, value in env
+ for key, value in env.items()
)
@classmethod
@@ -2017,11 +2017,11 @@ class FileMetadata(EmptyProvider):
self.path = path
def has_metadata(self, name):
- return name=='PKG-INFO'
+ return name=='PKG-INFO' and os.path.isfile(self.path)
def get_metadata(self, name):
if name=='PKG-INFO':
- with io.open(self.path, 'rU', encoding='utf-8') as f:
+ with io.open(self.path, encoding='utf-8') as f:
metadata = f.read()
return metadata
raise KeyError("No metadata except PKG-INFO is available")
diff --git a/release.py b/release.py
index a76d2de4..dd1d6a1c 100644
--- a/release.py
+++ b/release.py
@@ -4,24 +4,13 @@ install jaraco.packaging and run 'python -m jaraco.packaging.release'
"""
import os
-import subprocess
import pkg_resources
pkg_resources.require('jaraco.packaging>=2.0')
pkg_resources.require('wheel')
-
-def before_upload():
- BootstrapBookmark.add()
-
-
-def after_push():
- BootstrapBookmark.push()
-
-files_with_versions = (
- 'ez_setup.py', 'setuptools/version.py',
-)
+files_with_versions = 'setuptools/version.py',
# bdist_wheel must be included or pip will break
dist_commands = 'sdist', 'bdist_wheel'
@@ -29,22 +18,3 @@ dist_commands = 'sdist', 'bdist_wheel'
test_info = "Travis-CI tests: http://travis-ci.org/#!/jaraco/setuptools"
os.environ["SETUPTOOLS_INSTALL_WINDOWS_SPECIFIC_FILES"] = "1"
-
-class BootstrapBookmark:
- name = 'bootstrap'
-
- @classmethod
- def add(cls):
- cmd = ['hg', 'bookmark', '-i', cls.name, '-f']
- subprocess.Popen(cmd)
-
- @classmethod
- def push(cls):
- """
- Push the bootstrap bookmark
- """
- push_command = ['hg', 'push', '-B', cls.name]
- # don't use check_call here because mercurial will return a non-zero
- # code even if it succeeds at pushing the bookmark (because there are
- # no changesets to be pushed). !dm mercurial
- subprocess.call(push_command)
diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py
index a873d54b..8a50f032 100644
--- a/setuptools/command/build_py.py
+++ b/setuptools/command/build_py.py
@@ -2,9 +2,13 @@ from glob import glob
from distutils.util import convert_path
import distutils.command.build_py as orig
import os
-import sys
import fnmatch
import textwrap
+import io
+import distutils.errors
+import collections
+import itertools
+
try:
from setuptools.lib2to3_ex import Mixin2to3
@@ -157,17 +161,15 @@ class build_py(orig.build_py, Mixin2to3):
else:
return init_py
- f = open(init_py, 'rbU')
- if 'declare_namespace'.encode() not in f.read():
- from distutils.errors import DistutilsError
-
- raise DistutilsError(
+ with io.open(init_py, 'rb') as f:
+ contents = f.read()
+ if b'declare_namespace' not in contents:
+ raise distutils.errors.DistutilsError(
"Namespace package problem: %s is a namespace package, but "
"its\n__init__.py does not call declare_namespace()! Please "
'fix it.\n(See the setuptools manual under '
'"Namespace Packages" for details.)\n"' % (package,)
)
- f.close()
return init_py
def initialize_options(self):
@@ -182,20 +184,25 @@ class build_py(orig.build_py, Mixin2to3):
def exclude_data_files(self, package, src_dir, files):
"""Filter filenames for package's data files in 'src_dir'"""
- globs = (self.exclude_package_data.get('', [])
- + self.exclude_package_data.get(package, []))
- bad = []
- for pattern in globs:
- bad.extend(
- fnmatch.filter(
- files, os.path.join(src_dir, convert_path(pattern))
- )
+ globs = (
+ self.exclude_package_data.get('', [])
+ + self.exclude_package_data.get(package, [])
+ )
+ bad = set(
+ item
+ for pattern in globs
+ for item in fnmatch.filter(
+ files,
+ os.path.join(src_dir, convert_path(pattern)),
)
- bad = dict.fromkeys(bad)
- seen = {}
+ )
+ seen = collections.defaultdict(itertools.count)
return [
- f for f in files if f not in bad
- and f not in seen and seen.setdefault(f, 1) # ditch dupes
+ fn
+ for fn in files
+ if fn not in bad
+ # ditch dupes
+ and not next(seen[fn])
]
diff --git a/setuptools/command/develop.py b/setuptools/command/develop.py
index 5ae25d71..07b66ccb 100755
--- a/setuptools/command/develop.py
+++ b/setuptools/command/develop.py
@@ -3,6 +3,7 @@ from distutils import log
from distutils.errors import DistutilsError, DistutilsOptionError
import os
import glob
+import io
from pkg_resources import Distribution, PathMetadata, normalize_path
from setuptools.command.easy_install import easy_install
@@ -53,8 +54,8 @@ class develop(easy_install):
# pick up setup-dir .egg files only: no .egg-info
self.package_index.scan(glob.glob('*.egg'))
- self.egg_link = os.path.join(self.install_dir, ei.egg_name +
- '.egg-link')
+ egg_link_fn = ei.egg_name + '.egg-link'
+ self.egg_link = os.path.join(self.install_dir, egg_link_fn)
self.egg_base = ei.egg_base
if self.egg_path is None:
self.egg_path = os.path.abspath(ei.egg_base)
@@ -124,9 +125,8 @@ class develop(easy_install):
# create an .egg-link in the installation dir, pointing to our egg
log.info("Creating %s (link to %s)", self.egg_link, self.egg_base)
if not self.dry_run:
- f = open(self.egg_link, "w")
- f.write(self.egg_path + "\n" + self.setup_path)
- f.close()
+ with open(self.egg_link, "w") as f:
+ f.write(self.egg_path + "\n" + self.setup_path)
# postprocess the installed distro, fixing up .pth, installing scripts,
# and handling requirements
self.process_distribution(None, self.dist, not self.no_deps)
@@ -163,9 +163,8 @@ class develop(easy_install):
for script_name in self.distribution.scripts or []:
script_path = os.path.abspath(convert_path(script_name))
script_name = os.path.basename(script_path)
- f = open(script_path, 'rU')
- script_text = f.read()
- f.close()
+ with io.open(script_path) as strm:
+ script_text = strm.read()
self.install_script(dist, script_name, script_text, script_path)
def install_wrapper_scripts(self, dist):
diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py
index 9e9c5e54..9ca3b515 100755
--- a/setuptools/command/easy_install.py
+++ b/setuptools/command/easy_install.py
@@ -772,8 +772,8 @@ class easy_install(Command):
is_script = is_python_script(script_text, script_name)
if is_script:
- script_text = (ScriptWriter.get_header(script_text) +
- self._load_template(dev_path) % locals())
+ body = self._load_template(dev_path) % locals()
+ script_text = ScriptWriter.get_header(script_text) + body
self.write_script(script_name, _to_ascii(script_text), 'b')
@staticmethod
@@ -805,9 +805,8 @@ class easy_install(Command):
ensure_directory(target)
if os.path.exists(target):
os.unlink(target)
- f = open(target, "w" + mode)
- f.write(contents)
- f.close()
+ with open(target, "w" + mode) as f:
+ f.write(contents)
chmod(target, 0o777 - mask)
def install_eggs(self, spec, dist_filename, tmpdir):
@@ -1403,7 +1402,7 @@ def expand_paths(inputs):
def extract_wininst_cfg(dist_filename):
"""Extract configuration data from a bdist_wininst .exe
- Returns a ConfigParser.RawConfigParser, or None
+ Returns a configparser.RawConfigParser, or None
"""
f = open(dist_filename, 'rb')
try:
@@ -1416,7 +1415,7 @@ def extract_wininst_cfg(dist_filename):
return None
f.seek(prepended - 12)
- from setuptools.compat import StringIO, ConfigParser
+ from setuptools.compat import StringIO, configparser
import struct
tag, cfglen, bmlen = struct.unpack("<iii", f.read(12))
@@ -1424,7 +1423,7 @@ def extract_wininst_cfg(dist_filename):
return None # not a valid tag
f.seek(prepended - (12 + cfglen))
- cfg = ConfigParser.RawConfigParser(
+ cfg = configparser.RawConfigParser(
{'version': '', 'target_version': ''})
try:
part = f.read(cfglen)
@@ -1434,7 +1433,7 @@ def extract_wininst_cfg(dist_filename):
# be text, so decode it.
config = config.decode(sys.getfilesystemencoding())
cfg.readfp(StringIO(config))
- except ConfigParser.Error:
+ except configparser.Error:
return None
if not cfg.has_section('metadata') or not cfg.has_section('Setup'):
return None
diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py
index 50f3d5c0..1301bd84 100755
--- a/setuptools/command/egg_info.py
+++ b/setuptools/command/egg_info.py
@@ -10,16 +10,17 @@ import distutils.filelist
import os
import re
import sys
+import io
+import warnings
+import time
-try:
- from setuptools_svn import svn_utils
-except ImportError:
- pass
+from setuptools.compat import basestring, PY3, StringIO
from setuptools import Command
from setuptools.command.sdist import sdist
-from setuptools.compat import basestring, PY3, StringIO
from setuptools.command.sdist import walk_revctrl
+from setuptools.command.setopt import edit_config
+from setuptools.command import bdist_egg
from pkg_resources import (
parse_requirements, safe_name, parse_version,
safe_version, yield_lines, EntryPoint, iter_entry_points, to_filename)
@@ -27,6 +28,12 @@ import setuptools.unicode_utils as unicode_utils
from pkg_resources import packaging
+try:
+ from setuptools_svn import svn_utils
+except ImportError:
+ pass
+
+
class egg_info(Command):
description = "create a distribution's .egg-info directory"
@@ -58,8 +65,6 @@ class egg_info(Command):
self.vtags = None
def save_version_info(self, filename):
- from setuptools.command.setopt import edit_config
-
values = dict(
egg_info=dict(
tag_svn_revision=0,
@@ -184,12 +189,8 @@ class egg_info(Command):
if self.tag_build:
version += self.tag_build
if self.tag_svn_revision:
- rev = self.get_svn_revision()
- if rev: # is 0 if it's not an svn working copy
- version += '-r%s' % rev
+ version += '-r%s' % self.get_svn_revision()
if self.tag_date:
- import time
-
version += time.strftime("-%Y%m%d")
return version
@@ -390,7 +391,6 @@ def write_pkg_info(cmd, basename, filename):
metadata.name, metadata.version = oldname, oldver
safe = getattr(cmd.distribution, 'zip_safe', None)
- from setuptools.command import bdist_egg
bdist_egg.write_safety_flag(cmd.egg_info, safe)
@@ -467,14 +467,15 @@ def write_entries(cmd, basename, filename):
def get_pkg_info_revision():
- # See if we can get a -r### off of PKG-INFO, in case this is an sdist of
- # a subversion revision
- #
+ """
+ Get a -r### off of PKG-INFO Version in case this is an sdist of
+ a subversion revision.
+ """
+ warnings.warn("get_pkg_info_revision is deprecated.", DeprecationWarning)
if os.path.exists('PKG-INFO'):
- f = open('PKG-INFO', 'rU')
- for line in f:
- match = re.match(r"Version:.*-r(\d+)\s*$", line)
- if match:
- return int(match.group(1))
- f.close()
+ with io.open('PKG-INFO') as f:
+ for line in f:
+ match = re.match(r"Version:.*-r(\d+)\s*$", line)
+ if match:
+ return int(match.group(1))
return 0
diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py
index 851a1775..71196512 100755
--- a/setuptools/command/sdist.py
+++ b/setuptools/command/sdist.py
@@ -3,6 +3,7 @@ from distutils import log
import distutils.command.sdist as orig
import os
import sys
+import io
from setuptools.compat import PY3
from setuptools.utils import cs_path_exists
@@ -166,11 +167,8 @@ class sdist(orig.sdist):
if not os.path.isfile(self.manifest):
return False
- fp = open(self.manifest, 'rbU')
- try:
+ with io.open(self.manifest, 'rb') as fp:
first_line = fp.readline()
- finally:
- fp.close()
return (first_line !=
'# file GENERATED by distutils, do NOT edit\n'.encode())
diff --git a/setuptools/command/setopt.py b/setuptools/command/setopt.py
index a04d6032..74c7cad8 100755
--- a/setuptools/command/setopt.py
+++ b/setuptools/command/setopt.py
@@ -37,10 +37,10 @@ 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
+ from setuptools.compat import configparser
log.debug("Reading configuration from %s", filename)
- opts = ConfigParser.RawConfigParser()
+ opts = configparser.RawConfigParser()
opts.read([filename])
for section, options in settings.items():
if options is None:
diff --git a/setuptools/compat.py b/setuptools/compat.py
index 73e6e4aa..f0175a5d 100644
--- a/setuptools/compat.py
+++ b/setuptools/compat.py
@@ -7,7 +7,7 @@ PY2 = not PY3
if PY2:
basestring = basestring
import __builtin__ as builtins
- import ConfigParser
+ import ConfigParser as configparser
from StringIO import StringIO
BytesIO = StringIO
func_code = lambda o: o.func_code
@@ -29,6 +29,8 @@ if PY2:
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""")
@@ -36,7 +38,7 @@ if PY2:
if PY3:
basestring = str
import builtins
- import configparser as ConfigParser
+ import configparser
from io import StringIO, BytesIO
func_code = lambda o: o.__code__
func_globals = lambda o: o.__globals__
@@ -59,6 +61,8 @@ if PY3:
urlunsplit, splittag,
)
filterfalse = itertools.filterfalse
+ filter = filter
+ map = map
def reraise(tp, value, tb=None):
if value.__traceback__ is not tb:
diff --git a/setuptools/dist.py b/setuptools/dist.py
index c5f04b33..c1421b0f 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -267,8 +267,7 @@ class Distribution(_Distribution):
if attrs and 'setup_requires' in attrs:
self.fetch_build_eggs(attrs['setup_requires'])
for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
- if not hasattr(self,ep.name):
- setattr(self,ep.name,None)
+ vars(self).setdefault(ep.name, None)
_Distribution.__init__(self,attrs)
if isinstance(self.metadata.version, numbers.Number):
# Some people apparently take "version number" too literally :)
diff --git a/setuptools/package_index.py b/setuptools/package_index.py
index cabf1039..2c565e88 100755
--- a/setuptools/package_index.py
+++ b/setuptools/package_index.py
@@ -6,6 +6,7 @@ import shutil
import socket
import base64
import hashlib
+import itertools
from functools import wraps
from pkg_resources import (
@@ -16,11 +17,11 @@ 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)
+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
@@ -352,20 +353,30 @@ class PackageIndex(Environment):
self.warn(msg, url)
def scan_egg_links(self, search_path):
- for item in search_path:
- if os.path.isdir(item):
- for entry in os.listdir(item):
- if entry.endswith('.egg-link'):
- self.scan_egg_link(item, entry)
+ dirs = filter(os.path.isdir, search_path)
+ egg_links = (
+ (path, entry)
+ for path in dirs
+ for entry in os.listdir(path)
+ if entry.endswith('.egg-link')
+ )
+ list(itertools.starmap(self.scan_egg_link, egg_links))
def scan_egg_link(self, path, entry):
- lines = [_f for _f in map(str.strip,
- open(os.path.join(path, entry))) if _f]
- if len(lines)==2:
- for dist in find_distributions(os.path.join(path, lines[0])):
- dist.location = os.path.join(path, *lines)
- dist.precedence = SOURCE_DIST
- self.add(dist)
+ with open(os.path.join(path, entry)) as raw_lines:
+ # filter non-empty lines
+ lines = list(filter(None, map(str.strip, raw_lines)))
+
+ if len(lines) != 2:
+ # format is not recognized; punt
+ return
+
+ egg_path, setup_path = lines
+
+ for dist in find_distributions(os.path.join(path, egg_path)):
+ dist.location = os.path.join(path, *lines)
+ dist.precedence = SOURCE_DIST
+ self.add(dist)
def process_index(self,url,page):
"""Process the contents of a PyPI page"""
@@ -934,14 +945,14 @@ class Credential(object):
def __str__(self):
return '%(username)s:%(password)s' % vars(self)
-class PyPIConfig(ConfigParser.ConfigParser):
+class PyPIConfig(configparser.RawConfigParser):
def __init__(self):
"""
Load from ~/.pypirc
"""
defaults = dict.fromkeys(['username', 'password', 'repository'], '')
- ConfigParser.ConfigParser.__init__(self, defaults)
+ configparser.RawConfigParser.__init__(self, defaults)
rc = os.path.join(os.path.expanduser('~'), '.pypirc')
if os.path.exists(rc):
@@ -1031,16 +1042,18 @@ def local_open(url):
elif path.endswith('/') and os.path.isdir(filename):
files = []
for f in os.listdir(filename):
- if f=='index.html':
- with open(os.path.join(filename,f),'r') as fp:
+ filepath = os.path.join(filename, f)
+ if f == 'index.html':
+ with open(filepath, 'r') as fp:
body = fp.read()
break
- elif os.path.isdir(os.path.join(filename,f)):
- f+='/'
- files.append("<a href=%r>%s</a>" % (f,f))
+ elif os.path.isdir(filepath):
+ f += '/'
+ files.append('<a href="{name}">{name}</a>'.format(name=f))
else:
- body = ("<html><head><title>%s</title>" % url) + \
- "</head><body>%s</body></html>" % '\n'.join(files)
+ tmpl = ("<html><head><title>{url}</title>"
+ "</head><body>{files}</body></html>")
+ body = tmpl.format(url=url, files='\n'.join(files))
status, message = 200, "OK"
else:
status, message, body = 404, "Path not found", "Not found"
diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py
index 213cebff..85de85ff 100755
--- a/setuptools/sandbox.py
+++ b/setuptools/sandbox.py
@@ -98,8 +98,8 @@ class UnpickleableException(Exception):
"""
An exception representing another Exception that could not be pickled.
"""
- @classmethod
- def dump(cls, type, exc):
+ @staticmethod
+ def dump(type, exc):
"""
Always return a dumped (pickled) type and exc. If exc can't be pickled,
wrap it in UnpickleableException first.
@@ -107,6 +107,8 @@ class UnpickleableException(Exception):
try:
return pickle.dumps(type), pickle.dumps(exc)
except Exception:
+ # get UnpickleableException inside the sandbox
+ from setuptools.sandbox import UnpickleableException as cls
return cls.dump(cls, cls(repr(exc)))
@@ -382,6 +384,7 @@ class DirectorySandbox(AbstractSandbox):
AbstractSandbox.__init__(self)
def _violation(self, operation, *args, **kw):
+ from setuptools.sandbox import SandboxViolation
raise SandboxViolation(operation, args, kw)
if _file:
diff --git a/setuptools/ssl_support.py b/setuptools/ssl_support.py
index cc7db067..7394f4f5 100644
--- a/setuptools/ssl_support.py
+++ b/setuptools/ssl_support.py
@@ -223,6 +223,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/test_easy_install.py b/setuptools/tests/test_easy_install.py
index 00e16b63..f0330f17 100644
--- a/setuptools/tests/test_easy_install.py
+++ b/setuptools/tests/test_easy_install.py
@@ -446,10 +446,15 @@ class TestScriptHeader:
exe = tmpdir / 'exe.py'
with exe.open('w') as f:
f.write(header)
- exe = str(exe)
+
+ exe = ei.nt_quote_arg(os.path.normpath(str(exe)))
+
+ # Make sure Windows paths are quoted properly before they're sent
+ # through shlex.split by get_script_header
+ executable = '"%s"' % exe if os.path.splitdrive(exe)[0] else exe
header = ei.ScriptWriter.get_script_header('#!/usr/local/bin/python',
- executable=exe)
+ executable=executable)
assert header == '#!/usr/bin/env %s\n' % exe
expect_out = 'stdout' if sys.version_info < (2,7) else 'stderr'
@@ -458,7 +463,7 @@ class TestScriptHeader:
# 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)
+ executable=executable)
assert candidate == '#!%s -x\n' % exe
output = locals()[expect_out]
assert 'Unable to adapt shebang line' in output.getvalue()
diff --git a/setuptools/tests/test_packageindex.py b/setuptools/tests/test_packageindex.py
index dcd90d6f..746860d5 100644
--- a/setuptools/tests/test_packageindex.py
+++ b/setuptools/tests/test_packageindex.py
@@ -1,7 +1,11 @@
+from __future__ import absolute_import
+
import sys
+import os
import distutils.errors
from setuptools.compat import httplib, HTTPError, unicode, pathname2url
+from .textwrap import DALS
import pkg_resources
import setuptools.package_index
@@ -201,3 +205,20 @@ class TestContentCheckers:
'http://foo/bar#md5=f12895fdffbd45007040d2e44df98478')
rep = checker.report(lambda x: x, 'My message about %s')
assert rep == 'My message about md5'
+
+
+class TestPyPIConfig:
+ def test_percent_in_password(self, tmpdir, monkeypatch):
+ monkeypatch.setitem(os.environ, 'HOME', str(tmpdir))
+ pypirc = tmpdir / '.pypirc'
+ with pypirc.open('w') as strm:
+ strm.write(DALS("""
+ [pypi]
+ repository=https://pypi.python.org
+ username=jaraco
+ password=pity%
+ """))
+ cfg = setuptools.package_index.PyPIConfig()
+ cred = cfg.creds_by_repository['https://pypi.python.org']
+ assert cred.username == 'jaraco'
+ assert cred.password == 'pity%'
diff --git a/setuptools/tests/test_sandbox.py b/setuptools/tests/test_sandbox.py
index 6e1e9e1c..fefd46f7 100644
--- a/setuptools/tests/test_sandbox.py
+++ b/setuptools/tests/test_sandbox.py
@@ -100,3 +100,42 @@ class TestExceptionSaver:
saved_exc.resume()
assert str(caught.value) == "CantPickleThis('detail',)"
+
+ def test_unpickleable_exception_when_hiding_setuptools(self):
+ """
+ As revealed in #440, an infinite recursion can occur if an unpickleable
+ exception while setuptools is hidden. Ensure this doesn't happen.
+ """
+ class ExceptionUnderTest(Exception):
+ """
+ An unpickleable exception (not in globals).
+ """
+
+ with pytest.raises(setuptools.sandbox.UnpickleableException) as caught:
+ with setuptools.sandbox.save_modules():
+ setuptools.sandbox.hide_setuptools()
+ raise ExceptionUnderTest()
+
+ msg, = caught.value.args
+ assert msg == 'ExceptionUnderTest()'
+
+ def test_sandbox_violation_raised_hiding_setuptools(self, tmpdir):
+ """
+ When in a sandbox with setuptools hidden, a SandboxViolation
+ should reflect a proper exception and not be wrapped in
+ an UnpickleableException.
+ """
+ def write_file():
+ "Trigger a SandboxViolation by writing outside the sandbox"
+ with open('/etc/foo', 'w'):
+ pass
+ sandbox = DirectorySandbox(str(tmpdir))
+ with pytest.raises(setuptools.sandbox.SandboxViolation) as caught:
+ with setuptools.sandbox.save_modules():
+ setuptools.sandbox.hide_setuptools()
+ sandbox.run(write_file)
+
+ cmd, args, kwargs = caught.value.args
+ assert cmd == 'open'
+ assert args == ('/etc/foo', 'w')
+ assert kwargs == {}
diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py
index ec3c8aa9..8ec9a4cb 100644
--- a/setuptools/tests/test_sdist.py
+++ b/setuptools/tests/test_sdist.py
@@ -7,6 +7,7 @@ import sys
import tempfile
import unicodedata
import contextlib
+import io
import pytest
@@ -81,6 +82,11 @@ def decompose(path):
return path
+def read_all_bytes(filename):
+ with io.open(filename, 'rb') as fp:
+ return fp.read()
+
+
class TestSdistTest:
def setup_method(self, method):
@@ -172,9 +178,7 @@ class TestSdistTest:
mm.filelist.append(filename)
mm.write_manifest()
- manifest = open(mm.manifest, 'rbU')
- contents = manifest.read()
- manifest.close()
+ contents = read_all_bytes(mm.manifest)
# The manifest should be UTF-8 encoded
u_contents = contents.decode('UTF-8')
@@ -210,9 +214,7 @@ class TestSdistTest:
# Re-write manifest
mm.write_manifest()
- manifest = open(mm.manifest, 'rbU')
- contents = manifest.read()
- manifest.close()
+ contents = read_all_bytes(mm.manifest)
# The manifest should be UTF-8 encoded
contents.decode('UTF-8')
@@ -248,9 +250,7 @@ class TestSdistTest:
# Re-write manifest
mm.write_manifest()
- manifest = open(mm.manifest, 'rbU')
- contents = manifest.read()
- manifest.close()
+ contents = read_all_bytes(mm.manifest)
# The manifest should be UTF-8 encoded
contents.decode('UTF-8')
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/version.py b/setuptools/version.py
index 5e40c658..961ff96e 100644
--- a/setuptools/version.py
+++ b/setuptools/version.py
@@ -1 +1 @@
-__version__ = '18.7.2'
+__version__ = '19.1.2'