Commit 647ef564 authored by Jason R. Coombs's avatar Jason R. Coombs Committed by GitHub

Merge branch 'master' into master

parents 8fe36a57 4ae5bc9e
v38.3.1
-------
* #1231: Removed warning when PYTHONDONTWRITEBYTECODE is enabled.
v38.3.0
-------
* #1210: Add support for PEP 345 Project-URL metadata.
* #1207: Add support for ``long_description_type`` to setup.cfg
declarative config as intended and documented.
v38.2.5 v38.2.5
------- -------
* Removed warning when PYTHONDONTWRITEBYTECODE is enabled * #1232: Fix trailing slash handling in ``pkg_resources.ZipProvider``.
v38.2.4 v38.2.4
------- -------
......
...@@ -145,6 +145,11 @@ dependencies, and perhaps some data files and scripts:: ...@@ -145,6 +145,11 @@ dependencies, and perhaps some data files and scripts::
license="PSF", license="PSF",
keywords="hello world example examples", keywords="hello world example examples",
url="http://example.com/HelloWorld/", # project home page, if any url="http://example.com/HelloWorld/", # project home page, if any
project_urls={
"Bug Tracker": "https://bugs.example.com/HelloWorld/",
"Documentation": "https://docs.example.com/HelloWorld/",
"Source Code": "https://code.example.com/HelloWorld/",
}
# could also include long_description, download_url, classifiers, etc. # could also include long_description, download_url, classifiers, etc.
) )
...@@ -408,6 +413,11 @@ unless you need the associated ``setuptools`` feature. ...@@ -408,6 +413,11 @@ unless you need the associated ``setuptools`` feature.
A list of modules to search for additional fixers to be used during A list of modules to search for additional fixers to be used during
the 2to3 conversion. See :doc:`python3` for more details. the 2to3 conversion. See :doc:`python3` for more details.
``project_urls``
An arbitrary map of URL names to hyperlinks, allowing more extensible
documentation of where various resources can be found than the simple
``url`` and ``download_url`` options provide.
Using ``find_packages()`` Using ``find_packages()``
------------------------- -------------------------
...@@ -2406,6 +2416,7 @@ name str ...@@ -2406,6 +2416,7 @@ name str
version attr:, str version attr:, str
url home-page str url home-page str
download_url download-url str download_url download-url str
project_urls dict
author str author str
author_email author-email str author_email author-email str
maintainer str maintainer str
......
...@@ -1693,6 +1693,9 @@ class ZipProvider(EggProvider): ...@@ -1693,6 +1693,9 @@ class ZipProvider(EggProvider):
def _zipinfo_name(self, fspath): def _zipinfo_name(self, fspath):
# Convert a virtual filename (full path to file) into a zipfile subpath # Convert a virtual filename (full path to file) into a zipfile subpath
# usable with the zipimport directory cache for our target archive # usable with the zipimport directory cache for our target archive
fspath = fspath.rstrip(os.sep)
if fspath == self.loader.archive:
return ''
if fspath.startswith(self.zip_pre): if fspath.startswith(self.zip_pre):
return fspath[len(self.zip_pre):] return fspath[len(self.zip_pre):]
raise AssertionError( raise AssertionError(
......
...@@ -62,10 +62,21 @@ class TestZipProvider(object): ...@@ -62,10 +62,21 @@ class TestZipProvider(object):
zip_info.filename = 'data.dat' zip_info.filename = 'data.dat'
zip_info.date_time = cls.ref_time.timetuple() zip_info.date_time = cls.ref_time.timetuple()
zip_egg.writestr(zip_info, 'hello, world!') zip_egg.writestr(zip_info, 'hello, world!')
zip_info = zipfile.ZipInfo()
zip_info.filename = 'subdir/mod2.py'
zip_info.date_time = cls.ref_time.timetuple()
zip_egg.writestr(zip_info, 'x = 6\n')
zip_info = zipfile.ZipInfo()
zip_info.filename = 'subdir/data2.dat'
zip_info.date_time = cls.ref_time.timetuple()
zip_egg.writestr(zip_info, 'goodbye, world!')
zip_egg.close() zip_egg.close()
egg.close() egg.close()
sys.path.append(egg.name) sys.path.append(egg.name)
subdir = os.path.join(egg.name, 'subdir')
sys.path.append(subdir)
cls.finalizers.append(EggRemover(subdir))
cls.finalizers.append(EggRemover(egg.name)) cls.finalizers.append(EggRemover(egg.name))
@classmethod @classmethod
...@@ -73,6 +84,30 @@ class TestZipProvider(object): ...@@ -73,6 +84,30 @@ class TestZipProvider(object):
for finalizer in cls.finalizers: for finalizer in cls.finalizers:
finalizer() finalizer()
def test_resource_listdir(self):
import mod
zp = pkg_resources.ZipProvider(mod)
expected_root = ['data.dat', 'mod.py', 'subdir']
assert sorted(zp.resource_listdir('')) == expected_root
assert sorted(zp.resource_listdir('/')) == expected_root
expected_subdir = ['data2.dat', 'mod2.py']
assert sorted(zp.resource_listdir('subdir')) == expected_subdir
assert sorted(zp.resource_listdir('subdir/')) == expected_subdir
assert zp.resource_listdir('nonexistent') == []
assert zp.resource_listdir('nonexistent/') == []
import mod2
zp2 = pkg_resources.ZipProvider(mod2)
assert sorted(zp2.resource_listdir('')) == expected_subdir
assert sorted(zp2.resource_listdir('/')) == expected_subdir
assert zp2.resource_listdir('subdir') == []
assert zp2.resource_listdir('subdir/') == []
def test_resource_filename_rewrites_on_change(self): def test_resource_filename_rewrites_on_change(self):
""" """
If a previous call to get_resource_filename has saved the file, but If a previous call to get_resource_filename has saved the file, but
......
[bumpversion] [bumpversion]
current_version = 38.2.4 current_version = 38.3.0
commit = True commit = True
tag = True tag = True
......
...@@ -89,7 +89,7 @@ def pypi_link(pkg_filename): ...@@ -89,7 +89,7 @@ def pypi_link(pkg_filename):
setup_params = dict( setup_params = dict(
name="setuptools", name="setuptools",
version="38.2.4", version="38.3.0",
description="Easily download, build, install, upgrade, and uninstall " description="Easily download, build, install, upgrade, and uninstall "
"Python packages", "Python packages",
author="Python Packaging Authority", author="Python Packaging Authority",
...@@ -98,6 +98,9 @@ setup_params = dict( ...@@ -98,6 +98,9 @@ setup_params = dict(
long_description_content_type='text/x-rst; charset=UTF-8', long_description_content_type='text/x-rst; charset=UTF-8',
keywords="CPAN PyPI distutils eggs package management", keywords="CPAN PyPI distutils eggs package management",
url="https://github.com/pypa/setuptools", url="https://github.com/pypa/setuptools",
project_urls={
"Documentation": "https://setuptools.readthedocs.io/",
},
src_root=None, src_root=None,
packages=setuptools.find_packages(exclude=['*.tests']), packages=setuptools.find_packages(exclude=['*.tests']),
package_data=package_data, package_data=package_data,
......
...@@ -597,10 +597,7 @@ def write_pkg_info(cmd, basename, filename): ...@@ -597,10 +597,7 @@ def write_pkg_info(cmd, basename, filename):
metadata = cmd.distribution.metadata metadata = cmd.distribution.metadata
metadata.version, oldver = cmd.egg_version, metadata.version metadata.version, oldver = cmd.egg_version, metadata.version
metadata.name, oldname = cmd.egg_name, metadata.name metadata.name, oldname = cmd.egg_name, metadata.name
metadata.long_description_content_type = getattr(
cmd.distribution,
'long_description_content_type'
)
try: try:
# write unescaped data to PKG-INFO, so older pkg_resources # write unescaped data to PKG-INFO, so older pkg_resources
# can still parse it # can still parse it
......
...@@ -404,6 +404,7 @@ class ConfigMetadataHandler(ConfigHandler): ...@@ -404,6 +404,7 @@ class ConfigMetadataHandler(ConfigHandler):
"""Metadata item name to parser function mapping.""" """Metadata item name to parser function mapping."""
parse_list = self._parse_list parse_list = self._parse_list
parse_file = self._parse_file parse_file = self._parse_file
parse_dict = self._parse_dict
return { return {
'platforms': parse_list, 'platforms': parse_list,
...@@ -416,6 +417,7 @@ class ConfigMetadataHandler(ConfigHandler): ...@@ -416,6 +417,7 @@ class ConfigMetadataHandler(ConfigHandler):
'description': parse_file, 'description': parse_file,
'long_description': parse_file, 'long_description': parse_file,
'version': self._parse_version, 'version': self._parse_version,
'project_urls': parse_dict,
} }
def _parse_version(self, value): def _parse_version(self, value):
......
...@@ -44,7 +44,7 @@ def write_pkg_file(self, file): ...@@ -44,7 +44,7 @@ def write_pkg_file(self, file):
self.classifiers or self.download_url): self.classifiers or self.download_url):
version = '1.1' version = '1.1'
# Setuptools specific for PEP 345 # Setuptools specific for PEP 345
if hasattr(self, 'python_requires'): if hasattr(self, 'python_requires') or self.project_urls:
version = '1.2' version = '1.2'
file.write('Metadata-Version: %s\n' % version) file.write('Metadata-Version: %s\n' % version)
...@@ -57,12 +57,11 @@ def write_pkg_file(self, file): ...@@ -57,12 +57,11 @@ def write_pkg_file(self, file):
file.write('License: %s\n' % self.get_license()) file.write('License: %s\n' % self.get_license())
if self.download_url: if self.download_url:
file.write('Download-URL: %s\n' % self.download_url) file.write('Download-URL: %s\n' % self.download_url)
for project_url in self.project_urls.items():
file.write('Project-URL: %s, %s\n' % project_url)
long_desc_content_type = getattr( long_desc_content_type = \
self, self.long_description_content_type or 'UNKNOWN'
'long_description_content_type',
None
) or 'UNKNOWN'
file.write('Description-Content-Type: %s\n' % long_desc_content_type) file.write('Description-Content-Type: %s\n' % long_desc_content_type)
long_desc = rfc822_escape(self.get_long_description()) long_desc = rfc822_escape(self.get_long_description())
...@@ -326,14 +325,21 @@ class Distribution(Distribution_parse_config_files, _Distribution): ...@@ -326,14 +325,21 @@ class Distribution(Distribution_parse_config_files, _Distribution):
self.dist_files = [] self.dist_files = []
self.src_root = attrs.pop("src_root", None) self.src_root = attrs.pop("src_root", None)
self.patch_missing_pkg_info(attrs) self.patch_missing_pkg_info(attrs)
self.long_description_content_type = attrs.get( self.project_urls = attrs.get('project_urls', {})
'long_description_content_type'
)
self.dependency_links = attrs.pop('dependency_links', []) self.dependency_links = attrs.pop('dependency_links', [])
self.setup_requires = attrs.pop('setup_requires', []) self.setup_requires = attrs.pop('setup_requires', [])
for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
vars(self).setdefault(ep.name, None) vars(self).setdefault(ep.name, None)
_Distribution.__init__(self, attrs) _Distribution.__init__(self, attrs)
# The project_urls attribute may not be supported in distutils, so
# prime it here from our value if not automatically set
self.metadata.project_urls = getattr(
self.metadata, 'project_urls', self.project_urls)
self.metadata.long_description_content_type = attrs.get(
'long_description_content_type'
)
if isinstance(self.metadata.version, numbers.Number): if isinstance(self.metadata.version, numbers.Number):
# Some people apparently take "version number" too literally :) # Some people apparently take "version number" too literally :)
self.metadata.version = str(self.metadata.version) self.metadata.version = str(self.metadata.version)
......
...@@ -110,6 +110,7 @@ class TestMetadata: ...@@ -110,6 +110,7 @@ class TestMetadata:
'[metadata]\n' '[metadata]\n'
'version = 10.1.1\n' 'version = 10.1.1\n'
'description = Some description\n' 'description = Some description\n'
'long_description_content_type = text/something\n'
'long_description = file: README\n' 'long_description = file: README\n'
'name = fake_name\n' 'name = fake_name\n'
'keywords = one, two\n' 'keywords = one, two\n'
...@@ -131,6 +132,7 @@ class TestMetadata: ...@@ -131,6 +132,7 @@ class TestMetadata:
assert metadata.version == '10.1.1' assert metadata.version == '10.1.1'
assert metadata.description == 'Some description' assert metadata.description == 'Some description'
assert metadata.long_description_content_type == 'text/something'
assert metadata.long_description == 'readme contents\nline2' assert metadata.long_description == 'readme contents\nline2'
assert metadata.provides == ['package', 'package.sub'] assert metadata.provides == ['package', 'package.sub']
assert metadata.license == 'BSD 3-Clause License' assert metadata.license == 'BSD 3-Clause License'
...@@ -215,6 +217,22 @@ class TestMetadata: ...@@ -215,6 +217,22 @@ class TestMetadata:
'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.5',
] ]
def test_dict(self, tmpdir):
fake_env(
tmpdir,
'[metadata]\n'
'project_urls =\n'
' Link One = https://example.com/one/\n'
' Link Two = https://example.com/two/\n'
)
with get_dist(tmpdir) as dist:
metadata = dist.metadata
assert metadata.project_urls == {
'Link One': 'https://example.com/one/',
'Link Two': 'https://example.com/two/',
}
def test_version(self, tmpdir): def test_version(self, tmpdir):
_, config = fake_env( _, config = fake_env(
......
...@@ -445,6 +445,36 @@ class TestEggInfo(object): ...@@ -445,6 +445,36 @@ class TestEggInfo(object):
expected_line = 'Description-Content-Type: text/markdown' expected_line = 'Description-Content-Type: text/markdown'
assert expected_line in pkg_info_lines assert expected_line in pkg_info_lines
def test_project_urls(self, tmpdir_cwd, env):
# Test that specifying a `project_urls` dict to the `setup`
# function results in writing multiple `Project-URL` lines to
# the `PKG-INFO` file in the `<distribution>.egg-info`
# directory.
# `Project-URL` is described at https://packaging.python.org
# /specifications/core-metadata/#project-url-multiple-use
self._setup_script_with_requires(
"""project_urls={
'Link One': 'https://example.com/one/',
'Link Two': 'https://example.com/two/',
},""")
environ = os.environ.copy().update(
HOME=env.paths['home'],
)
code, data = environment.run_setup_py(
cmd=['egg_info'],
pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
data_stream=1,
env=environ,
)
egg_info_dir = os.path.join('.', 'foo.egg-info')
with open(os.path.join(egg_info_dir, 'PKG-INFO')) as pkginfo_file:
pkg_info_lines = pkginfo_file.read().split('\n')
expected_line = 'Project-URL: Link One, https://example.com/one/'
assert expected_line in pkg_info_lines
expected_line = 'Project-URL: Link Two, https://example.com/two/'
assert expected_line in pkg_info_lines
def test_python_requires_egg_info(self, tmpdir_cwd, env): def test_python_requires_egg_info(self, tmpdir_cwd, env):
self._setup_script_with_requires( self._setup_script_with_requires(
"""python_requires='>=2.7.12',""") """python_requires='>=2.7.12',""")
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment