aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.hgtags21
-rw-r--r--.travis.yml8
-rw-r--r--CHANGES.rst128
-rw-r--r--MANIFEST.in3
-rwxr-xr-xREADME.rst3
-rw-r--r--docs/conf.py120
-rw-r--r--docs/easy_install.txt2
-rw-r--r--docs/releases.txt19
-rw-r--r--docs/requirements.txt1
-rw-r--r--docs/setuptools.txt3
-rw-r--r--ez_setup.py417
-rw-r--r--pavement.py38
-rw-r--r--pkg_resources/__init__.py41
-rw-r--r--pkg_resources/_vendor/packaging/__about__.py2
-rw-r--r--pkg_resources/_vendor/packaging/markers.py11
-rw-r--r--pkg_resources/_vendor/vendored.txt2
-rw-r--r--pkg_resources/tests/test_resources.py99
-rwxr-xr-xsetup.cfg21
-rwxr-xr-xsetup.py8
-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
37 files changed, 884 insertions, 762 deletions
diff --git a/.hgtags b/.hgtags
index 95b15456..86954cd8 100644
--- a/.hgtags
+++ b/.hgtags
@@ -251,3 +251,24 @@ a3d4006688fe5e754d0e709a52a00b8191819979 v20.6.1
57d63b38e85515d06e06d3cea62e35e6c54b5093 v20.6.6
b04dbdd161d7f68903a53e1dbd1fa5b5fde73f94 v20.6.6
0804d30b6ead64e0e324aefd67439b84df2d1c01 v20.6.7
+a00910db03ec15865e4c8506820d4ad1df3e26f3 v20.6.8
+0262ab29fc2417b502a55f49b7fd43528fbd3df4 v20.7.0
+7f56b6f40de39456c78507a14c288709712881cb v20.8.0
+8cf9340669ae26e2b31f68b9c3f885ab7bdd65ce v20.8.1
+8bf8aaa139bb6a36fcd243214d6730a214ae08f5 v20.9.0
+c72faa468919fd2f226c97e94d4e64a6506860e5 v20.10.0
+3b5fdd077c7d83d02c4979ad69cc0bf199b47587 v20.10.1
+ddd3f81eb9e0860bf95c380c50a72c52a215231f v21.0.0
+018e4a727cf691d6404cd24ffb25e8eebea2fad4 v20.6.8
+02643fe9503033edd2fc5a54c8d4361a6c185be4 v21.1.0
+40b8fac6db119aca9c462993d01908492769fc4f v21.2.0
+40b8fac6db119aca9c462993d01908492769fc4f v21.2.0
+9959424676a4aac1c14e430ff6f4210fdb0442d9 v21.2.0
+694111eadb10fe6003078895a2cbb803ce514ef2 v21.2.1
+274f33435e9c3ba5019f2a2bfe478fa2db0da41d v21.2.2
+451fbedb4c226d8ea5b6eab1e21679c9a4ec4a93 v22.0.0
+f5c4923b0400d61f67699c2d54388878f9e0c8bd v22.0.1
+8610a8b9635f15d33f94fccb295fd34aa6fbddee v22.0.2
+efee7d74a8478c0d08c801fb520e41b6e04d0dda v22.0.3
+77b20c09b04775cc936ab5d16cbc46ff05fc7080 v22.0.4
+d5832e5deb77027da474e79e5f047e9a81f7edf8 v22.0.5
diff --git a/.travis.yml b/.travis.yml
index 2611f02a..e91f7e78 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,9 +7,6 @@ python:
- 3.5
- pypy
- pypy3
-matrix:
- allow_failures:
- - python: pypy3
env:
- ""
- LC_ALL=C LC_CTYPE=C
@@ -29,11 +26,14 @@ before_deploy:
- export SETUPTOOLS_INSTALL_WINDOWS_SPECIFIC_FILES=1
deploy:
provider: pypi
+ # Also update server in setup.cfg
+ server: https://upload.pypi.io/legacy/
on:
tags: true
all_branches: true
+ python: 3.5
+ condition: $LC_ALL != "C"
user: jaraco
password:
secure: tfWrsQMH2bHrWjqnP+08IX1WlkbW94Q30f4d7lCyhWS1FIf/jBDx4jrEILNfMxQ1NCwuBRje5sihj1Ow0BFf0vVrkaeff2IdvnNDEGFduMejaEQJL3s3QrLfpiAvUbtqwyWaHfAdGfk48PovDKTx0ZTvXZKYGXZhxGCYSlG2CE6Y6RDvnEl6Tk8e+LqUohkcSOwxrRwUoyxSnUaavdGohXxDT8MJlfWOXgr2u+KsRrriZqp3l6Fdsnk4IGvy6pXpy42L1HYQyyVu9XyJilR2JTbC6eCp5f8p26093m1Qas49+t6vYb0VLqQe12dO+Jm3v4uztSS5pPQzS7PFyjEYd2Rdb6ijsdbsy1074S4q7G9Sz+T3RsPUwYEJ07lzez8cxP64dtj5j94RL8m35A1Fb1OE8hHN+4c1yLG1gudfXbem+fUhi2eqhJrzQo5vsvDv1xS5x5GIS5ZHgKHCsWcW1Tv+dsFkrhaup3uU6VkOuc9UN+7VPsGEY7NvquGpTm8O1CnGJRzuJg6nbYRGj8ORwDpI0KmrExx6akV92P72fMC/I5TCgbSQSZn370H3Jj40gz1SM30WAli9M+wFHFd4ddMVY65yxj0NLmrP+m1tvnWdKtNh/RHuoW92d9/UFtiA5IhMf1/3djfsjBq6S9NT1uaLkVkTttqrPYJ7hOql8+g=
distributions: release
- python: 3.5
diff --git a/CHANGES.rst b/CHANGES.rst
index dee622ac..b763607c 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,6 +2,132 @@
CHANGES
=======
+v22.0.5
+-------
+
+* #604: Restore repository for upload_docs command
+ to restore publishing of docs during release.
+
+v22.0.4
+-------
+
+* #589: Upload releases to pypi.io using the upload
+ hostname and legacy path.
+
+v22.0.3
+-------
+
+* #589: Releases are now uploaded to pypi.io (Warehouse)
+ even when releases are made on Twine via Travis.
+
+v22.0.2
+-------
+
+* #589: Releases are now uploaded to pypi.io (Warehouse).
+
+v22.0.1
+-------
+
+* #190: On Python 2, if unicode is passed for packages to
+ ``build_py`` command, it will be handled just as with
+ text on Python 3.
+
+v22.0.0
+-------
+
+Intended to be v21.3.0, but jaraco accidentally released as
+a major bump.
+
+* #598: Setuptools now lists itself first in the User-Agent
+ for web requests, better following the guidelines in
+ `RFC 7231
+ <https://tools.ietf.org/html/rfc7231#section-5.5.3>`_.
+
+v21.2.2
+-------
+
+* Minor fixes to changelog and docs.
+
+v21.2.1
+-------
+
+* #261: Exclude directories when resolving globs in
+ package_data.
+
+v21.2.0
+-------
+
+* #539: In the easy_install get_site_dirs, honor all
+ paths found in ``site.getsitepackages``.
+
+v21.1.0
+-------
+
+* #572: In build_ext, now always import ``_CONFIG_VARS``
+ from ``distutils`` rather than from ``sysconfig``
+ to allow ``distutils.sysconfig.customize_compiler``
+ configure the OS X compiler for ``-dynamiclib``.
+
+v21.0.0
+-------
+
+* Removed ez_setup.py from Setuptools sdist. The
+ bootstrap script will be maintained in its own
+ branch and should be generally be retrieved from
+ its canonical location at
+ https://bootstrap.pypa.io/ez_setup.py.
+
+v20.10.0
+--------
+
+* #553: egg_info section is now generated in a
+ deterministic order, matching the order generated
+ by earlier versions of Python. Except on Python 2.6,
+ order is preserved when existing settings are present.
+* #556: Update to Packaging 16.7, restoring support
+ for deprecated ``python_implmentation`` marker.
+* #555: Upload command now prompts for a password
+ when uploading to PyPI (or other repository) if no
+ password is present in .pypirc or in the keyring.
+
+v20.9.0
+-------
+
+* #548: Update certify version to 2016.2.28
+* #545: Safely handle deletion of non-zip eggs in rotate
+ command.
+
+v20.8.1
+-------
+
+* Issue #544: Fix issue with extra environment marker
+ processing in WorkingSet due to refactor in v20.7.0.
+
+v20.8.0
+-------
+
+* Issue #543: Re-release so that latest release doesn't
+ cause déjà vu with distribute and setuptools 0.7 in
+ older environments.
+
+v20.7.0
+-------
+
+* Refactored extra enviroment marker processing
+ in WorkingSet.
+* Issue #533: Fixed intermittent test failures.
+* Issue #536: In msvc9_support, trap additional exceptions
+ that might occur when importing
+ ``distutils.msvc9compiler`` in mingw environments.
+* Issue #537: Provide better context when package
+ metadata fails to decode in UTF-8.
+
+v20.6.8
+-------
+
+* Issue #523: Restored support for environment markers,
+ now honoring 'extra' environment markers.
+
v20.6.7
-------
@@ -29,7 +155,7 @@ v20.6.0
20.5
----
-* BB Pull Request #185: Add support for environment markers
+* BB Pull Request #185, #470: Add support for environment markers
in requirements in install_requires, setup_requires,
tests_require as well as adding a test for the existing
extra_requires machinery.
diff --git a/MANIFEST.in b/MANIFEST.in
index dfea2049..cfd1b001 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -2,11 +2,10 @@ recursive-include setuptools *.py *.exe *.xml
recursive-include tests *.py
recursive-include setuptools/tests *.html
recursive-include docs *.py *.txt *.conf *.css *.css_t Makefile indexsidebar.html
-recursive-include _markerlib *.py
recursive-include setuptools/_vendor *
recursive-include pkg_resources *.py *.txt
include *.py
-include *.txt
+include *.rst
include MANIFEST.in
include launcher.c
include msvc-build-launcher.cmd
diff --git a/README.rst b/README.rst
index f94c6fcb..4f833ecf 100755
--- a/README.rst
+++ b/README.rst
@@ -5,7 +5,8 @@ Installing and Using Setuptools
.. contents:: **Table of Contents**
-`Change History <https://pythonhosted.org/setuptools/history.html>`_.
+.. image:: https://setuptools.readthedocs.io/en/latest/?badge=latest
+ :target: https://setuptools.readthedocs.io
-------------------------
Installation Instructions
diff --git a/docs/conf.py b/docs/conf.py
index 8bba3e94..72c1ce43 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -196,64 +196,64 @@ latex_documents = [
#latex_use_modindex = True
link_files = {
- 'CHANGES.rst': dict(
- using=dict(
- BB='https://bitbucket.org',
- GH='https://github.com',
- ),
- replace=[
- dict(
- pattern=r"(Issue )?#(?P<issue>\d+)",
- url='{GH}/pypa/setuptools/issues/{issue}',
- ),
- dict(
- pattern=r"BB Pull Request ?#(?P<bb_pull_request>\d+)",
- url='{BB}/pypa/setuptools/pull-request/{bb_pull_request}',
- ),
- dict(
- pattern=r"Distribute #(?P<distribute>\d+)",
- url='{BB}/tarek/distribute/issue/{distribute}',
- ),
- dict(
- pattern=r"Buildout #(?P<buildout>\d+)",
- url='{GH}/buildout/buildout/issues/{buildout}',
- ),
- dict(
- pattern=r"Old Setuptools #(?P<old_setuptools>\d+)",
- url='http://bugs.python.org/setuptools/issue{old_setuptools}',
- ),
- dict(
- pattern=r"Jython #(?P<jython>\d+)",
- url='http://bugs.jython.org/issue{jython}',
- ),
- dict(
- pattern=r"Python #(?P<python>\d+)",
- url='http://bugs.python.org/issue{python}',
- ),
- dict(
- pattern=r"Interop #(?P<interop>\d+)",
- url='{GH}/pypa/interoperability-peps/issues/{interop}',
- ),
- dict(
- pattern=r"Pip #(?P<pip>\d+)",
- url='{GH}/pypa/pip/issues/{pip}',
- ),
- dict(
- pattern=r"Packaging #(?P<packaging>\d+)",
- url='{GH}/pypa/packaging/issues/{packaging}',
- ),
- dict(
- pattern=r"[Pp]ackaging (?P<packaging_ver>\d+(\.\d+)+)",
- url='{GH}/pypa/packaging/blob/{packaging_ver}/CHANGELOG.rst',
- ),
- dict(
- pattern=r"PEP[- ](?P<pep_number>\d+)",
- url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/',
- ),
- dict(
- pattern=r"^(?m)((?P<scm_version>v?\d+(\.\d+){1,2}))\n[-=]+\n",
- with_scm="{text}\n{rev[timestamp]:%d %b %Y}\n",
- ),
- ],
- ),
+ '../CHANGES.rst': dict(
+ using=dict(
+ BB='https://bitbucket.org',
+ GH='https://github.com',
+ ),
+ replace=[
+ dict(
+ pattern=r"(Issue )?#(?P<issue>\d+)",
+ url='{GH}/pypa/setuptools/issues/{issue}',
+ ),
+ dict(
+ pattern=r"BB Pull Request ?#(?P<bb_pull_request>\d+)",
+ url='{BB}/pypa/setuptools/pull-request/{bb_pull_request}',
+ ),
+ dict(
+ pattern=r"Distribute #(?P<distribute>\d+)",
+ url='{BB}/tarek/distribute/issue/{distribute}',
+ ),
+ dict(
+ pattern=r"Buildout #(?P<buildout>\d+)",
+ url='{GH}/buildout/buildout/issues/{buildout}',
+ ),
+ dict(
+ pattern=r"Old Setuptools #(?P<old_setuptools>\d+)",
+ url='http://bugs.python.org/setuptools/issue{old_setuptools}',
+ ),
+ dict(
+ pattern=r"Jython #(?P<jython>\d+)",
+ url='http://bugs.jython.org/issue{jython}',
+ ),
+ dict(
+ pattern=r"Python #(?P<python>\d+)",
+ url='http://bugs.python.org/issue{python}',
+ ),
+ dict(
+ pattern=r"Interop #(?P<interop>\d+)",
+ url='{GH}/pypa/interoperability-peps/issues/{interop}',
+ ),
+ dict(
+ pattern=r"Pip #(?P<pip>\d+)",
+ url='{GH}/pypa/pip/issues/{pip}',
+ ),
+ dict(
+ pattern=r"Packaging #(?P<packaging>\d+)",
+ url='{GH}/pypa/packaging/issues/{packaging}',
+ ),
+ dict(
+ pattern=r"[Pp]ackaging (?P<packaging_ver>\d+(\.\d+)+)",
+ url='{GH}/pypa/packaging/blob/{packaging_ver}/CHANGELOG.rst',
+ ),
+ dict(
+ pattern=r"PEP[- ](?P<pep_number>\d+)",
+ url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/',
+ ),
+ dict(
+ pattern=r"^(?m)((?P<scm_version>v?\d+(\.\d+){1,2}))\n[-=]+\n",
+ with_scm="{text}\n{rev[timestamp]:%d %b %Y}\n",
+ ),
+ ],
+ ),
}
diff --git a/docs/easy_install.txt b/docs/easy_install.txt
index 8dd176fd..a66909b1 100644
--- a/docs/easy_install.txt
+++ b/docs/easy_install.txt
@@ -677,7 +677,7 @@ locations, build options, etc., EasyInstall will respect your existing settings
until and unless you override them explicitly in an ``[easy_install]`` section.
For more information, see also the current Python documentation on the `use and
-location of distutils configuration files <http://docs.python.org/inst/config-syntax.html>`_.
+location of distutils configuration files <https://docs.python.org/install/index.html#inst-config-files>`_.
Notice that ``easy_install`` will use the ``setup.cfg`` from the current
working directory only if it was triggered from ``setup.py`` through the
diff --git a/docs/releases.txt b/docs/releases.txt
index 3f29334a..c84ddd75 100644
--- a/docs/releases.txt
+++ b/docs/releases.txt
@@ -10,23 +10,20 @@ successful build of a tagged release per
To cut a release, install and run ``bumpversion {part}`` where ``part``
is major, minor, or patch based on the scope of the changes in the
release. Then, push the commits to the master branch. If tests pass,
-the release will be uploaded to PyPI.
+the release will be uploaded to PyPI (from the Python 3.5 tests).
-Bootstrap Bookmark
-------------------
+Bootstrap Branch
+----------------
-Setuptools has a bootstrap script (ez_setup.py) which is hosted in the
-repository and must be updated with each release (to bump the default version).
-The "published" version of the script is the one indicated by the ``bootstrap``
-branch.
+Setuptools has a bootstrap script (ez_setup.py), which is hosted in the
+repository in the ``bootstrap`` branch.
-Therefore, the latest bootstrap script can be retrieved by checking out the
-repository at that bookmark. It's also possible to get the bootstrap script for
-any particular release by grabbing the script from that tagged release.
+Therefore, the latest bootstrap script can be retrieved by checking out
+that branch.
The officially-published location of the bootstrap script is hosted on Python
infrastructure (#python-infra on freenode) at https://bootstrap.pypa.io and
-is updated every fifteen minutes from the bootstrap script. Sometimes,
+is updated every fifteen minutes from the bootstrap branch. Sometimes,
especially when the bootstrap script is rolled back, this
process doesn't work as expected and requires manual intervention.
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 00000000..0871ed76
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1 @@
+rst.linker>=1.6.1
diff --git a/docs/setuptools.txt b/docs/setuptools.txt
index eeeab937..57818281 100644
--- a/docs/setuptools.txt
+++ b/docs/setuptools.txt
@@ -1270,7 +1270,8 @@ Creating System Packages
Setting the ``zip_safe`` flag
-----------------------------
-For maximum performance, Python packages are best installed as zip files.
+For some use cases (such as bundling as part of a larger application), Python
+packages may be run directly from a zip file.
Not all packages, however, are capable of running in compressed form, because
they may expect to be able to access either source code or data files as
normal operating system files. So, ``setuptools`` can install your project
diff --git a/ez_setup.py b/ez_setup.py
deleted file mode 100644
index d6f4b78c..00000000
--- a/ez_setup.py
+++ /dev/null
@@ -1,417 +0,0 @@
-#!/usr/bin/env python
-
-"""
-Setuptools bootstrapping installer.
-
-Run this script to install or upgrade setuptools.
-"""
-
-import os
-import shutil
-import sys
-import tempfile
-import zipfile
-import optparse
-import subprocess
-import platform
-import textwrap
-import contextlib
-import json
-import codecs
-
-from distutils import log
-
-try:
- from urllib.request import urlopen
-except ImportError:
- from urllib2 import urlopen
-
-try:
- from site import USER_SITE
-except ImportError:
- USER_SITE = None
-
-LATEST = object()
-DEFAULT_VERSION = LATEST
-DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/"
-DEFAULT_SAVE_DIR = os.curdir
-
-
-def _python_cmd(*args):
- """
- Execute a command.
-
- Return True if the command succeeded.
- """
- args = (sys.executable,) + args
- return subprocess.call(args) == 0
-
-
-def _install(archive_filename, install_args=()):
- """Install Setuptools."""
- with archive_context(archive_filename):
- # installing
- log.warn('Installing Setuptools')
- if not _python_cmd('setup.py', 'install', *install_args):
- log.warn('Something went wrong during the installation.')
- log.warn('See the error message above.')
- # exitcode will be 2
- return 2
-
-
-def _build_egg(egg, archive_filename, to_dir):
- """Build Setuptools egg."""
- with archive_context(archive_filename):
- # building an egg
- log.warn('Building a Setuptools egg in %s', to_dir)
- _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
- # returning the result
- log.warn(egg)
- if not os.path.exists(egg):
- raise IOError('Could not build the egg.')
-
-
-class ContextualZipFile(zipfile.ZipFile):
-
- """Supplement ZipFile class to support context manager for Python 2.6."""
-
- def __enter__(self):
- return self
-
- def __exit__(self, type, value, traceback):
- self.close()
-
- def __new__(cls, *args, **kwargs):
- """Construct a ZipFile or ContextualZipFile as appropriate."""
- if hasattr(zipfile.ZipFile, '__exit__'):
- return zipfile.ZipFile(*args, **kwargs)
- return super(ContextualZipFile, cls).__new__(cls)
-
-
-@contextlib.contextmanager
-def archive_context(filename):
- """
- Unzip filename to a temporary directory, set to the cwd.
-
- The unzipped target is cleaned up after.
- """
- tmpdir = tempfile.mkdtemp()
- log.warn('Extracting in %s', tmpdir)
- old_wd = os.getcwd()
- try:
- os.chdir(tmpdir)
- with ContextualZipFile(filename) as archive:
- archive.extractall()
-
- # going in the directory
- subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
- os.chdir(subdir)
- log.warn('Now working in %s', subdir)
- yield
-
- finally:
- os.chdir(old_wd)
- shutil.rmtree(tmpdir)
-
-
-def _do_download(version, download_base, to_dir, download_delay):
- """Download Setuptools."""
- egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg'
- % (version, sys.version_info[0], sys.version_info[1]))
- if not os.path.exists(egg):
- archive = download_setuptools(version, download_base,
- to_dir, download_delay)
- _build_egg(egg, archive, to_dir)
- sys.path.insert(0, egg)
-
- # Remove previously-imported pkg_resources if present (see
- # https://bitbucket.org/pypa/setuptools/pull-request/7/ for details).
- if 'pkg_resources' in sys.modules:
- _unload_pkg_resources()
-
- import setuptools
- setuptools.bootstrap_install_from = egg
-
-
-def use_setuptools(
- version=DEFAULT_VERSION, download_base=DEFAULT_URL,
- to_dir=DEFAULT_SAVE_DIR, download_delay=15):
- """
- Ensure that a setuptools version is installed.
-
- 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
- # representative modules.
- rep_modules = 'pkg_resources', 'setuptools'
- imported = set(sys.modules).intersection(rep_modules)
-
- try:
- import pkg_resources
- pkg_resources.require("setuptools>=" + version)
- # a suitable version is already installed
- return
- except ImportError:
- # pkg_resources not available; setuptools is not installed; download
- pass
- except pkg_resources.DistributionNotFound:
- # no version of setuptools was found; allow download
- pass
- except pkg_resources.VersionConflict as VC_err:
- if imported:
- _conflict_bail(VC_err, version)
-
- # otherwise, unload pkg_resources to allow the downloaded version to
- # take precedence.
- del pkg_resources
- _unload_pkg_resources()
-
- return _do_download(version, download_base, to_dir, download_delay)
-
-
-def _conflict_bail(VC_err, version):
- """
- Setuptools was imported prior to invocation, so it is
- unsafe to unload it. Bail out.
- """
- conflict_tmpl = textwrap.dedent("""
- The required version of setuptools (>={version}) is not available,
- and can't be installed while this script is running. Please
- install a more recent version first, using
- 'easy_install -U setuptools'.
-
- (Currently using {VC_err.args[0]!r})
- """)
- msg = conflict_tmpl.format(**locals())
- sys.stderr.write(msg)
- sys.exit(2)
-
-
-def _unload_pkg_resources():
- sys.meta_path = [importer for importer in sys.meta_path if
- importer.__class__.__module__ != 'pkg_resources.extern']
- del_modules = [
- name for name in sys.modules
- if name.startswith('pkg_resources')
- ]
- for mod_name in del_modules:
- del sys.modules[mod_name]
-
-
-def _clean_check(cmd, target):
- """
- Run the command to download target.
-
- If the command fails, clean up before re-raising the error.
- """
- try:
- subprocess.check_call(cmd)
- except subprocess.CalledProcessError:
- if os.access(target, os.F_OK):
- os.unlink(target)
- raise
-
-
-def download_file_powershell(url, target):
- """
- Download the file at url to target using Powershell.
-
- Powershell will validate trust.
- Raise an exception if the command cannot complete.
- """
- target = os.path.abspath(target)
- ps_cmd = (
- "[System.Net.WebRequest]::DefaultWebProxy.Credentials = "
- "[System.Net.CredentialCache]::DefaultCredentials; "
- '(new-object System.Net.WebClient).DownloadFile("%(url)s", "%(target)s")'
- % locals()
- )
- cmd = [
- 'powershell',
- '-Command',
- ps_cmd,
- ]
- _clean_check(cmd, target)
-
-
-def has_powershell():
- """Determine if Powershell is available."""
- if platform.system() != 'Windows':
- return False
- cmd = ['powershell', '-Command', 'echo test']
- with open(os.path.devnull, 'wb') as devnull:
- try:
- subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
- except Exception:
- return False
- return True
-download_file_powershell.viable = has_powershell
-
-
-def download_file_curl(url, target):
- cmd = ['curl', url, '--silent', '--output', target]
- _clean_check(cmd, target)
-
-
-def has_curl():
- cmd = ['curl', '--version']
- with open(os.path.devnull, 'wb') as devnull:
- try:
- subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
- except Exception:
- return False
- return True
-download_file_curl.viable = has_curl
-
-
-def download_file_wget(url, target):
- cmd = ['wget', url, '--quiet', '--output-document', target]
- _clean_check(cmd, target)
-
-
-def has_wget():
- cmd = ['wget', '--version']
- with open(os.path.devnull, 'wb') as devnull:
- try:
- subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
- except Exception:
- return False
- return True
-download_file_wget.viable = has_wget
-
-
-def download_file_insecure(url, target):
- """Use Python to download the file, without connection authentication."""
- src = urlopen(url)
- try:
- # Read all the data in one block.
- data = src.read()
- finally:
- src.close()
-
- # Write all the data in one block to avoid creating a partial file.
- with open(target, "wb") as dst:
- dst.write(data)
-download_file_insecure.viable = lambda: True
-
-
-def get_best_downloader():
- downloaders = (
- download_file_powershell,
- download_file_curl,
- download_file_wget,
- download_file_insecure,
- )
- viable_downloaders = (dl for dl in downloaders if dl.viable())
- return next(viable_downloaders, None)
-
-
-def download_setuptools(
- version=DEFAULT_VERSION, download_base=DEFAULT_URL,
- to_dir=DEFAULT_SAVE_DIR, delay=15,
- downloader_factory=get_best_downloader):
- """
- Download setuptools from a specified location and return its filename.
-
- `version` should be a valid setuptools version number that is available
- as an sdist for download under the `download_base` URL (which should end
- with a '/'). `to_dir` is the directory where the egg will be downloaded.
- `delay` is the number of seconds to pause before an actual download
- attempt.
-
- ``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
- url = download_base + zip_name
- saveto = os.path.join(to_dir, zip_name)
- if not os.path.exists(saveto): # Avoid repeated downloads
- log.warn("Downloading %s", url)
- downloader = downloader_factory()
- downloader(url, saveto)
- 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.
-
- Returns list of command line arguments.
- """
- return ['--user'] if options.user_install else []
-
-
-def _parse_args():
- """Parse the command line for options."""
- parser = optparse.OptionParser()
- parser.add_option(
- '--user', dest='user_install', action='store_true', default=False,
- help='install in user site package (requires Python 2.6 or later)')
- parser.add_option(
- '--download-base', dest='download_base', metavar="URL",
- default=DEFAULT_URL,
- help='alternative URL from where to download the setuptools package')
- parser.add_option(
- '--insecure', dest='downloader_factory', action='store_const',
- const=lambda: download_file_insecure, default=get_best_downloader,
- help='Use internal, non-validating downloader'
- )
- parser.add_option(
- '--version', help="Specify which version to download",
- default=DEFAULT_VERSION,
- )
- parser.add_option(
- '--to-dir',
- help="Directory to save (and re-use) package",
- default=DEFAULT_SAVE_DIR,
- )
- options, args = parser.parse_args()
- # positional arguments are ignored
- return options
-
-
-def _download_args(options):
- """Return args for download_setuptools function from cmdline args."""
- return dict(
- version=options.version,
- download_base=options.download_base,
- downloader_factory=options.downloader_factory,
- to_dir=options.to_dir,
- )
-
-
-def main():
- """Install or upgrade setuptools and EasyInstall."""
- options = _parse_args()
- archive = download_setuptools(**_download_args(options))
- return _install(archive, _build_install_args(options))
-
-if __name__ == '__main__':
- sys.exit(main())
diff --git a/pavement.py b/pavement.py
index 8d7574e2..303e9bac 100644
--- a/pavement.py
+++ b/pavement.py
@@ -4,25 +4,25 @@ from paver.easy import task, path as Path
import pip
def remove_all(paths):
- for path in paths:
- path.rmtree() if path.isdir() else path.remove()
+ for path in paths:
+ path.rmtree() if path.isdir() else path.remove()
@task
def update_vendored():
- vendor = Path('pkg_resources/_vendor')
- remove_all(vendor.glob('packaging*'))
- remove_all(vendor.glob('six*'))
- remove_all(vendor.glob('pyparsing*'))
- install_args = [
- 'install',
- '-r', str(vendor/'vendored.txt'),
- '-t', str(vendor),
- ]
- pip.main(install_args)
- packaging = vendor / 'packaging'
- for file in packaging.glob('*.py'):
- text = file.text()
- text = re.sub(r' (pyparsing|six)', r' pkg_resources.extern.\1', text)
- file.write_text(text)
- remove_all(vendor.glob('*.dist-info'))
- remove_all(vendor.glob('*.egg-info'))
+ vendor = Path('pkg_resources/_vendor')
+ remove_all(vendor.glob('packaging*'))
+ remove_all(vendor.glob('six*'))
+ remove_all(vendor.glob('pyparsing*'))
+ install_args = [
+ 'install',
+ '-r', str(vendor/'vendored.txt'),
+ '-t', str(vendor),
+ ]
+ pip.main(install_args)
+ packaging = vendor / 'packaging'
+ for file in packaging.glob('*.py'):
+ text = file.text()
+ text = re.sub(r' (pyparsing|six)', r' pkg_resources.extern.\1', text)
+ file.write_text(text)
+ remove_all(vendor.glob('*.dist-info'))
+ remove_all(vendor.glob('*.egg-info'))
diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py
index eb84f4ba..2eab8230 100644
--- a/pkg_resources/__init__.py
+++ b/pkg_resources/__init__.py
@@ -792,6 +792,8 @@ class WorkingSet(object):
best = {}
to_activate = []
+ req_extras = _ReqExtras()
+
# Mapping of requirement to set of distributions that required it;
# useful for reporting info about conflicts.
required_by = collections.defaultdict(set)
@@ -802,12 +804,10 @@ class WorkingSet(object):
if req in processed:
# Ignore cyclic or redundant dependencies
continue
- # If the req has a marker, evaluate it -- skipping the req if
- # it evaluates to False.
- # https://github.com/pypa/setuptools/issues/523
- _issue_523_bypass = True
- if not _issue_523_bypass and req.marker and not req.marker.evaluate():
- continue
+
+ if not req_extras.markers_pass(req):
+ continue
+
dist = best.get(req.key)
if dist is None:
# Find the best distribution and add it to the map
@@ -840,6 +840,7 @@ class WorkingSet(object):
# Register the new requirements needed by req
for new_requirement in new_requirements:
required_by[new_requirement].add(req.project_name)
+ req_extras[new_requirement] = req.extras
processed[req] = True
@@ -972,6 +973,26 @@ class WorkingSet(object):
self.callbacks = callbacks[:]
+class _ReqExtras(dict):
+ """
+ Map each requirement to the extras that demanded it.
+ """
+
+ def markers_pass(self, req):
+ """
+ Evaluate markers for req against each extra that
+ demanded it.
+
+ Return False if the req has a marker and fails
+ evaluation. Otherwise, return True.
+ """
+ extra_evals = (
+ req.marker.evaluate({'extra': extra})
+ for extra in self.get(req, ()) + (None,)
+ )
+ return not req.marker or any(extra_evals)
+
+
class Environment(object):
"""Searchable snapshot of distributions on a search path"""
@@ -1838,7 +1859,13 @@ class FileMetadata(EmptyProvider):
def get_metadata(self, name):
if name=='PKG-INFO':
with io.open(self.path, encoding='utf-8') as f:
- metadata = f.read()
+ try:
+ metadata = f.read()
+ except UnicodeDecodeError as exc:
+ # add path context to error message
+ tmpl = " in {self.path}"
+ exc.reason += tmpl.format(self=self)
+ raise
return metadata
raise KeyError("No metadata except PKG-INFO is available")
diff --git a/pkg_resources/_vendor/packaging/__about__.py b/pkg_resources/_vendor/packaging/__about__.py
index baa90755..c21a758b 100644
--- a/pkg_resources/_vendor/packaging/__about__.py
+++ b/pkg_resources/_vendor/packaging/__about__.py
@@ -12,7 +12,7 @@ __title__ = "packaging"
__summary__ = "Core utilities for Python packages"
__uri__ = "https://github.com/pypa/packaging"
-__version__ = "16.6"
+__version__ = "16.7"
__author__ = "Donald Stufft and individual contributors"
__email__ = "donald@stufft.io"
diff --git a/pkg_resources/_vendor/packaging/markers.py b/pkg_resources/_vendor/packaging/markers.py
index bac852df..c5d29cd9 100644
--- a/pkg_resources/_vendor/packaging/markers.py
+++ b/pkg_resources/_vendor/packaging/markers.py
@@ -78,9 +78,18 @@ VARIABLE = (
L("platform.version") | # PEP-345
L("platform.machine") | # PEP-345
L("platform.python_implementation") | # PEP-345
+ L("python_implementation") | # undocumented setuptools legacy
L("extra")
)
-VARIABLE.setParseAction(lambda s, l, t: Variable(t[0].replace('.', '_')))
+ALIASES = {
+ 'os.name': 'os_name',
+ 'sys.platform': 'sys_platform',
+ 'platform.version': 'platform_version',
+ 'platform.machine': 'platform_machine',
+ 'platform.python_implementation': 'platform_python_implementation',
+ 'python_implementation': 'platform_python_implementation'
+}
+VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0])))
VERSION_CMP = (
L("===") |
diff --git a/pkg_resources/_vendor/vendored.txt b/pkg_resources/_vendor/vendored.txt
index 1d03759f..46532c0a 100644
--- a/pkg_resources/_vendor/vendored.txt
+++ b/pkg_resources/_vendor/vendored.txt
@@ -1,3 +1,3 @@
-packaging==16.6
+packaging==16.7
pyparsing==2.0.6
six==1.10.0
diff --git a/pkg_resources/tests/test_resources.py b/pkg_resources/tests/test_resources.py
index 98b8dcd7..31847dc8 100644
--- a/pkg_resources/tests/test_resources.py
+++ b/pkg_resources/tests/test_resources.py
@@ -171,16 +171,100 @@ class TestDistro:
msg = 'Foo 0.9 is installed but Foo==1.2 is required'
assert vc.value.report() == msg
- @pytest.mark.xfail(reason="Functionality disabled; see #523")
- def test_environment_markers(self):
- """
- Environment markers are evaluated at resolution time.
- """
+ def test_environment_marker_evaluation_negative(self):
+ """Environment markers are evaluated at resolution time."""
ad = pkg_resources.Environment([])
ws = WorkingSet([])
res = ws.resolve(parse_requirements("Foo;python_version<'2'"), ad)
assert list(res) == []
+ def test_environment_marker_evaluation_positive(self):
+ ad = pkg_resources.Environment([])
+ ws = WorkingSet([])
+ Foo = Distribution.from_filename("/foo_dir/Foo-1.2.dist-info")
+ ad.add(Foo)
+ res = ws.resolve(parse_requirements("Foo;python_version>='2'"), ad)
+ assert list(res) == [Foo]
+
+ def test_environment_marker_evaluation_called(self):
+ """
+ If one package foo requires bar without any extras,
+ markers should pass for bar without extras.
+ """
+ parent_req, = parse_requirements("foo")
+ req, = parse_requirements("bar;python_version>='2'")
+ req_extras = pkg_resources._ReqExtras({req: parent_req.extras})
+ assert req_extras.markers_pass(req)
+
+ parent_req, = parse_requirements("foo[]")
+ req, = parse_requirements("bar;python_version>='2'")
+ req_extras = pkg_resources._ReqExtras({req: parent_req.extras})
+ assert req_extras.markers_pass(req)
+
+ def test_marker_evaluation_with_extras(self):
+ """Extras are also evaluated as markers at resolution time."""
+ ad = pkg_resources.Environment([])
+ ws = WorkingSet([])
+ # Metadata needs to be native strings due to cStringIO behaviour in
+ # 2.6, so use str().
+ Foo = Distribution.from_filename(
+ "/foo_dir/Foo-1.2.dist-info",
+ metadata=Metadata(("METADATA", str("Provides-Extra: baz\n"
+ "Requires-Dist: quux; extra=='baz'")))
+ )
+ ad.add(Foo)
+ assert list(ws.resolve(parse_requirements("Foo"), ad)) == [Foo]
+ quux = Distribution.from_filename("/foo_dir/quux-1.0.dist-info")
+ ad.add(quux)
+ res = list(ws.resolve(parse_requirements("Foo[baz]"), ad))
+ assert res == [Foo,quux]
+
+ def test_marker_evaluation_with_multiple_extras(self):
+ ad = pkg_resources.Environment([])
+ ws = WorkingSet([])
+ # Metadata needs to be native strings due to cStringIO behaviour in
+ # 2.6, so use str().
+ Foo = Distribution.from_filename(
+ "/foo_dir/Foo-1.2.dist-info",
+ metadata=Metadata(("METADATA", str("Provides-Extra: baz\n"
+ "Requires-Dist: quux; extra=='baz'\n"
+ "Provides-Extra: bar\n"
+ "Requires-Dist: fred; extra=='bar'\n")))
+ )
+ ad.add(Foo)
+ quux = Distribution.from_filename("/foo_dir/quux-1.0.dist-info")
+ ad.add(quux)
+ fred = Distribution.from_filename("/foo_dir/fred-0.1.dist-info")
+ ad.add(fred)
+ res = list(ws.resolve(parse_requirements("Foo[baz,bar]"), ad))
+ assert sorted(res) == [fred,quux,Foo]
+
+ def test_marker_evaluation_with_extras_loop(self):
+ ad = pkg_resources.Environment([])
+ ws = WorkingSet([])
+ # Metadata needs to be native strings due to cStringIO behaviour in
+ # 2.6, so use str().
+ a = Distribution.from_filename(
+ "/foo_dir/a-0.2.dist-info",
+ metadata=Metadata(("METADATA", str("Requires-Dist: c[a]")))
+ )
+ b = Distribution.from_filename(
+ "/foo_dir/b-0.3.dist-info",
+ metadata=Metadata(("METADATA", str("Requires-Dist: c[b]")))
+ )
+ c = Distribution.from_filename(
+ "/foo_dir/c-1.0.dist-info",
+ metadata=Metadata(("METADATA", str("Provides-Extra: a\n"
+ "Requires-Dist: b;extra=='a'\n"
+ "Provides-Extra: b\n"
+ "Requires-Dist: foo;extra=='b'")))
+ )
+ foo = Distribution.from_filename("/foo_dir/foo-0.1.dist-info")
+ for dist in (a, b, c, foo):
+ ad.add(dist)
+ res = list(ws.resolve(parse_requirements("a"), ad))
+ assert res == [a, c, b, foo]
+
def testDistroDependsOptions(self):
d = self.distRequires("""
Twisted>=1.5
@@ -313,7 +397,10 @@ class TestEntryPoints:
def checkSubMap(self, m):
assert len(m) == len(self.submap_expect)
for key, ep in self.submap_expect.items():
- assert repr(m.get(key)) == repr(ep)
+ assert m.get(key).name == ep.name
+ assert m.get(key).module_name == ep.module_name
+ assert sorted(m.get(key).attrs) == sorted(ep.attrs)
+ assert sorted(m.get(key).extras) == sorted(ep.extras)
submap_expect = dict(
feature1=EntryPoint('feature1', 'somemodule', ['somefunction']),
diff --git a/setup.cfg b/setup.cfg
index 988a8970..49dc7067 100755
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,30 +1,19 @@
-[bumpversion]
-current_version = 20.6.7
-commit = True
-tag = True
-
[egg_info]
tag_build = .post
tag_date = 1
[aliases]
clean_egg_info = egg_info -RDb ''
-release = clean_egg_info sdist bdist_wheel build_sphinx
+release = clean_egg_info sdist bdist_wheel
+source = register sdist binary
+binary = bdist_egg upload --show-response
test = pytest
-[build_sphinx]
-source-dir = docs/
-build-dir = docs/build
-all_files = 1
-
-[upload_docs]
-upload-dir = docs/build/html
+[upload]
+repository = https://upload.pypi.io/legacy/
[sdist]
formats = gztar zip
[wheel]
universal = 1
-
-[bumpversion:file:setup.py]
-
diff --git a/setup.py b/setup.py
index 7d20480b..21b0aaa8 100755
--- a/setup.py
+++ b/setup.py
@@ -61,8 +61,6 @@ if (sys.platform == 'win32' or (os.name == 'java' and os._name == 'nt')) \
needs_pytest = set(['ptr', 'pytest', 'test']).intersection(sys.argv)
pytest_runner = ['pytest-runner'] if needs_pytest else []
-needs_sphinx = set(['build_sphinx', 'upload_docs', 'release']).intersection(sys.argv)
-sphinx = ['sphinx', 'rst.linker>=1.5'] if needs_sphinx else []
needs_wheel = set(['release', 'bdist_wheel']).intersection(sys.argv)
wheel = ['wheel'] if needs_wheel else []
@@ -146,10 +144,10 @@ setup_params = dict(
""").strip().splitlines(),
extras_require={
"ssl:sys_platform=='win32'": "wincertstore==0.2",
- "certs": "certifi==2015.11.20",
+ "certs": "certifi==2016.2.28",
},
dependency_links=[
- 'https://pypi.python.org/packages/source/c/certifi/certifi-2015.11.20.tar.gz#md5=25134646672c695c1ff1593c2dd75d08',
+ 'https://pypi.python.org/packages/source/c/certifi/certifi-2016.2.28.tar.gz#md5=5d672aa766e1f773c75cfeccd02d3650',
'https://pypi.python.org/packages/source/w/wincertstore/wincertstore-0.2.zip#md5=ae728f2f007185648d0c7a8679b361e2',
],
scripts=[],
@@ -159,7 +157,7 @@ setup_params = dict(
] + (['mock'] if sys.version_info[:2] < (3, 3) else []),
setup_requires=[
'setuptools_scm>=1.9',
- ] + sphinx + pytest_runner + wheel,
+ ] + pytest_runner + wheel,
)
if __name__ == '__main__':
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