+v40.0.0
+-------
+* #1342: Drop support for Python 3.3.
+* #1366: In package_index, fixed handling of encoded entities in URLs.
+* #1383: In pkg_resources VendorImporter, avoid removing packages imported from the root.
+* #1379: Minor doc fixes after actually using the new release process.
+* #1385: Removed section on non-package data files.
+* #1403: Fix developer's guide.
+* #1404: Fix PEP 518 configuration: set build requirements in ``pyproject.toml`` to ``["wheel"]``.
+
+
v39.2.0
-------
a text file.
* #1360: Fixed issue with a mismatch between the name of the package and the
name of the .dist-info file in wheel files
+* #1364: Add `__dir__()` implementation to `pkg_resources.Distribution()` that
+ includes the attributes in the `_provider` instance variable.
* #1365: Take the package_dir option into account when loading the version from
a module attribute.
* #1353: Added coverage badge to README.
after any ``distutils`` ``setup_keywords`` calls, allowing them to override
values.
* #1352: Added ``tox`` environment for documentation builds.
-* #1354: Added ``towncrier`` for changelog managment.
+* #1354: Added ``towncrier`` for changelog management.
* #1355: Add PR template.
* #1368: Fixed tests which failed without network connectivity.
* #1369: Added unit tests for PEP 425 compatibility tags support.
User's Guide for instructions on installing, upgrading, and uninstalling
Setuptools.
-The project is `maintained at GitHub <https://github.com/pypa/setuptools>`_.
+The project is `maintained at GitHub <https://github.com/pypa/setuptools>`_
+by the `Setuptools Developers
+<https://github.com/orgs/pypa/teams/setuptools-developers>`_.
Questions and comments should be directed to the `distutils-sig
mailing list <http://mail.python.org/pipermail/distutils-sig/>`_.
+++ /dev/null
-Add `__dir__()` implementation to `pkg_resources.Distribution()` that includes the attributes in the `_provider` instance variable.
\ No newline at end of file
+import sys
+
+
pytest_plugins = 'setuptools.tests.fixtures'
"--package_name", action="append", default=[],
help="list of package_name to pass to test functions",
)
+
+
+collect_ignore = [
+ 'tests/manual_test.py',
+ 'setuptools/tests/mod_with_constant.py',
+]
+
+
+if sys.version_info < (3,):
+ collect_ignore.append('setuptools/lib2to3_ex.py')
+
+
+if sys.version_info < (3, 6):
+ collect_ignore.append('pavement.py')
All PRs with code changes should include tests. All changes should include a
changelog entry.
-``setuptools`` uses `towncrier <https://town-crier.readthedocs.io/en/latest/>`_
+``setuptools`` uses `towncrier <https://pypi.org/project/towncrier/>`_
for changelog managment, so when making a PR, please add a news fragment in the
``changelog.d/`` folder. Changelog files are written in Restructured Text and
should be a 1 or 2 sentence description of the substantive changes in the PR.
$ cat changelog.d/1288.change.rst
Add support for maintainer in PKG-INFO
------------
-Source Code
------------
-
-Grab the code at Github::
-
- $ git clone https://github.com/pypa/setuptools
-
-If you want to contribute changes, we recommend you fork the repository on
-Github, commit the changes to your repository, and then make a pull request
-on Github. If you make some changes, don't forget to:
-
-- add a note in CHANGES.rst
-
-Please commit all changes in the 'master' branch against the latest available
-commit or for bug-fixes, against an earlier commit or release in which the
-bug occurred.
-
-If you find yourself working on more than one issue at a time, Setuptools
-generally prefers Git-style branches, so use Mercurial bookmarks or Git
-branches or multiple forks to maintain separate efforts.
-
-The Continuous Integration tests that validate every release are run
-from this repository.
-
-------
Testing
-------
for download links and basic installation instructions for each of the
supported platforms.
-You will need at least Python 3.3 or 2.7. An ``easy_install`` script will be
+You will need at least Python 3.4 or 2.7. An ``easy_install`` script will be
installed in the normal location for Python scripts on your platform.
Note that the instructions on the setuptools PyPI page assume that you are
re-invigorated the community on the project, encouraged renewed innovation,
and addressed many defects.
-* Since the merge with Distribute, Jason R. Coombs is the
- maintainer of setuptools. The project is maintained in coordination with
- the Python Packaging Authority (PyPA) and the larger Python community.
-
+* Jason R. Coombs performed the merge with Distribute, maintaining the
+ project for several years in coordination with the Python Packaging
+ Authority (PyPA).
``git add`` for any modified files.
3. Run ``towncrier --version {new.version.number}`` to stage the changelog
updates in git.
+4. Verify that there are no remaining ``changelog.d/*.rst`` files. If a
+ file was named incorrectly, it may be ignored by towncrier.
+5. Review the updated ``CHANGES.rst`` file. If any changes are needed,
+ make the edits and stage them via ``git add CHANGES.rst``.
Once the changelog edits are staged and ready to commit, cut a release by
-installing and running ``bump2version {part}`` where ``part``
+installing and running ``bump2version --allow-dirty {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::
Roadmap
=======
-Setuptools is primarily in maintenance mode. The project attempts to address
-user issues, concerns, and feature requests in a timely fashion.
+Setuptools has the following large-scale goals on the roadmap:
+
+- Harmonize declarative config with pyproject.toml syntax.
+- Deprecate and remove setup_requires and easy_install in
+ favor of PEP 518 build requirements and pip install.
+- Adopt the Distutils package and stop monkeypatching stdlib.
Non-Package Data Files
----------------------
-The ``distutils`` normally install general "data files" to a platform-specific
-location (e.g. ``/usr/share``). This feature intended to be used for things
-like documentation, example configuration files, and the like. ``setuptools``
-does not install these data files in a separate location, however. They are
-bundled inside the egg file or directory, alongside the Python modules and
-packages. The data files can also be accessed using the :ref:`ResourceManager
-API`, by specifying a ``Requirement`` instead of a package name::
-
- from pkg_resources import Requirement, resource_filename
- filename = resource_filename(Requirement.parse("MyProject"),"sample.conf")
-
-The above code will obtain the filename of the "sample.conf" file in the data
-root of the "MyProject" distribution.
-
-Note, by the way, that this encapsulation of data files means that you can't
-actually install data files to some arbitrary location on a user's machine;
-this is a feature, not a bug. You can always include a script in your
-distribution that extracts and copies your the documentation or data files to
-a user-specified location, at their discretion. If you put related data files
-in a single directory, you can use ``resource_filename()`` with the directory
-name to get a filesystem directory that then can be copied with the ``shutil``
-module. (Even if your package is installed as a zipfile, calling
-``resource_filename()`` on a directory will return an actual filesystem
-directory, whose contents will be that entire subtree of your distribution.)
-
-(Of course, if you're writing a new package, you can just as easily place your
-data files or directories inside one of your packages, rather than using the
-distutils' approach. However, if you're updating an existing application, it
-may be simpler not to change the way it currently specifies these data files.)
+Historically, ``setuptools`` by way of ``easy_install`` would encapsulate data
+files from the distribution into the egg (see `the old docs
+<https://github.com/pypa/setuptools/blob/52aacd5b276fedd6849c3a648a0014f5da563e93/docs/setuptools.txt#L970-L1001>`_). As eggs are deprecated and pip-based installs
+fall back to the platform-specific location for installing data files, there is
+no supported facility to reliably retrieve these resources.
+
+Instead, the PyPA recommends that any data files you wish to be accessible at
+run time be included in the package.
Automatic Resource Extraction
__import__('pkg_resources.extern.packaging.markers')
-if (3, 0) < sys.version_info < (3, 3):
- raise RuntimeError("Python 3.3 or later is required")
+__metaclass__ = type
+
+
+if (3, 0) < sys.version_info < (3, 4):
+ raise RuntimeError("Python 3.4 or later is required")
if six.PY2:
# Those builtin exceptions are only defined in Python 3
"""List of resource names in the directory (like ``os.listdir()``)"""
-class WorkingSet(object):
+class WorkingSet:
"""A collection of active distributions on sys.path (or a similar list)"""
def __init__(self, entries=None):
return not req.marker or any(extra_evals)
-class Environment(object):
+class Environment:
"""Searchable snapshot of distributions on a search path"""
def __init__(
`platform` is an optional string specifying the name of the platform
that platform-specific distributions must be compatible with. If
unspecified, it defaults to the current platform. `python` is an
- optional string naming the desired version of Python (e.g. ``'3.3'``);
+ optional string naming the desired version of Python (e.g. ``'3.6'``);
it defaults to the current version.
You may explicitly set `platform` (and/or `python`) to ``None`` if you
).match
-class EntryPoint(object):
+class EntryPoint:
"""Object representing an advertised importable object"""
def __init__(self, name, module_name, attrs=(), extras=(), dist=None):
return safe_version(value.strip()) or None
-class Distribution(object):
+class Distribution:
"""Wrap an actual or potential sys.path entry w/metadata"""
PKG_INFO = 'PKG-INFO'
# on later Python versions to cause relative imports
# in the vendor package to resolve the same modules
# as those going through this importer.
- if sys.version_info > (3, 3):
+ if prefix and sys.version_info > (3, 3):
del sys.modules[extant]
return mod
except ImportError:
# and exists_ok considerations are disentangled.
# See https://github.com/pypa/setuptools/pull/1083#issuecomment-315168663
needs_makedirs = (
- sys.version_info < (3, 2, 5) or
- (3, 3) <= sys.version_info < (3, 3, 6) or
+ sys.version_info.major == 2 or
(3, 4) <= sys.version_info < (3, 4, 1)
)
makedirs = _makedirs_31 if needs_makedirs else os.makedirs
import distutils.command.install_egg_info
from pkg_resources.extern.six.moves import map
+from pkg_resources.extern.six import text_type, string_types
import pytest
import pkg_resources
-try:
- unicode
-except NameError:
- unicode = str
+__metaclass__ = type
def timestamp(dt):
return time.mktime(dt.timetuple())
-class EggRemover(unicode):
+class EggRemover(text_type):
def __call__(self):
if self in sys.path:
sys.path.remove(self)
os.remove(self)
-class TestZipProvider(object):
+class TestZipProvider:
finalizers = []
ref_time = datetime.datetime(2013, 5, 12, 13, 25, 0)
manager.cleanup_resources()
-class TestResourceManager(object):
+class TestResourceManager:
def test_get_cache_path(self):
mgr = pkg_resources.ResourceManager()
path = mgr.get_cache_path('foo')
type_ = str(type(path))
message = "Unexpected type from get_cache_path: " + type_
- assert isinstance(path, (unicode, str)), message
+ assert isinstance(path, string_types), message
class TestIndependence:
subprocess.check_call(cmd)
-class TestDeepVersionLookupDistutils(object):
+class TestDeepVersionLookupDistutils:
@pytest.fixture
def env(self, tmpdir):
"""
assert (
Requirement.parse("name==1.1;python_version=='2.7'")
!=
- Requirement.parse("name==1.1;python_version=='3.3'")
+ Requirement.parse("name==1.1;python_version=='3.6'")
)
assert (
Requirement.parse("name==1.0;python_version=='2.7'")
Requirement.parse("name==1.2;python_version=='2.7'")
)
assert (
- Requirement.parse("name[foo]==1.0;python_version=='3.3'")
+ Requirement.parse("name[foo]==1.0;python_version=='3.6'")
!=
- Requirement.parse("name[foo,bar]==1.0;python_version=='3.3'")
+ Requirement.parse("name[foo,bar]==1.0;python_version=='3.6'")
)
def test_local_version(self):
from .test_resources import Metadata
+__metaclass__ = type
+
def strip_comments(s):
return '\n'.join(
requires=['foo>=3.0', 'baz; extra=="feature"']
'''
s = s.strip()
- for spec in re.split('\n(?=[^\s])', s):
+ for spec in re.split(r'\n(?=[^\s])', s):
if not spec:
continue
fields = spec.split('\n', 1)
yield dist
-class FakeInstaller(object):
+class FakeInstaller:
def __init__(self, installable_dists):
self._installable_dists = installable_dists
):
idlist.append(id_)
expected = strip_comments(expected.strip())
- if re.match('\w+$', expected):
+ if re.match(r'\w+$', expected):
expected = getattr(pkg_resources, expected)
assert issubclass(expected, Exception)
else:
+[build-system]
+requires = ["wheel"]
+
[tool.towncrier]
package = "setuptools"
package_dir = "setuptools"
[pytest]
-addopts=--doctest-modules --ignore release.py --ignore setuptools/lib2to3_ex.py --ignore tests/manual_test.py --ignore tests/test_pypi.py --ignore tests/shlib_test --doctest-glob=pkg_resources/api_tests.txt --ignore scripts/upload-old-releases-as-zip.py --ignore pavement.py --ignore setuptools/tests/mod_with_constant.py -rsxX
+addopts=--doctest-modules --doctest-glob=pkg_resources/api_tests.txt -rsxX
norecursedirs=dist build *.egg setuptools/extern pkg_resources/extern .*
flake8-ignore =
setuptools/site-patch.py F821
setuptools/py*compat.py F811
+doctest_optionflags=ELLIPSIS ALLOW_UNICODE
[bumpversion]
-current_version = 39.2.0
+current_version = 40.0.0
commit = True
tag = True
setup_params = dict(
name="setuptools",
- version="39.2.0",
+ version="40.0.0",
description=(
"Easily download, build, install, upgrade, and uninstall "
"Python packages"
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
- Programming Language :: Python :: 3.3
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Topic :: System :: Systems Administration
Topic :: Utilities
""").strip().splitlines(),
- python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*',
+ python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*',
extras_require={
"ssl:sys_platform=='win32'": "wincertstore==0.2",
"certs": "certifi==2016.9.26",
from setuptools.depends import Require
from . import monkey
+__metaclass__ = type
+
__all__ = [
'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require',
'find_packages',
lib2to3_fixer_packages = ['lib2to3.fixes']
-class PackageFinder(object):
+class PackageFinder:
"""
Generate a list of all Python packages found within a directory
"""
return True # Extension module
pkg = base[len(egg_dir) + 1:].replace(os.sep, '.')
module = pkg + (pkg and '.' or '') + os.path.splitext(name)[0]
- if sys.version_info < (3, 3):
+ if sys.version_info.major == 2:
skip = 8 # skip magic & date
elif sys.version_info < (3, 7):
skip = 12 # skip magic & date & file size
and get_abi3_suffix()
)
if use_abi3:
- so_ext = _get_config_var_837('EXT_SUFFIX')
+ so_ext = get_config_var('EXT_SUFFIX')
filename = filename[:-len(so_ext)]
filename = filename + get_abi3_suffix()
if isinstance(ext, Library):
self.create_static_lib(
objects, basename, output_dir, debug, target_lang
)
-
-
-def _get_config_var_837(name):
- """
- In https://github.com/pypa/setuptools/pull/837, we discovered
- Python 3.3.0 exposes the extension suffix under the name 'SO'.
- """
- if sys.version_info < (3, 3, 1):
- name = 'SO'
- return get_config_var(name)
from setuptools import namespaces
import setuptools
+__metaclass__ = type
+
class develop(namespaces.DevelopInstaller, easy_install):
"""Set up package for development"""
return easy_install.install_wrapper_scripts(self, dist)
-class VersionlessRequirement(object):
+class VersionlessRequirement:
"""
Adapt a pkg_resources.Distribution to simply return the project
name as the 'requirement' so that scripts will work across
import shlex
import io
+from sysconfig import get_config_vars, get_path
+
from setuptools.extern import six
from setuptools.extern.six.moves import configparser, map
from setuptools import Command
from setuptools.sandbox import run_setup
-from setuptools.py31compat import get_path, get_config_vars
from setuptools.py27compat import rmtree_safe
from setuptools.command import setopt
from setuptools.archive_util import unpack_archive
)
import pkg_resources.py31compat
+__metaclass__ = type
+
# Turn on PEP440Warnings
warnings.filterwarnings("default", category=pkg_resources.PEP440Warning)
split_args = dict(posix=False)
-class ScriptWriter(object):
+class ScriptWriter:
"""
Encapsulates behavior around writing entry point scripts for console and
gui apps.
add_activation_listener, require, EntryPoint)
from setuptools import Command
+__metaclass__ = type
+
class ScanningLoader(TestLoader):
# adapted from jaraco.classes.properties:NonDataProperty
-class NonDataProperty(object):
+class NonDataProperty:
def __init__(self, fget):
self.fget = fget
from setuptools.extern.six import string_types
+__metaclass__ = type
+
+
def read_configuration(
filepath, find_others=False, ignore_option_errors=False):
"""Read given configuration file and returns options from it as a dict.
return meta, options
-class ConfigHandler(object):
+class ConfigHandler:
"""Handles metadata supplied in configuration files."""
section_prefix = None
# on later Python versions to cause relative imports
# in the vendor package to resolve the same modules
# as those going through this importer.
- if sys.version_info > (3, 3):
+ if sys.version_info.major >= 3:
del sys.modules[extant]
return mod
except ImportError:
needs_warehouse = (
sys.version_info < (2, 7, 13)
or
- (3, 0) < sys.version_info < (3, 3, 7)
- or
(3, 4) < sys.version_info < (3, 4, 6)
or
(3, 5) < sys.version_info <= (3, 5, 3)
from setuptools.py33compat import unescape
from setuptools.wheel import Wheel
+__metaclass__ = type
+
EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.+!]+)$')
-HREF = re.compile("""href\\s*=\\s*['"]?([^'"> ]+)""", re.I)
-# this is here to fix emacs' cruddy broken syntax highlighting
+HREF = re.compile(r"""href\s*=\s*['"]?([^'"> ]+)""", re.I)
PYPI_MD5 = re.compile(
- '<a href="([^"#]+)">([^<]+)</a>\n\\s+\\(<a (?:title="MD5 hash"\n\\s+)'
- 'href="[^?]+\\?:action=show_md5&digest=([0-9a-f]{32})">md5</a>\\)'
+ r'<a href="([^"#]+)">([^<]+)</a>\n\s+\(<a (?:title="MD5 hash"\n\s+)'
+ r'href="[^?]+\?:action=show_md5&digest=([0-9a-f]{32})">md5</a>\)'
)
URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):', re.I).match
EXTENSIONS = ".tar.gz .tar.bz2 .tar .zip .tgz".split()
yield urllib.parse.urljoin(url, htmldecode(match.group(1)))
-class ContentChecker(object):
+class ContentChecker:
"""
A null content checker that defines the interface for checking content
"""
def decode_entity(match):
- what = match.group(1)
+ what = match.group(0)
return unescape(what)
def htmldecode(text):
- """Decode HTML entities in the given text."""
+ """
+ Decode HTML entities in the given text.
+
+ >>> htmldecode(
+ ... 'https://../package_name-0.1.2.tar.gz'
+ ... '?tokena=A&tokenb=B">package_name-0.1.2.tar.gz')
+ 'https://../package_name-0.1.2.tar.gz?tokena=A&tokenb=B">package_name-0.1.2.tar.gz'
+ """
return entity_sub(decode_entity, text)
return encoded.replace('\n', '')
-class Credential(object):
+class Credential:
"""
A username/password pair. Use like a namedtuple.
"""
lambda: sys.maxunicode == 0x10ffff,
expected=4,
warn=(impl == 'cp' and
- sys.version_info < (3, 3))) \
- and sys.version_info < (3, 3):
+ sys.version_info.major == 2)) \
+ and sys.version_info.major == 2:
u = 'u'
abi = '%s%s%s%s%s' % (impl, get_impl_ver(), d, m, u)
elif soabi and soabi.startswith('cpython-'):
-__all__ = ['get_config_vars', 'get_path']
+__all__ = []
-try:
- # Python 2.7 or >=3.2
- from sysconfig import get_config_vars, get_path
-except ImportError:
- from distutils.sysconfig import get_config_vars, get_python_lib
-
- def get_path(name):
- if name not in ('platlib', 'purelib'):
- raise ValueError("Name must be purelib or platlib")
- return get_python_lib(name == 'platlib')
+__metaclass__ = type
try:
import shutil
import tempfile
- class TemporaryDirectory(object):
+ class TemporaryDirectory:
"""
Very simple temporary directory context manager.
Will try to delete afterward, but will also ignore OS and similar
from setuptools.extern import six
from setuptools.extern.six.moves import html_parser
+__metaclass__ = type
OpArg = collections.namedtuple('OpArg', 'opcode arg')
-class Bytecode_compat(object):
+class Bytecode_compat:
def __init__(self, code):
self.code = code
__requires__ = %(spec)r
__import__('pkg_resources').require(%(spec)r)
__file__ = %(dev_path)r
-exec(compile(open(__file__).read(), __file__, 'exec'))
+with open(__file__) as f:
+ exec(compile(f.read(), __file__, 'exec'))
break
else:
try:
- import imp # Avoid import loop in Python >= 3.3
+ import imp # Avoid import loop in Python 3
stream, path, descr = imp.find_module('site', [item])
except ImportError:
continue
from .files import build_files
from .textwrap import DALS
+__metaclass__ = type
+
futures = pytest.importorskip('concurrent.futures')
importlib = pytest.importorskip('importlib')
-class BuildBackendBase(object):
+class BuildBackendBase:
def __init__(self, cwd=None, env={}, backend_name='setuptools.build_meta'):
self.cwd = cwd
self.env = env
from . import contexts
from .textwrap import DALS
+__metaclass__ = type
-class FakeDist(object):
+
+class FakeDist:
def get_entry_map(self, group):
if group != 'console_scripts':
return {}
)
class TestScriptHeader:
non_ascii_exe = '/Users/José/bin/python'
- exe_with_spaces = r'C:\Program Files\Python33\python.exe'
+ exe_with_spaces = r'C:\Program Files\Python36\python.exe'
def test_get_script_header(self):
expected = '#!%s\n' % ei.nt_quote_arg(os.path.normpath(sys.executable))
from .textwrap import DALS
from . import contexts
+__metaclass__ = type
+
class Environment(str):
pass
-class TestEggInfo(object):
+class TestEggInfo:
setup_script = DALS("""
from setuptools import setup
)
invalid_marker = "<=>++"
- class RequiresTestHelper(object):
+ class RequiresTestHelper:
@staticmethod
def parametrize(*test_list, **format_dict):
def test_doesnt_provides_extra(self, tmpdir_cwd, env):
self._setup_script_with_requires(
- '''install_requires=["spam ; python_version<'3.3'"]''')
+ '''install_requires=["spam ; python_version<'3.6'"]''')
environ = os.environ.copy().update(
HOME=env.paths['home'],
)
import warnings
+import pytest
+
from setuptools.glibc import check_glibc_version
+__metaclass__ = type
+
+
+@pytest.fixture(params=[
+ "2.20",
+ # used by "linaro glibc", see gh-3588
+ "2.20-2014.11",
+ # weird possibilities that I just made up
+ "2.20+dev",
+ "2.20-custom",
+ "2.20.1",
+ ])
+def two_twenty(request):
+ return request.param
+
+
+@pytest.fixture(params=["asdf", "", "foo.bar"])
+def bad_string(request):
+ return request.param
-class TestGlibc(object):
- def test_manylinux1_check_glibc_version(self):
+
+class TestGlibc:
+ def test_manylinux1_check_glibc_version(self, two_twenty):
"""
Test that the check_glibc_version function is robust against weird
glibc version strings.
"""
- for two_twenty in ["2.20",
- # used by "linaro glibc", see gh-3588
- "2.20-2014.11",
- # weird possibilities that I just made up
- "2.20+dev",
- "2.20-custom",
- "2.20.1",
- ]:
- assert check_glibc_version(two_twenty, 2, 15)
- assert check_glibc_version(two_twenty, 2, 20)
- assert not check_glibc_version(two_twenty, 2, 21)
- assert not check_glibc_version(two_twenty, 3, 15)
- assert not check_glibc_version(two_twenty, 1, 15)
-
- # For strings that we just can't parse at all, we should warn and
- # return false
- for bad_string in ["asdf", "", "foo.bar"]:
- with warnings.catch_warnings(record=True) as ws:
- warnings.filterwarnings("always")
- assert not check_glibc_version(bad_string, 2, 5)
- for w in ws:
- if "Expected glibc version with" in str(w.message):
- break
- else:
- # Didn't find the warning we were expecting
- assert False
+ assert check_glibc_version(two_twenty, 2, 15)
+ assert check_glibc_version(two_twenty, 2, 20)
+ assert not check_glibc_version(two_twenty, 2, 21)
+ assert not check_glibc_version(two_twenty, 3, 15)
+ assert not check_glibc_version(two_twenty, 1, 15)
+
+ def test_bad_versions(self, bad_string):
+ """
+ For unparseable strings, warn and return False
+ """
+ with warnings.catch_warnings(record=True) as ws:
+ warnings.filterwarnings("always")
+ assert not check_glibc_version(bad_string, 2, 5)
+ for w in ws:
+ if "Expected glibc version with" in str(w.message):
+ break
+ else:
+ # Didn't find the warning we were expecting
+ assert False
)
unix_exe = '/usr/dummy-test-path/local/bin/python'
unix_spaces_exe = '/usr/bin/env dummy-test-python'
- win32_exe = 'C:\\Dummy Test Path\\Program Files\\Python 3.3\\python.exe'
+ win32_exe = 'C:\\Dummy Test Path\\Program Files\\Python 3.6\\python.exe'
def _run_install_scripts(self, install_dir, executable=None):
dist = Distribution(self.settings)
import pytest
+__metaclass__ = type
+
py3_only = pytest.mark.xfail(six.PY2, reason="Test runs on Python 3 only")
assert not translate_pattern(pattern).match(target)
-class TempDirTestCase(object):
+class TempDirTestCase:
def setup_method(self, method):
self.temp_dir = tempfile.mkdtemp()
self.old_cwd = os.getcwd()
from setuptools import pep425tags
+__metaclass__ = type
-class TestPEP425Tags(object):
+
+class TestPEP425Tags:
def mock_get_config_var(self, **kwd):
"""
self.abi_tag_unicode('dm', {'Py_DEBUG': True, 'WITH_PYMALLOC': True})
-class TestManylinux1Tags(object):
+class TestManylinux1Tags:
@patch('setuptools.pep425tags.get_platform', lambda: 'linux_x86_64')
@patch('setuptools.glibc.have_compatible_glibc',
from .files import build_files
from .textwrap import DALS
+__metaclass__ = type
+
WHEEL_INFO_TESTS = (
('invalid.whl', ValueError),
assert requires_txt == dist.get_metadata('requires.txt').lstrip()
-class Record(object):
+class Record:
def __init__(self, id, **kwargs):
self._id = id
-'''Wheels support.'''
+"""Wheels support."""
from distutils.util import get_platform
import email
from setuptools.command.egg_info import write_requirements
+__metaclass__ = type
+
+
WHEEL_NAME = re.compile(
r"""^(?P<project_name>.+?)-(?P<version>\d.*?)
((-(?P<build>\d.*?))?-(?P<py_version>.+?)-(?P<abi>.+?)-(?P<platform>.+?)
)\.whl$""",
-re.VERBOSE).match
+ re.VERBOSE).match
NAMESPACE_PACKAGE_INIT = '''\
try:
os.rmdir(dirpath)
-class Wheel(object):
+class Wheel:
def __init__(self, filename):
match = WHEEL_NAME(os.path.basename(filename))
def tags(self):
'''List tags (py_version, abi, platform) supported by this wheel.'''
- return itertools.product(self.py_version.split('.'),
- self.abi.split('.'),
- self.platform.split('.'))
+ return itertools.product(
+ self.py_version.split('.'),
+ self.abi.split('.'),
+ self.platform.split('.'),
+ )
def is_compatible(self):
'''Is the wheel is compatible with the current platform?'''
def install_as_egg(self, destination_eggdir):
'''Install wheel as an egg directory.'''
with zipfile.ZipFile(self.filename) as zf:
- dist_basename = '%s-%s' % (self.project_name, self.version)
- dist_info = self.get_dist_info(zf)
- dist_data = '%s.data' % dist_basename
- def get_metadata(name):
- with zf.open(posixpath.join(dist_info, name)) as fp:
- value = fp.read().decode('utf-8') if PY3 else fp.read()
- return email.parser.Parser().parsestr(value)
- wheel_metadata = get_metadata('WHEEL')
- dist_metadata = get_metadata('METADATA')
- # Check wheel format version is supported.
- wheel_version = parse_version(wheel_metadata.get('Wheel-Version'))
- if not parse_version('1.0') <= wheel_version < parse_version('2.0dev0'):
- raise ValueError('unsupported wheel format version: %s' % wheel_version)
- # Extract to target directory.
- os.mkdir(destination_eggdir)
- zf.extractall(destination_eggdir)
- # Convert metadata.
- dist_info = os.path.join(destination_eggdir, dist_info)
- dist = Distribution.from_location(
- destination_eggdir, dist_info,
- metadata=PathMetadata(destination_eggdir, dist_info)
+ self._install_as_egg(destination_eggdir, zf)
+
+ def _install_as_egg(self, destination_eggdir, zf):
+ dist_basename = '%s-%s' % (self.project_name, self.version)
+ dist_info = self.get_dist_info(zf)
+ dist_data = '%s.data' % dist_basename
+ egg_info = os.path.join(destination_eggdir, 'EGG-INFO')
+
+ self._convert_metadata(zf, destination_eggdir, dist_info, egg_info)
+ self._move_data_entries(destination_eggdir, dist_data)
+ self._fix_namespace_packages(egg_info, destination_eggdir)
+
+ @staticmethod
+ def _convert_metadata(zf, destination_eggdir, dist_info, egg_info):
+ def get_metadata(name):
+ with zf.open(posixpath.join(dist_info, name)) as fp:
+ value = fp.read().decode('utf-8') if PY3 else fp.read()
+ return email.parser.Parser().parsestr(value)
+
+ wheel_metadata = get_metadata('WHEEL')
+ # Check wheel format version is supported.
+ wheel_version = parse_version(wheel_metadata.get('Wheel-Version'))
+ wheel_v1 = (
+ parse_version('1.0') <= wheel_version < parse_version('2.0dev0')
+ )
+ if not wheel_v1:
+ raise ValueError(
+ 'unsupported wheel format version: %s' % wheel_version)
+ # Extract to target directory.
+ os.mkdir(destination_eggdir)
+ zf.extractall(destination_eggdir)
+ # Convert metadata.
+ dist_info = os.path.join(destination_eggdir, dist_info)
+ dist = Distribution.from_location(
+ destination_eggdir, dist_info,
+ metadata=PathMetadata(destination_eggdir, dist_info),
+ )
+
+ # Note: Evaluate and strip markers now,
+ # as it's difficult to convert back from the syntax:
+ # foobar; "linux" in sys_platform and extra == 'test'
+ def raw_req(req):
+ req.marker = None
+ return str(req)
+ install_requires = list(sorted(map(raw_req, dist.requires())))
+ extras_require = {
+ extra: sorted(
+ req
+ for req in map(raw_req, dist.requires((extra,)))
+ if req not in install_requires
)
- # Note: we need to evaluate and strip markers now,
- # as we can't easily convert back from the syntax:
- # foobar; "linux" in sys_platform and extra == 'test'
- def raw_req(req):
- req.marker = None
- return str(req)
- install_requires = list(sorted(map(raw_req, dist.requires())))
- extras_require = {
- extra: list(sorted(
- req
- for req in map(raw_req, dist.requires((extra,)))
- if req not in install_requires
- ))
- for extra in dist.extras
- }
- egg_info = os.path.join(destination_eggdir, 'EGG-INFO')
- os.rename(dist_info, egg_info)
- os.rename(os.path.join(egg_info, 'METADATA'),
- os.path.join(egg_info, 'PKG-INFO'))
- setup_dist = SetuptoolsDistribution(attrs=dict(
+ for extra in dist.extras
+ }
+ os.rename(dist_info, egg_info)
+ os.rename(
+ os.path.join(egg_info, 'METADATA'),
+ os.path.join(egg_info, 'PKG-INFO'),
+ )
+ setup_dist = SetuptoolsDistribution(
+ attrs=dict(
install_requires=install_requires,
extras_require=extras_require,
- ))
- write_requirements(setup_dist.get_command_obj('egg_info'),
- None, os.path.join(egg_info, 'requires.txt'))
- # Move data entries to their correct location.
- dist_data = os.path.join(destination_eggdir, dist_data)
- dist_data_scripts = os.path.join(dist_data, 'scripts')
- if os.path.exists(dist_data_scripts):
- egg_info_scripts = os.path.join(destination_eggdir,
- 'EGG-INFO', 'scripts')
- os.mkdir(egg_info_scripts)
- for entry in os.listdir(dist_data_scripts):
- # Remove bytecode, as it's not properly handled
- # during easy_install scripts install phase.
- if entry.endswith('.pyc'):
- os.unlink(os.path.join(dist_data_scripts, entry))
- else:
- os.rename(os.path.join(dist_data_scripts, entry),
- os.path.join(egg_info_scripts, entry))
- os.rmdir(dist_data_scripts)
- for subdir in filter(os.path.exists, (
- os.path.join(dist_data, d)
- for d in ('data', 'headers', 'purelib', 'platlib')
- )):
- unpack(subdir, destination_eggdir)
- if os.path.exists(dist_data):
- os.rmdir(dist_data)
- # Fix namespace packages.
- namespace_packages = os.path.join(egg_info, 'namespace_packages.txt')
- if os.path.exists(namespace_packages):
- with open(namespace_packages) as fp:
- namespace_packages = fp.read().split()
- for mod in namespace_packages:
- mod_dir = os.path.join(destination_eggdir, *mod.split('.'))
- mod_init = os.path.join(mod_dir, '__init__.py')
- if os.path.exists(mod_dir) and not os.path.exists(mod_init):
- with open(mod_init, 'w') as fp:
- fp.write(NAMESPACE_PACKAGE_INIT)
+ ),
+ )
+ write_requirements(
+ setup_dist.get_command_obj('egg_info'),
+ None,
+ os.path.join(egg_info, 'requires.txt'),
+ )
+
+ @staticmethod
+ def _move_data_entries(destination_eggdir, dist_data):
+ """Move data entries to their correct location."""
+ dist_data = os.path.join(destination_eggdir, dist_data)
+ dist_data_scripts = os.path.join(dist_data, 'scripts')
+ if os.path.exists(dist_data_scripts):
+ egg_info_scripts = os.path.join(
+ destination_eggdir, 'EGG-INFO', 'scripts')
+ os.mkdir(egg_info_scripts)
+ for entry in os.listdir(dist_data_scripts):
+ # Remove bytecode, as it's not properly handled
+ # during easy_install scripts install phase.
+ if entry.endswith('.pyc'):
+ os.unlink(os.path.join(dist_data_scripts, entry))
+ else:
+ os.rename(
+ os.path.join(dist_data_scripts, entry),
+ os.path.join(egg_info_scripts, entry),
+ )
+ os.rmdir(dist_data_scripts)
+ for subdir in filter(os.path.exists, (
+ os.path.join(dist_data, d)
+ for d in ('data', 'headers', 'purelib', 'platlib')
+ )):
+ unpack(subdir, destination_eggdir)
+ if os.path.exists(dist_data):
+ os.rmdir(dist_data)
+
+ @staticmethod
+ def _fix_namespace_packages(egg_info, destination_eggdir):
+ namespace_packages = os.path.join(
+ egg_info, 'namespace_packages.txt')
+ if os.path.exists(namespace_packages):
+ with open(namespace_packages) as fp:
+ namespace_packages = fp.read().split()
+ for mod in namespace_packages:
+ mod_dir = os.path.join(destination_eggdir, *mod.split('.'))
+ mod_init = os.path.join(mod_dir, '__init__.py')
+ if os.path.exists(mod_dir) and not os.path.exists(mod_init):
+ with open(mod_init, 'w') as fp:
+ fp.write(NAMESPACE_PACKAGE_INIT)
assert len(eggs) == 3
assert eggs[1].startswith('setuptools')
del eggs[1]
- assert eggs == ['extensions-0.3-py2.6.egg',
- 'zc.recipe.egg-1.2.2-py2.6.egg']
+ assert eggs == [
+ 'extensions-0.3-py2.6.egg',
+ 'zc.recipe.egg-1.2.2-py2.6.egg',
+ ]
if __name__ == '__main__':
-importlib; python_version<"2.7"
mock
-pytest-flake8<=1.0.0; python_version>="3.3" and python_version<"3.5"
-pytest-flake8; python_version>="2.7" and python_version!="3.3" and python_version!="3.4"
+pytest-flake8; python_version!="3.4"
+pytest-flake8<=1.0.0; python_version=="3.4"
virtualenv>=13.0.0
pytest-virtualenv>=1.2.7
pytest>=3.0.2
wheel
coverage>=4.5.1
pytest-cov>=2.5.1
+paver; python_version>="3.6"
+++ /dev/null
-import os
-import subprocess
-
-import virtualenv
-from setuptools.extern.six.moves import http_client
-from setuptools.extern.six.moves import xmlrpc_client
-
-TOP = 200
-PYPI_HOSTNAME = 'pypi.python.org'
-
-
-def rpc_pypi(method, *args):
- """Call an XML-RPC method on the Pypi server."""
- conn = http_client.HTTPSConnection(PYPI_HOSTNAME)
- headers = {'Content-Type': 'text/xml'}
- payload = xmlrpc_client.dumps(args, method)
-
- conn.request("POST", "/pypi", payload, headers)
- response = conn.getresponse()
- if response.status == 200:
- result = xmlrpc_client.loads(response.read())[0][0]
- return result
- else:
- raise RuntimeError("Unable to download the list of top "
- "packages from Pypi.")
-
-
-def get_top_packages(limit):
- """Collect the name of the top packages on Pypi."""
- packages = rpc_pypi('top_packages')
- return packages[:limit]
-
-
-def _package_install(package_name, tmp_dir=None, local_setuptools=True):
- """Try to install a package and return the exit status.
-
- This function creates a virtual environment, install setuptools using pip
- and then install the required package. If local_setuptools is True, it
- will install the local version of setuptools.
- """
- package_dir = os.path.join(tmp_dir, "test_%s" % package_name)
- if not local_setuptools:
- package_dir = package_dir + "_baseline"
-
- virtualenv.create_environment(package_dir)
-
- pip_path = os.path.join(package_dir, "bin", "pip")
- if local_setuptools:
- subprocess.check_call([pip_path, "install", "."])
- returncode = subprocess.call([pip_path, "install", package_name])
- return returncode
-
-
-def test_package_install(package_name, tmpdir):
- """Test to verify the outcome of installing a package.
-
- This test compare that the return code when installing a package is the
- same as with the current stable version of setuptools.
- """
- new_exit_status = _package_install(package_name, tmp_dir=str(tmpdir))
- if new_exit_status:
- print("Installation failed, testing against stable setuptools",
- package_name)
- old_exit_status = _package_install(package_name, tmp_dir=str(tmpdir),
- local_setuptools=False)
- assert new_exit_status == old_exit_status
-
-
-def pytest_generate_tests(metafunc):
- """Generator function for test_package_install.
-
- This function will generate calls to test_package_install. If a package
- list has been specified on the command line, it will be used. Otherwise,
- Pypi will be queried to get the current list of top packages.
- """
- if "package_name" in metafunc.fixturenames:
- if not metafunc.config.option.package_name:
- packages = get_top_packages(TOP)
- packages = [name for name, downloads in packages]
- else:
- packages = metafunc.config.option.package_name
- metafunc.parametrize("package_name", packages)
#
# To run Tox against all supported Python interpreters, you can set:
#
-# export TOXENV='py27,py3{3,4,5,6},pypy,pypy3'
+# export TOXENV='py27,py3{4,5,6},pypy,pypy3'
[tox]
envlist=python