aboutsummaryrefslogtreecommitdiffstats
path: root/setuptools
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2019-01-27 10:02:52 -0500
committerGitHub <noreply@github.com>2019-01-27 10:02:52 -0500
commit0551421f082eea3f633bc6be23c16a04483aca98 (patch)
tree76c5b37e3a56a232b4b5b66ab7e933edbe64cd25 /setuptools
parent28872fc9e7d15a1acf3bc557795c76c5e64dbad3 (diff)
parent78fd73026ad7284819936b651f7cfbe8a1ec98c8 (diff)
downloadexternal_python_setuptools-0551421f082eea3f633bc6be23c16a04483aca98.tar.gz
external_python_setuptools-0551421f082eea3f633bc6be23c16a04483aca98.tar.bz2
external_python_setuptools-0551421f082eea3f633bc6be23c16a04483aca98.zip
Merge branch 'master' into license-fix-357
Diffstat (limited to 'setuptools')
-rw-r--r--setuptools/__init__.py39
-rw-r--r--setuptools/_deprecation_warning.py7
-rw-r--r--setuptools/build_meta.py27
-rw-r--r--setuptools/command/develop.py25
-rw-r--r--setuptools/command/easy_install.py15
-rw-r--r--setuptools/command/egg_info.py14
-rw-r--r--setuptools/command/upload.py145
-rw-r--r--setuptools/config.py94
-rw-r--r--setuptools/dist.py268
-rw-r--r--setuptools/monkey.py12
-rw-r--r--setuptools/package_index.py16
-rw-r--r--setuptools/pep425tags.py2
-rw-r--r--setuptools/py36compat.py82
-rw-r--r--setuptools/ssl_support.py2
-rw-r--r--setuptools/tests/files.py9
-rw-r--r--setuptools/tests/server.py14
-rw-r--r--setuptools/tests/test_build_clib.py9
-rw-r--r--setuptools/tests/test_build_meta.py72
-rw-r--r--setuptools/tests/test_config.py128
-rw-r--r--setuptools/tests/test_depends.py18
-rw-r--r--setuptools/tests/test_dist.py122
-rw-r--r--setuptools/tests/test_easy_install.py20
-rw-r--r--setuptools/tests/test_egg_info.py103
-rw-r--r--setuptools/tests/test_find_packages.py11
-rw-r--r--setuptools/tests/test_install_scripts.py6
-rw-r--r--setuptools/tests/test_integration.py23
-rw-r--r--setuptools/tests/test_manifest.py134
-rw-r--r--setuptools/tests/test_msvc.py5
-rw-r--r--setuptools/tests/test_namespaces.py3
-rw-r--r--setuptools/tests/test_packageindex.py74
-rw-r--r--setuptools/tests/test_pep425tags.py4
-rw-r--r--setuptools/tests/test_sandbox.py3
-rw-r--r--setuptools/tests/test_sdist.py48
-rw-r--r--setuptools/tests/test_setuptools.py9
-rw-r--r--setuptools/tests/test_test.py5
-rw-r--r--setuptools/tests/test_upload.py170
-rw-r--r--setuptools/tests/test_virtualenv.py14
-rw-r--r--setuptools/tests/test_wheel.py2
-rw-r--r--setuptools/tests/test_windows_wrappers.py13
-rw-r--r--setuptools/unicode_utils.py13
-rw-r--r--setuptools/wheel.py13
41 files changed, 1442 insertions, 351 deletions
diff --git a/setuptools/__init__.py b/setuptools/__init__.py
index 54309b57..a71b2bbd 100644
--- a/setuptools/__init__.py
+++ b/setuptools/__init__.py
@@ -5,10 +5,14 @@ import sys
import functools
import distutils.core
import distutils.filelist
+import re
+from distutils.errors import DistutilsOptionError
from distutils.util import convert_path
from fnmatch import fnmatchcase
-from setuptools.extern.six import PY3
+from ._deprecation_warning import SetuptoolsDeprecationWarning
+
+from setuptools.extern.six import PY3, string_types
from setuptools.extern.six.moves import filter, map
import setuptools.version
@@ -22,6 +26,7 @@ __metaclass__ = type
__all__ = [
'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require',
+ 'SetuptoolsDeprecationWarning',
'find_packages'
]
@@ -158,6 +163,37 @@ class Command(_Command):
_Command.__init__(self, dist)
vars(self).update(kw)
+ def _ensure_stringlike(self, option, what, default=None):
+ val = getattr(self, option)
+ if val is None:
+ setattr(self, option, default)
+ return default
+ elif not isinstance(val, string_types):
+ raise DistutilsOptionError("'%s' must be a %s (got `%s`)"
+ % (option, what, val))
+ return val
+
+ def ensure_string_list(self, option):
+ r"""Ensure that 'option' is a list of strings. If 'option' is
+ currently a string, we split it either on /,\s*/ or /\s+/, so
+ "foo bar baz", "foo,bar,baz", and "foo, bar baz" all become
+ ["foo", "bar", "baz"].
+ """
+ val = getattr(self, option)
+ if val is None:
+ return
+ elif isinstance(val, string_types):
+ setattr(self, option, re.split(r',\s*|\s+', val))
+ else:
+ if isinstance(val, list):
+ ok = all(isinstance(v, string_types) for v in val)
+ else:
+ ok = False
+ if not ok:
+ raise DistutilsOptionError(
+ "'%s' must be a list of strings (got %r)"
+ % (option, val))
+
def reinitialize_command(self, command, reinit_subcommands=0, **kw):
cmd = _Command.reinitialize_command(self, command, reinit_subcommands)
vars(cmd).update(kw)
@@ -188,4 +224,5 @@ def findall(dir=os.curdir):
return list(files)
+# Apply monkey patches
monkey.patch_all()
diff --git a/setuptools/_deprecation_warning.py b/setuptools/_deprecation_warning.py
new file mode 100644
index 00000000..086b64dd
--- /dev/null
+++ b/setuptools/_deprecation_warning.py
@@ -0,0 +1,7 @@
+class SetuptoolsDeprecationWarning(Warning):
+ """
+ Base class for warning deprecations in ``setuptools``
+
+ This class is not derived from ``DeprecationWarning``, and as such is
+ visible by default.
+ """
diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py
index 0067a7ac..c883d92f 100644
--- a/setuptools/build_meta.py
+++ b/setuptools/build_meta.py
@@ -112,12 +112,12 @@ def _get_immediate_subdirectories(a_dir):
def get_requires_for_build_wheel(config_settings=None):
config_settings = _fix_config(config_settings)
- return _get_build_requires(config_settings, requirements=['setuptools', 'wheel'])
+ return _get_build_requires(config_settings, requirements=['wheel'])
def get_requires_for_build_sdist(config_settings=None):
config_settings = _fix_config(config_settings)
- return _get_build_requires(config_settings, requirements=['setuptools'])
+ return _get_build_requires(config_settings, requirements=[])
def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
@@ -149,6 +149,15 @@ def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
return dist_infos[0]
+def _file_with_extension(directory, extension):
+ matching = (
+ f for f in os.listdir(directory)
+ if f.endswith(extension)
+ )
+ file, = matching
+ return file
+
+
def build_wheel(wheel_directory, config_settings=None,
metadata_directory=None):
config_settings = _fix_config(config_settings)
@@ -160,23 +169,15 @@ def build_wheel(wheel_directory, config_settings=None,
shutil.rmtree(wheel_directory)
shutil.copytree('dist', wheel_directory)
- wheels = [f for f in os.listdir(wheel_directory)
- if f.endswith('.whl')]
-
- assert len(wheels) == 1
- return wheels[0]
+ return _file_with_extension(wheel_directory, '.whl')
def build_sdist(sdist_directory, config_settings=None):
config_settings = _fix_config(config_settings)
sdist_directory = os.path.abspath(sdist_directory)
- sys.argv = sys.argv[:1] + ['sdist'] + \
+ sys.argv = sys.argv[:1] + ['sdist', '--formats', 'gztar'] + \
config_settings["--global-option"] + \
["--dist-dir", sdist_directory]
_run_setup()
- sdists = [f for f in os.listdir(sdist_directory)
- if f.endswith('.tar.gz')]
-
- assert len(sdists) == 1
- return sdists[0]
+ return _file_with_extension(sdist_directory, '.tar.gz')
diff --git a/setuptools/command/develop.py b/setuptools/command/develop.py
index fdc9fc43..009e4f93 100644
--- a/setuptools/command/develop.py
+++ b/setuptools/command/develop.py
@@ -7,7 +7,7 @@ import io
from setuptools.extern import six
-from pkg_resources import Distribution, PathMetadata, normalize_path
+import pkg_resources
from setuptools.command.easy_install import easy_install
from setuptools import namespaces
import setuptools
@@ -65,9 +65,9 @@ class develop(namespaces.DevelopInstaller, easy_install):
if self.egg_path is None:
self.egg_path = os.path.abspath(ei.egg_base)
- target = normalize_path(self.egg_base)
- egg_path = normalize_path(os.path.join(self.install_dir,
- self.egg_path))
+ target = pkg_resources.normalize_path(self.egg_base)
+ egg_path = pkg_resources.normalize_path(
+ os.path.join(self.install_dir, self.egg_path))
if egg_path != target:
raise DistutilsOptionError(
"--egg-path must be a relative path from the install"
@@ -75,9 +75,9 @@ class develop(namespaces.DevelopInstaller, easy_install):
)
# Make a distribution for the package's source
- self.dist = Distribution(
+ self.dist = pkg_resources.Distribution(
target,
- PathMetadata(target, os.path.abspath(ei.egg_info)),
+ pkg_resources.PathMetadata(target, os.path.abspath(ei.egg_info)),
project_name=ei.egg_name
)
@@ -97,13 +97,14 @@ class develop(namespaces.DevelopInstaller, easy_install):
path_to_setup = egg_base.replace(os.sep, '/').rstrip('/')
if path_to_setup != os.curdir:
path_to_setup = '../' * (path_to_setup.count('/') + 1)
- resolved = normalize_path(
+ resolved = pkg_resources.normalize_path(
os.path.join(install_dir, egg_path, path_to_setup)
)
- if resolved != normalize_path(os.curdir):
+ if resolved != pkg_resources.normalize_path(os.curdir):
raise DistutilsOptionError(
"Can't get a consistent path to setup script from"
- " installation directory", resolved, normalize_path(os.curdir))
+ " installation directory", resolved,
+ pkg_resources.normalize_path(os.curdir))
return path_to_setup
def install_for_development(self):
@@ -114,7 +115,7 @@ class develop(namespaces.DevelopInstaller, easy_install):
self.reinitialize_command('build_py', inplace=0)
self.run_command('build_py')
bpy_cmd = self.get_finalized_command("build_py")
- build_path = normalize_path(bpy_cmd.build_lib)
+ build_path = pkg_resources.normalize_path(bpy_cmd.build_lib)
# Build extensions
self.reinitialize_command('egg_info', egg_base=build_path)
@@ -128,7 +129,8 @@ class develop(namespaces.DevelopInstaller, easy_install):
self.egg_path = build_path
self.dist.location = build_path
# XXX
- self.dist._provider = PathMetadata(build_path, ei_cmd.egg_info)
+ self.dist._provider = pkg_resources.PathMetadata(
+ build_path, ei_cmd.egg_info)
else:
# Without 2to3 inplace works fine:
self.run_command('egg_info')
@@ -200,6 +202,7 @@ class VersionlessRequirement:
name as the 'requirement' so that scripts will work across
multiple versions.
+ >>> from pkg_resources import Distribution
>>> dist = Distribution(project_name='foo', version='1.0')
>>> str(dist.as_requirement())
'foo==1.0'
diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py
index c670a16e..06c98271 100644
--- a/setuptools/command/easy_install.py
+++ b/setuptools/command/easy_install.py
@@ -40,8 +40,11 @@ import subprocess
import shlex
import io
+
from sysconfig import get_config_vars, get_path
+from setuptools import SetuptoolsDeprecationWarning
+
from setuptools.extern import six
from setuptools.extern.six.moves import configparser, map
@@ -2077,7 +2080,7 @@ class ScriptWriter:
@classmethod
def get_script_args(cls, dist, executable=None, wininst=False):
# for backward compatibility
- warnings.warn("Use get_args", DeprecationWarning)
+ warnings.warn("Use get_args", EasyInstallDeprecationWarning)
writer = (WindowsScriptWriter if wininst else ScriptWriter).best()
header = cls.get_script_header("", executable, wininst)
return writer.get_args(dist, header)
@@ -2085,7 +2088,7 @@ class ScriptWriter:
@classmethod
def get_script_header(cls, script_text, executable=None, wininst=False):
# for backward compatibility
- warnings.warn("Use get_header", DeprecationWarning, stacklevel=2)
+ warnings.warn("Use get_header", EasyInstallDeprecationWarning, stacklevel=2)
if wininst:
executable = "python.exe"
return cls.get_header(script_text, executable)
@@ -2120,7 +2123,7 @@ class ScriptWriter:
@classmethod
def get_writer(cls, force_windows):
# for backward compatibility
- warnings.warn("Use best", DeprecationWarning)
+ warnings.warn("Use best", EasyInstallDeprecationWarning)
return WindowsScriptWriter.best() if force_windows else cls.best()
@classmethod
@@ -2152,7 +2155,7 @@ class WindowsScriptWriter(ScriptWriter):
@classmethod
def get_writer(cls):
# for backward compatibility
- warnings.warn("Use best", DeprecationWarning)
+ warnings.warn("Use best", EasyInstallDeprecationWarning)
return cls.best()
@classmethod
@@ -2333,3 +2336,7 @@ def _patch_usage():
yield
finally:
distutils.core.gen_usage = saved
+
+class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning):
+ """Class for warning about deprecations in EasyInstall in SetupTools. Not ignored by default, unlike DeprecationWarning."""
+
diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py
index 93100ab9..5d8f451e 100644
--- a/setuptools/command/egg_info.py
+++ b/setuptools/command/egg_info.py
@@ -31,7 +31,7 @@ import setuptools.unicode_utils as unicode_utils
from setuptools.glob import glob
from setuptools.extern import packaging
-
+from setuptools import SetuptoolsDeprecationWarning
def translate_pattern(glob):
"""
@@ -576,6 +576,12 @@ class manifest_maker(sdist):
self.filelist.extend(rcfiles)
elif os.path.exists(self.manifest):
self.read_manifest()
+
+ if os.path.exists("setup.py"):
+ # setup.py should be included by default, even if it's not
+ # the script called to create the sdist
+ self.filelist.append("setup.py")
+
ei_cmd = self.get_finalized_command('egg_info')
self.filelist.graft(ei_cmd.egg_info)
@@ -697,7 +703,7 @@ def get_pkg_info_revision():
Get a -r### off of PKG-INFO Version in case this is an sdist of
a subversion revision.
"""
- warnings.warn("get_pkg_info_revision is deprecated.", DeprecationWarning)
+ warnings.warn("get_pkg_info_revision is deprecated.", EggInfoDeprecationWarning)
if os.path.exists('PKG-INFO'):
with io.open('PKG-INFO') as f:
for line in f:
@@ -705,3 +711,7 @@ def get_pkg_info_revision():
if match:
return int(match.group(1))
return 0
+
+
+class EggInfoDeprecationWarning(SetuptoolsDeprecationWarning):
+ """Class for warning about deprecations in eggInfo in setupTools. Not ignored by default, unlike DeprecationWarning."""
diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py
index 72f24d8f..6db8888b 100644
--- a/setuptools/command/upload.py
+++ b/setuptools/command/upload.py
@@ -1,6 +1,19 @@
+import io
+import os
+import hashlib
import getpass
+
+from base64 import standard_b64encode
+
from distutils import log
from distutils.command import upload as orig
+from distutils.spawn import spawn
+
+from distutils.errors import DistutilsError
+
+from setuptools.extern.six.moves.urllib.request import urlopen, Request
+from setuptools.extern.six.moves.urllib.error import HTTPError
+from setuptools.extern.six.moves.urllib.parse import urlparse
class upload(orig.upload):
@@ -8,7 +21,6 @@ class upload(orig.upload):
Override default upload behavior to obtain password
in a variety of different ways.
"""
-
def run(self):
try:
orig.upload.run(self)
@@ -33,6 +45,137 @@ class upload(orig.upload):
self._prompt_for_password()
)
+ def upload_file(self, command, pyversion, filename):
+ # Makes sure the repository URL is compliant
+ schema, netloc, url, params, query, fragments = \
+ urlparse(self.repository)
+ if params or query or fragments:
+ raise AssertionError("Incompatible url %s" % self.repository)
+
+ if schema not in ('http', 'https'):
+ raise AssertionError("unsupported schema " + schema)
+
+ # Sign if requested
+ if self.sign:
+ gpg_args = ["gpg", "--detach-sign", "-a", filename]
+ if self.identity:
+ gpg_args[2:2] = ["--local-user", self.identity]
+ spawn(gpg_args,
+ dry_run=self.dry_run)
+
+ # Fill in the data - send all the meta-data in case we need to
+ # register a new release
+ with open(filename, 'rb') as f:
+ content = f.read()
+
+ meta = self.distribution.metadata
+
+ data = {
+ # action
+ ':action': 'file_upload',
+ 'protocol_version': '1',
+
+ # identify release
+ 'name': meta.get_name(),
+ 'version': meta.get_version(),
+
+ # file content
+ 'content': (os.path.basename(filename), content),
+ 'filetype': command,
+ 'pyversion': pyversion,
+ 'md5_digest': hashlib.md5(content).hexdigest(),
+
+ # additional meta-data
+ 'metadata_version': str(meta.get_metadata_version()),
+ 'summary': meta.get_description(),
+ 'home_page': meta.get_url(),
+ 'author': meta.get_contact(),
+ 'author_email': meta.get_contact_email(),
+ 'license': meta.get_licence(),
+ 'description': meta.get_long_description(),
+ 'keywords': meta.get_keywords(),
+ 'platform': meta.get_platforms(),
+ 'classifiers': meta.get_classifiers(),
+ 'download_url': meta.get_download_url(),
+ # PEP 314
+ 'provides': meta.get_provides(),
+ 'requires': meta.get_requires(),
+ 'obsoletes': meta.get_obsoletes(),
+ }
+
+ data['comment'] = ''
+
+ if self.sign:
+ data['gpg_signature'] = (os.path.basename(filename) + ".asc",
+ open(filename+".asc", "rb").read())
+
+ # set up the authentication
+ user_pass = (self.username + ":" + self.password).encode('ascii')
+ # The exact encoding of the authentication string is debated.
+ # Anyway PyPI only accepts ascii for both username or password.
+ auth = "Basic " + standard_b64encode(user_pass).decode('ascii')
+
+ # Build up the MIME payload for the POST data
+ boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
+ sep_boundary = b'\r\n--' + boundary.encode('ascii')
+ end_boundary = sep_boundary + b'--\r\n'
+ body = io.BytesIO()
+ for key, value in data.items():
+ title = '\r\nContent-Disposition: form-data; name="%s"' % key
+ # handle multiple entries for the same name
+ if not isinstance(value, list):
+ value = [value]
+ for value in value:
+ if type(value) is tuple:
+ title += '; filename="%s"' % value[0]
+ value = value[1]
+ else:
+ value = str(value).encode('utf-8')
+ body.write(sep_boundary)
+ body.write(title.encode('utf-8'))
+ body.write(b"\r\n\r\n")
+ body.write(value)
+ body.write(end_boundary)
+ body = body.getvalue()
+
+ msg = "Submitting %s to %s" % (filename, self.repository)
+ self.announce(msg, log.INFO)
+
+ # build the Request
+ headers = {
+ 'Content-type': 'multipart/form-data; boundary=%s' % boundary,
+ 'Content-length': str(len(body)),
+ 'Authorization': auth,
+ }
+
+ request = Request(self.repository, data=body,
+ headers=headers)
+ # send the data
+ try:
+ result = urlopen(request)
+ status = result.getcode()
+ reason = result.msg
+ except HTTPError as e:
+ status = e.code
+ reason = e.msg
+ except OSError as e:
+ self.announce(str(e), log.ERROR)
+ raise
+
+ if status == 200:
+ self.announce('Server response (%s): %s' % (status, reason),
+ log.INFO)
+ if self.show_response:
+ text = getattr(self, '_read_pypi_response',
+ lambda x: None)(result)
+ if text is not None:
+ msg = '\n'.join(('-' * 75, text, '-' * 75))
+ self.announce(msg, log.INFO)
+ else:
+ msg = 'Upload failed (%s): %s' % (status, reason)
+ self.announce(msg, log.ERROR)
+ raise DistutilsError(msg)
+
def _load_password_from_keyring(self):
"""
Attempt to load password from keyring. Suppress Exceptions.
diff --git a/setuptools/config.py b/setuptools/config.py
index 73a3bf70..b6626043 100644
--- a/setuptools/config.py
+++ b/setuptools/config.py
@@ -2,8 +2,12 @@ from __future__ import absolute_import, unicode_literals
import io
import os
import sys
+
+import warnings
+import functools
from collections import defaultdict
from functools import partial
+from functools import wraps
from importlib import import_module
from distutils.errors import DistutilsOptionError, DistutilsFileError
@@ -61,6 +65,18 @@ def read_configuration(
return configuration_to_dict(handlers)
+def _get_option(target_obj, key):
+ """
+ Given a target object and option key, get that option from
+ the target object, either through a get_{key} method or
+ from an attribute directly.
+ """
+ getter_name = 'get_{key}'.format(**locals())
+ by_attribute = functools.partial(getattr, target_obj, key)
+ getter = getattr(target_obj, getter_name, by_attribute)
+ return getter()
+
+
def configuration_to_dict(handlers):
"""Returns configuration data gathered by given handlers as a dict.
@@ -72,20 +88,9 @@ def configuration_to_dict(handlers):
config_dict = defaultdict(dict)
for handler in handlers:
-
- obj_alias = handler.section_prefix
- target_obj = handler.target_obj
-
for option in handler.set_options:
- getter = getattr(target_obj, 'get_%s' % option, None)
-
- if getter is None:
- value = getattr(target_obj, option)
-
- else:
- value = getter()
-
- config_dict[obj_alias][option] = value
+ value = _get_option(handler.target_obj, option)
+ config_dict[handler.section_prefix][option] = value
return config_dict
@@ -110,7 +115,8 @@ def parse_configuration(
options.parse()
meta = ConfigMetadataHandler(
- distribution.metadata, command_options, ignore_option_errors, distribution.package_dir)
+ distribution.metadata, command_options, ignore_option_errors,
+ distribution.package_dir)
meta.parse()
return meta, options
@@ -241,6 +247,26 @@ class ConfigHandler:
return value in ('1', 'true', 'yes')
@classmethod
+ def _exclude_files_parser(cls, key):
+ """Returns a parser function to make sure field inputs
+ are not files.
+
+ Parses a value after getting the key so error messages are
+ more informative.
+
+ :param key:
+ :rtype: callable
+ """
+ def parser(value):
+ exclude_directive = 'file:'
+ if value.startswith(exclude_directive):
+ raise ValueError(
+ 'Only strings are accepted for the {0} field, '
+ 'files are not accepted'.format(key))
+ return value
+ return parser
+
+ @classmethod
def _parse_file(cls, value):
"""Represents value as a string, allowing including text
from nearest files using `file:` directive.
@@ -249,7 +275,6 @@ class ConfigHandler:
directory with setup.py.
Examples:
- file: LICENSE
file: README.rst, CHANGELOG.md, src/file.txt
:param str value:
@@ -388,7 +413,7 @@ class ConfigHandler:
section_parser_method = getattr(
self,
- # Dots in section names are tranlsated into dunderscores.
+ # Dots in section names are translated into dunderscores.
('parse_section%s' % method_postfix).replace('.', '__'),
None)
@@ -399,6 +424,20 @@ class ConfigHandler:
section_parser_method(section_options)
+ def _deprecated_config_handler(self, func, msg, warning_class):
+ """ this function will wrap around parameters that are deprecated
+
+ :param msg: deprecation message
+ :param warning_class: class of warning exception to be raised
+ :param func: function to be wrapped around
+ """
+ @wraps(func)
+ def config_handler(*args, **kwargs):
+ warnings.warn(msg, warning_class)
+ return func(*args, **kwargs)
+
+ return config_handler
+
class ConfigMetadataHandler(ConfigHandler):
@@ -429,15 +468,20 @@ class ConfigMetadataHandler(ConfigHandler):
parse_list = self._parse_list
parse_file = self._parse_file
parse_dict = self._parse_dict
+ exclude_files_parser = self._exclude_files_parser
return {
'platforms': parse_list,
'keywords': parse_list,
'provides': parse_list,
- 'requires': parse_list,
+ 'requires': self._deprecated_config_handler(
+ parse_list,
+ "The requires parameter is deprecated, please use "
+ "install_requires for runtime dependencies.",
+ DeprecationWarning),
'obsoletes': parse_list,
'classifiers': self._get_parser_compound(parse_file, parse_list),
- 'license': parse_file,
+ 'license': exclude_files_parser('license'),
'description': parse_file,
'long_description': parse_file,
'version': self._parse_version,
@@ -458,9 +502,12 @@ class ConfigMetadataHandler(ConfigHandler):
# Be strict about versions loaded from file because it's easy to
# accidentally include newlines and other unintended content
if isinstance(parse(version), LegacyVersion):
- raise DistutilsOptionError('Version loaded from %s does not comply with PEP 440: %s' % (
- value, version
- ))
+ tmpl = (
+ 'Version loaded from {value} does not '
+ 'comply with PEP 440: {version}'
+ )
+ raise DistutilsOptionError(tmpl.format(**locals()))
+
return version
version = self._parse_attr(value, self.package_dir)
@@ -518,12 +565,13 @@ class ConfigOptionsHandler(ConfigHandler):
find_directives = ['find:', 'find_namespace:']
trimmed_value = value.strip()
- if not trimmed_value in find_directives:
+ if trimmed_value not in find_directives:
return self._parse_list(value)
findns = trimmed_value == find_directives[1]
if findns and not PY3:
- raise DistutilsOptionError('find_namespace: directive is unsupported on Python < 3.3')
+ raise DistutilsOptionError(
+ 'find_namespace: directive is unsupported on Python < 3.3')
# Read function arguments from a dedicated section.
find_kwargs = self.parse_section_packages__find(
diff --git a/setuptools/dist.py b/setuptools/dist.py
index 6ee4a97f..b8551228 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
__all__ = ['Distribution']
+import io
+import sys
import re
import os
import warnings
@@ -9,8 +11,15 @@ import distutils.log
import distutils.core
import distutils.cmd
import distutils.dist
+from distutils.errors import DistutilsOptionError
+from distutils.util import strtobool
+from distutils.debug import DEBUG
+from distutils.fancy_getopt import translate_longopt
import itertools
+
from collections import defaultdict
+from email import message_from_file
+
from distutils.errors import (
DistutilsOptionError, DistutilsPlatformError, DistutilsSetupError,
)
@@ -21,51 +30,121 @@ from setuptools.extern import six
from setuptools.extern import packaging
from setuptools.extern.six.moves import map, filter, filterfalse
+from . import SetuptoolsDeprecationWarning
+
from setuptools.depends import Require
from setuptools import windows_support
from setuptools.monkey import get_unpatched
from setuptools.config import parse_configuration
+from .unicode_utils import detect_encoding
import pkg_resources
-from .py36compat import Distribution_parse_config_files
__import__('setuptools.extern.packaging.specifiers')
__import__('setuptools.extern.packaging.version')
def _get_unpatched(cls):
- warnings.warn("Do not call this function", DeprecationWarning)
+ warnings.warn("Do not call this function", DistDeprecationWarning)
return get_unpatched(cls)
-def get_metadata_version(dist_md):
- if dist_md.long_description_content_type or dist_md.provides_extras:
- return StrictVersion('2.1')
- elif (dist_md.maintainer is not None or
- dist_md.maintainer_email is not None or
- getattr(dist_md, 'python_requires', None) is not None):
- return StrictVersion('1.2')
- elif (dist_md.provides or dist_md.requires or dist_md.obsoletes or
- dist_md.classifiers or dist_md.download_url):
- return StrictVersion('1.1')
+def get_metadata_version(self):
+ mv = getattr(self, 'metadata_version', None)
- return StrictVersion('1.0')
+ if mv is None:
+ if self.long_description_content_type or self.provides_extras:
+ mv = StrictVersion('2.1')
+ elif (self.maintainer is not None or
+ self.maintainer_email is not None or
+ getattr(self, 'python_requires', None) is not None):
+ mv = StrictVersion('1.2')
+ elif (self.provides or self.requires or self.obsoletes or
+ self.classifiers or self.download_url):
+ mv = StrictVersion('1.1')
+ else:
+ mv = StrictVersion('1.0')
+
+ self.metadata_version = mv
+
+ return mv
+
+
+def read_pkg_file(self, file):
+ """Reads the metadata values from a file object."""
+ msg = message_from_file(file)
+
+ def _read_field(name):
+ value = msg[name]
+ if value == 'UNKNOWN':
+ return None
+ return value
+
+ def _read_list(name):
+ values = msg.get_all(name, None)
+ if values == []:
+ return None
+ return values
+
+ self.metadata_version = StrictVersion(msg['metadata-version'])
+ self.name = _read_field('name')
+ self.version = _read_field('version')
+ self.description = _read_field('summary')
+ # we are filling author only.
+ self.author = _read_field('author')
+ self.maintainer = None
+ self.author_email = _read_field('author-email')
+ self.maintainer_email = None
+ self.url = _read_field('home-page')
+ self.license = _read_field('license')
+
+ if 'download-url' in msg:
+ self.download_url = _read_field('download-url')
+ else:
+ self.download_url = None
+
+ self.long_description = _read_field('description')
+ self.description = _read_field('summary')
+
+ if 'keywords' in msg:
+ self.keywords = _read_field('keywords').split(',')
+
+ self.platforms = _read_list('platform')
+ self.classifiers = _read_list('classifier')
+
+ # PEP 314 - these fields only exist in 1.1
+ if self.metadata_version == StrictVersion('1.1'):
+ self.requires = _read_list('requires')
+ self.provides = _read_list('provides')
+ self.obsoletes = _read_list('obsoletes')
+ else:
+ self.requires = None
+ self.provides = None
+ self.obsoletes = None
# Based on Python 3.5 version
def write_pkg_file(self, file):
"""Write the PKG-INFO format data to a file object.
"""
- version = get_metadata_version(self)
+ version = self.get_metadata_version()
+
+ if six.PY2:
+ def write_field(key, value):
+ file.write("%s: %s\n" % (key, self._encode_field(value)))
+ else:
+ def write_field(key, value):
+ file.write("%s: %s\n" % (key, value))
+
- file.write('Metadata-Version: %s\n' % version)
- file.write('Name: %s\n' % self.get_name())
- file.write('Version: %s\n' % self.get_version())
- file.write('Summary: %s\n' % self.get_description())
- file.write('Home-page: %s\n' % self.get_url())
+ write_field('Metadata-Version', str(version))
+ write_field('Name', self.get_name())
+ write_field('Version', self.get_version())
+ write_field('Summary', self.get_description())
+ write_field('Home-page', self.get_url())
if version < StrictVersion('1.2'):
- file.write('Author: %s\n' % self.get_contact())
- file.write('Author-email: %s\n' % self.get_contact_email())
+ write_field('Author', self.get_contact())
+ write_field('Author-email', self.get_contact_email())
else:
optional_fields = (
('Author', 'author'),
@@ -76,28 +155,26 @@ def write_pkg_file(self, file):
for field, attr in optional_fields:
attr_val = getattr(self, attr)
- if six.PY2:
- attr_val = self._encode_field(attr_val)
if attr_val is not None:
- file.write('%s: %s\n' % (field, attr_val))
+ write_field(field, attr_val)
- file.write('License: %s\n' % self.get_license())
+ write_field('License', self.get_license())
if self.download_url:
- file.write('Download-URL: %s\n' % self.download_url)
+ write_field('Download-URL', self.download_url)
for project_url in self.project_urls.items():
- file.write('Project-URL: %s, %s\n' % project_url)
+ write_field('Project-URL', '%s, %s' % project_url)
long_desc = rfc822_escape(self.get_long_description())
- file.write('Description: %s\n' % long_desc)
+ write_field('Description', long_desc)
keywords = ','.join(self.get_keywords())
if keywords:
- file.write('Keywords: %s\n' % keywords)
+ write_field('Keywords', keywords)
if version >= StrictVersion('1.2'):
for platform in self.get_platforms():
- file.write('Platform: %s\n' % platform)
+ write_field('Platform', platform)
else:
self._write_list(file, 'Platform', self.get_platforms())
@@ -110,17 +187,17 @@ def write_pkg_file(self, file):
# Setuptools specific for PEP 345
if hasattr(self, 'python_requires'):
- file.write('Requires-Python: %s\n' % self.python_requires)
+ write_field('Requires-Python', self.python_requires)
# PEP 566
if self.long_description_content_type:
- file.write(
- 'Description-Content-Type: %s\n' %
+ write_field(
+ 'Description-Content-Type',
self.long_description_content_type
)
if self.provides_extras:
for extra in self.provides_extras:
- file.write('Provides-Extra: %s\n' % extra)
+ write_field('Provides-Extra', extra)
sequence = tuple, list
@@ -260,7 +337,7 @@ def check_packages(dist, attr, value):
_Distribution = get_unpatched(distutils.core.Distribution)
-class Distribution(Distribution_parse_config_files, _Distribution):
+class Distribution(_Distribution):
"""Distribution with support for features, tests, and package data
This is an enhanced version of 'distutils.dist.Distribution' that
@@ -484,12 +561,125 @@ class Distribution(Distribution_parse_config_files, _Distribution):
req.marker = None
return req
+ def _parse_config_files(self, filenames=None):
+ """
+ Adapted from distutils.dist.Distribution.parse_config_files,
+ this method provides the same functionality in subtly-improved
+ ways.
+ """
+ from setuptools.extern.six.moves.configparser import ConfigParser
+
+ # Ignore install directory options if we have a venv
+ if six.PY3 and sys.prefix != sys.base_prefix:
+ ignore_options = [
+ 'install-base', 'install-platbase', 'install-lib',
+ 'install-platlib', 'install-purelib', 'install-headers',
+ 'install-scripts', 'install-data', 'prefix', 'exec-prefix',
+ 'home', 'user', 'root']
+ else:
+ ignore_options = []
+
+ ignore_options = frozenset(ignore_options)
+
+ if filenames is None:
+ filenames = self.find_config_files()
+
+ if DEBUG:
+ self.announce("Distribution.parse_config_files():")
+
+ parser = ConfigParser()
+ for filename in filenames:
+ with io.open(filename, 'rb') as fp:
+ encoding = detect_encoding(fp)
+ if DEBUG:
+ self.announce(" reading %s [%s]" % (
+ filename, encoding or 'locale')
+ )
+ reader = io.TextIOWrapper(fp, encoding=encoding)
+ (parser.read_file if six.PY3 else parser.readfp)(reader)
+ for section in parser.sections():
+ options = parser.options(section)
+ opt_dict = self.get_option_dict(section)
+
+ for opt in options:
+ if opt != '__name__' and opt not in ignore_options:
+ val = parser.get(section, opt)
+ opt = opt.replace('-', '_')
+ opt_dict[opt] = (filename, val)
+
+ # Make the ConfigParser forget everything (so we retain
+ # the original filenames that options come from)
+ parser.__init__()
+
+ # If there was a "global" section in the config file, use it
+ # to set Distribution options.
+
+ if 'global' in self.command_options:
+ for (opt, (src, val)) in self.command_options['global'].items():
+ alias = self.negative_opt.get(opt)
+ try:
+ if alias:
+ setattr(self, alias, not strtobool(val))
+ elif opt in ('verbose', 'dry_run'): # ugh!
+ setattr(self, opt, strtobool(val))
+ else:
+ setattr(self, opt, val)
+ except ValueError as msg:
+ raise DistutilsOptionError(msg)
+
+ def _set_command_options(self, command_obj, option_dict=None):
+ """
+ Set the options for 'command_obj' from 'option_dict'. Basically
+ this means copying elements of a dictionary ('option_dict') to
+ attributes of an instance ('command').
+
+ 'command_obj' must be a Command instance. If 'option_dict' is not
+ supplied, uses the standard option dictionary for this command
+ (from 'self.command_options').
+
+ (Adopted from distutils.dist.Distribution._set_command_options)
+ """
+ command_name = command_obj.get_command_name()
+ if option_dict is None:
+ option_dict = self.get_option_dict(command_name)
+
+ if DEBUG:
+ self.announce(" setting options for '%s' command:" % command_name)
+ for (option, (source, value)) in option_dict.items():
+ if DEBUG:
+ self.announce(" %s = %s (from %s)" % (option, value,
+ source))
+ try:
+ bool_opts = [translate_longopt(o)
+ for o in command_obj.boolean_options]
+ except AttributeError:
+ bool_opts = []
+ try:
+ neg_opt = command_obj.negative_opt
+ except AttributeError:
+ neg_opt = {}
+
+ try:
+ is_string = isinstance(value, six.string_types)
+ if option in neg_opt and is_string:
+ setattr(command_obj, neg_opt[option], not strtobool(value))
+ elif option in bool_opts and is_string:
+ setattr(command_obj, option, strtobool(value))
+ elif hasattr(command_obj, option):
+ setattr(command_obj, option, value)
+ else:
+ raise DistutilsOptionError(
+ "error in %s: command '%s' has no such option '%s'"
+ % (source, command_name, option))
+ except ValueError as msg:
+ raise DistutilsOptionError(msg)
+
def parse_config_files(self, filenames=None, ignore_option_errors=False):
"""Parses configuration files from various levels
and loads configuration.
"""
- _Distribution.parse_config_files(self, filenames=filenames)
+ self._parse_config_files(filenames=filenames)
parse_configuration(self, self.command_options,
ignore_option_errors=ignore_option_errors)
@@ -980,7 +1170,7 @@ class Feature:
"Features are deprecated and will be removed in a future "
"version. See https://github.com/pypa/setuptools/issues/65."
)
- warnings.warn(msg, DeprecationWarning, stacklevel=3)
+ warnings.warn(msg, DistDeprecationWarning, stacklevel=3)
def __init__(
self, description, standard=False, available=True,
@@ -1069,3 +1259,7 @@ class Feature:
" doesn't contain any packages or modules under %s"
% (self.description, item, item)
)
+
+
+class DistDeprecationWarning(SetuptoolsDeprecationWarning):
+ """Class for warning about deprecations in dist in setuptools. Not ignored by default, unlike DeprecationWarning."""
diff --git a/setuptools/monkey.py b/setuptools/monkey.py
index 05a738b0..3c77f8cf 100644
--- a/setuptools/monkey.py
+++ b/setuptools/monkey.py
@@ -84,7 +84,7 @@ def patch_all():
warehouse = 'https://upload.pypi.org/legacy/'
distutils.config.PyPIRCCommand.DEFAULT_REPOSITORY = warehouse
- _patch_distribution_metadata_write_pkg_file()
+ _patch_distribution_metadata()
# Install Distribution throughout the distutils
for module in distutils.dist, distutils.core, distutils.cmd:
@@ -101,11 +101,11 @@ def patch_all():
patch_for_msvc_specialized_compiler()
-def _patch_distribution_metadata_write_pkg_file():
- """Patch write_pkg_file to also write Requires-Python/Requires-External"""
- distutils.dist.DistributionMetadata.write_pkg_file = (
- setuptools.dist.write_pkg_file
- )
+def _patch_distribution_metadata():
+ """Patch write_pkg_file and read_pkg_file for higher metadata standards"""
+ for attr in ('write_pkg_file', 'read_pkg_file', 'get_metadata_version'):
+ new_val = getattr(setuptools.dist, attr)
+ setattr(distutils.dist.DistributionMetadata, attr, new_val)
def patch_func(replacement, target_mod, func_name):
diff --git a/setuptools/package_index.py b/setuptools/package_index.py
index 1608b91a..7e9517ce 100644
--- a/setuptools/package_index.py
+++ b/setuptools/package_index.py
@@ -850,13 +850,16 @@ class PackageIndex(Environment):
def _download_svn(self, url, filename):
warnings.warn("SVN download support is deprecated", UserWarning)
+ def splituser(host):
+ user, delim, host = host.rpartition('@')
+ return user, host
url = url.split('#', 1)[0] # remove any fragment for svn's sake
creds = ''
if url.lower().startswith('svn:') and '@' in url:
scheme, netloc, path, p, q, f = urllib.parse.urlparse(url)
if not netloc and path.startswith('//') and '/' in path[2:]:
netloc, path = path[2:].split('/', 1)
- auth, host = urllib.parse.splituser(netloc)
+ auth, host = splituser(netloc)
if auth:
if ':' in auth:
user, pw = auth.split(':', 1)
@@ -1047,15 +1050,16 @@ class PyPIConfig(configparser.RawConfigParser):
def open_with_auth(url, opener=urllib.request.urlopen):
"""Open a urllib2 request, handling HTTP authentication"""
- scheme, netloc, path, params, query, frag = urllib.parse.urlparse(url)
+ parsed = urllib.parse.urlparse(url)
+ scheme, netloc, path, params, query, frag = parsed
# Double scheme does not raise on Mac OS X as revealed by a
# failing test. We would expect "nonnumeric port". Refs #20.
if netloc.endswith(':'):
raise http_client.InvalidURL("nonnumeric port: ''")
- if scheme in ('http', 'https'):
- auth, host = urllib.parse.splituser(netloc)
+ if scheme in ('http', 'https') and parsed.username:
+ auth = ':'.join((parsed.username, parsed.password))
else:
auth = None
@@ -1068,7 +1072,7 @@ def open_with_auth(url, opener=urllib.request.urlopen):
if auth:
auth = "Basic " + _encode_auth(auth)
- parts = scheme, host, path, params, query, frag
+ parts = scheme, parsed.hostname, path, params, query, frag
new_url = urllib.parse.urlunparse(parts)
request = urllib.request.Request(new_url)
request.add_header("Authorization", auth)
@@ -1082,7 +1086,7 @@ def open_with_auth(url, opener=urllib.request.urlopen):
# Put authentication info back into request URL if same host,
# so that links found on the page will work
s2, h2, path2, param2, query2, frag2 = urllib.parse.urlparse(fp.url)
- if s2 == scheme and h2 == host:
+ if s2 == scheme and h2 == parsed.hostname:
parts = s2, netloc, path2, param2, query2, frag2
fp.url = urllib.parse.urlunparse(parts)
diff --git a/setuptools/pep425tags.py b/setuptools/pep425tags.py
index 8bf4277d..48745a29 100644
--- a/setuptools/pep425tags.py
+++ b/setuptools/pep425tags.py
@@ -161,7 +161,7 @@ def is_manylinux1_compatible():
def get_darwin_arches(major, minor, machine):
"""Return a list of supported arches (including group arches) for
- the given major, minor and machine architecture of an macOS machine.
+ the given major, minor and machine architecture of a macOS machine.
"""
arches = []
diff --git a/setuptools/py36compat.py b/setuptools/py36compat.py
deleted file mode 100644
index f5279696..00000000
--- a/setuptools/py36compat.py
+++ /dev/null
@@ -1,82 +0,0 @@
-import sys
-from distutils.errors import DistutilsOptionError
-from distutils.util import strtobool
-from distutils.debug import DEBUG
-
-
-class Distribution_parse_config_files:
- """
- Mix-in providing forward-compatibility for functionality to be
- included by default on Python 3.7.
-
- Do not edit the code in this class except to update functionality
- as implemented in distutils.
- """
- def parse_config_files(self, filenames=None):
- from configparser import ConfigParser
-
- # Ignore install directory options if we have a venv
- if sys.prefix != sys.base_prefix:
- ignore_options = [
- 'install-base', 'install-platbase', 'install-lib',
- 'install-platlib', 'install-purelib', 'install-headers',
- 'install-scripts', 'install-data', 'prefix', 'exec-prefix',
- 'home', 'user', 'root']
- else:
- ignore_options = []
-
- ignore_options = frozenset(ignore_options)
-
- if filenames is None:
- filenames = self.find_config_files()
-
- if DEBUG:
- self.announce("Distribution.parse_config_files():")
-
- parser = ConfigParser(interpolation=None)
- for filename in filenames:
- if DEBUG:
- self.announce(" reading %s" % filename)
- parser.read(filename)
- for section in parser.sections():
- options = parser.options(section)
- opt_dict = self.get_option_dict(section)
-
- for opt in options:
- if opt != '__name__' and opt not in ignore_options:
- val = parser.get(section,opt)
- opt = opt.replace('-', '_')
- opt_dict[opt] = (filename, val)
-
- # Make the ConfigParser forget everything (so we retain
- # the original filenames that options come from)
- parser.__init__()
-
- # If there was a "global" section in the config file, use it
- # to set Distribution options.
-
- if 'global' in self.command_options:
- for (opt, (src, val)) in self.command_options['global'].items():
- alias = self.negative_opt.get(opt)
- try:
- if alias:
- setattr(self, alias, not strtobool(val))
- elif opt in ('verbose', 'dry_run'): # ugh!
- setattr(self, opt, strtobool(val))
- else:
- setattr(self, opt, val)
- except ValueError as msg:
- raise DistutilsOptionError(msg)
-
-
-if sys.version_info < (3,):
- # Python 2 behavior is sufficient
- class Distribution_parse_config_files:
- pass
-
-
-if False:
- # When updated behavior is available upstream,
- # disable override here.
- class Distribution_parse_config_files:
- pass
diff --git a/setuptools/ssl_support.py b/setuptools/ssl_support.py
index 6362f1f4..226db694 100644
--- a/setuptools/ssl_support.py
+++ b/setuptools/ssl_support.py
@@ -59,7 +59,7 @@ if not match_hostname:
def _dnsname_match(dn, hostname, max_wildcards=1):
"""Matching according to RFC 6125, section 6.4.3
- http://tools.ietf.org/html/rfc6125#section-6.4.3
+ https://tools.ietf.org/html/rfc6125#section-6.4.3
"""
pats = []
if not dn:
diff --git a/setuptools/tests/files.py b/setuptools/tests/files.py
index 465a6b41..bad2189d 100644
--- a/setuptools/tests/files.py
+++ b/setuptools/tests/files.py
@@ -6,10 +6,13 @@ import pkg_resources.py31compat
def build_files(file_defs, prefix=""):
"""
- Build a set of files/directories, as described by the file_defs dictionary.
+ Build a set of files/directories, as described by the
+ file_defs dictionary.
- Each key/value pair in the dictionary is interpreted as a filename/contents
- pair. If the contents value is a dictionary, a directory is created, and the
+ Each key/value pair in the dictionary is interpreted as
+ a filename/contents
+ pair. If the contents value is a dictionary, a directory
+ is created, and the
dictionary interpreted as the files within it, recursively.
For example:
diff --git a/setuptools/tests/server.py b/setuptools/tests/server.py
index 35312120..fc3a5975 100644
--- a/setuptools/tests/server.py
+++ b/setuptools/tests/server.py
@@ -19,10 +19,11 @@ class IndexServer(BaseHTTPServer.HTTPServer):
s.stop()
"""
- def __init__(self, server_address=('', 0),
+ def __init__(
+ self, server_address=('', 0),
RequestHandlerClass=SimpleHTTPServer.SimpleHTTPRequestHandler):
- BaseHTTPServer.HTTPServer.__init__(self, server_address,
- RequestHandlerClass)
+ BaseHTTPServer.HTTPServer.__init__(
+ self, server_address, RequestHandlerClass)
self._run = True
def start(self):
@@ -56,10 +57,11 @@ class MockServer(BaseHTTPServer.HTTPServer, threading.Thread):
A simple HTTP Server that records the requests made to it.
"""
- def __init__(self, server_address=('', 0),
+ def __init__(
+ self, server_address=('', 0),
RequestHandlerClass=RequestRecorder):
- BaseHTTPServer.HTTPServer.__init__(self, server_address,
- RequestHandlerClass)
+ BaseHTTPServer.HTTPServer.__init__(
+ self, server_address, RequestHandlerClass)
threading.Thread.__init__(self)
self.setDaemon(True)
self.requests = []
diff --git a/setuptools/tests/test_build_clib.py b/setuptools/tests/test_build_clib.py
index aebcc350..3779e679 100644
--- a/setuptools/tests/test_build_clib.py
+++ b/setuptools/tests/test_build_clib.py
@@ -1,6 +1,4 @@
import pytest
-import os
-import shutil
import mock
from distutils.errors import DistutilsSetupError
@@ -40,13 +38,14 @@ class TestBuildCLib:
# with that out of the way, let's see if the crude dependency
# system works
cmd.compiler = mock.MagicMock(spec=cmd.compiler)
- mock_newer.return_value = ([],[])
+ mock_newer.return_value = ([], [])
obj_deps = {'': ('global.h',), 'example.c': ('example.h',)}
- libs = [('example', {'sources': ['example.c'] ,'obj_deps': obj_deps})]
+ libs = [('example', {'sources': ['example.c'], 'obj_deps': obj_deps})]
cmd.build_libraries(libs)
- assert [['example.c', 'global.h', 'example.h']] in mock_newer.call_args[0]
+ assert [['example.c', 'global.h', 'example.h']] in \
+ mock_newer.call_args[0]
assert not cmd.compiler.compile.called
assert cmd.compiler.create_static_lib.call_count == 1
diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py
index 7b195e2c..82b44c89 100644
--- a/setuptools/tests/test_build_meta.py
+++ b/setuptools/tests/test_build_meta.py
@@ -2,16 +2,20 @@ from __future__ import unicode_literals
import os
import shutil
+import tarfile
import pytest
+from setuptools.build_meta import build_sdist
from .files import build_files
from .textwrap import DALS
+from . import py2_only
__metaclass__ = type
-futures = pytest.importorskip('concurrent.futures')
-importlib = pytest.importorskip('importlib')
+# Backports on Python 2.7
+import importlib
+from concurrent import futures
class BuildBackendBase:
@@ -108,13 +112,13 @@ def build_backend(tmpdir, request):
def test_get_requires_for_build_wheel(build_backend):
actual = build_backend.get_requires_for_build_wheel()
- expected = ['six', 'setuptools', 'wheel']
+ expected = ['six', 'wheel']
assert sorted(actual) == sorted(expected)
def test_get_requires_for_build_sdist(build_backend):
actual = build_backend.get_requires_for_build_sdist()
- expected = ['six', 'setuptools']
+ expected = ['six']
assert sorted(actual) == sorted(expected)
@@ -143,7 +147,7 @@ def test_prepare_metadata_for_build_wheel(build_backend):
assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA'))
-@pytest.mark.skipif('sys.version_info > (3,)')
+@py2_only
def test_prepare_metadata_for_build_wheel_with_str(build_backend):
dist_dir = os.path.abspath(str('pip-dist-info'))
os.makedirs(dist_dir)
@@ -168,15 +172,67 @@ def test_build_sdist_version_change(build_backend):
sdist_name = build_backend.build_sdist(sdist_into_directory)
assert os.path.isfile(os.path.join(sdist_into_directory, sdist_name))
- # if the setup.py changes subsequent call of the build meta should still succeed, given the
+ # if the setup.py changes subsequent call of the build meta
+ # should still succeed, given the
# sdist_directory the frontend specifies is empty
with open(os.path.abspath("setup.py"), 'rt') as file_handler:
content = file_handler.read()
with open(os.path.abspath("setup.py"), 'wt') as file_handler:
- file_handler.write(content.replace("version='0.0.0'", "version='0.0.1'"))
+ file_handler.write(
+ content.replace("version='0.0.0'", "version='0.0.1'"))
shutil.rmtree(sdist_into_directory)
os.makedirs(sdist_into_directory)
sdist_name = build_backend.build_sdist("out_sdist")
- assert os.path.isfile(os.path.join(os.path.abspath("out_sdist"), sdist_name))
+ assert os.path.isfile(
+ os.path.join(os.path.abspath("out_sdist"), sdist_name))
+
+
+def test_build_sdist_setup_py_exists(tmpdir_cwd):
+ # If build_sdist is called from a script other than setup.py,
+ # ensure setup.py is include
+ build_files(defns[0])
+ targz_path = build_sdist("temp")
+ with tarfile.open(os.path.join("temp", targz_path)) as tar:
+ assert any('setup.py' in name for name in tar.getnames())
+
+
+def test_build_sdist_setup_py_manifest_excluded(tmpdir_cwd):
+ # Ensure that MANIFEST.in can exclude setup.py
+ files = {
+ 'setup.py': DALS("""
+ __import__('setuptools').setup(
+ name='foo',
+ version='0.0.0',
+ py_modules=['hello']
+ )"""),
+ 'hello.py': '',
+ 'MANIFEST.in': DALS("""
+ exclude setup.py
+ """)
+ }
+
+ build_files(files)
+ targz_path = build_sdist("temp")
+ with tarfile.open(os.path.join("temp", targz_path)) as tar:
+ assert not any('setup.py' in name for name in tar.getnames())
+
+
+def test_build_sdist_builds_targz_even_if_zip_indicated(tmpdir_cwd):
+ files = {
+ 'setup.py': DALS("""
+ __import__('setuptools').setup(
+ name='foo',
+ version='0.0.0',
+ py_modules=['hello']
+ )"""),
+ 'hello.py': '',
+ 'setup.cfg': DALS("""
+ [sdist]
+ formats=zip
+ """)
+ }
+
+ build_files(files)
+ build_sdist("temp")
diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py
index 76759ec5..6b177709 100644
--- a/setuptools/tests/test_config.py
+++ b/setuptools/tests/test_config.py
@@ -1,10 +1,18 @@
+# -*- coding: UTF-8 -*-
+from __future__ import unicode_literals
+
import contextlib
import pytest
+
from distutils.errors import DistutilsOptionError, DistutilsFileError
from mock import patch
from setuptools.dist import Distribution, _Distribution
from setuptools.config import ConfigHandler, read_configuration
+from setuptools.extern.six.moves.configparser import InterpolationMissingOptionError
+from setuptools.tests import is_ascii
from . import py2_only, py3_only
+from .textwrap import DALS
+
class ErrConfigHandler(ConfigHandler):
"""Erroneous handler. Fails to implement required methods."""
@@ -16,12 +24,12 @@ def make_package_dir(name, base_dir, ns=False):
dir_package = dir_package.mkdir(dir_name)
init_file = None
if not ns:
- init_file = dir_package.join('__init__.py')
- init_file.write('')
+ init_file = dir_package.join('__init__.py')
+ init_file.write('')
return dir_package, init_file
-def fake_env(tmpdir, setup_cfg, setup_py=None, package_path='fake_package'):
+def fake_env(tmpdir, setup_cfg, setup_py=None, encoding='ascii', package_path='fake_package'):
if setup_py is None:
setup_py = (
@@ -31,7 +39,7 @@ def fake_env(tmpdir, setup_cfg, setup_py=None, package_path='fake_package'):
tmpdir.join('setup.py').write(setup_py)
config = tmpdir.join('setup.cfg')
- config.write(setup_cfg)
+ config.write(setup_cfg.encode(encoding), mode='wb')
package_dir, init_file = make_package_dir(package_path, tmpdir)
@@ -146,6 +154,24 @@ class TestMetadata:
assert metadata.download_url == 'http://test.test.com/test/'
assert metadata.maintainer_email == 'test@test.com'
+ def test_license_cfg(self, tmpdir):
+ fake_env(
+ tmpdir,
+ DALS("""
+ [metadata]
+ name=foo
+ version=0.0.1
+ license=Apache 2.0
+ """)
+ )
+
+ with get_dist(tmpdir) as dist:
+ metadata = dist.metadata
+
+ assert metadata.name == "foo"
+ assert metadata.version == "0.0.1"
+ assert metadata.license == "Apache 2.0"
+
def test_file_mixed(self, tmpdir):
fake_env(
@@ -288,7 +314,7 @@ class TestMetadata:
tmpdir.join('fake_package', 'version.txt').write('1.2.3\n4.5.6\n')
with pytest.raises(DistutilsOptionError):
with get_dist(tmpdir) as dist:
- _ = dist.metadata.version
+ dist.metadata.version
def test_version_with_package_dir_simple(self, tmpdir):
@@ -391,6 +417,89 @@ class TestMetadata:
with get_dist(tmpdir) as dist:
assert set(dist.metadata.classifiers) == expected
+ def test_deprecated_config_handlers(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'version = 10.1.1\n'
+ 'description = Some description\n'
+ 'requires = some, requirement\n'
+ )
+
+ with pytest.deprecated_call():
+ with get_dist(tmpdir) as dist:
+ metadata = dist.metadata
+
+ assert metadata.version == '10.1.1'
+ assert metadata.description == 'Some description'
+ assert metadata.requires == ['some', 'requirement']
+
+ def test_interpolation(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'description = %(message)s\n'
+ )
+ with pytest.raises(InterpolationMissingOptionError):
+ with get_dist(tmpdir):
+ pass
+
+ skip_if_not_ascii = pytest.mark.skipif(not is_ascii, reason='Test not supported with this locale')
+
+ @skip_if_not_ascii
+ def test_non_ascii_1(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'description = éàïôñ\n',
+ encoding='utf-8'
+ )
+ with pytest.raises(UnicodeDecodeError):
+ with get_dist(tmpdir):
+ pass
+
+ def test_non_ascii_2(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '# -*- coding: invalid\n'
+ )
+ with pytest.raises(LookupError):
+ with get_dist(tmpdir):
+ pass
+
+ def test_non_ascii_3(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '\n'
+ '# -*- coding: invalid\n'
+ )
+ with get_dist(tmpdir):
+ pass
+
+ @skip_if_not_ascii
+ def test_non_ascii_4(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '# -*- coding: utf-8\n'
+ '[metadata]\n'
+ 'description = éàïôñ\n',
+ encoding='utf-8'
+ )
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.description == 'éàïôñ'
+
+ @skip_if_not_ascii
+ def test_non_ascii_5(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '# vim: set fileencoding=iso-8859-15 :\n'
+ '[metadata]\n'
+ 'description = éàïôñ\n',
+ encoding='iso-8859-15'
+ )
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.description == 'éàïôñ'
+
class TestOptions:
@@ -414,7 +523,7 @@ class TestOptions:
'tests_require = mock==0.7.2; pytest\n'
'setup_requires = docutils>=0.3; spack ==1.1, ==1.3; there\n'
'dependency_links = http://some.com/here/1, '
- 'http://some.com/there/2\n'
+ 'http://some.com/there/2\n'
'python_requires = >=1.0, !=2.8\n'
'py_modules = module1, module2\n'
)
@@ -622,7 +731,7 @@ class TestOptions:
dir_sub_two, _ = make_package_dir('sub_two', dir_package, ns=True)
with get_dist(tmpdir) as dist:
- assert set(dist.packages) == {
+ assert set(dist.packages) == {
'fake_package', 'fake_package.sub_two', 'fake_package.sub_one'
}
@@ -674,7 +783,7 @@ class TestOptions:
tmpdir,
'[options.entry_points]\n'
'group1 = point1 = pack.module:func, '
- '.point2 = pack.module2:func_rest [rest]\n'
+ '.point2 = pack.module2:func_rest [rest]\n'
'group2 = point3 = pack.module:func2\n'
)
@@ -720,7 +829,10 @@ class TestOptions:
]
assert sorted(dist.data_files) == sorted(expected)
+
saved_dist_init = _Distribution.__init__
+
+
class TestExternalSetters:
# During creation of the setuptools Distribution() object, we call
# the init of the parent distutils Distribution object via
diff --git a/setuptools/tests/test_depends.py b/setuptools/tests/test_depends.py
index e0cfa880..bff1dfb1 100644
--- a/setuptools/tests/test_depends.py
+++ b/setuptools/tests/test_depends.py
@@ -5,12 +5,12 @@ from setuptools import depends
class TestGetModuleConstant:
- def test_basic(self):
- """
- Invoke get_module_constant on a module in
- the test package.
- """
- mod_name = 'setuptools.tests.mod_with_constant'
- val = depends.get_module_constant(mod_name, 'value')
- assert val == 'three, sir!'
- assert 'setuptools.tests.mod_with_constant' not in sys.modules
+ def test_basic(self):
+ """
+ Invoke get_module_constant on a module in
+ the test package.
+ """
+ mod_name = 'setuptools.tests.mod_with_constant'
+ val = depends.get_module_constant(mod_name, 'value')
+ assert val == 'three, sir!'
+ assert 'setuptools.tests.mod_with_constant' not in sys.modules
diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py
index 5162e1c9..390c3dfc 100644
--- a/setuptools/tests/test_dist.py
+++ b/setuptools/tests/test_dist.py
@@ -3,10 +3,11 @@
from __future__ import unicode_literals
import io
-
+from setuptools.dist import DistDeprecationWarning, _get_unpatched
from setuptools import Distribution
from setuptools.extern.six.moves.urllib.request import pathname2url
from setuptools.extern.six.moves.urllib_parse import urljoin
+from setuptools.extern import six
from .textwrap import DALS
from .test_easy_install import make_nspkg_sdist
@@ -56,6 +57,125 @@ def test_dist_fetch_build_egg(tmpdir):
assert [dist.key for dist in resolved_dists if dist] == reqs
+def test_dist__get_unpatched_deprecated():
+ pytest.warns(DistDeprecationWarning, _get_unpatched, [""])
+
+
+def __read_test_cases():
+ # Metadata version 1.0
+ base_attrs = {
+ "name": "package",
+ "version": "0.0.1",
+ "author": "Foo Bar",
+ "author_email": "foo@bar.net",
+ "long_description": "Long\ndescription",
+ "description": "Short description",
+ "keywords": ["one", "two"]
+ }
+
+ def merge_dicts(d1, d2):
+ d1 = d1.copy()
+ d1.update(d2)
+
+ return d1
+
+ test_cases = [
+ ('Metadata version 1.0', base_attrs.copy()),
+ ('Metadata version 1.1: Provides', merge_dicts(base_attrs, {
+ 'provides': ['package']
+ })),
+ ('Metadata version 1.1: Obsoletes', merge_dicts(base_attrs, {
+ 'obsoletes': ['foo']
+ })),
+ ('Metadata version 1.1: Classifiers', merge_dicts(base_attrs, {
+ 'classifiers': [
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.7',
+ 'License :: OSI Approved :: MIT License',
+ ]})),
+ ('Metadata version 1.1: Download URL', merge_dicts(base_attrs, {
+ 'download_url': 'https://example.com'
+ })),
+ ('Metadata Version 1.2: Requires-Python', merge_dicts(base_attrs, {
+ 'python_requires': '>=3.7'
+ })),
+ pytest.param(
+ 'Metadata Version 1.2: Project-Url',
+ merge_dicts(base_attrs, {
+ 'project_urls': {
+ 'Foo': 'https://example.bar'
+ }
+ }), marks=pytest.mark.xfail(
+ reason="Issue #1578: project_urls not read"
+ )),
+ ('Metadata Version 2.1: Long Description Content Type',
+ merge_dicts(base_attrs, {
+ 'long_description_content_type': 'text/x-rst; charset=UTF-8'
+ })),
+ pytest.param(
+ 'Metadata Version 2.1: Provides Extra',
+ merge_dicts(base_attrs, {
+ 'provides_extras': ['foo', 'bar']
+ }), marks=pytest.mark.xfail(reason="provides_extras not read")),
+ ('Missing author, missing author e-mail',
+ {'name': 'foo', 'version': '1.0.0'}),
+ ('Missing author',
+ {'name': 'foo',
+ 'version': '1.0.0',
+ 'author_email': 'snorri@sturluson.name'}),
+ ('Missing author e-mail',
+ {'name': 'foo',
+ 'version': '1.0.0',
+ 'author': 'Snorri Sturluson'}),
+ ('Missing author',
+ {'name': 'foo',
+ 'version': '1.0.0',
+ 'author': 'Snorri Sturluson'}),
+ ]
+
+ return test_cases
+
+
+@pytest.mark.parametrize('name,attrs', __read_test_cases())
+def test_read_metadata(name, attrs):
+ dist = Distribution(attrs)
+ metadata_out = dist.metadata
+ dist_class = metadata_out.__class__
+
+ # Write to PKG_INFO and then load into a new metadata object
+ if six.PY2:
+ PKG_INFO = io.BytesIO()
+ else:
+ PKG_INFO = io.StringIO()
+
+ metadata_out.write_pkg_file(PKG_INFO)
+
+ PKG_INFO.seek(0)
+ metadata_in = dist_class()
+ metadata_in.read_pkg_file(PKG_INFO)
+
+ tested_attrs = [
+ ('name', dist_class.get_name),
+ ('version', dist_class.get_version),
+ ('author', dist_class.get_contact),
+ ('author_email', dist_class.get_contact_email),
+ ('metadata_version', dist_class.get_metadata_version),
+ ('provides', dist_class.get_provides),
+ ('description', dist_class.get_description),
+ ('download_url', dist_class.get_download_url),
+ ('keywords', dist_class.get_keywords),
+ ('platforms', dist_class.get_platforms),
+ ('obsoletes', dist_class.get_obsoletes),
+ ('requires', dist_class.get_requires),
+ ('classifiers', dist_class.get_classifiers),
+ ('project_urls', lambda s: getattr(s, 'project_urls', {})),
+ ('provides_extras', lambda s: getattr(s, 'provides_extras', set())),
+ ]
+
+ for attr, getter in tested_attrs:
+ assert getter(metadata_in) == getter(metadata_out)
+
+
def __maintainer_test_cases():
attrs = {"name": "package",
"version": "1.0",
diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py
index b0cc4c9f..c3fd1c6e 100644
--- a/setuptools/tests/test_easy_install.py
+++ b/setuptools/tests/test_easy_install.py
@@ -15,7 +15,9 @@ import distutils.errors
import io
import zipfile
import mock
-
+from setuptools.command.easy_install import (
+ EasyInstallDeprecationWarning, ScriptWriter, WindowsScriptWriter,
+)
import time
from setuptools.extern import six
from setuptools.extern.six.moves import urllib
@@ -287,6 +289,22 @@ class TestEasyInstallTest:
cmd.easy_install(sdist_script)
assert (target / 'mypkg_script').exists()
+ def test_dist_get_script_args_deprecated(self):
+ with pytest.warns(EasyInstallDeprecationWarning):
+ ScriptWriter.get_script_args(None, None)
+
+ def test_dist_get_script_header_deprecated(self):
+ with pytest.warns(EasyInstallDeprecationWarning):
+ ScriptWriter.get_script_header("")
+
+ def test_dist_get_writer_deprecated(self):
+ with pytest.warns(EasyInstallDeprecationWarning):
+ ScriptWriter.get_writer(None)
+
+ def test_dist_WindowsScriptWriter_get_writer_deprecated(self):
+ with pytest.warns(EasyInstallDeprecationWarning):
+ WindowsScriptWriter.get_writer()
+
@pytest.mark.filterwarnings('ignore:Unbuilt egg')
class TestPTHFileWriter:
diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py
index 7c862e61..db9c3873 100644
--- a/setuptools/tests/test_egg_info.py
+++ b/setuptools/tests/test_egg_info.py
@@ -1,4 +1,3 @@
-import datetime
import sys
import ast
import os
@@ -7,7 +6,9 @@ import re
import stat
import time
-from setuptools.command.egg_info import egg_info, manifest_maker
+from setuptools.command.egg_info import (
+ egg_info, manifest_maker, EggInfoDeprecationWarning, get_pkg_info_revision,
+)
from setuptools.dist import Distribution
from setuptools.extern.six.moves import map
@@ -148,6 +149,37 @@ class TestEggInfo:
]
assert sorted(actual) == expected
+ def test_license_is_a_string(self, tmpdir_cwd, env):
+ setup_config = DALS("""
+ [metadata]
+ name=foo
+ version=0.0.1
+ license=file:MIT
+ """)
+
+ setup_script = DALS("""
+ from setuptools import setup
+
+ setup()
+ """)
+
+ build_files({'setup.py': setup_script,
+ 'setup.cfg': setup_config})
+
+ # This command should fail with a ValueError, but because it's
+ # currently configured to use a subprocess, the actual traceback
+ # object is lost and we need to parse it from stderr
+ with pytest.raises(AssertionError) as exc:
+ self._run_egg_info_command(tmpdir_cwd, env)
+
+ # Hopefully this is not too fragile: the only argument to the
+ # assertion error should be a traceback, ending with:
+ # ValueError: ....
+ #
+ # assert not 1
+ tb = exc.value.args[0].split('\n')
+ assert tb[-3].lstrip().startswith('ValueError')
+
def test_rebuilt(self, tmpdir_cwd, env):
"""Ensure timestamps are updated when the command is re-run."""
self._create_project()
@@ -618,6 +650,20 @@ class TestEggInfo:
for msg in fixtures:
assert manifest_maker._should_suppress_warning(msg)
+ def test_egg_info_includes_setup_py(self, tmpdir_cwd):
+ self._create_project()
+ dist = Distribution({"name": "foo", "version": "0.0.1"})
+ dist.script_name = "non_setup.py"
+ egg_info_instance = egg_info(dist)
+ egg_info_instance.finalize_options()
+ egg_info_instance.run()
+
+ assert 'setup.py' in egg_info_instance.filelist.files
+
+ with open(egg_info_instance.egg_info + "/SOURCES.txt") as f:
+ sources = f.read().split('\n')
+ assert 'setup.py' in sources
+
def _run_egg_info_command(self, tmpdir_cwd, env, cmd=None, output=None):
environ = os.environ.copy().update(
HOME=env.paths['home'],
@@ -632,8 +678,8 @@ class TestEggInfo:
data_stream=1,
env=environ,
)
- if code:
- raise AssertionError(data)
+ assert not code, data
+
if output:
assert output in data
@@ -652,3 +698,52 @@ class TestEggInfo:
with open(os.path.join(egg_info_dir, 'PKG-INFO')) as pkginfo_file:
pkg_info_lines = pkginfo_file.read().split('\n')
assert 'Version: 0.0.0.dev0' in pkg_info_lines
+
+ def test_get_pkg_info_revision_deprecated(self):
+ pytest.warns(EggInfoDeprecationWarning, get_pkg_info_revision)
+
+ EGG_INFO_TESTS = (
+ # Check for issue #1136: invalid string type when
+ # reading declarative `setup.cfg` under Python 2.
+ {
+ 'setup.py': DALS(
+ """
+ from setuptools import setup
+ setup(
+ name="foo",
+ )
+ """),
+ 'setup.cfg': DALS(
+ """
+ [options]
+ package_dir =
+ = src
+ """),
+ 'src': {},
+ },
+ # Check Unicode can be used in `setup.py` under Python 2.
+ {
+ 'setup.py': DALS(
+ """
+ # -*- coding: utf-8 -*-
+ from __future__ import unicode_literals
+ from setuptools import setup, find_packages
+ setup(
+ name="foo",
+ package_dir={'': 'src'},
+ )
+ """),
+ 'src': {},
+ }
+ )
+
+ @pytest.mark.parametrize('package_files', EGG_INFO_TESTS)
+ def test_egg_info(self, tmpdir_cwd, env, package_files):
+ """
+ """
+ build_files(package_files)
+ code, data = environment.run_setup_py(
+ cmd=['egg_info'],
+ data_stream=1,
+ )
+ assert not code, data
diff --git a/setuptools/tests/test_find_packages.py b/setuptools/tests/test_find_packages.py
index b08f91c7..ab26b4f1 100644
--- a/setuptools/tests/test_find_packages.py
+++ b/setuptools/tests/test_find_packages.py
@@ -12,10 +12,10 @@ from . import py3_only
from setuptools.extern.six import PY3
from setuptools import find_packages
if PY3:
- from setuptools import find_namespace_packages
+ from setuptools import find_namespace_packages
-# modeled after CPython's test.support.can_symlink
+# modeled after CPython's test.support.can_symlink
def can_symlink():
TESTFN = tempfile.mktemp()
symlink_path = TESTFN + "can_symlink"
@@ -164,12 +164,14 @@ class TestFindPackages:
def test_pep420_ns_package_no_includes(self):
packages = find_namespace_packages(
self.dist_dir, exclude=['pkg.subpkg.assets'])
- self._assert_packages(packages, ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg'])
+ self._assert_packages(
+ packages, ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg'])
@py3_only
def test_pep420_ns_package_no_includes_or_excludes(self):
packages = find_namespace_packages(self.dist_dir)
- expected = ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg', 'pkg.subpkg.assets']
+ expected = [
+ 'docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg', 'pkg.subpkg.assets']
self._assert_packages(packages, expected)
@py3_only
@@ -185,4 +187,3 @@ class TestFindPackages:
shutil.rmtree(os.path.join(self.dist_dir, 'pkg/subpkg/assets'))
packages = find_namespace_packages(self.dist_dir)
self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg'])
-
diff --git a/setuptools/tests/test_install_scripts.py b/setuptools/tests/test_install_scripts.py
index 727ad65b..4338c792 100644
--- a/setuptools/tests/test_install_scripts.py
+++ b/setuptools/tests/test_install_scripts.py
@@ -64,7 +64,8 @@ class TestInstallScripts:
@pytest.mark.skipif(sys.platform == 'win32', reason='non-Windows only')
def test_executable_with_spaces_escaping_unix(self, tmpdir):
"""
- Ensure that shebang on Unix is not quoted, even when a value with spaces
+ Ensure that shebang on Unix is not quoted, even when
+ a value with spaces
is specified using --executable.
"""
expected = '#!%s\n' % self.unix_spaces_exe
@@ -77,7 +78,8 @@ class TestInstallScripts:
@pytest.mark.skipif(sys.platform != 'win32', reason='Windows only')
def test_executable_arg_escaping_win32(self, tmpdir):
"""
- Ensure that shebang on Windows is quoted when getting a path with spaces
+ Ensure that shebang on Windows is quoted when
+ getting a path with spaces
from --executable, that is itself properly quoted.
"""
expected = '#!"%s"\n' % self.win32_exe
diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py
index 3a9a6c50..e54f3209 100644
--- a/setuptools/tests/test_integration.py
+++ b/setuptools/tests/test_integration.py
@@ -6,6 +6,11 @@ Try to install a few packages.
import glob
import os
import sys
+import re
+import subprocess
+import functools
+import tarfile
+import zipfile
from setuptools.extern.six.moves import urllib
import pytest
@@ -114,15 +119,12 @@ def test_pyuri(install_context):
assert os.path.exists(os.path.join(pyuri.location, 'pyuri', 'uri.regex'))
-import re
-import subprocess
-import functools
-import tarfile, zipfile
+build_deps = ['appdirs', 'packaging', 'pyparsing', 'six']
-build_deps = ['appdirs', 'packaging', 'pyparsing', 'six']
@pytest.mark.parametrize("build_dep", build_deps)
-@pytest.mark.skipif(sys.version_info < (3, 6), reason='run only on late versions')
+@pytest.mark.skipif(
+ sys.version_info < (3, 6), reason='run only on late versions')
def test_build_deps_on_distutils(request, tmpdir_factory, build_dep):
"""
All setuptools build dependencies must build without
@@ -149,13 +151,16 @@ def install(pkg_dir, install_dir):
breaker.write('raise ImportError()')
cmd = [sys.executable, 'setup.py', 'install', '--prefix', install_dir]
env = dict(os.environ, PYTHONPATH=pkg_dir)
- output = subprocess.check_output(cmd, cwd=pkg_dir, env=env, stderr=subprocess.STDOUT)
+ output = subprocess.check_output(
+ cmd, cwd=pkg_dir, env=env, stderr=subprocess.STDOUT)
return output.decode('utf-8')
def download_and_extract(request, req, target):
- cmd = [sys.executable, '-m', 'pip', 'download', '--no-deps',
- '--no-binary', ':all:', req]
+ cmd = [
+ sys.executable, '-m', 'pip', 'download', '--no-deps',
+ '--no-binary', ':all:', req,
+ ]
output = subprocess.check_output(cmd, encoding='utf-8')
filename = re.search('Saved (.*)', output).group(1)
request.addfinalizer(functools.partial(os.remove, filename))
diff --git a/setuptools/tests/test_manifest.py b/setuptools/tests/test_manifest.py
index c9533dda..2a0e9c86 100644
--- a/setuptools/tests/test_manifest.py
+++ b/setuptools/tests/test_manifest.py
@@ -20,8 +20,6 @@ import pytest
__metaclass__ = type
-py3_only = pytest.mark.xfail(six.PY2, reason="Test runs on Python 3 only")
-
def make_local_path(s):
"""Converts '/' in a string to os.sep"""
@@ -75,7 +73,9 @@ translate_specs = [
# Glob matching
('*.txt', ['foo.txt', 'bar.txt'], ['foo/foo.txt']),
- ('dir/*.txt', ['dir/foo.txt', 'dir/bar.txt', 'dir/.txt'], ['notdir/foo.txt']),
+ (
+ 'dir/*.txt',
+ ['dir/foo.txt', 'dir/bar.txt', 'dir/.txt'], ['notdir/foo.txt']),
('*/*.py', ['bin/start.py'], []),
('docs/page-?.txt', ['docs/page-9.txt'], ['docs/page-10.txt']),
@@ -244,77 +244,77 @@ class TestManifestTest(TempDirTestCase):
def test_exclude(self):
"""Include everything in app/ except the text files"""
- l = make_local_path
+ ml = make_local_path
self.make_manifest(
"""
include app/*
exclude app/*.txt
""")
- files = default_files | set([l('app/c.rst')])
+ files = default_files | set([ml('app/c.rst')])
assert files == self.get_files()
def test_include_multiple(self):
"""Include with multiple patterns."""
- l = make_local_path
+ ml = make_local_path
self.make_manifest("include app/*.txt app/static/*")
files = default_files | set([
- l('app/a.txt'), l('app/b.txt'),
- l('app/static/app.js'), l('app/static/app.js.map'),
- l('app/static/app.css'), l('app/static/app.css.map')])
+ ml('app/a.txt'), ml('app/b.txt'),
+ ml('app/static/app.js'), ml('app/static/app.js.map'),
+ ml('app/static/app.css'), ml('app/static/app.css.map')])
assert files == self.get_files()
def test_graft(self):
"""Include the whole app/static/ directory."""
- l = make_local_path
+ ml = make_local_path
self.make_manifest("graft app/static")
files = default_files | set([
- l('app/static/app.js'), l('app/static/app.js.map'),
- l('app/static/app.css'), l('app/static/app.css.map')])
+ ml('app/static/app.js'), ml('app/static/app.js.map'),
+ ml('app/static/app.css'), ml('app/static/app.css.map')])
assert files == self.get_files()
def test_graft_glob_syntax(self):
"""Include the whole app/static/ directory."""
- l = make_local_path
+ ml = make_local_path
self.make_manifest("graft */static")
files = default_files | set([
- l('app/static/app.js'), l('app/static/app.js.map'),
- l('app/static/app.css'), l('app/static/app.css.map')])
+ ml('app/static/app.js'), ml('app/static/app.js.map'),
+ ml('app/static/app.css'), ml('app/static/app.css.map')])
assert files == self.get_files()
def test_graft_global_exclude(self):
"""Exclude all *.map files in the project."""
- l = make_local_path
+ ml = make_local_path
self.make_manifest(
"""
graft app/static
global-exclude *.map
""")
files = default_files | set([
- l('app/static/app.js'), l('app/static/app.css')])
+ ml('app/static/app.js'), ml('app/static/app.css')])
assert files == self.get_files()
def test_global_include(self):
"""Include all *.rst, *.js, and *.css files in the whole tree."""
- l = make_local_path
+ ml = make_local_path
self.make_manifest(
"""
global-include *.rst *.js *.css
""")
files = default_files | set([
- '.hidden.rst', 'testing.rst', l('app/c.rst'),
- l('app/static/app.js'), l('app/static/app.css')])
+ '.hidden.rst', 'testing.rst', ml('app/c.rst'),
+ ml('app/static/app.js'), ml('app/static/app.css')])
assert files == self.get_files()
def test_graft_prune(self):
"""Include all files in app/, except for the whole app/static/ dir."""
- l = make_local_path
+ ml = make_local_path
self.make_manifest(
"""
graft app
prune app/static
""")
files = default_files | set([
- l('app/a.txt'), l('app/b.txt'), l('app/c.rst')])
+ ml('app/a.txt'), ml('app/b.txt'), ml('app/c.rst')])
assert files == self.get_files()
@@ -370,7 +370,7 @@ class TestFileListTest(TempDirTestCase):
def test_process_template_line(self):
# testing all MANIFEST.in template patterns
file_list = FileList()
- l = make_local_path
+ ml = make_local_path
# simulated file list
self.make_files([
@@ -378,16 +378,16 @@ class TestFileListTest(TempDirTestCase):
'buildout.cfg',
# filelist does not filter out VCS directories,
# it's sdist that does
- l('.hg/last-message.txt'),
- l('global/one.txt'),
- l('global/two.txt'),
- l('global/files.x'),
- l('global/here.tmp'),
- l('f/o/f.oo'),
- l('dir/graft-one'),
- l('dir/dir2/graft2'),
- l('dir3/ok'),
- l('dir3/sub/ok.txt'),
+ ml('.hg/last-message.txt'),
+ ml('global/one.txt'),
+ ml('global/two.txt'),
+ ml('global/files.x'),
+ ml('global/here.tmp'),
+ ml('f/o/f.oo'),
+ ml('dir/graft-one'),
+ ml('dir/dir2/graft2'),
+ ml('dir3/ok'),
+ ml('dir3/sub/ok.txt'),
])
MANIFEST_IN = DALS("""\
@@ -414,12 +414,12 @@ class TestFileListTest(TempDirTestCase):
'buildout.cfg',
'four.txt',
'ok',
- l('.hg/last-message.txt'),
- l('dir/graft-one'),
- l('dir/dir2/graft2'),
- l('f/o/f.oo'),
- l('global/one.txt'),
- l('global/two.txt'),
+ ml('.hg/last-message.txt'),
+ ml('dir/graft-one'),
+ ml('dir/dir2/graft2'),
+ ml('f/o/f.oo'),
+ ml('global/one.txt'),
+ ml('global/two.txt'),
]
file_list.sort()
@@ -476,10 +476,10 @@ class TestFileListTest(TempDirTestCase):
assert False, "Should have thrown an error"
def test_include(self):
- l = make_local_path
+ ml = make_local_path
# include
file_list = FileList()
- self.make_files(['a.py', 'b.txt', l('d/c.py')])
+ self.make_files(['a.py', 'b.txt', ml('d/c.py')])
file_list.process_template_line('include *.py')
file_list.sort()
@@ -492,42 +492,42 @@ class TestFileListTest(TempDirTestCase):
self.assertWarnings()
def test_exclude(self):
- l = make_local_path
+ ml = make_local_path
# exclude
file_list = FileList()
- file_list.files = ['a.py', 'b.txt', l('d/c.py')]
+ file_list.files = ['a.py', 'b.txt', ml('d/c.py')]
file_list.process_template_line('exclude *.py')
file_list.sort()
- assert file_list.files == ['b.txt', l('d/c.py')]
+ assert file_list.files == ['b.txt', ml('d/c.py')]
self.assertNoWarnings()
file_list.process_template_line('exclude *.rb')
file_list.sort()
- assert file_list.files == ['b.txt', l('d/c.py')]
+ assert file_list.files == ['b.txt', ml('d/c.py')]
self.assertWarnings()
def test_global_include(self):
- l = make_local_path
+ ml = make_local_path
# global-include
file_list = FileList()
- self.make_files(['a.py', 'b.txt', l('d/c.py')])
+ self.make_files(['a.py', 'b.txt', ml('d/c.py')])
file_list.process_template_line('global-include *.py')
file_list.sort()
- assert file_list.files == ['a.py', l('d/c.py')]
+ assert file_list.files == ['a.py', ml('d/c.py')]
self.assertNoWarnings()
file_list.process_template_line('global-include *.rb')
file_list.sort()
- assert file_list.files == ['a.py', l('d/c.py')]
+ assert file_list.files == ['a.py', ml('d/c.py')]
self.assertWarnings()
def test_global_exclude(self):
- l = make_local_path
+ ml = make_local_path
# global-exclude
file_list = FileList()
- file_list.files = ['a.py', 'b.txt', l('d/c.py')]
+ file_list.files = ['a.py', 'b.txt', ml('d/c.py')]
file_list.process_template_line('global-exclude *.py')
file_list.sort()
@@ -540,65 +540,65 @@ class TestFileListTest(TempDirTestCase):
self.assertWarnings()
def test_recursive_include(self):
- l = make_local_path
+ ml = make_local_path
# recursive-include
file_list = FileList()
- self.make_files(['a.py', l('d/b.py'), l('d/c.txt'), l('d/d/e.py')])
+ self.make_files(['a.py', ml('d/b.py'), ml('d/c.txt'), ml('d/d/e.py')])
file_list.process_template_line('recursive-include d *.py')
file_list.sort()
- assert file_list.files == [l('d/b.py'), l('d/d/e.py')]
+ assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')]
self.assertNoWarnings()
file_list.process_template_line('recursive-include e *.py')
file_list.sort()
- assert file_list.files == [l('d/b.py'), l('d/d/e.py')]
+ assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')]
self.assertWarnings()
def test_recursive_exclude(self):
- l = make_local_path
+ ml = make_local_path
# recursive-exclude
file_list = FileList()
- file_list.files = ['a.py', l('d/b.py'), l('d/c.txt'), l('d/d/e.py')]
+ file_list.files = ['a.py', ml('d/b.py'), ml('d/c.txt'), ml('d/d/e.py')]
file_list.process_template_line('recursive-exclude d *.py')
file_list.sort()
- assert file_list.files == ['a.py', l('d/c.txt')]
+ assert file_list.files == ['a.py', ml('d/c.txt')]
self.assertNoWarnings()
file_list.process_template_line('recursive-exclude e *.py')
file_list.sort()
- assert file_list.files == ['a.py', l('d/c.txt')]
+ assert file_list.files == ['a.py', ml('d/c.txt')]
self.assertWarnings()
def test_graft(self):
- l = make_local_path
+ ml = make_local_path
# graft
file_list = FileList()
- self.make_files(['a.py', l('d/b.py'), l('d/d/e.py'), l('f/f.py')])
+ self.make_files(['a.py', ml('d/b.py'), ml('d/d/e.py'), ml('f/f.py')])
file_list.process_template_line('graft d')
file_list.sort()
- assert file_list.files == [l('d/b.py'), l('d/d/e.py')]
+ assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')]
self.assertNoWarnings()
file_list.process_template_line('graft e')
file_list.sort()
- assert file_list.files == [l('d/b.py'), l('d/d/e.py')]
+ assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')]
self.assertWarnings()
def test_prune(self):
- l = make_local_path
+ ml = make_local_path
# prune
file_list = FileList()
- file_list.files = ['a.py', l('d/b.py'), l('d/d/e.py'), l('f/f.py')]
+ file_list.files = ['a.py', ml('d/b.py'), ml('d/d/e.py'), ml('f/f.py')]
file_list.process_template_line('prune d')
file_list.sort()
- assert file_list.files == ['a.py', l('f/f.py')]
+ assert file_list.files == ['a.py', ml('f/f.py')]
self.assertNoWarnings()
file_list.process_template_line('prune e')
file_list.sort()
- assert file_list.files == ['a.py', l('f/f.py')]
+ assert file_list.files == ['a.py', ml('f/f.py')]
self.assertWarnings()
diff --git a/setuptools/tests/test_msvc.py b/setuptools/tests/test_msvc.py
index 32d7a907..24e38ea8 100644
--- a/setuptools/tests/test_msvc.py
+++ b/setuptools/tests/test_msvc.py
@@ -49,7 +49,8 @@ def mock_reg(hkcu=None, hklm=None):
for k in hive if k.startswith(key.lower())
)
- return mock.patch.multiple(distutils.msvc9compiler.Reg,
+ return mock.patch.multiple(
+ distutils.msvc9compiler.Reg,
read_keys=read_keys, read_values=read_values)
@@ -61,7 +62,7 @@ class TestModulePatch:
"""
key_32 = r'software\microsoft\devdiv\vcforpython\9.0\installdir'
- key_64 = r'software\wow6432node\microsoft\devdiv\vcforpython\9.0\installdir'
+ key_64 = key_32.replace(r'\microsoft', r'\wow6432node\microsoft')
def test_patched(self):
"Test the module is actually patched"
diff --git a/setuptools/tests/test_namespaces.py b/setuptools/tests/test_namespaces.py
index da19bd79..f937d981 100644
--- a/setuptools/tests/test_namespaces.py
+++ b/setuptools/tests/test_namespaces.py
@@ -1,6 +1,5 @@
from __future__ import absolute_import, unicode_literals
-import os
import sys
import subprocess
@@ -12,7 +11,7 @@ from setuptools.command import test
class TestNamespaces:
- @pytest.mark.xfail(
+ @pytest.mark.skipif(
sys.version_info < (3, 5),
reason="Requires importlib.util.module_from_spec",
)
diff --git a/setuptools/tests/test_packageindex.py b/setuptools/tests/test_packageindex.py
index 63b92946..ab371884 100644
--- a/setuptools/tests/test_packageindex.py
+++ b/setuptools/tests/test_packageindex.py
@@ -6,6 +6,8 @@ import distutils.errors
from setuptools.extern import six
from setuptools.extern.six.moves import urllib, http_client
+import mock
+import pytest
import pkg_resources
import setuptools.package_index
@@ -42,7 +44,10 @@ class TestPackageIndex:
hosts=('www.example.com',)
)
- url = 'url:%20https://svn.plone.org/svn/collective/inquant.contentmirror.plone/trunk'
+ url = (
+ 'url:%20https://svn.plone.org/svn'
+ '/collective/inquant.contentmirror.plone/trunk'
+ )
try:
v = index.open_url(url)
except Exception as v:
@@ -61,9 +66,9 @@ class TestPackageIndex:
index.opener = _urlopen
url = 'http://example.com'
try:
- v = index.open_url(url)
- except Exception as v:
- assert 'line' in str(v)
+ index.open_url(url)
+ except Exception as exc:
+ assert 'line' in str(exc)
else:
raise AssertionError('Should have raise here!')
@@ -81,7 +86,11 @@ class TestPackageIndex:
index.open_url(url)
except distutils.errors.DistutilsError as error:
msg = six.text_type(error)
- assert 'nonnumeric port' in msg or 'getaddrinfo failed' in msg or 'Name or service not known' in msg
+ assert (
+ 'nonnumeric port' in msg
+ or 'getaddrinfo failed' in msg
+ or 'Name or service not known' in msg
+ )
return
raise RuntimeError("Did not raise")
@@ -223,6 +232,61 @@ class TestPackageIndex:
assert dists[0].version == ''
assert dists[1].version == vc
+ def test_download_git_with_rev(self, tmpdir):
+ url = 'git+https://github.example/group/project@master#egg=foo'
+ index = setuptools.package_index.PackageIndex()
+
+ with mock.patch("os.system") as os_system_mock:
+ result = index.download(url, str(tmpdir))
+
+ os_system_mock.assert_called()
+
+ expected_dir = str(tmpdir / 'project@master')
+ expected = (
+ 'git clone --quiet '
+ 'https://github.example/group/project {expected_dir}'
+ ).format(**locals())
+ first_call_args = os_system_mock.call_args_list[0][0]
+ assert first_call_args == (expected,)
+
+ tmpl = '(cd {expected_dir} && git checkout --quiet master)'
+ expected = tmpl.format(**locals())
+ assert os_system_mock.call_args_list[1][0] == (expected,)
+ assert result == expected_dir
+
+ def test_download_git_no_rev(self, tmpdir):
+ url = 'git+https://github.example/group/project#egg=foo'
+ index = setuptools.package_index.PackageIndex()
+
+ with mock.patch("os.system") as os_system_mock:
+ result = index.download(url, str(tmpdir))
+
+ os_system_mock.assert_called()
+
+ expected_dir = str(tmpdir / 'project')
+ expected = (
+ 'git clone --quiet '
+ 'https://github.example/group/project {expected_dir}'
+ ).format(**locals())
+ os_system_mock.assert_called_once_with(expected)
+
+ def test_download_svn(self, tmpdir):
+ url = 'svn+https://svn.example/project#egg=foo'
+ index = setuptools.package_index.PackageIndex()
+
+ with pytest.warns(UserWarning):
+ with mock.patch("os.system") as os_system_mock:
+ result = index.download(url, str(tmpdir))
+
+ os_system_mock.assert_called()
+
+ expected_dir = str(tmpdir / 'project')
+ expected = (
+ 'svn checkout -q '
+ 'svn+https://svn.example/project {expected_dir}'
+ ).format(**locals())
+ os_system_mock.assert_called_once_with(expected)
+
class TestContentCheckers:
def test_md5(self):
diff --git a/setuptools/tests/test_pep425tags.py b/setuptools/tests/test_pep425tags.py
index f558a0d8..30afdec7 100644
--- a/setuptools/tests/test_pep425tags.py
+++ b/setuptools/tests/test_pep425tags.py
@@ -32,7 +32,9 @@ class TestPEP425Tags:
if sys.version_info < (3, 3):
config_vars.update({'Py_UNICODE_SIZE': 2})
mock_gcf = self.mock_get_config_var(**config_vars)
- with patch('setuptools.pep425tags.sysconfig.get_config_var', mock_gcf):
+ with patch(
+ 'setuptools.pep425tags.sysconfig.get_config_var',
+ mock_gcf):
abi_tag = pep425tags.get_abi_tag()
assert abi_tag == base + flags
diff --git a/setuptools/tests/test_sandbox.py b/setuptools/tests/test_sandbox.py
index d8675422..99398cdb 100644
--- a/setuptools/tests/test_sandbox.py
+++ b/setuptools/tests/test_sandbox.py
@@ -26,7 +26,8 @@ class TestSandbox:
"""
It should be possible to execute a setup.py with a Byte Order Mark
"""
- target = pkg_resources.resource_filename(__name__,
+ target = pkg_resources.resource_filename(
+ __name__,
'script-with-bom.py')
namespace = types.ModuleType('namespace')
setuptools.sandbox._execfile(target, vars(namespace))
diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py
index 02222da5..d2c4e0cf 100644
--- a/setuptools/tests/test_sdist.py
+++ b/setuptools/tests/test_sdist.py
@@ -20,8 +20,8 @@ from setuptools.command.egg_info import manifest_maker
from setuptools.dist import Distribution
from setuptools.tests import fail_on_ascii
from .text import Filenames
+from . import py3_only
-py3_only = pytest.mark.xfail(six.PY2, reason="Test runs on Python 3 only")
SETUP_ATTRS = {
'name': 'sdist_test',
@@ -92,9 +92,8 @@ fail_on_latin1_encoded_filenames = pytest.mark.xfail(
class TestSdistTest:
def setup_method(self, method):
self.temp_dir = tempfile.mkdtemp()
- f = open(os.path.join(self.temp_dir, 'setup.py'), 'w')
- f.write(SETUP_PY)
- f.close()
+ with open(os.path.join(self.temp_dir, 'setup.py'), 'w') as f:
+ f.write(SETUP_PY)
# Set up the rest of the test package
test_pkg = os.path.join(self.temp_dir, 'sdist_test')
@@ -135,6 +134,47 @@ class TestSdistTest:
assert os.path.join('sdist_test', 'c.rst') not in manifest
assert os.path.join('d', 'e.dat') in manifest
+ def test_setup_py_exists(self):
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'foo.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ with quiet():
+ cmd.run()
+
+ manifest = cmd.filelist.files
+ assert 'setup.py' in manifest
+
+ def test_setup_py_missing(self):
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'foo.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ if os.path.exists("setup.py"):
+ os.remove("setup.py")
+ with quiet():
+ cmd.run()
+
+ manifest = cmd.filelist.files
+ assert 'setup.py' not in manifest
+
+ def test_setup_py_excluded(self):
+ with open("MANIFEST.in", "w") as manifest_file:
+ manifest_file.write("exclude setup.py")
+
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'foo.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ with quiet():
+ cmd.run()
+
+ manifest = cmd.filelist.files
+ assert 'setup.py' not in manifest
+
def test_defaults_case_sensitivity(self):
"""
Make sure default files (README.*, etc.) are added in a case-sensitive
diff --git a/setuptools/tests/test_setuptools.py b/setuptools/tests/test_setuptools.py
index 7aae3a16..5896a69a 100644
--- a/setuptools/tests/test_setuptools.py
+++ b/setuptools/tests/test_setuptools.py
@@ -77,7 +77,8 @@ class TestDepends:
from json import __version__
assert dep.get_module_constant('json', '__version__') == __version__
assert dep.get_module_constant('sys', 'version') == sys.version
- assert dep.get_module_constant('setuptools.tests.test_setuptools', '__doc__') == __doc__
+ assert dep.get_module_constant(
+ 'setuptools.tests.test_setuptools', '__doc__') == __doc__
@needs_bytecode
def testRequire(self):
@@ -216,7 +217,8 @@ class TestFeatures:
self.req = Require('Distutils', '1.0.3', 'distutils')
self.dist = makeSetup(
features={
- 'foo': Feature("foo", standard=True, require_features=['baz', self.req]),
+ 'foo': Feature(
+ "foo", standard=True, require_features=['baz', self.req]),
'bar': Feature("bar", standard=True, packages=['pkg.bar'],
py_modules=['bar_et'], remove=['bar.ext'],
),
@@ -252,7 +254,8 @@ class TestFeatures:
('with-dwim', None, 'include DWIM') in dist.feature_options
)
assert (
- ('without-dwim', None, 'exclude DWIM (default)') in dist.feature_options
+ ('without-dwim', None, 'exclude DWIM (default)')
+ in dist.feature_options
)
assert (
('with-bar', None, 'include bar (default)') in dist.feature_options
diff --git a/setuptools/tests/test_test.py b/setuptools/tests/test_test.py
index 960527bc..faaa6ba9 100644
--- a/setuptools/tests/test_test.py
+++ b/setuptools/tests/test_test.py
@@ -4,7 +4,6 @@ from __future__ import unicode_literals
from distutils import log
import os
-import sys
import pytest
@@ -93,10 +92,6 @@ def test_test(capfd):
assert out == 'Foo\n'
-@pytest.mark.xfail(
- sys.version_info < (2, 7),
- reason="No discover support for unittest on Python 2.6",
-)
@pytest.mark.usefixtures('tmpdir_cwd', 'quiet_log')
def test_tests_are_run_once(capfd):
params = dict(
diff --git a/setuptools/tests/test_upload.py b/setuptools/tests/test_upload.py
index 95a8d16b..320c6959 100644
--- a/setuptools/tests/test_upload.py
+++ b/setuptools/tests/test_upload.py
@@ -1,13 +1,100 @@
import mock
+import os
+import re
+
from distutils import log
+from distutils.errors import DistutilsError
import pytest
from setuptools.command.upload import upload
from setuptools.dist import Distribution
+from setuptools.extern import six
+
+
+def _parse_upload_body(body):
+ boundary = u'\r\n----------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
+ entries = []
+ name_re = re.compile(u'^Content-Disposition: form-data; name="([^\"]+)"')
+
+ for entry in body.split(boundary):
+ pair = entry.split(u'\r\n\r\n')
+ if not len(pair) == 2:
+ continue
+
+ key, value = map(six.text_type.strip, pair)
+ m = name_re.match(key)
+ if m is not None:
+ key = m.group(1)
+
+ entries.append((key, value))
+
+ return entries
+
+
+@pytest.fixture
+def patched_upload(tmpdir):
+ class Fix:
+ def __init__(self, cmd, urlopen):
+ self.cmd = cmd
+ self.urlopen = urlopen
+
+ def __iter__(self):
+ return iter((self.cmd, self.urlopen))
+
+ def get_uploaded_metadata(self):
+ request = self.urlopen.call_args_list[0][0][0]
+ body = request.data.decode('utf-8')
+ entries = dict(_parse_upload_body(body))
+
+ return entries
+
+ class ResponseMock(mock.Mock):
+ def getheader(self, name, default=None):
+ """Mocked getheader method for response object"""
+ return {
+ 'content-type': 'text/plain; charset=utf-8',
+ }.get(name.lower(), default)
+
+ with mock.patch('setuptools.command.upload.urlopen') as urlopen:
+ urlopen.return_value = ResponseMock()
+ urlopen.return_value.getcode.return_value = 200
+ urlopen.return_value.read.return_value = b''
+
+ content = os.path.join(str(tmpdir), "content_data")
+
+ with open(content, 'w') as f:
+ f.write("Some content")
+
+ dist = Distribution()
+ dist.dist_files = [('sdist', '3.7.0', content)]
+
+ cmd = upload(dist)
+ cmd.announce = mock.Mock()
+ cmd.username = 'user'
+ cmd.password = 'hunter2'
+
+ yield Fix(cmd, urlopen)
class TestUploadTest:
+ def test_upload_metadata(self, patched_upload):
+ cmd, patch = patched_upload
+
+ # Set the metadata version to 2.1
+ cmd.distribution.metadata.metadata_version = '2.1'
+
+ # Run the command
+ cmd.ensure_finalized()
+ cmd.run()
+
+ # Make sure we did the upload
+ patch.assert_called_once()
+
+ # Make sure the metadata version is correct in the headers
+ entries = patched_upload.get_uploaded_metadata()
+ assert entries['metadata_version'] == '2.1'
+
def test_warns_deprecation(self):
dist = Distribution()
dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())]
@@ -41,3 +128,86 @@ class TestUploadTest:
"upload instead (https://pypi.org/p/twine/)",
log.WARN
)
+
+ @pytest.mark.parametrize('url', [
+ 'https://example.com/a;parameter', # Has parameters
+ 'https://example.com/a?query', # Has query
+ 'https://example.com/a#fragment', # Has fragment
+ 'ftp://example.com', # Invalid scheme
+
+ ])
+ def test_upload_file_invalid_url(self, url, patched_upload):
+ patched_upload.urlopen.side_effect = Exception("Should not be reached")
+
+ cmd = patched_upload.cmd
+ cmd.repository = url
+
+ cmd.ensure_finalized()
+ with pytest.raises(AssertionError):
+ cmd.run()
+
+ def test_upload_file_http_error(self, patched_upload):
+ patched_upload.urlopen.side_effect = six.moves.urllib.error.HTTPError(
+ 'https://example.com',
+ 404,
+ 'File not found',
+ None,
+ None
+ )
+
+ cmd = patched_upload.cmd
+ cmd.ensure_finalized()
+
+ with pytest.raises(DistutilsError):
+ cmd.run()
+
+ cmd.announce.assert_any_call(
+ 'Upload failed (404): File not found',
+ log.ERROR)
+
+ def test_upload_file_os_error(self, patched_upload):
+ patched_upload.urlopen.side_effect = OSError("Invalid")
+
+ cmd = patched_upload.cmd
+ cmd.ensure_finalized()
+
+ with pytest.raises(OSError):
+ cmd.run()
+
+ cmd.announce.assert_any_call('Invalid', log.ERROR)
+
+ @mock.patch('setuptools.command.upload.spawn')
+ def test_upload_file_gpg(self, spawn, patched_upload):
+ cmd, urlopen = patched_upload
+
+ cmd.sign = True
+ cmd.identity = "Alice"
+ cmd.dry_run = True
+ content_fname = cmd.distribution.dist_files[0][2]
+ signed_file = content_fname + '.asc'
+
+ with open(signed_file, 'wb') as f:
+ f.write("signed-data".encode('utf-8'))
+
+ cmd.ensure_finalized()
+ cmd.run()
+
+ # Make sure that GPG was called
+ spawn.assert_called_once_with([
+ "gpg", "--detach-sign", "--local-user", "Alice", "-a",
+ content_fname
+ ], dry_run=True)
+
+ # Read the 'signed' data that was transmitted
+ entries = patched_upload.get_uploaded_metadata()
+ assert entries['gpg_signature'] == 'signed-data'
+
+ def test_show_response_no_error(self, patched_upload):
+ # This test is just that show_response doesn't throw an error
+ # It is not really important what the printed response looks like
+ # in a deprecated command, but we don't want to introduce new
+ # errors when importing this function from distutils
+
+ patched_upload.cmd.show_response = True
+ patched_upload.cmd.ensure_finalized()
+ patched_upload.cmd.run()
diff --git a/setuptools/tests/test_virtualenv.py b/setuptools/tests/test_virtualenv.py
index b66a311d..3d5c84b0 100644
--- a/setuptools/tests/test_virtualenv.py
+++ b/setuptools/tests/test_virtualenv.py
@@ -57,9 +57,6 @@ def test_pip_upgrade_from_source(virtualenv):
Check pip can upgrade setuptools from source.
"""
dist_dir = virtualenv.workspace
- if sys.version_info < (2, 7):
- # Python 2.6 support was dropped in wheel 0.30.0.
- virtualenv.run('pip install -U "wheel<0.30.0"')
# Generate source distribution / wheel.
virtualenv.run(' && '.join((
'cd {source}',
@@ -137,3 +134,14 @@ def test_test_command_install_requirements(bare_virtualenv, tmpdir):
'python setup.py test -s test',
)).format(tmpdir=tmpdir))
assert tmpdir.join('success').check()
+
+
+def test_no_missing_dependencies(bare_virtualenv):
+ """
+ Quick and dirty test to ensure all external dependencies are vendored.
+ """
+ for command in ('upload',): # sorted(distutils.command.__all__):
+ bare_virtualenv.run(' && '.join((
+ 'cd {source}',
+ 'python setup.py {command} -h',
+ )).format(command=command, source=SOURCE_DIR))
diff --git a/setuptools/tests/test_wheel.py b/setuptools/tests/test_wheel.py
index 6db5fa11..e85a4a7e 100644
--- a/setuptools/tests/test_wheel.py
+++ b/setuptools/tests/test_wheel.py
@@ -63,6 +63,7 @@ WHEEL_INFO_TESTS = (
}),
)
+
@pytest.mark.parametrize(
('filename', 'info'), WHEEL_INFO_TESTS,
ids=[t[0] for t in WHEEL_INFO_TESTS]
@@ -487,6 +488,7 @@ WHEEL_INSTALL_TESTS = (
)
+
@pytest.mark.parametrize(
'params', WHEEL_INSTALL_TESTS,
ids=list(params['id'] for params in WHEEL_INSTALL_TESTS),
diff --git a/setuptools/tests/test_windows_wrappers.py b/setuptools/tests/test_windows_wrappers.py
index d2871c0f..2553394a 100644
--- a/setuptools/tests/test_windows_wrappers.py
+++ b/setuptools/tests/test_windows_wrappers.py
@@ -97,7 +97,8 @@ class TestCLI(WrapperTester):
'arg 4\\',
'arg5 a\\\\b',
]
- proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
+ proc = subprocess.Popen(
+ cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
stdout, stderr = proc.communicate('hello\nworld\n'.encode('ascii'))
actual = stdout.decode('ascii').replace('\r\n', '\n')
expected = textwrap.dedent(r"""
@@ -134,7 +135,11 @@ class TestCLI(WrapperTester):
with (tmpdir / 'foo-script.py').open('w') as f:
f.write(self.prep_script(tmpl))
cmd = [str(tmpdir / 'foo.exe')]
- proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT)
+ proc = subprocess.Popen(
+ cmd,
+ stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
stdout, stderr = proc.communicate()
actual = stdout.decode('ascii').replace('\r\n', '\n')
expected = textwrap.dedent(r"""
@@ -172,7 +177,9 @@ class TestGUI(WrapperTester):
str(tmpdir / 'test_output.txt'),
'Test Argument',
]
- proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT)
+ proc = subprocess.Popen(
+ cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
stdout, stderr = proc.communicate()
assert not stdout
assert not stderr
diff --git a/setuptools/unicode_utils.py b/setuptools/unicode_utils.py
index 7c63efd2..3b8179a8 100644
--- a/setuptools/unicode_utils.py
+++ b/setuptools/unicode_utils.py
@@ -1,5 +1,6 @@
import unicodedata
import sys
+import re
from setuptools.extern import six
@@ -42,3 +43,15 @@ def try_encode(string, enc):
return string.encode(enc)
except UnicodeEncodeError:
return None
+
+
+CODING_RE = re.compile(br'^[ \t\f]*#.*?coding[:=][ \t]*([-\w.]+)')
+
+
+def detect_encoding(fp):
+ first_line = fp.readline()
+ fp.seek(0)
+ m = CODING_RE.match(first_line)
+ if m is None:
+ return None
+ return m.group(1).decode('ascii')
diff --git a/setuptools/wheel.py b/setuptools/wheel.py
index 95a794a8..e11f0a1d 100644
--- a/setuptools/wheel.py
+++ b/setuptools/wheel.py
@@ -8,10 +8,11 @@ import posixpath
import re
import zipfile
-from pkg_resources import Distribution, PathMetadata, parse_version
+import pkg_resources
+import setuptools
+from pkg_resources import parse_version
from setuptools.extern.packaging.utils import canonicalize_name
from setuptools.extern.six import PY3
-from setuptools import Distribution as SetuptoolsDistribution
from setuptools import pep425tags
from setuptools.command.egg_info import write_requirements
@@ -79,7 +80,7 @@ class Wheel:
return next((True for t in self.tags() if t in supported_tags), False)
def egg_name(self):
- return Distribution(
+ return pkg_resources.Distribution(
project_name=self.project_name, version=self.version,
platform=(None if self.platform == 'any' else get_platform()),
).egg_name() + '.egg'
@@ -130,9 +131,9 @@ class Wheel:
zf.extractall(destination_eggdir)
# Convert metadata.
dist_info = os.path.join(destination_eggdir, dist_info)
- dist = Distribution.from_location(
+ dist = pkg_resources.Distribution.from_location(
destination_eggdir, dist_info,
- metadata=PathMetadata(destination_eggdir, dist_info),
+ metadata=pkg_resources.PathMetadata(destination_eggdir, dist_info),
)
# Note: Evaluate and strip markers now,
@@ -155,7 +156,7 @@ class Wheel:
os.path.join(egg_info, 'METADATA'),
os.path.join(egg_info, 'PKG-INFO'),
)
- setup_dist = SetuptoolsDistribution(
+ setup_dist = setuptools.Distribution(
attrs=dict(
install_requires=install_requires,
extras_require=extras_require,