Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add python_requires keywords to setup #631

Merged
merged 3 commits into from
Jul 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/setuptools.txt
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,10 @@ unless you need the associated ``setuptools`` feature.
installed to support those features. See the section below on `Declaring
Dependencies`_ for details and examples of the format of this argument.

``python_requires``
A string corresponding to a version specifier (as defined in PEP 440) for
the Python version, used to specify the Requires-Python defined in PEP 345.

``setup_requires``
A string or list of strings specifying what other distributions need to
be present in order for the *setup script* to run. ``setuptools`` will
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ def _gen_console_scripts():
"install_requires = setuptools.dist:check_requirements",
"tests_require = setuptools.dist:check_requirements",
"setup_requires = setuptools.dist:check_requirements",
"python_requires = setuptools.dist:check_specifier",
"entry_points = setuptools.dist:check_entry_points",
"test_suite = setuptools.dist:check_test_suite",
"zip_safe = setuptools.dist:assert_bool",
Expand Down
28 changes: 28 additions & 0 deletions setuptools/dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ def _get_unpatched(cls):
_Distribution = _get_unpatched(_Distribution)


def _patch_distribution_metadata_write_pkg_file():
"""Patch write_pkg_file to also write Requires-Python/Requires-External"""
original_write = distutils.dist.DistributionMetadata.write_pkg_file
def write_pkg_file(self, file):
"""Write the PKG-INFO format data to a file object.
"""
original_write(self, file)
if hasattr(self, 'python_requires'):
file.write('Requires-Python: %s\n' % self.python_requires)

distutils.dist.DistributionMetadata.write_pkg_file = write_pkg_file
_patch_distribution_metadata_write_pkg_file()


def _patch_distribution_metadata_write_pkg_info():
"""
Workaround issue #197 - Python 3 prior to 3.2.2 uses an environment-local
Expand Down Expand Up @@ -138,6 +152,18 @@ def check_requirements(dist, attr, value):
raise DistutilsSetupError(tmpl.format(attr=attr, error=error))


def check_specifier(dist, attr, value):
"""Verify that value is a valid version specifier"""
try:
packaging.specifiers.SpecifierSet(value)
except packaging.specifiers.InvalidSpecifier as error:
tmpl = (
"{attr!r} must be a string or list of strings "
"containing valid version specifiers; {error}"
)
raise DistutilsSetupError(tmpl.format(attr=attr, error=error))


def check_entry_points(dist, attr, value):
"""Verify that entry_points map is parseable"""
try:
Expand Down Expand Up @@ -303,6 +329,8 @@ def __init__(self, attrs=None):
"setuptools, pip, and PyPI. Please see PEP 440 for more "
"details." % self.metadata.version
)
if getattr(self, 'python_requires', None):
self.metadata.python_requires = self.python_requires

def parse_command_line(self):
"""Process features after parsing command line options"""
Expand Down
24 changes: 24 additions & 0 deletions setuptools/tests/test_egg_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,30 @@ def test_extra_requires_with_markers(self, tmpdir_cwd, env):
self._run_install_command(tmpdir_cwd, env)
assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []

def test_python_requires_egg_info(self, tmpdir_cwd, env):
self._setup_script_with_requires(
"""python_requires='>=2.7.12',""")
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')
pkginfo = os.path.join(egg_info_dir, 'PKG-INFO')
assert 'Requires-Python: >=2.7.12' in open(pkginfo).read().split('\n')

def test_python_requires_install(self, tmpdir_cwd, env):
self._setup_script_with_requires(
"""python_requires='>=1.2.3',""")
self._run_install_command(tmpdir_cwd, env)
egg_info_dir = self._find_egg_info_files(env.paths['lib']).base
pkginfo = os.path.join(egg_info_dir, 'PKG-INFO')
assert 'Requires-Python: >=1.2.3' in open(pkginfo).read().split('\n')

def _run_install_command(self, tmpdir_cwd, env, cmd=None, output=None):
environ = os.environ.copy().update(
HOME=env.paths['home'],
Expand Down