summaryrefslogtreecommitdiff
path: root/setuptools
diff options
context:
space:
mode:
Diffstat (limited to 'setuptools')
-rw-r--r--setuptools/command/build_ext.py48
-rw-r--r--setuptools/command/build_py.py93
-rwxr-xr-xsetuptools/command/easy_install.py171
-rwxr-xr-xsetuptools/command/egg_info.py23
-rwxr-xr-xsetuptools/command/rotate.py6
-rw-r--r--setuptools/command/test.py21
-rw-r--r--setuptools/command/upload.py25
-rw-r--r--setuptools/command/upload_docs.py88
-rw-r--r--setuptools/launch.py36
-rw-r--r--setuptools/msvc9_support.py2
-rwxr-xr-xsetuptools/package_index.py9
-rw-r--r--setuptools/py26compat.py20
-rw-r--r--setuptools/py27compat.py12
-rw-r--r--setuptools/tests/py26compat.py8
-rw-r--r--setuptools/tests/test_build_py.py31
-rw-r--r--setuptools/tests/test_easy_install.py14
-rw-r--r--setuptools/tests/test_egg_info.py80
-rw-r--r--setuptools/tests/test_upload_docs.py12
18 files changed, 491 insertions, 208 deletions
diff --git a/setuptools/command/build_ext.py b/setuptools/command/build_ext.py
index 92e4a189..1caf8c81 100644
--- a/setuptools/command/build_ext.py
+++ b/setuptools/command/build_ext.py
@@ -16,15 +16,32 @@ try:
except ImportError:
_build_ext = _du_build_ext
-try:
- # Python 2.7 or >=3.2
- from sysconfig import _CONFIG_VARS
-except ImportError:
- from distutils.sysconfig import get_config_var
+from distutils.sysconfig import get_config_var
+
+get_config_var("LDSHARED") # make sure _config_vars is initialized
+del get_config_var
+from distutils.sysconfig import _config_vars as _CONFIG_VARS
+
+
+def _customize_compiler_for_shlib(compiler):
+ if sys.platform == "darwin":
+ # building .dylib requires additional compiler flags on OSX; here we
+ # temporarily substitute the pyconfig.h variables so that distutils'
+ # 'customize_compiler' uses them before we build the shared libraries.
+ tmp = _CONFIG_VARS.copy()
+ try:
+ # XXX Help! I don't have any idea whether these are right...
+ _CONFIG_VARS['LDSHARED'] = (
+ "gcc -Wl,-x -dynamiclib -undefined dynamic_lookup")
+ _CONFIG_VARS['CCSHARED'] = " -dynamiclib"
+ _CONFIG_VARS['SO'] = ".dylib"
+ customize_compiler(compiler)
+ finally:
+ _CONFIG_VARS.clear()
+ _CONFIG_VARS.update(tmp)
+ else:
+ customize_compiler(compiler)
- get_config_var("LDSHARED") # make sure _config_vars is initialized
- del get_config_var
- from distutils.sysconfig import _config_vars as _CONFIG_VARS
have_rtld = False
use_stubs = False
@@ -124,20 +141,7 @@ class build_ext(_build_ext):
compiler = self.shlib_compiler = new_compiler(
compiler=self.compiler, dry_run=self.dry_run, force=self.force
)
- if sys.platform == "darwin":
- tmp = _CONFIG_VARS.copy()
- try:
- # XXX Help! I don't have any idea whether these are right...
- _CONFIG_VARS['LDSHARED'] = (
- "gcc -Wl,-x -dynamiclib -undefined dynamic_lookup")
- _CONFIG_VARS['CCSHARED'] = " -dynamiclib"
- _CONFIG_VARS['SO'] = ".dylib"
- customize_compiler(compiler)
- finally:
- _CONFIG_VARS.clear()
- _CONFIG_VARS.update(tmp)
- else:
- customize_compiler(compiler)
+ _customize_compiler_for_shlib(compiler)
if self.include_dirs is not None:
compiler.set_include_dirs(self.include_dirs)
diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py
index 8623c777..0bad8295 100644
--- a/setuptools/command/build_py.py
+++ b/setuptools/command/build_py.py
@@ -6,10 +6,10 @@ import fnmatch
import textwrap
import io
import distutils.errors
-import collections
import itertools
-from setuptools.extern.six.moves import map
+from setuptools.extern import six
+from setuptools.extern.six.moves import map, filter, filterfalse
try:
from setuptools.lib2to3_ex import Mixin2to3
@@ -67,6 +67,9 @@ class build_py(orig.build_py, Mixin2to3):
return orig.build_py.__getattr__(self, attr)
def build_module(self, module, module_file, package):
+ if six.PY2 and isinstance(package, six.string_types):
+ # avoid errors on Python 2 when unicode is passed (#190)
+ package = package.split('.')
outfile, copied = orig.build_py.build_module(self, module, module_file,
package)
if copied:
@@ -94,12 +97,19 @@ class build_py(orig.build_py, Mixin2to3):
def find_data_files(self, package, src_dir):
"""Return filenames for package's data files in 'src_dir'"""
- globs = (self.package_data.get('', [])
- + self.package_data.get(package, []))
- files = self.manifest_files.get(package, [])[:]
- for pattern in globs:
- # Each pattern has to be converted to a platform-specific path
- files.extend(glob(os.path.join(src_dir, convert_path(pattern))))
+ patterns = self._get_platform_patterns(
+ self.package_data,
+ package,
+ src_dir,
+ )
+ globs_expanded = map(glob, patterns)
+ # flatten the expanded globs into an iterable of matches
+ globs_matches = itertools.chain.from_iterable(globs_expanded)
+ glob_files = filter(os.path.isfile, globs_matches)
+ files = itertools.chain(
+ self.manifest_files.get(package, []),
+ glob_files,
+ )
return self.exclude_data_files(package, src_dir, files)
def build_package_data(self):
@@ -184,26 +194,63 @@ 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, [])
+ files = list(files)
+ patterns = self._get_platform_patterns(
+ self.exclude_package_data,
+ package,
+ src_dir,
)
- bad = set(
- item
- for pattern in globs
- for item in fnmatch.filter(
- files,
- os.path.join(src_dir, convert_path(pattern)),
- )
+ match_groups = (
+ fnmatch.filter(files, pattern)
+ for pattern in patterns
)
- seen = collections.defaultdict(itertools.count)
- return [
+ # flatten the groups of matches into an iterable of matches
+ matches = itertools.chain.from_iterable(match_groups)
+ bad = set(matches)
+ keepers = (
fn
for fn in files
if fn not in bad
- # ditch dupes
- and not next(seen[fn])
- ]
+ )
+ # ditch dupes
+ return list(_unique_everseen(keepers))
+
+ @staticmethod
+ def _get_platform_patterns(spec, package, src_dir):
+ """
+ yield platfrom-specific path patterns (suitable for glob
+ or fn_match) from a glob-based spec (such as
+ self.package_data or self.exclude_package_data)
+ matching package in src_dir.
+ """
+ raw_patterns = itertools.chain(
+ spec.get('', []),
+ spec.get(package, []),
+ )
+ return (
+ # Each pattern has to be converted to a platform-specific path
+ os.path.join(src_dir, convert_path(pattern))
+ for pattern in raw_patterns
+ )
+
+
+# from Python docs
+def _unique_everseen(iterable, key=None):
+ "List unique elements, preserving order. Remember all elements ever seen."
+ # unique_everseen('AAAABBBCCDAABBB') --> A B C D
+ # unique_everseen('ABBCcAD', str.lower) --> A B C D
+ seen = set()
+ seen_add = seen.add
+ if key is None:
+ for element in filterfalse(seen.__contains__, iterable):
+ seen_add(element)
+ yield element
+ else:
+ for element in iterable:
+ k = key(element)
+ if k not in seen:
+ seen_add(k)
+ yield element
def assert_relative(path):
diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py
index ea5cb028..ccc66cf7 100755
--- a/setuptools/command/easy_install.py
+++ b/setuptools/command/easy_install.py
@@ -15,8 +15,10 @@ __ https://pythonhosted.org/setuptools/easy_install.html
from glob import glob
from distutils.util import get_platform
from distutils.util import convert_path, subst_vars
-from distutils.errors import DistutilsArgError, DistutilsOptionError, \
- DistutilsError, DistutilsPlatformError
+from distutils.errors import (
+ DistutilsArgError, DistutilsOptionError,
+ DistutilsError, DistutilsPlatformError,
+)
from distutils.command.install import INSTALL_SCHEMES, SCHEME_KEYS
from distutils import log, dir_util
from distutils.command.build_scripts import first_line_re
@@ -74,6 +76,12 @@ def is_64bit():
def samefile(p1, p2):
+ """
+ Determine if two paths reference the same file.
+
+ Augments os.path.samefile to work on Windows and
+ suppresses errors if the path doesn't exist.
+ """
both_exist = os.path.exists(p1) and os.path.exists(p2)
use_samefile = hasattr(os.path, 'samefile') and both_exist
if use_samefile:
@@ -105,6 +113,9 @@ else:
return False
+_one_liner = lambda text: textwrap.dedent(text).strip().replace('\n', '; ')
+
+
class easy_install(Command):
"""Manage a download/build/install process"""
description = "Find/get/install Python packages"
@@ -258,8 +269,10 @@ class easy_install(Command):
self.expand_basedirs()
self.expand_dirs()
- self._expand('install_dir', 'script_dir', 'build_directory',
- 'site_dirs')
+ self._expand(
+ 'install_dir', 'script_dir', 'build_directory',
+ 'site_dirs',
+ )
# If a non-default installation directory was specified, default the
# script directory to match it.
if self.script_dir is None:
@@ -379,9 +392,15 @@ class easy_install(Command):
def expand_dirs(self):
"""Calls `os.path.expanduser` on install dirs."""
- self._expand_attrs(['install_purelib', 'install_platlib',
- 'install_lib', 'install_headers',
- 'install_scripts', 'install_data', ])
+ dirs = [
+ 'install_purelib',
+ 'install_platlib',
+ 'install_lib',
+ 'install_headers',
+ 'install_scripts',
+ 'install_data',
+ ]
+ self._expand_attrs(dirs)
def run(self):
if self.verbose != self.distribution.verbose:
@@ -515,6 +534,12 @@ class easy_install(Command):
pth_file = self.pseudo_tempname() + ".pth"
ok_file = pth_file + '.ok'
ok_exists = os.path.exists(ok_file)
+ tmpl = _one_liner("""
+ import os
+ f = open({ok_file!r}, 'w')
+ f.write('OK')
+ f.close()
+ """) + '\n'
try:
if ok_exists:
os.unlink(ok_file)
@@ -526,16 +551,18 @@ class easy_install(Command):
self.cant_write_to_target()
else:
try:
- f.write("import os; f = open(%r, 'w'); f.write('OK'); "
- "f.close()\n" % (ok_file,))
+ f.write(tmpl.format(**locals()))
f.close()
f = None
executable = sys.executable
if os.name == 'nt':
dirname, basename = os.path.split(executable)
alt = os.path.join(dirname, 'pythonw.exe')
- if (basename.lower() == 'python.exe' and
- os.path.exists(alt)):
+ use_alt = (
+ basename.lower() == 'python.exe' and
+ os.path.exists(alt)
+ )
+ if use_alt:
# use pythonw.exe to avoid opening a console window
executable = alt
@@ -602,7 +629,6 @@ class easy_install(Command):
def easy_install(self, spec, deps=False):
tmpdir = tempfile.mkdtemp(prefix="easy_install-")
- download = None
if not self.editable:
self.install_site_py()
@@ -611,9 +637,8 @@ class easy_install(Command):
if URL_SCHEME(spec):
# It's a url, download it to tmpdir and process
self.not_editable(spec)
- download = self.package_index.download(spec, tmpdir)
- return self.install_item(None, download, tmpdir, deps,
- True)
+ dl = self.package_index.download(spec, tmpdir)
+ return self.install_item(None, dl, tmpdir, deps, True)
elif os.path.exists(spec):
# Existing file or directory, just process it directly
@@ -739,8 +764,9 @@ class easy_install(Command):
def maybe_move(self, spec, dist_filename, setup_base):
dst = os.path.join(self.build_directory, spec.key)
if os.path.exists(dst):
- msg = ("%r already exists in %s; build directory %s will not be "
- "kept")
+ msg = (
+ "%r already exists in %s; build directory %s will not be kept"
+ )
log.warn(msg, spec.key, self.build_directory, setup_base)
return setup_base
if os.path.isdir(dist_filename):
@@ -858,8 +884,10 @@ class easy_install(Command):
return Distribution.from_filename(egg_path, metadata=metadata)
def install_egg(self, egg_path, tmpdir):
- destination = os.path.join(self.install_dir,
- os.path.basename(egg_path))
+ destination = os.path.join(
+ self.install_dir,
+ os.path.basename(egg_path),
+ )
destination = os.path.abspath(destination)
if not self.dry_run:
ensure_directory(destination)
@@ -869,8 +897,11 @@ class easy_install(Command):
if os.path.isdir(destination) and not os.path.islink(destination):
dir_util.remove_tree(destination, dry_run=self.dry_run)
elif os.path.exists(destination):
- self.execute(os.unlink, (destination,), "Removing " +
- destination)
+ self.execute(
+ os.unlink,
+ (destination,),
+ "Removing " + destination,
+ )
try:
new_dist_is_zipped = False
if os.path.isdir(egg_path):
@@ -887,12 +918,18 @@ class easy_install(Command):
f, m = shutil.move, "Moving"
else:
f, m = shutil.copy2, "Copying"
- self.execute(f, (egg_path, destination),
- (m + " %s to %s") %
- (os.path.basename(egg_path),
- os.path.dirname(destination)))
- update_dist_caches(destination,
- fix_zipimporter_caches=new_dist_is_zipped)
+ self.execute(
+ f,
+ (egg_path, destination),
+ (m + " %s to %s") % (
+ os.path.basename(egg_path),
+ os.path.dirname(destination)
+ ),
+ )
+ update_dist_caches(
+ destination,
+ fix_zipimporter_caches=new_dist_is_zipped,
+ )
except:
update_dist_caches(destination, fix_zipimporter_caches=False)
raise
@@ -915,8 +952,8 @@ class easy_install(Command):
)
# Convert the .exe to an unpacked egg
- egg_path = dist.location = os.path.join(tmpdir, dist.egg_name() +
- '.egg')
+ egg_path = os.path.join(tmpdir, dist.egg_name() + '.egg')
+ dist.location = egg_path
egg_tmp = egg_path + '.tmp'
_egg_info = os.path.join(egg_tmp, 'EGG-INFO')
pkg_inf = os.path.join(_egg_info, 'PKG-INFO')
@@ -934,13 +971,13 @@ class easy_install(Command):
f.close()
script_dir = os.path.join(_egg_info, 'scripts')
# delete entry-point scripts to avoid duping
- self.delete_blockers(
- [os.path.join(script_dir, args[0]) for args in
- ScriptWriter.get_args(dist)]
- )
+ self.delete_blockers([
+ os.path.join(script_dir, args[0])
+ for args in ScriptWriter.get_args(dist)
+ ])
# Build .egg file from tmpdir
bdist_egg.make_zipfile(
- egg_path, egg_tmp, verbose=self.verbose, dry_run=self.dry_run
+ egg_path, egg_tmp, verbose=self.verbose, dry_run=self.dry_run,
)
# install the .egg
return self.install_egg(egg_path, tmpdir)
@@ -1128,7 +1165,7 @@ class easy_install(Command):
if dist.location in self.pth_file.paths:
log.info(
"%s is already the active version in easy-install.pth",
- dist
+ dist,
)
else:
log.info("Adding %s to easy-install.pth file", dist)
@@ -1189,7 +1226,7 @@ class easy_install(Command):
if self.optimize:
byte_compile(
to_compile, optimize=self.optimize, force=1,
- dry_run=self.dry_run
+ dry_run=self.dry_run,
)
finally:
log.set_verbosity(self.verbose) # restore original verbosity
@@ -1317,15 +1354,20 @@ def get_site_dirs():
if sys.platform in ('os2emx', 'riscos'):
sitedirs.append(os.path.join(prefix, "Lib", "site-packages"))
elif os.sep == '/':
- sitedirs.extend([os.path.join(prefix,
- "lib",
- "python" + sys.version[:3],
- "site-packages"),
- os.path.join(prefix, "lib", "site-python")])
+ sitedirs.extend([
+ os.path.join(
+ prefix,
+ "lib",
+ "python" + sys.version[:3],
+ "site-packages",
+ ),
+ os.path.join(prefix, "lib", "site-python"),
+ ])
else:
- sitedirs.extend(
- [prefix, os.path.join(prefix, "lib", "site-packages")]
- )
+ sitedirs.extend([
+ prefix,
+ os.path.join(prefix, "lib", "site-packages"),
+ ])
if sys.platform == 'darwin':
# for framework builds *only* we add the standard Apple
# locations. Currently only per-user, but /Library and
@@ -1333,12 +1375,14 @@ def get_site_dirs():
if 'Python.framework' in prefix:
home = os.environ.get('HOME')
if home:
- sitedirs.append(
- os.path.join(home,
- 'Library',
- 'Python',
- sys.version[:3],
- 'site-packages'))
+ home_sp = os.path.join(
+ home,
+ 'Library',
+ 'Python',
+ sys.version[:3],
+ 'site-packages',
+ )
+ sitedirs.append(home_sp)
lib_paths = get_path('purelib'), get_path('platlib')
for site_lib in lib_paths:
if site_lib not in sitedirs:
@@ -1347,6 +1391,11 @@ def get_site_dirs():
if site.ENABLE_USER_SITE:
sitedirs.append(site.USER_SITE)
+ try:
+ sitedirs.extend(site.getsitepackages())
+ except AttributeError:
+ pass
+
sitedirs = list(map(normalize_path, sitedirs))
return sitedirs
@@ -1414,8 +1463,8 @@ def extract_wininst_cfg(dist_filename):
return None # not a valid tag
f.seek(prepended - (12 + cfglen))
- cfg = configparser.RawConfigParser(
- {'version': '', 'target_version': ''})
+ init = {'version': '', 'target_version': ''}
+ cfg = configparser.RawConfigParser(init)
try:
part = f.read(cfglen)
# Read up to the first null byte.
@@ -1438,7 +1487,8 @@ def get_exe_prefixes(exe_filename):
"""Get exe->egg path translations for a given .exe file"""
prefixes = [
- ('PURELIB/', ''), ('PLATLIB/pywin32_system32', ''),
+ ('PURELIB/', ''),
+ ('PLATLIB/pywin32_system32', ''),
('PLATLIB/', ''),
('SCRIPTS/', 'EGG-INFO/scripts/'),
('DATA/lib/site-packages', ''),
@@ -1598,12 +1648,11 @@ class RewritePthDistributions(PthDistributions):
yield line
yield cls.postlude
- _inline = lambda text: textwrap.dedent(text).strip().replace('\n', '; ')
- prelude = _inline("""
+ prelude = _one_liner("""
import sys
sys.__plen = len(sys.path)
""")
- postlude = _inline("""
+ postlude = _one_liner("""
import sys
new = sys.path[sys.__plen:]
del sys.path[sys.__plen:]
@@ -2071,8 +2120,11 @@ class WindowsScriptWriter(ScriptWriter):
"For Windows, add a .py extension"
ext = dict(console='.pya', gui='.pyw')[type_]
if ext not in os.environ['PATHEXT'].lower().split(';'):
- warnings.warn("%s not listed in PATHEXT; scripts will not be "
- "recognized as executables." % ext, UserWarning)
+ msg = (
+ "{ext} not listed in PATHEXT; scripts will not be "
+ "recognized as executables."
+ ).format(**locals())
+ warnings.warn(msg, UserWarning)
old = ['.pya', '.py', '-script.py', '.pyc', '.pyo', '.pyw', '.exe']
old.remove(ext)
header = cls._adjust_header(type_, header)
@@ -2238,7 +2290,8 @@ def main(argv=None, **kw):
setup(
script_args=['-q', 'easy_install', '-v'] + argv,
script_name=sys.argv[0] or 'easy_install',
- distclass=DistributionWithoutHelpCommands, **kw
+ distclass=DistributionWithoutHelpCommands,
+ **kw
)
diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py
index d1bd9b04..8e1502a5 100755
--- a/setuptools/command/egg_info.py
+++ b/setuptools/command/egg_info.py
@@ -13,6 +13,7 @@ import sys
import io
import warnings
import time
+import collections
from setuptools.extern import six
from setuptools.extern.six.moves import map
@@ -66,14 +67,20 @@ class egg_info(Command):
self.vtags = None
def save_version_info(self, filename):
- values = dict(
- egg_info=dict(
- tag_svn_revision=0,
- tag_date=0,
- tag_build=self.tags(),
- )
- )
- edit_config(filename, values)
+ """
+ Materialize the values of svn_revision and date into the
+ build tag. Install these keys in a deterministic order
+ to avoid arbitrary reordering on subsequent builds.
+ """
+ # python 2.6 compatibility
+ odict = getattr(collections, 'OrderedDict', dict)
+ egg_info = odict()
+ # follow the order these keys would have been added
+ # when PYTHONHASHSEED=0
+ egg_info['tag_build'] = self.tags()
+ egg_info['tag_date'] = 0
+ egg_info['tag_svn_revision'] = 0
+ edit_config(filename, dict(egg_info=egg_info))
def finalize_options(self):
self.egg_name = safe_name(self.distribution.get_name())
diff --git a/setuptools/command/rotate.py b/setuptools/command/rotate.py
index 804f962a..b89353f5 100755
--- a/setuptools/command/rotate.py
+++ b/setuptools/command/rotate.py
@@ -2,6 +2,7 @@ from distutils.util import convert_path
from distutils import log
from distutils.errors import DistutilsOptionError
import os
+import shutil
from setuptools.extern import six
@@ -59,4 +60,7 @@ class rotate(Command):
for (t, f) in files:
log.info("Deleting %s", f)
if not self.dry_run:
- os.unlink(f)
+ if os.path.isdir(f):
+ shutil.rmtree(f)
+ else:
+ os.unlink(f)
diff --git a/setuptools/command/test.py b/setuptools/command/test.py
index 371e913b..39746a02 100644
--- a/setuptools/command/test.py
+++ b/setuptools/command/test.py
@@ -1,6 +1,7 @@
+import sys
+import contextlib
from distutils.errors import DistutilsOptionError
from unittest import TestLoader
-import sys
from setuptools.extern import six
from setuptools.extern.six.moves import map
@@ -102,6 +103,14 @@ class test(Command):
yield self.test_suite
def with_project_on_sys_path(self, func):
+ """
+ Backward compatibility for project_on_sys_path context.
+ """
+ with self.project_on_sys_path():
+ func()
+
+ @contextlib.contextmanager
+ def project_on_sys_path(self):
with_2to3 = six.PY3 and getattr(self.distribution, 'use_2to3', False)
if with_2to3:
@@ -137,7 +146,7 @@ class test(Command):
working_set.__init__()
add_activation_listener(lambda dist: dist.activate())
require('%s==%s' % (ei_cmd.egg_name, ei_cmd.egg_version))
- func()
+ yield
finally:
sys.path[:] = old_path
sys.modules.clear()
@@ -154,9 +163,11 @@ class test(Command):
cmd = ' '.join(self._argv)
if self.dry_run:
self.announce('skipping "%s" (dry run)' % cmd)
- else:
- self.announce('running "%s"' % cmd)
- self.with_project_on_sys_path(self.run_tests)
+ return
+
+ self.announce('running "%s"' % cmd)
+ with self.project_on_sys_path():
+ self.run_tests()
def run_tests(self):
# Purge modules under test from sys.modules. The test loader will
diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py
index 08c20ba8..484baa5a 100644
--- a/setuptools/command/upload.py
+++ b/setuptools/command/upload.py
@@ -1,15 +1,22 @@
+import getpass
from distutils.command import upload as orig
class upload(orig.upload):
"""
- Override default upload behavior to look up password
- in the keyring if available.
+ Override default upload behavior to obtain password
+ in a variety of different ways.
"""
def finalize_options(self):
orig.upload.finalize_options(self)
- self.password or self._load_password_from_keyring()
+ # Attempt to obtain password. Short circuit evaluation at the first
+ # sign of success.
+ self.password = (
+ self.password or
+ self._load_password_from_keyring() or
+ self._prompt_for_password()
+ )
def _load_password_from_keyring(self):
"""
@@ -17,7 +24,15 @@ class upload(orig.upload):
"""
try:
keyring = __import__('keyring')
- self.password = keyring.get_password(self.repository,
- self.username)
+ return keyring.get_password(self.repository, self.username)
except Exception:
pass
+
+ def _prompt_for_password(self):
+ """
+ Prompt for a password on the tty. Suppress Exceptions.
+ """
+ try:
+ return getpass.getpass()
+ except (Exception, KeyboardInterrupt):
+ pass
diff --git a/setuptools/command/upload_docs.py b/setuptools/command/upload_docs.py
index f887b47e..01b49046 100644
--- a/setuptools/command/upload_docs.py
+++ b/setuptools/command/upload_docs.py
@@ -13,6 +13,8 @@ import socket
import zipfile
import tempfile
import shutil
+import itertools
+import functools
from setuptools.extern import six
from setuptools.extern.six.moves import http_client, urllib
@@ -21,15 +23,9 @@ from pkg_resources import iter_entry_points
from .upload import upload
-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, six.text_type):
- return s.encode(encoding, errors)
- return s
+def _encode(s):
+ errors = 'surrogateescape' if six.PY3 else 'strict'
+ return s.encode('utf-8', errors)
class upload_docs(upload):
@@ -101,10 +97,48 @@ class upload_docs(upload):
finally:
shutil.rmtree(tmp_dir)
+ @staticmethod
+ def _build_part(item, sep_boundary):
+ key, values = item
+ title = '\nContent-Disposition: form-data; name="%s"' % key
+ # handle multiple entries for the same name
+ if not isinstance(values, list):
+ values = [values]
+ for value in values:
+ if type(value) is tuple:
+ title += '; filename="%s"' % value[0]
+ value = value[1]
+ else:
+ value = _encode(value)
+ yield sep_boundary
+ yield _encode(title)
+ yield b"\n\n"
+ yield value
+ if value and value[-1:] == b'\r':
+ yield b'\n' # write an extra newline (lurve Macs)
+
+ @classmethod
+ def _build_multipart(cls, data):
+ """
+ Build up the MIME payload for the POST data
+ """
+ boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
+ sep_boundary = b'\n--' + boundary
+ end_boundary = sep_boundary + b'--'
+ end_items = end_boundary, b"\n",
+ builder = functools.partial(
+ cls._build_part,
+ sep_boundary=sep_boundary,
+ )
+ part_groups = map(builder, data.items())
+ parts = itertools.chain.from_iterable(part_groups)
+ body_items = itertools.chain(parts, end_items)
+ content_type = 'multipart/form-data; boundary=%s' % boundary
+ return b''.join(body_items), content_type
+
def upload_file(self, filename):
- f = open(filename, 'rb')
- content = f.read()
- f.close()
+ with open(filename, 'rb') as f:
+ content = f.read()
meta = self.distribution.metadata
data = {
':action': 'doc_upload',
@@ -112,37 +146,13 @@ class upload_docs(upload):
'content': (os.path.basename(filename), content),
}
# set up the authentication
- credentials = b(self.username + ':' + self.password)
+ credentials = _encode(self.username + ':' + self.password)
credentials = standard_b64encode(credentials)
if six.PY3:
credentials = credentials.decode('ascii')
auth = "Basic " + credentials
- # Build up the MIME payload for the POST data
- boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
- sep_boundary = b('\n--') + b(boundary)
- end_boundary = sep_boundary + b('--')
- body = []
- 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):
- values = [values]
- for value in values:
- if type(value) is tuple:
- title += '; filename="%s"' % value[0]
- value = value[1]
- else:
- value = b(value)
- body.append(sep_boundary)
- body.append(b(title))
- body.append(b("\n\n"))
- body.append(value)
- if value and value[-1:] == b('\r'):
- body.append(b('\n')) # write an extra newline (lurve Macs)
- body.append(end_boundary)
- body.append(b("\n"))
- body = b('').join(body)
+ body, ct = self._build_multipart(data)
self.announce("Submitting documentation to %s" % (self.repository),
log.INFO)
@@ -164,7 +174,7 @@ class upload_docs(upload):
try:
conn.connect()
conn.putrequest("POST", url)
- content_type = 'multipart/form-data; boundary=%s' % boundary
+ content_type = ct
conn.putheader('Content-type', content_type)
conn.putheader('Content-length', str(len(body)))
conn.putheader('Authorization', auth)
diff --git a/setuptools/launch.py b/setuptools/launch.py
index 06e15e1e..b05cbd2c 100644
--- a/setuptools/launch.py
+++ b/setuptools/launch.py
@@ -11,25 +11,25 @@ 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:]
+ """
+ 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)
+ 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()
+ run()
diff --git a/setuptools/msvc9_support.py b/setuptools/msvc9_support.py
index a69c7474..9d869580 100644
--- a/setuptools/msvc9_support.py
+++ b/setuptools/msvc9_support.py
@@ -1,6 +1,6 @@
try:
import distutils.msvc9compiler
-except ImportError:
+except Exception:
pass
unpatched = dict()
diff --git a/setuptools/package_index.py b/setuptools/package_index.py
index c53343e4..e87504db 100755
--- a/setuptools/package_index.py
+++ b/setuptools/package_index.py
@@ -17,6 +17,7 @@ except ImportError:
from setuptools.extern import six
from setuptools.extern.six.moves import urllib, http_client, configparser, map
+import setuptools
from pkg_resources import (
CHECKOUT_DIST, Distribution, BINARY_DIST, normalize_path, SOURCE_DIST,
require, Environment, find_distributions, safe_name, safe_version,
@@ -46,6 +47,11 @@ __all__ = [
_SOCKET_TIMEOUT = 15
+
+_tmpl = "setuptools/{setuptools.__version__} Python-urllib/{py_major}"
+user_agent = _tmpl.format(py_major=sys.version[:3], **globals())
+
+
def parse_bdist_wininst(name):
"""Return (base,pyversion) or (None,None) for possible .exe name"""
@@ -202,9 +208,6 @@ def find_external_links(url, page):
if match:
yield urllib.parse.urljoin(url, htmldecode(match.group(1)))
-user_agent = "Python-urllib/%s setuptools/%s" % (
- sys.version[:3], require('setuptools')[0].version
-)
class ContentChecker(object):
"""
diff --git a/setuptools/py26compat.py b/setuptools/py26compat.py
index e52bd85b..40cbb88e 100644
--- a/setuptools/py26compat.py
+++ b/setuptools/py26compat.py
@@ -5,18 +5,18 @@ Compatibility Support for Python 2.6 and earlier
import sys
try:
- from urllib.parse import splittag
+ from urllib.parse import splittag
except ImportError:
- from urllib import splittag
+ from urllib import splittag
def strip_fragment(url):
- """
- In `Python 8280 <http://bugs.python.org/issue8280>`_, Python 2.7 and
- later was patched to disregard the fragment when making URL requests.
- Do the same for Python 2.6 and earlier.
- """
- url, fragment = splittag(url)
- return url
+ """
+ In `Python 8280 <http://bugs.python.org/issue8280>`_, Python 2.7 and
+ later was patched to disregard the fragment when making URL requests.
+ Do the same for Python 2.6 and earlier.
+ """
+ url, fragment = splittag(url)
+ return url
if sys.version_info >= (2,7):
- strip_fragment = lambda x: x
+ strip_fragment = lambda x: x
diff --git a/setuptools/py27compat.py b/setuptools/py27compat.py
index 9d2886db..702f7d65 100644
--- a/setuptools/py27compat.py
+++ b/setuptools/py27compat.py
@@ -5,11 +5,11 @@ Compatibility Support for Python 2.7 and earlier
import sys
def get_all_headers(message, key):
- """
- Given an HTTPMessage, return all headers matching a given key.
- """
- return message.get_all(key)
+ """
+ Given an HTTPMessage, return all headers matching a given key.
+ """
+ return message.get_all(key)
if sys.version_info < (3,):
- def get_all_headers(message, key):
- return message.getheaders(key)
+ def get_all_headers(message, key):
+ return message.getheaders(key)
diff --git a/setuptools/tests/py26compat.py b/setuptools/tests/py26compat.py
index c5680881..7211f275 100644
--- a/setuptools/tests/py26compat.py
+++ b/setuptools/tests/py26compat.py
@@ -3,10 +3,10 @@ import tarfile
import contextlib
def _tarfile_open_ex(*args, **kwargs):
- """
- Extend result as a context manager.
- """
- return contextlib.closing(tarfile.open(*args, **kwargs))
+ """
+ Extend result as a context manager.
+ """
+ return contextlib.closing(tarfile.open(*args, **kwargs))
if sys.version_info[:2] < (2, 7) or (3, 0) <= sys.version_info[:2] < (3, 2):
tarfile_open = _tarfile_open_ex
diff --git a/setuptools/tests/test_build_py.py b/setuptools/tests/test_build_py.py
new file mode 100644
index 00000000..ed1703ac
--- /dev/null
+++ b/setuptools/tests/test_build_py.py
@@ -0,0 +1,31 @@
+import os
+
+import pytest
+
+from setuptools.dist import Distribution
+
+
+@pytest.yield_fixture
+def tmpdir_as_cwd(tmpdir):
+ with tmpdir.as_cwd():
+ yield tmpdir
+
+
+def test_directories_in_package_data_glob(tmpdir_as_cwd):
+ """
+ Directories matching the glob in package_data should
+ not be included in the package data.
+
+ Regression test for #261.
+ """
+ dist = Distribution(dict(
+ script_name='setup.py',
+ script_args=['build_py'],
+ packages=[''],
+ name='foo',
+ package_data={'': ['path/*']},
+ ))
+ os.makedirs('path/subpath')
+ #with contexts.quiet():
+ dist.parse_command_line()
+ dist.run_commands()
diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py
index 55b8b05a..fd06b6ef 100644
--- a/setuptools/tests/test_easy_install.py
+++ b/setuptools/tests/test_easy_install.py
@@ -6,7 +6,6 @@ from __future__ import absolute_import
import sys
import os
-import shutil
import tempfile
import site
import contextlib
@@ -119,6 +118,19 @@ class TestEasyInstallTest:
with pytest.raises(distutils.errors.DistutilsError):
cmd.cant_write_to_target()
+ def test_all_site_dirs(self, monkeypatch):
+ """
+ get_site_dirs should always return site dirs reported by
+ site.getsitepackages.
+ """
+ mock_gsp = lambda: ['/setuptools/test/site-packages']
+ monkeypatch.setattr(site, 'getsitepackages', mock_gsp, raising=False)
+ assert '/setuptools/test/site-packages' in ei.get_site_dirs()
+
+ def test_all_site_dirs_works_without_getsitepackages(self, monkeypatch):
+ monkeypatch.delattr(site, 'getsitepackages', raising=False)
+ assert ei.get_site_dirs()
+
class TestPTHFileWriter:
def test_add_from_cwd_site_sets_dirty(self):
diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py
index d37567b4..3a0db58f 100644
--- a/setuptools/tests/test_egg_info.py
+++ b/setuptools/tests/test_egg_info.py
@@ -1,7 +1,11 @@
import os
import glob
+import re
import stat
+import sys
+from setuptools.command.egg_info import egg_info
+from setuptools.dist import Distribution
from setuptools.extern.six.moves import map
import pytest
@@ -59,6 +63,79 @@ class TestEggInfo(object):
})
yield env
+ def test_egg_info_save_version_info_setup_empty(self, tmpdir_cwd, env):
+ """
+ When the egg_info section is empty or not present, running
+ save_version_info should add the settings to the setup.cfg
+ in a deterministic order, consistent with the ordering found
+ on Python 2.6 and 2.7 with PYTHONHASHSEED=0.
+ """
+ setup_cfg = os.path.join(env.paths['home'], 'setup.cfg')
+ dist = Distribution()
+ ei = egg_info(dist)
+ ei.initialize_options()
+ ei.save_version_info(setup_cfg)
+
+ with open(setup_cfg, 'r') as f:
+ content = f.read()
+
+ assert '[egg_info]' in content
+ assert 'tag_build =' in content
+ assert 'tag_date = 0' in content
+ assert 'tag_svn_revision = 0' in content
+
+ expected_order = 'tag_build', 'tag_date', 'tag_svn_revision'
+
+ self._validate_content_order(content, expected_order)
+
+ @staticmethod
+ def _validate_content_order(content, expected):
+ """
+ Assert that the strings in expected appear in content
+ in order.
+ """
+ if sys.version_info < (2, 7):
+ # On Python 2.6, expect dict key order.
+ expected = dict.fromkeys(expected).keys()
+
+ pattern = '.*'.join(expected)
+ flags = re.MULTILINE | re.DOTALL
+ assert re.search(pattern, content, flags)
+
+ def test_egg_info_save_version_info_setup_defaults(self, tmpdir_cwd, env):
+ """
+ When running save_version_info on an existing setup.cfg
+ with the 'default' values present from a previous run,
+ the file should remain unchanged, except on Python 2.6,
+ where the order of the keys will be changed to match the
+ order as found in a dictionary of those keys.
+ """
+ setup_cfg = os.path.join(env.paths['home'], 'setup.cfg')
+ build_files({
+ setup_cfg: DALS("""
+ [egg_info]
+ tag_build =
+ tag_date = 0
+ tag_svn_revision = 0
+ """),
+ })
+ dist = Distribution()
+ ei = egg_info(dist)
+ ei.initialize_options()
+ ei.save_version_info(setup_cfg)
+
+ with open(setup_cfg, 'r') as f:
+ content = f.read()
+
+ assert '[egg_info]' in content
+ assert 'tag_build =' in content
+ assert 'tag_date = 0' in content
+ assert 'tag_svn_revision = 0' in content
+
+ expected_order = 'tag_build', 'tag_date', 'tag_svn_revision'
+
+ self._validate_content_order(content, expected_order)
+
def test_egg_base_installed_egg_info(self, tmpdir_cwd, env):
self._create_project()
@@ -104,7 +181,6 @@ class TestEggInfo(object):
'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'"],""")
@@ -115,14 +191,12 @@ class TestEggInfo(object):
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'"],""")
diff --git a/setuptools/tests/test_upload_docs.py b/setuptools/tests/test_upload_docs.py
index cc71cadb..d3dee616 100644
--- a/setuptools/tests/test_upload_docs.py
+++ b/setuptools/tests/test_upload_docs.py
@@ -57,3 +57,15 @@ class TestUploadDocsTest:
with contextlib.closing(zipfile.ZipFile(tmp_file)) as zip_file:
assert zip_file.namelist() == ['index.html']
+
+ def test_build_multipart(self):
+ data = dict(
+ a="foo",
+ b="bar",
+ file=('file.txt', b'content'),
+ )
+ body, content_type = upload_docs._build_multipart(data)
+ assert 'form-data' in content_type
+ assert isinstance(body, bytes)
+ assert b'foo' in body
+ assert b'content' in body