install:
# need tox to get started
-- pip install tox
+- pip install tox 'tox-venv; python_version!="3.3"'
# Output the env, to verify behavior
- env
+v38.6.1
+-------
+
+* #1292: Avoid generating ``Provides-Extra`` in metadata when
+ no extra is present (but environment markers are).
+
+v38.6.0
+-------
+
+* #1286: Add support for Metadata 2.1 (PEP 566).
+
+v38.5.2
+-------
+
+* #1285: Fixed RuntimeError in pkg_resources.parse_requirements
+ on Python 3.7 (stemming from PEP 479).
+
+v38.5.1
+-------
+
+* #1271: Revert to Cython legacy ``build_ext`` behavior for
+ compatibility.
+
+v38.5.0
+-------
+
+* #1229: Expand imports in ``build_ext`` to refine detection of
+ Cython availability.
+
+* #1270: When Cython is available, ``build_ext`` now uses the
+ new_build_ext.
+
+v38.4.1
+-------
+
+* #1257: In bdist_egg.scan_module, fix ValueError on Python 3.7.
+
+v38.4.0
+-------
+
+* #1231: Removed warning when PYTHONDONTWRITEBYTECODE is enabled.
+
+v38.3.0
+-------
+
+* #1210: Add support for PEP 345 Project-URL metadata.
+* #1207: Add support for ``long_description_type`` to setup.cfg
+ declarative config as intended and documented.
+
v38.2.5
-------
build: off
+cache:
+ - '%LOCALAPPDATA%\pip\Cache'
+
test_script:
- "python bootstrap.py"
- "python -m pip install tox"
- "tox"
+
+version: '{build}'
The Continuous Integration tests that validate every release are run
from this repository.
-For posterity, the old `Bitbucket mirror
-<https://bitbucket.org/pypa/setuptools>`_ is available.
-
-------
Testing
-------
sorted into ascending version order, and used to establish what ranges of
versions are acceptable. Adjacent redundant conditions are effectively
consolidated (e.g. ``">1, >2"`` produces the same results as ``">2"``, and
- ``"<2,<3"`` produces the same results as``"<2"``). ``"!="`` versions are
+ ``"<2,<3"`` produces the same results as ``"<2"``). ``"!="`` versions are
excised from the ranges they fall within. The version being tested for
acceptability is then checked for membership in the resulting ranges.
license="PSF",
keywords="hello world example examples",
url="http://example.com/HelloWorld/", # project home page, if any
+ project_urls={
+ "Bug Tracker": "https://bugs.example.com/HelloWorld/",
+ "Documentation": "https://docs.example.com/HelloWorld/",
+ "Source Code": "https://code.example.com/HelloWorld/",
+ }
# could also include long_description, download_url, classifiers, etc.
)
A list of modules to search for additional fixers to be used during
the 2to3 conversion. See :doc:`python3` for more details.
+``project_urls``
+ An arbitrary map of URL names to hyperlinks, allowing more extensible
+ documentation of where various resources can be found than the simple
+ ``url`` and ``download_url`` options provide.
+
Using ``find_packages()``
-------------------------
``setup()`` arguments, but that's just a normal distutils thing.)
Anyway, ``find_packages()`` walks the target directory, filtering by inclusion
-patterns, and finds Python packages (any directory). On Python 3.2 and
-earlier, packages are only recognized if they include an ``__init__.py`` file.
-Finally, exclusion patterns are applied to remove matching packages.
+patterns, and finds Python packages (any directory). Packages are only
+recognized if they include an ``__init__.py`` file. Finally, exclusion
+patterns are applied to remove matching packages.
Inclusion and exclusion patterns are package names, optionally including
wildcards. For
Version specifiers for a given project are internally sorted into ascending
version order, and used to establish what ranges of versions are acceptable.
Adjacent redundant conditions are also consolidated (e.g. ``">1, >2"`` becomes
-``">1"``, and ``"<2,<3"`` becomes ``"<3"``). ``"!="`` versions are excised from
+``">2"``, and ``"<2,<3"`` becomes ``"<2"``). ``"!="`` versions are excised from
the ranges they fall within. A project's version is then checked for
membership in the resulting ranges. (Note that providing conflicting conditions
for the same version (e.g. "<2,>=2" or "==2,!=2") is meaningless and may
the path separator, even if you are on Windows. Setuptools automatically
converts slashes to appropriate platform-specific separators at build time.
+If datafiles are contained in a subdirectory of a package that isn't a package
+itself (no ``__init__.py``), then the subdirectory names (or ``*``) are required
+in the ``package_data`` argument (as shown above with ``'data/*.dat'``).
+
+When building an ``sdist``, the datafiles are also drawn from the
+``package_name.egg-info/SOURCES.txt`` file, so make sure that this is removed if
+the ``setup.py`` ``package_data`` list is updated before calling ``setup.py``.
+
(Note: although the ``package_data`` argument was previously only available in
``setuptools``, it was also added to the Python ``distutils`` package as of
Python 2.4; there is `some documentation for the feature`__ available on the
Accept all data files and directories matched by ``MANIFEST.in``.
``package_data``
- Specify additional patterns to match files and directories that may or may
+ Specify additional patterns to match files that may or may
not be matched by ``MANIFEST.in`` or found in source control.
``exclude_package_data``
version attr:, str
url home-page str
download_url download-url str
+project_urls dict
author str
author_email author-email str
maintainer str
# satisfy the linters.
require = None
working_set = None
+add_activation_listener = None
+resources_stream = None
+cleanup_resources = None
+resource_dir = None
+resource_stream = None
+set_extraction_path = None
+resource_isdir = None
+resource_string = None
+iter_entry_points = None
+resource_listdir = None
+resource_filename = None
+resource_exists = None
+_distribution_finders = None
+_namespace_handlers = None
+_namespace_packages = None
class PEP440Warning(RuntimeWarning):
requirements specified when this environment was created, or False
is returned.
"""
- return (self.python is None or dist.py_version is None
- or dist.py_version == self.python) \
- and compatible_platforms(dist.platform, self.platform)
+ py_compat = (
+ self.python is None
+ or dist.py_version is None
+ or dist.py_version == self.python
+ )
+ return py_compat and compatible_platforms(dist.platform, self.platform)
def remove(self, dist):
"""Remove `dist` from the environment"""
target_path = os.path.join(extract_path, archive_name + '-tmp', *names)
try:
_bypass_ensure_directory(target_path)
- except:
+ except Exception:
self.extraction_error()
self._warn_unsafe_extraction_path(extract_path)
class EmptyProvider(NullProvider):
"""Provider that returns nothing for all requests"""
- _isdir = _has = lambda self, path: False
- _get = lambda self, path: ''
- _listdir = lambda self, path: []
module_path = None
+ _isdir = _has = lambda self, path: False
+
+ def _get(self, path):
+ return ''
+
+ def _listdir(self, path):
+ return []
+
def __init__(self):
pass
Given an iterable of lines from a Metadata file, return
the value of the Version field, if present, or None otherwise.
"""
- is_version_line = lambda line: line.lower().startswith('version:')
+ def is_version_line(line):
+ return line.lower().startswith('version:')
version_lines = filter(is_version_line, lines)
line = next(iter(version_lines), '')
_, _, value = line.partition(':')
@property
def _dep_map(self):
+ """
+ A map of extra to its list of (direct) requirements
+ for this distribution, including the null extra.
+ """
try:
return self.__dep_map
except AttributeError:
- dm = self.__dep_map = {None: []}
- for name in 'requires.txt', 'depends.txt':
- for extra, reqs in split_sections(self._get_metadata(name)):
- if extra:
- if ':' in extra:
- extra, marker = extra.split(':', 1)
- if invalid_marker(marker):
- # XXX warn
- reqs = []
- elif not evaluate_marker(marker):
- reqs = []
- extra = safe_extra(extra) or None
- dm.setdefault(extra, []).extend(parse_requirements(reqs))
- return dm
+ self.__dep_map = self._filter_extras(self._build_dep_map())
+ return self.__dep_map
+
+ @staticmethod
+ def _filter_extras(dm):
+ """
+ Given a mapping of extras to dependencies, strip off
+ environment markers and filter out any dependencies
+ not matching the markers.
+ """
+ for extra in list(filter(None, dm)):
+ new_extra = extra
+ reqs = dm.pop(extra)
+ new_extra, _, marker = extra.partition(':')
+ fails_marker = marker and (
+ invalid_marker(marker)
+ or not evaluate_marker(marker)
+ )
+ if fails_marker:
+ reqs = []
+ new_extra = safe_extra(new_extra) or None
+
+ dm.setdefault(new_extra, []).extend(reqs)
+ return dm
+
+ def _build_dep_map(self):
+ dm = {}
+ for name in 'requires.txt', 'depends.txt':
+ for extra, reqs in split_sections(self._get_metadata(name)):
+ dm.setdefault(extra, []).extend(parse_requirements(reqs))
+ return dm
def requires(self, extras=()):
"""List of Requirements needed for this distro if `extras` are used"""
# If there is a line continuation, drop it, and append the next line.
if line.endswith('\\'):
line = line[:-2].strip()
- line += next(lines)
+ try:
+ line += next(lines)
+ except StopIteration:
+ return
yield Requirement(line)
)
""".lstrip()
+
class TestFindDistributions:
@pytest.fixture
lines = (
'import pkg_resources',
'import sys',
- 'assert "setuptools" not in sys.modules, '
- '"setuptools was imported"',
+ (
+ 'assert "setuptools" not in sys.modules, '
+ '"setuptools was imported"'
+ ),
)
cmd = [sys.executable, '-c', '; '.join(lines)]
subprocess.check_call(cmd)
from pkg_resources.extern import packaging
import pkg_resources
-from pkg_resources import (parse_requirements, VersionConflict, parse_version,
+from pkg_resources import (
+ parse_requirements, VersionConflict, parse_version,
Distribution, EntryPoint, Requirement, safe_version, safe_name,
WorkingSet)
assert list(ad) == ['foopkg']
# Distributions sort by version
- assert [dist.version for dist in ad['FooPkg']] == ['1.4', '1.3-1', '1.2']
+ expected = ['1.4', '1.3-1', '1.2']
+ assert [dist.version for dist in ad['FooPkg']] == expected
# Removing a distribution leaves sequence alone
ad.remove(ad['FooPkg'][1])
def testDistroBasics(self):
d = Distribution(
"/some/path",
- project_name="FooPkg", version="1.3-1", py_version="2.4", platform="win32"
+ project_name="FooPkg",
+ version="1.3-1",
+ py_version="2.4",
+ platform="win32",
)
self.checkFooPkg(d)
def testDistroMetadata(self):
d = Distribution(
- "/some/path", project_name="FooPkg", py_version="2.4", platform="win32",
+ "/some/path", project_name="FooPkg",
+ py_version="2.4", platform="win32",
metadata=Metadata(
('PKG-INFO', "Metadata-Version: 1.0\nVersion: 1.3-1\n")
- )
+ ),
)
self.checkFooPkg(d)
ad.add(Baz)
# Activation list now includes resolved dependency
- assert list(ws.resolve(parse_requirements("Foo[bar]"), ad)) == [Foo, Baz]
+ assert (
+ list(ws.resolve(parse_requirements("Foo[bar]"), ad))
+ == [Foo, Baz]
+ )
# Requests for conflicting versions produce VersionConflict
with pytest.raises(VersionConflict) as vc:
ws.resolve(parse_requirements("Foo==1.2\nFoo!=1.2"), ad)
submap_expect = dict(
feature1=EntryPoint('feature1', 'somemodule', ['somefunction']),
- feature2=EntryPoint('feature2', 'another.module', ['SomeClass'], ['extra1', 'extra2']),
+ feature2=EntryPoint(
+ 'feature2', 'another.module', ['SomeClass'], ['extra1', 'extra2']),
feature3=EntryPoint('feature3', 'this.module', extras=['something'])
)
submap_str = """
Requirement.parse('setuptools').project_name == 'setuptools')
# setuptools 0.7 and higher means setuptools.
assert (
- Requirement.parse('setuptools == 0.7').project_name == 'setuptools')
+ Requirement.parse('setuptools == 0.7').project_name
+ == 'setuptools'
+ )
assert (
- Requirement.parse('setuptools == 0.7a1').project_name == 'setuptools')
+ Requirement.parse('setuptools == 0.7a1').project_name
+ == 'setuptools'
+ )
assert (
- Requirement.parse('setuptools >= 0.7').project_name == 'setuptools')
+ Requirement.parse('setuptools >= 0.7').project_name
+ == 'setuptools'
+ )
class TestParsing:
"""
assert (
list(pkg_resources.split_sections(sample))
- ==
+ ==
[
(None, ["x"]),
("Y", ["z", "a"]),
subpkg = nspkg / 'subpkg'
subpkg.ensure_dir()
(nspkg / '__init__.py').write_text(self.ns_str, encoding='utf-8')
- (subpkg / '__init__.py').write_text(vers_str % number, encoding='utf-8')
+ (subpkg / '__init__.py').write_text(
+ vers_str % number, encoding='utf-8')
import nspkg.subpkg
import nspkg
import inspect
import re
import textwrap
+import functools
import pytest
if l.strip() and not l.strip().startswith('#')
)
+
def parse_distributions(s):
'''
Parse a series of distribution specs of the form:
yield 2 distributions:
- project_name=foo, version=0.2
- - project_name=bar, version=1.0, requires=['foo>=3.0', 'baz; extra=="feature"']
+ - project_name=bar, version=1.0,
+ requires=['foo>=3.0', 'baz; extra=="feature"']
'''
s = s.strip()
for spec in re.split('\n(?=[^\s])', s):
name, version = fields.pop(0).split('-')
if fields:
requires = textwrap.dedent(fields.pop(0))
- metadata=Metadata(('requires.txt', requires))
+ metadata = Metadata(('requires.txt', requires))
else:
metadata = None
dist = pkg_resources.Distribution(project_name=name,
replace_conflicting, resolved_dists_or_exception):
ws = pkg_resources.WorkingSet([])
list(map(ws.add, installed_dists))
- resolve_call = lambda: ws.resolve(
+ resolve_call = functools.partial(
+ ws.resolve,
requirements, installer=FakeInstaller(installable_dists),
replace_conflicting=replace_conflicting,
)
[bumpversion]
-current_version = 38.2.5
+current_version = 38.6.1
commit = True
tag = True
setup_params = dict(
name="setuptools",
- version="38.2.5",
+ version="38.6.1",
description="Easily download, build, install, upgrade, and uninstall "
"Python packages",
author="Python Packaging Authority",
long_description_content_type='text/x-rst; charset=UTF-8',
keywords="CPAN PyPI distutils eggs package management",
url="https://github.com/pypa/setuptools",
+ project_urls={
+ "Documentation": "https://setuptools.readthedocs.io/",
+ },
src_root=None,
packages=setuptools.find_packages(exclude=['*.tests']),
package_data=package_data,
filename = filename[:-6]
return filename
+
def sorted_walk(dir):
"""Do os.walk in a reproducible way,
independent of indeterministic filesystem readdir order
files.sort()
yield base, dirs, files
+
def write_stub(resource, pyfile):
_stub_template = textwrap.dedent("""
def __bootstrap__():
pattern = r'(?P<name>.+)\.(?P<magic>[^.]+)\.pyc'
m = re.match(pattern, name)
- path_new = os.path.join(base, os.pardir, m.group('name') + '.pyc')
- log.info("Renaming file from [%s] to [%s]" % (path_old, path_new))
+ path_new = os.path.join(
+ base, os.pardir, m.group('name') + '.pyc')
+ log.info(
+ "Renaming file from [%s] to [%s]"
+ % (path_old, path_new))
try:
os.remove(path_new)
except OSError:
pass
os.rename(path_old, path_new)
-
def zip_safe(self):
safe = getattr(self.distribution, 'zip_safe', None)
if safe is not None:
module = pkg + (pkg and '.' or '') + os.path.splitext(name)[0]
if sys.version_info < (3, 3):
skip = 8 # skip magic & date
- else:
+ elif sys.version_info < (3, 7):
skip = 12 # skip magic & date & file size
+ else:
+ skip = 16 # skip magic & reserved? & date & file size
f = open(filename, 'rb')
f.read(skip)
code = marshal.load(f)
try:
# Attempt to use Cython for building extensions, if available
from Cython.Distutils.build_ext import build_ext as _build_ext
+ # Additionally, assert that the compiler module will load
+ # also. Ref #1229.
+ __import__('Cython.Compiler.Main')
except ImportError:
_build_ext = _du_build_ext
target = os.path.join(self.script_dir, script_name)
self.add_output(target)
+ if self.dry_run:
+ return
+
mask = current_umask()
- if not self.dry_run:
- ensure_directory(target)
- if os.path.exists(target):
- os.unlink(target)
- with open(target, "w" + mode) as f:
- f.write(contents)
- chmod(target, 0o777 - mask)
+ ensure_directory(target)
+ if os.path.exists(target):
+ os.unlink(target)
+ with open(target, "w" + mode) as f:
+ f.write(contents)
+ chmod(target, 0o777 - mask)
def install_eggs(self, spec, dist_filename, tmpdir):
# .egg dirs or files are already built, so just return them
def byte_compile(self, to_compile):
if sys.dont_write_bytecode:
- self.warn('byte-compiling is disabled, skipping.')
return
from distutils.util import byte_compile
metadata = cmd.distribution.metadata
metadata.version, oldver = cmd.egg_version, metadata.version
metadata.name, oldname = cmd.egg_name, metadata.name
- metadata.long_description_content_type = getattr(
- cmd.distribution,
- 'long_description_content_type'
- )
+
try:
# write unescaped data to PKG-INFO, so older pkg_resources
# can still parse it
distribution, command_options, ignore_option_errors)
options.parse()
- return [meta, options]
+ return meta, options
class ConfigHandler(object):
"""Metadata item name to parser function mapping."""
parse_list = self._parse_list
parse_file = self._parse_file
+ parse_dict = self._parse_dict
return {
'platforms': parse_list,
'description': parse_file,
'long_description': parse_file,
'version': self._parse_version,
+ 'project_urls': parse_dict,
}
def _parse_version(self, value):
self.classifiers or self.download_url):
version = '1.1'
# Setuptools specific for PEP 345
- if hasattr(self, 'python_requires'):
+ if hasattr(self, 'python_requires') or self.project_urls:
version = '1.2'
+ if self.long_description_content_type or self.provides_extras:
+ version = '2.1'
file.write('Metadata-Version: %s\n' % version)
file.write('Name: %s\n' % self.get_name())
file.write('License: %s\n' % self.get_license())
if self.download_url:
file.write('Download-URL: %s\n' % self.download_url)
-
- long_desc_content_type = getattr(
- self,
- 'long_description_content_type',
- None
- ) or 'UNKNOWN'
- file.write('Description-Content-Type: %s\n' % long_desc_content_type)
+ for project_url in self.project_urls.items():
+ file.write('Project-URL: %s, %s\n' % project_url)
long_desc = rfc822_escape(self.get_long_description())
file.write('Description: %s\n' % long_desc)
if hasattr(self, 'python_requires'):
file.write('Requires-Python: %s\n' % self.python_requires)
+ # PEP 566
+ if self.long_description_content_type:
+ file.write(
+ 'Description-Content-Type: %s\n' %
+ self.long_description_content_type
+ )
+ if self.provides_extras:
+ for extra in self.provides_extras:
+ file.write('Provides-Extra: %s\n' % extra)
+
# from Python 3.4
def write_pkg_info(self, base_dir):
self.dist_files = []
self.src_root = attrs.pop("src_root", None)
self.patch_missing_pkg_info(attrs)
- self.long_description_content_type = attrs.get(
- 'long_description_content_type'
- )
+ self.project_urls = attrs.get('project_urls', {})
self.dependency_links = attrs.pop('dependency_links', [])
self.setup_requires = attrs.pop('setup_requires', [])
for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
vars(self).setdefault(ep.name, None)
_Distribution.__init__(self, attrs)
+
+ # The project_urls attribute may not be supported in distutils, so
+ # prime it here from our value if not automatically set
+ self.metadata.project_urls = getattr(
+ self.metadata, 'project_urls', self.project_urls)
+ self.metadata.long_description_content_type = attrs.get(
+ 'long_description_content_type'
+ )
+ self.metadata.provides_extras = getattr(
+ self.metadata, 'provides_extras', set()
+ )
+
if isinstance(self.metadata.version, numbers.Number):
# Some people apparently take "version number" too literally :)
self.metadata.version = str(self.metadata.version)
"""
if getattr(self, 'python_requires', None):
self.metadata.python_requires = self.python_requires
+
+ if getattr(self, 'extras_require', None):
+ for extra in self.extras_require.keys():
+ # Since this gets called multiple times at points where the
+ # keys have become 'converted' extras, ensure that we are only
+ # truly adding extras we haven't seen before here.
+ extra = extra.split(':')[0]
+ if extra:
+ self.metadata.provides_extras.add(extra)
+
self._convert_extras_requirements()
self._move_install_requirements_markers()
'[metadata]\n'
'version = 10.1.1\n'
'description = Some description\n'
+ 'long_description_content_type = text/something\n'
'long_description = file: README\n'
'name = fake_name\n'
'keywords = one, two\n'
assert metadata.version == '10.1.1'
assert metadata.description == 'Some description'
+ assert metadata.long_description_content_type == 'text/something'
assert metadata.long_description == 'readme contents\nline2'
assert metadata.provides == ['package', 'package.sub']
assert metadata.license == 'BSD 3-Clause License'
'Programming Language :: Python :: 3.5',
]
+ def test_dict(self, tmpdir):
+
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'project_urls =\n'
+ ' Link One = https://example.com/one/\n'
+ ' Link Two = https://example.com/two/\n'
+ )
+ with get_dist(tmpdir) as dist:
+ metadata = dist.metadata
+ assert metadata.project_urls == {
+ 'Link One': 'https://example.com/one/',
+ 'Link Two': 'https://example.com/two/',
+ }
+
def test_version(self, tmpdir):
_, config = fake_env(
'pdf': ['ReportLab>=1.2', 'RXP'],
'rest': ['docutils>=0.3', 'pack==1.1,==1.3']
}
+ assert dist.metadata.provides_extras == set(['pdf', 'rest'])
def test_entry_points(self, tmpdir):
_, config = fake_env(
cmd.ensure_finalized()
cmd.easy_install(sdist_unicode)
+ @pytest.fixture
+ def sdist_script(self, tmpdir):
+ files = [
+ (
+ 'setup.py',
+ DALS("""
+ import setuptools
+ setuptools.setup(
+ name="setuptools-test-script",
+ version="1.0",
+ scripts=["mypkg_script"],
+ )
+ """),
+ ),
+ (
+ u'mypkg_script',
+ DALS("""
+ #/usr/bin/python
+ print('mypkg_script')
+ """),
+ ),
+ ]
+ sdist_name = 'setuptools-test-script-1.0.zip'
+ sdist = str(tmpdir / sdist_name)
+ make_sdist(sdist, files)
+ return sdist
+
+ @pytest.mark.skipif(not sys.platform.startswith('linux'),
+ reason="Test can only be run on Linux")
+ def test_script_install(self, sdist_script, tmpdir, monkeypatch):
+ """
+ Check scripts are installed.
+ """
+ dist = Distribution({'script_args': ['easy_install']})
+ target = (tmpdir / 'target').ensure_dir()
+ cmd = ei.easy_install(
+ dist,
+ install_dir=str(target),
+ args=['x'],
+ )
+ monkeypatch.setitem(os.environ, 'PYTHONPATH', str(target))
+ cmd.ensure_finalized()
+ cmd.easy_install(sdist_script)
+ assert (target / 'mypkg_script').exists()
+
class TestPTHFileWriter:
def test_add_from_cwd_site_sets_dirty(self):
self._run_install_command(tmpdir_cwd, env)
assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
+ def test_provides_extra(self, tmpdir_cwd, env):
+ self._setup_script_with_requires(
+ 'extras_require={"foobar": ["barbazquux"]},')
+ environ = os.environ.copy().update(
+ HOME=env.paths['home'],
+ )
+ code, data = environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+ data_stream=1,
+ env=environ,
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+ with open(os.path.join(egg_info_dir, 'PKG-INFO')) as pkginfo_file:
+ pkg_info_lines = pkginfo_file.read().split('\n')
+ assert 'Provides-Extra: foobar' in pkg_info_lines
+ assert 'Metadata-Version: 2.1' in pkg_info_lines
+
+ def test_doesnt_provides_extra(self, tmpdir_cwd, env):
+ self._setup_script_with_requires(
+ '''install_requires=["spam ; python_version<'3.3'"]''')
+ environ = os.environ.copy().update(
+ HOME=env.paths['home'],
+ )
+ environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+ data_stream=1,
+ env=environ,
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+ with open(os.path.join(egg_info_dir, 'PKG-INFO')) as pkginfo_file:
+ pkg_info_text = pkginfo_file.read()
+ assert 'Provides-Extra:' not in pkg_info_text
+
def test_long_description_content_type(self, tmpdir_cwd, env):
# Test that specifying a `long_description_content_type` keyword arg to
# the `setup` function results in writing a `Description-Content-Type`
pkg_info_lines = pkginfo_file.read().split('\n')
expected_line = 'Description-Content-Type: text/markdown'
assert expected_line in pkg_info_lines
+ assert 'Metadata-Version: 2.1' in pkg_info_lines
+
+ def test_project_urls(self, tmpdir_cwd, env):
+ # Test that specifying a `project_urls` dict to the `setup`
+ # function results in writing multiple `Project-URL` lines to
+ # the `PKG-INFO` file in the `<distribution>.egg-info`
+ # directory.
+ # `Project-URL` is described at https://packaging.python.org
+ # /specifications/core-metadata/#project-url-multiple-use
+
+ self._setup_script_with_requires(
+ """project_urls={
+ 'Link One': 'https://example.com/one/',
+ 'Link Two': 'https://example.com/two/',
+ },""")
+ environ = os.environ.copy().update(
+ HOME=env.paths['home'],
+ )
+ code, data = environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+ data_stream=1,
+ env=environ,
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+ with open(os.path.join(egg_info_dir, 'PKG-INFO')) as pkginfo_file:
+ pkg_info_lines = pkginfo_file.read().split('\n')
+ expected_line = 'Project-URL: Link One, https://example.com/one/'
+ assert expected_line in pkg_info_lines
+ expected_line = 'Project-URL: Link Two, https://example.com/two/'
+ assert expected_line in pkg_info_lines
def test_python_requires_egg_info(self, tmpdir_cwd, env):
self._setup_script_with_requires(
def test_unpickleable_exception(self):
class CantPickleThis(Exception):
"This Exception is unpickleable because it's not in globals"
+ def __repr__(self):
+ return 'CantPickleThis%r' % (self.args,)
with setuptools.sandbox.ExceptionSaver() as saved_exc:
raise CantPickleThis('detail')
import os
import sys
+import pytest
from pytest import yield_fixture
from pytest_fixture_config import yield_requires_config
from .test_easy_install import make_nspkg_sdist
+@pytest.fixture(autouse=True)
+def pytest_virtualenv_works(virtualenv):
+ """
+ pytest_virtualenv may not work. if it doesn't, skip these
+ tests. See #1284.
+ """
+ venv_prefix = virtualenv.run(
+ 'python -c "import sys; print(sys.prefix)"',
+ capture=True,
+ ).strip()
+ if venv_prefix == sys.prefix:
+ pytest.skip("virtualenv is broken (see pypa/setuptools#1284)")
+
+
@yield_requires_config(pytest_virtualenv.CONFIG, ['virtualenv_executable'])
@yield_fixture(scope='function')
def bare_virtualenv():
SOURCE_DIR = os.path.join(os.path.dirname(__file__), '../..')
+
def test_clean_env_install(bare_virtualenv):
"""
Check setuptools can be installed in a clean environment.
'python setup.py install',
)).format(source=SOURCE_DIR))
+
def test_pip_upgrade_from_source(virtualenv):
"""
Check pip can upgrade setuptools from source.
# And finally try to upgrade from source.
virtualenv.run('pip install --no-cache-dir --upgrade ' + sdist)
+
def test_test_command_install_requirements(bare_virtualenv, tmpdir):
"""
Check the test command will install all required dependencies.
'cd {source}',
'python setup.py develop',
)).format(source=SOURCE_DIR))
+
def sdist(distname, version):
dist_path = tmpdir.join('%s-%s.tar.gz' % (distname, version))
make_nspkg_sdist(str(dist_path), distname, version)
pytest-flake8; python_version>="2.7"
virtualenv>=13.0.0
pytest-virtualenv>=1.2.7
-pytest>=3.0.2,<=3.2.5
+pytest>=3.0.2
wheel