update
This commit is contained in:
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
+57
@@ -0,0 +1,57 @@
|
||||
import re
|
||||
import time
|
||||
from pathlib import Path
|
||||
from urllib.error import HTTPError
|
||||
from urllib.request import urlopen
|
||||
|
||||
__all__ = ["DOWNLOAD_DIR", "retrieve_file", "output_file", "urls_from_file"]
|
||||
|
||||
|
||||
NAME_REMOVE = ("http://", "https://", "github.com/", "/raw/")
|
||||
DOWNLOAD_DIR = Path(__file__).parent
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Please update ./preload.py accordingly when modifying this file
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
|
||||
def output_file(url: str, download_dir: Path = DOWNLOAD_DIR):
|
||||
file_name = url.strip()
|
||||
for part in NAME_REMOVE:
|
||||
file_name = file_name.replace(part, '').strip().strip('/:').strip()
|
||||
return Path(download_dir, re.sub(r"[^\-_\.\w\d]+", "_", file_name))
|
||||
|
||||
|
||||
def retrieve_file(url: str, download_dir: Path = DOWNLOAD_DIR, wait: float = 5):
|
||||
path = output_file(url, download_dir)
|
||||
if path.exists():
|
||||
print(f"Skipping {url} (already exists: {path})")
|
||||
else:
|
||||
download_dir.mkdir(exist_ok=True, parents=True)
|
||||
print(f"Downloading {url} to {path}")
|
||||
try:
|
||||
download(url, path)
|
||||
except HTTPError:
|
||||
time.sleep(wait) # wait a few seconds and try again.
|
||||
download(url, path)
|
||||
return path
|
||||
|
||||
|
||||
def urls_from_file(list_file: Path):
|
||||
"""``list_file`` should be a text file where each line corresponds to a URL to
|
||||
download.
|
||||
"""
|
||||
print(f"file: {list_file}")
|
||||
content = list_file.read_text(encoding="utf-8")
|
||||
return [url for url in content.splitlines() if not url.startswith("#")]
|
||||
|
||||
|
||||
def download(url: str, dest: Path):
|
||||
with urlopen(url) as f:
|
||||
data = f.read()
|
||||
|
||||
with open(dest, "wb") as f:
|
||||
f.write(data)
|
||||
|
||||
assert Path(dest).exists()
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,18 @@
|
||||
"""This file can be used to preload files needed for testing.
|
||||
|
||||
For example you can use::
|
||||
|
||||
cd setuptools/tests/config
|
||||
python -m downloads.preload setupcfg_examples.txt
|
||||
|
||||
to make sure the `setup.cfg` examples are downloaded before starting the tests.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from . import retrieve_file, urls_from_file
|
||||
|
||||
if __name__ == "__main__":
|
||||
urls = urls_from_file(Path(sys.argv[1]))
|
||||
list(map(retrieve_file, urls))
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# ====================================================================
|
||||
# Some popular packages that use setup.cfg (and others not so popular)
|
||||
# Reference: https://hugovk.github.io/top-pypi-packages/
|
||||
# ====================================================================
|
||||
https://github.com/pypa/setuptools/raw/52c990172fec37766b3566679724aa8bf70ae06d/setup.cfg
|
||||
https://github.com/pypa/wheel/raw/0acd203cd896afec7f715aa2ff5980a403459a3b/setup.cfg
|
||||
https://github.com/python/importlib_metadata/raw/2f05392ca980952a6960d82b2f2d2ea10aa53239/setup.cfg
|
||||
https://github.com/jaraco/skeleton/raw/d9008b5c510cd6969127a6a2ab6f832edddef296/setup.cfg
|
||||
https://github.com/jaraco/zipp/raw/700d3a96390e970b6b962823bfea78b4f7e1c537/setup.cfg
|
||||
https://github.com/pallets/jinja/raw/7d72eb7fefb7dce065193967f31f805180508448/setup.cfg
|
||||
https://github.com/tkem/cachetools/raw/2fd87a94b8d3861d80e9e4236cd480bfdd21c90d/setup.cfg
|
||||
https://github.com/aio-libs/aiohttp/raw/5e0e6b7080f2408d5f1dd544c0e1cf88378b7b10/setup.cfg
|
||||
https://github.com/pallets/flask/raw/9486b6cf57bd6a8a261f67091aca8ca78eeec1e3/setup.cfg
|
||||
https://github.com/pallets/click/raw/6411f425fae545f42795665af4162006b36c5e4a/setup.cfg
|
||||
https://github.com/sqlalchemy/sqlalchemy/raw/533f5718904b620be8d63f2474229945d6f8ba5d/setup.cfg
|
||||
https://github.com/pytest-dev/pluggy/raw/461ef63291d13589c4e21aa182cd1529257e9a0a/setup.cfg
|
||||
https://github.com/pytest-dev/pytest/raw/c7be96dae487edbd2f55b561b31b68afac1dabe6/setup.cfg
|
||||
https://github.com/platformdirs/platformdirs/raw/7b7852128dd6f07511b618d6edea35046bd0c6ff/setup.cfg
|
||||
https://github.com/pandas-dev/pandas/raw/bc17343f934a33dc231c8c74be95d8365537c376/setup.cfg
|
||||
https://github.com/django/django/raw/4e249d11a6e56ca8feb4b055b681cec457ef3a3d/setup.cfg
|
||||
https://github.com/pyscaffold/pyscaffold/raw/de7aa5dc059fbd04307419c667cc4961bc9df4b8/setup.cfg
|
||||
https://github.com/pypa/virtualenv/raw/f92eda6e3da26a4d28c2663ffb85c4960bdb990c/setup.cfg
|
||||
+511
@@ -0,0 +1,511 @@
|
||||
"""Make sure that applying the configuration from pyproject.toml is equivalent to
|
||||
applying a similar configuration from setup.cfg
|
||||
|
||||
To run these tests offline, please have a look on ``./downloads/preload.py``
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import re
|
||||
import tarfile
|
||||
from inspect import cleandoc
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
from ini2toml.api import LiteTranslator
|
||||
from packaging.metadata import Metadata
|
||||
|
||||
import setuptools # noqa ensure monkey patch to metadata
|
||||
from setuptools.command.egg_info import write_requirements
|
||||
from setuptools.config import expand, pyprojecttoml, setupcfg
|
||||
from setuptools.config._apply_pyprojecttoml import _MissingDynamic, _some_attrgetter
|
||||
from setuptools.dist import Distribution
|
||||
from setuptools.errors import RemovedConfigError
|
||||
|
||||
from .downloads import retrieve_file, urls_from_file
|
||||
|
||||
HERE = Path(__file__).parent
|
||||
EXAMPLES_FILE = "setupcfg_examples.txt"
|
||||
|
||||
|
||||
def makedist(path, **attrs):
|
||||
return Distribution({"src_root": path, **attrs})
|
||||
|
||||
|
||||
@pytest.mark.parametrize("url", urls_from_file(HERE / EXAMPLES_FILE))
|
||||
@pytest.mark.filterwarnings("ignore")
|
||||
@pytest.mark.uses_network
|
||||
def test_apply_pyproject_equivalent_to_setupcfg(url, monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(expand, "read_attr", Mock(return_value="0.0.1"))
|
||||
setupcfg_example = retrieve_file(url)
|
||||
pyproject_example = Path(tmp_path, "pyproject.toml")
|
||||
setupcfg_text = setupcfg_example.read_text(encoding="utf-8")
|
||||
toml_config = LiteTranslator().translate(setupcfg_text, "setup.cfg")
|
||||
pyproject_example.write_text(toml_config, encoding="utf-8")
|
||||
|
||||
dist_toml = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject_example)
|
||||
dist_cfg = setupcfg.apply_configuration(makedist(tmp_path), setupcfg_example)
|
||||
|
||||
pkg_info_toml = core_metadata(dist_toml)
|
||||
pkg_info_cfg = core_metadata(dist_cfg)
|
||||
assert pkg_info_toml == pkg_info_cfg
|
||||
|
||||
if any(getattr(d, "license_files", None) for d in (dist_toml, dist_cfg)):
|
||||
assert set(dist_toml.license_files) == set(dist_cfg.license_files)
|
||||
|
||||
if any(getattr(d, "entry_points", None) for d in (dist_toml, dist_cfg)):
|
||||
print(dist_cfg.entry_points)
|
||||
ep_toml = {
|
||||
(k, *sorted(i.replace(" ", "") for i in v))
|
||||
for k, v in dist_toml.entry_points.items()
|
||||
}
|
||||
ep_cfg = {
|
||||
(k, *sorted(i.replace(" ", "") for i in v))
|
||||
for k, v in dist_cfg.entry_points.items()
|
||||
}
|
||||
assert ep_toml == ep_cfg
|
||||
|
||||
if any(getattr(d, "package_data", None) for d in (dist_toml, dist_cfg)):
|
||||
pkg_data_toml = {(k, *sorted(v)) for k, v in dist_toml.package_data.items()}
|
||||
pkg_data_cfg = {(k, *sorted(v)) for k, v in dist_cfg.package_data.items()}
|
||||
assert pkg_data_toml == pkg_data_cfg
|
||||
|
||||
if any(getattr(d, "data_files", None) for d in (dist_toml, dist_cfg)):
|
||||
data_files_toml = {(k, *sorted(v)) for k, v in dist_toml.data_files}
|
||||
data_files_cfg = {(k, *sorted(v)) for k, v in dist_cfg.data_files}
|
||||
assert data_files_toml == data_files_cfg
|
||||
|
||||
assert set(dist_toml.install_requires) == set(dist_cfg.install_requires)
|
||||
if any(getattr(d, "extras_require", None) for d in (dist_toml, dist_cfg)):
|
||||
extra_req_toml = {(k, *sorted(v)) for k, v in dist_toml.extras_require.items()}
|
||||
extra_req_cfg = {(k, *sorted(v)) for k, v in dist_cfg.extras_require.items()}
|
||||
assert extra_req_toml == extra_req_cfg
|
||||
|
||||
|
||||
PEP621_EXAMPLE = """\
|
||||
[project]
|
||||
name = "spam"
|
||||
version = "2020.0.0"
|
||||
description = "Lovely Spam! Wonderful Spam!"
|
||||
readme = "README.rst"
|
||||
requires-python = ">=3.8"
|
||||
license = {file = "LICENSE.txt"}
|
||||
keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]
|
||||
authors = [
|
||||
{email = "hi@pradyunsg.me"},
|
||||
{name = "Tzu-Ping Chung"}
|
||||
]
|
||||
maintainers = [
|
||||
{name = "Brett Cannon", email = "brett@python.org"},
|
||||
{name = "John X. Ãørçeč", email = "john@utf8.org"},
|
||||
{name = "Γαμα קּ 東", email = "gama@utf8.org"},
|
||||
]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Programming Language :: Python"
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
"httpx",
|
||||
"gidgethub[httpx]>4.0.0",
|
||||
"django>2.1; os_name != 'nt'",
|
||||
"django>2.0; os_name == 'nt'"
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
test = [
|
||||
"pytest < 5.0.0",
|
||||
"pytest-cov[all]"
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
homepage = "http://example.com"
|
||||
documentation = "http://readthedocs.org"
|
||||
repository = "http://github.com"
|
||||
changelog = "http://github.com/me/spam/blob/master/CHANGELOG.md"
|
||||
|
||||
[project.scripts]
|
||||
spam-cli = "spam:main_cli"
|
||||
|
||||
[project.gui-scripts]
|
||||
spam-gui = "spam:main_gui"
|
||||
|
||||
[project.entry-points."spam.magical"]
|
||||
tomatoes = "spam:main_tomatoes"
|
||||
"""
|
||||
|
||||
PEP621_INTERNATIONAL_EMAIL_EXAMPLE = """\
|
||||
[project]
|
||||
name = "spam"
|
||||
version = "2020.0.0"
|
||||
authors = [
|
||||
{email = "hi@pradyunsg.me"},
|
||||
{name = "Tzu-Ping Chung"}
|
||||
]
|
||||
maintainers = [
|
||||
{name = "Степан Бандера", email = "криївка@оун-упа.укр"},
|
||||
]
|
||||
"""
|
||||
|
||||
PEP621_EXAMPLE_SCRIPT = """
|
||||
def main_cli(): pass
|
||||
def main_gui(): pass
|
||||
def main_tomatoes(): pass
|
||||
"""
|
||||
|
||||
|
||||
def _pep621_example_project(
|
||||
tmp_path,
|
||||
readme="README.rst",
|
||||
pyproject_text=PEP621_EXAMPLE,
|
||||
):
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
text = pyproject_text
|
||||
replacements = {'readme = "README.rst"': f'readme = "{readme}"'}
|
||||
for orig, subst in replacements.items():
|
||||
text = text.replace(orig, subst)
|
||||
pyproject.write_text(text, encoding="utf-8")
|
||||
|
||||
(tmp_path / readme).write_text("hello world", encoding="utf-8")
|
||||
(tmp_path / "LICENSE.txt").write_text("--- LICENSE stub ---", encoding="utf-8")
|
||||
(tmp_path / "spam.py").write_text(PEP621_EXAMPLE_SCRIPT, encoding="utf-8")
|
||||
return pyproject
|
||||
|
||||
|
||||
def test_pep621_example(tmp_path):
|
||||
"""Make sure the example in PEP 621 works"""
|
||||
pyproject = _pep621_example_project(tmp_path)
|
||||
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
||||
assert dist.metadata.license == "--- LICENSE stub ---"
|
||||
assert set(dist.metadata.license_files) == {"LICENSE.txt"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"readme, ctype",
|
||||
[
|
||||
("Readme.txt", "text/plain"),
|
||||
("readme.md", "text/markdown"),
|
||||
("text.rst", "text/x-rst"),
|
||||
],
|
||||
)
|
||||
def test_readme_content_type(tmp_path, readme, ctype):
|
||||
pyproject = _pep621_example_project(tmp_path, readme)
|
||||
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
||||
assert dist.metadata.long_description_content_type == ctype
|
||||
|
||||
|
||||
def test_undefined_content_type(tmp_path):
|
||||
pyproject = _pep621_example_project(tmp_path, "README.tex")
|
||||
with pytest.raises(ValueError, match="Undefined content type for README.tex"):
|
||||
pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
||||
|
||||
|
||||
def test_no_explicit_content_type_for_missing_extension(tmp_path):
|
||||
pyproject = _pep621_example_project(tmp_path, "README")
|
||||
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
||||
assert dist.metadata.long_description_content_type is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('pyproject_text', 'expected_maintainers_meta_value'),
|
||||
(
|
||||
pytest.param(
|
||||
PEP621_EXAMPLE,
|
||||
(
|
||||
'Brett Cannon <brett@python.org>, "John X. Ãørçeč" <john@utf8.org>, '
|
||||
'Γαμα קּ 東 <gama@utf8.org>'
|
||||
),
|
||||
id='non-international-emails',
|
||||
),
|
||||
pytest.param(
|
||||
PEP621_INTERNATIONAL_EMAIL_EXAMPLE,
|
||||
'Степан Бандера <криївка@оун-упа.укр>',
|
||||
marks=pytest.mark.xfail(
|
||||
reason="CPython's `email.headerregistry.Address` only supports "
|
||||
'RFC 5322, as of Nov 10, 2022 and latest Python 3.11.0',
|
||||
strict=True,
|
||||
),
|
||||
id='international-email',
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_utf8_maintainer_in_metadata( # issue-3663
|
||||
expected_maintainers_meta_value,
|
||||
pyproject_text,
|
||||
tmp_path,
|
||||
):
|
||||
pyproject = _pep621_example_project(
|
||||
tmp_path,
|
||||
"README",
|
||||
pyproject_text=pyproject_text,
|
||||
)
|
||||
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
||||
assert dist.metadata.maintainer_email == expected_maintainers_meta_value
|
||||
pkg_file = tmp_path / "PKG-FILE"
|
||||
with open(pkg_file, "w", encoding="utf-8") as fh:
|
||||
dist.metadata.write_pkg_file(fh)
|
||||
content = pkg_file.read_text(encoding="utf-8")
|
||||
assert f"Maintainer-email: {expected_maintainers_meta_value}" in content
|
||||
|
||||
|
||||
class TestLicenseFiles:
|
||||
# TODO: After PEP 639 is accepted, we have to move the license-files
|
||||
# to the `project` table instead of `tool.setuptools`
|
||||
|
||||
def base_pyproject(self, tmp_path, additional_text):
|
||||
pyproject = _pep621_example_project(tmp_path, "README")
|
||||
text = pyproject.read_text(encoding="utf-8")
|
||||
|
||||
# Sanity-check
|
||||
assert 'license = {file = "LICENSE.txt"}' in text
|
||||
assert "[tool.setuptools]" not in text
|
||||
|
||||
text = f"{text}\n{additional_text}\n"
|
||||
pyproject.write_text(text, encoding="utf-8")
|
||||
return pyproject
|
||||
|
||||
def test_both_license_and_license_files_defined(self, tmp_path):
|
||||
setuptools_config = '[tool.setuptools]\nlicense-files = ["_FILE*"]'
|
||||
pyproject = self.base_pyproject(tmp_path, setuptools_config)
|
||||
|
||||
(tmp_path / "_FILE.txt").touch()
|
||||
(tmp_path / "_FILE.rst").touch()
|
||||
|
||||
# Would normally match the `license_files` patterns, but we want to exclude it
|
||||
# by being explicit. On the other hand, contents should be added to `license`
|
||||
license = tmp_path / "LICENSE.txt"
|
||||
license.write_text("LicenseRef-Proprietary\n", encoding="utf-8")
|
||||
|
||||
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
||||
assert set(dist.metadata.license_files) == {"_FILE.rst", "_FILE.txt"}
|
||||
assert dist.metadata.license == "LicenseRef-Proprietary\n"
|
||||
|
||||
def test_default_patterns(self, tmp_path):
|
||||
setuptools_config = '[tool.setuptools]\nzip-safe = false'
|
||||
# ^ used just to trigger section validation
|
||||
pyproject = self.base_pyproject(tmp_path, setuptools_config)
|
||||
|
||||
license_files = "LICENCE-a.html COPYING-abc.txt AUTHORS-xyz NOTICE,def".split()
|
||||
|
||||
for fname in license_files:
|
||||
(tmp_path / fname).write_text(f"{fname}\n", encoding="utf-8")
|
||||
|
||||
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
||||
assert (tmp_path / "LICENSE.txt").exists() # from base example
|
||||
assert set(dist.metadata.license_files) == {*license_files, "LICENSE.txt"}
|
||||
|
||||
|
||||
class TestPyModules:
|
||||
# https://github.com/pypa/setuptools/issues/4316
|
||||
|
||||
def dist(self, name):
|
||||
toml_config = f"""
|
||||
[project]
|
||||
name = "test"
|
||||
version = "42.0"
|
||||
[tool.setuptools]
|
||||
py-modules = [{name!r}]
|
||||
"""
|
||||
pyproject = Path("pyproject.toml")
|
||||
pyproject.write_text(cleandoc(toml_config), encoding="utf-8")
|
||||
return pyprojecttoml.apply_configuration(Distribution({}), pyproject)
|
||||
|
||||
@pytest.mark.parametrize("module", ["pip-run", "abc-d.λ-xyz-e"])
|
||||
def test_valid_module_name(self, tmp_path, monkeypatch, module):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
assert module in self.dist(module).py_modules
|
||||
|
||||
@pytest.mark.parametrize("module", ["pip run", "-pip-run", "pip-run-stubs"])
|
||||
def test_invalid_module_name(self, tmp_path, monkeypatch, module):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
with pytest.raises(ValueError, match="py-modules"):
|
||||
self.dist(module).py_modules
|
||||
|
||||
|
||||
class TestExtModules:
|
||||
def test_pyproject_sets_attribute(self, tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
pyproject = Path("pyproject.toml")
|
||||
toml_config = """
|
||||
[project]
|
||||
name = "test"
|
||||
version = "42.0"
|
||||
[tool.setuptools]
|
||||
ext-modules = [
|
||||
{name = "my.ext", sources = ["hello.c", "world.c"]}
|
||||
]
|
||||
"""
|
||||
pyproject.write_text(cleandoc(toml_config), encoding="utf-8")
|
||||
with pytest.warns(pyprojecttoml._ExperimentalConfiguration):
|
||||
dist = pyprojecttoml.apply_configuration(Distribution({}), pyproject)
|
||||
assert len(dist.ext_modules) == 1
|
||||
assert dist.ext_modules[0].name == "my.ext"
|
||||
assert set(dist.ext_modules[0].sources) == {"hello.c", "world.c"}
|
||||
|
||||
|
||||
class TestDeprecatedFields:
|
||||
def test_namespace_packages(self, tmp_path):
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
config = """
|
||||
[project]
|
||||
name = "myproj"
|
||||
version = "42"
|
||||
[tool.setuptools]
|
||||
namespace-packages = ["myproj.pkg"]
|
||||
"""
|
||||
pyproject.write_text(cleandoc(config), encoding="utf-8")
|
||||
with pytest.raises(RemovedConfigError, match="namespace-packages"):
|
||||
pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
|
||||
|
||||
|
||||
class TestPresetField:
|
||||
def pyproject(self, tmp_path, dynamic, extra_content=""):
|
||||
content = f"[project]\nname = 'proj'\ndynamic = {dynamic!r}\n"
|
||||
if "version" not in dynamic:
|
||||
content += "version = '42'\n"
|
||||
file = tmp_path / "pyproject.toml"
|
||||
file.write_text(content + extra_content, encoding="utf-8")
|
||||
return file
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"attr, field, value",
|
||||
[
|
||||
("classifiers", "classifiers", ["Private :: Classifier"]),
|
||||
("entry_points", "scripts", {"console_scripts": ["foobar=foobar:main"]}),
|
||||
("entry_points", "gui-scripts", {"gui_scripts": ["bazquux=bazquux:main"]}),
|
||||
pytest.param(
|
||||
*("install_requires", "dependencies", ["six"]),
|
||||
marks=[
|
||||
pytest.mark.filterwarnings("ignore:.*install_requires. overwritten")
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_not_listed_in_dynamic(self, tmp_path, attr, field, value):
|
||||
"""Setuptools cannot set a field if not listed in ``dynamic``"""
|
||||
pyproject = self.pyproject(tmp_path, [])
|
||||
dist = makedist(tmp_path, **{attr: value})
|
||||
msg = re.compile(f"defined outside of `pyproject.toml`:.*{field}", re.S)
|
||||
with pytest.warns(_MissingDynamic, match=msg):
|
||||
dist = pyprojecttoml.apply_configuration(dist, pyproject)
|
||||
|
||||
dist_value = _some_attrgetter(f"metadata.{attr}", attr)(dist)
|
||||
assert not dist_value
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"attr, field, value",
|
||||
[
|
||||
("install_requires", "dependencies", []),
|
||||
("extras_require", "optional-dependencies", {}),
|
||||
("install_requires", "dependencies", ["six"]),
|
||||
("classifiers", "classifiers", ["Private :: Classifier"]),
|
||||
],
|
||||
)
|
||||
def test_listed_in_dynamic(self, tmp_path, attr, field, value):
|
||||
pyproject = self.pyproject(tmp_path, [field])
|
||||
dist = makedist(tmp_path, **{attr: value})
|
||||
dist = pyprojecttoml.apply_configuration(dist, pyproject)
|
||||
dist_value = _some_attrgetter(f"metadata.{attr}", attr)(dist)
|
||||
assert dist_value == value
|
||||
|
||||
def test_warning_overwritten_dependencies(self, tmp_path):
|
||||
src = "[project]\nname='pkg'\nversion='0.1'\ndependencies=['click']\n"
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
pyproject.write_text(src, encoding="utf-8")
|
||||
dist = makedist(tmp_path, install_requires=["wheel"])
|
||||
with pytest.warns(match="`install_requires` overwritten"):
|
||||
dist = pyprojecttoml.apply_configuration(dist, pyproject)
|
||||
assert "wheel" not in dist.install_requires
|
||||
|
||||
def test_optional_dependencies_dont_remove_env_markers(self, tmp_path):
|
||||
"""
|
||||
Internally setuptools converts dependencies with markers to "extras".
|
||||
If ``install_requires`` is given by ``setup.py``, we have to ensure that
|
||||
applying ``optional-dependencies`` does not overwrite the mandatory
|
||||
dependencies with markers (see #3204).
|
||||
"""
|
||||
# If setuptools replace its internal mechanism that uses `requires.txt`
|
||||
# this test has to be rewritten to adapt accordingly
|
||||
extra = "\n[project.optional-dependencies]\nfoo = ['bar>1']\n"
|
||||
pyproject = self.pyproject(tmp_path, ["dependencies"], extra)
|
||||
install_req = ['importlib-resources (>=3.0.0) ; python_version < "3.7"']
|
||||
dist = makedist(tmp_path, install_requires=install_req)
|
||||
dist = pyprojecttoml.apply_configuration(dist, pyproject)
|
||||
assert "foo" in dist.extras_require
|
||||
egg_info = dist.get_command_obj("egg_info")
|
||||
write_requirements(egg_info, tmp_path, tmp_path / "requires.txt")
|
||||
reqs = (tmp_path / "requires.txt").read_text(encoding="utf-8")
|
||||
assert "importlib-resources" in reqs
|
||||
assert "bar" in reqs
|
||||
assert ':python_version < "3.7"' in reqs
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"field,group", [("scripts", "console_scripts"), ("gui-scripts", "gui_scripts")]
|
||||
)
|
||||
@pytest.mark.filterwarnings("error")
|
||||
def test_scripts_dont_require_dynamic_entry_points(self, tmp_path, field, group):
|
||||
# Issue 3862
|
||||
pyproject = self.pyproject(tmp_path, [field])
|
||||
dist = makedist(tmp_path, entry_points={group: ["foobar=foobar:main"]})
|
||||
dist = pyprojecttoml.apply_configuration(dist, pyproject)
|
||||
assert group in dist.entry_points
|
||||
|
||||
|
||||
class TestMeta:
|
||||
def test_example_file_in_sdist(self, setuptools_sdist):
|
||||
"""Meta test to ensure tests can run from sdist"""
|
||||
with tarfile.open(setuptools_sdist) as tar:
|
||||
assert any(name.endswith(EXAMPLES_FILE) for name in tar.getnames())
|
||||
|
||||
|
||||
class TestInteropCommandLineParsing:
|
||||
def test_version(self, tmp_path, monkeypatch, capsys):
|
||||
# See pypa/setuptools#4047
|
||||
# This test can be removed once the CLI interface of setup.py is removed
|
||||
monkeypatch.chdir(tmp_path)
|
||||
toml_config = """
|
||||
[project]
|
||||
name = "test"
|
||||
version = "42.0"
|
||||
"""
|
||||
pyproject = Path(tmp_path, "pyproject.toml")
|
||||
pyproject.write_text(cleandoc(toml_config), encoding="utf-8")
|
||||
opts = {"script_args": ["--version"]}
|
||||
dist = pyprojecttoml.apply_configuration(Distribution(opts), pyproject)
|
||||
dist.parse_command_line() # <-- there should be no exception here.
|
||||
captured = capsys.readouterr()
|
||||
assert "42.0" in captured.out
|
||||
|
||||
|
||||
# --- Auxiliary Functions ---
|
||||
|
||||
|
||||
def core_metadata(dist) -> str:
|
||||
with io.StringIO() as buffer:
|
||||
dist.metadata.write_pkg_file(buffer)
|
||||
pkg_file_txt = buffer.getvalue()
|
||||
|
||||
# Make sure core metadata is valid
|
||||
Metadata.from_email(pkg_file_txt, validate=True) # can raise exceptions
|
||||
|
||||
skip_prefixes: tuple[str, ...] = ()
|
||||
skip_lines = set()
|
||||
# ---- DIFF NORMALISATION ----
|
||||
# PEP 621 is very particular about author/maintainer metadata conversion, so skip
|
||||
skip_prefixes += ("Author:", "Author-email:", "Maintainer:", "Maintainer-email:")
|
||||
# May be redundant with Home-page
|
||||
skip_prefixes += ("Project-URL: Homepage,", "Home-page:")
|
||||
# May be missing in original (relying on default) but backfilled in the TOML
|
||||
skip_prefixes += ("Description-Content-Type:",)
|
||||
# Remove empty lines
|
||||
skip_lines.add("")
|
||||
|
||||
result = []
|
||||
for line in pkg_file_txt.splitlines():
|
||||
if line.startswith(skip_prefixes) or line in skip_lines:
|
||||
continue
|
||||
result.append(line + "\n")
|
||||
|
||||
return "".join(result)
|
||||
@@ -0,0 +1,221 @@
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from setuptools.config import expand
|
||||
from setuptools.discovery import find_package_path
|
||||
|
||||
from distutils.errors import DistutilsOptionError
|
||||
|
||||
|
||||
def write_files(files, root_dir):
|
||||
for file, content in files.items():
|
||||
path = root_dir / file
|
||||
path.parent.mkdir(exist_ok=True, parents=True)
|
||||
path.write_text(content, encoding="utf-8")
|
||||
|
||||
|
||||
def test_glob_relative(tmp_path, monkeypatch):
|
||||
files = {
|
||||
"dir1/dir2/dir3/file1.txt",
|
||||
"dir1/dir2/file2.txt",
|
||||
"dir1/file3.txt",
|
||||
"a.ini",
|
||||
"b.ini",
|
||||
"dir1/c.ini",
|
||||
"dir1/dir2/a.ini",
|
||||
}
|
||||
|
||||
write_files({k: "" for k in files}, tmp_path)
|
||||
patterns = ["**/*.txt", "[ab].*", "**/[ac].ini"]
|
||||
monkeypatch.chdir(tmp_path)
|
||||
assert set(expand.glob_relative(patterns)) == files
|
||||
# Make sure the same APIs work outside cwd
|
||||
assert set(expand.glob_relative(patterns, tmp_path)) == files
|
||||
|
||||
|
||||
def test_read_files(tmp_path, monkeypatch):
|
||||
dir_ = tmp_path / "dir_"
|
||||
(tmp_path / "_dir").mkdir(exist_ok=True)
|
||||
(tmp_path / "a.txt").touch()
|
||||
files = {"a.txt": "a", "dir1/b.txt": "b", "dir1/dir2/c.txt": "c"}
|
||||
write_files(files, dir_)
|
||||
|
||||
secrets = Path(str(dir_) + "secrets")
|
||||
secrets.mkdir(exist_ok=True)
|
||||
write_files({"secrets.txt": "secret keys"}, secrets)
|
||||
|
||||
with monkeypatch.context() as m:
|
||||
m.chdir(dir_)
|
||||
assert expand.read_files(list(files)) == "a\nb\nc"
|
||||
|
||||
cannot_access_msg = r"Cannot access '.*\.\..a\.txt'"
|
||||
with pytest.raises(DistutilsOptionError, match=cannot_access_msg):
|
||||
expand.read_files(["../a.txt"])
|
||||
|
||||
cannot_access_secrets_msg = r"Cannot access '.*secrets\.txt'"
|
||||
with pytest.raises(DistutilsOptionError, match=cannot_access_secrets_msg):
|
||||
expand.read_files(["../dir_secrets/secrets.txt"])
|
||||
|
||||
# Make sure the same APIs work outside cwd
|
||||
assert expand.read_files(list(files), dir_) == "a\nb\nc"
|
||||
with pytest.raises(DistutilsOptionError, match=cannot_access_msg):
|
||||
expand.read_files(["../a.txt"], dir_)
|
||||
|
||||
|
||||
class TestReadAttr:
|
||||
@pytest.mark.parametrize(
|
||||
"example",
|
||||
[
|
||||
# No cookie means UTF-8:
|
||||
b"__version__ = '\xc3\xa9'\nraise SystemExit(1)\n",
|
||||
# If a cookie is present, honor it:
|
||||
b"# -*- coding: utf-8 -*-\n__version__ = '\xc3\xa9'\nraise SystemExit(1)\n",
|
||||
b"# -*- coding: latin1 -*-\n__version__ = '\xe9'\nraise SystemExit(1)\n",
|
||||
],
|
||||
)
|
||||
def test_read_attr_encoding_cookie(self, example, tmp_path):
|
||||
(tmp_path / "mod.py").write_bytes(example)
|
||||
assert expand.read_attr('mod.__version__', root_dir=tmp_path) == 'é'
|
||||
|
||||
def test_read_attr(self, tmp_path, monkeypatch):
|
||||
files = {
|
||||
"pkg/__init__.py": "",
|
||||
"pkg/sub/__init__.py": "VERSION = '0.1.1'",
|
||||
"pkg/sub/mod.py": (
|
||||
"VALUES = {'a': 0, 'b': {42}, 'c': (0, 1, 1)}\nraise SystemExit(1)"
|
||||
),
|
||||
}
|
||||
write_files(files, tmp_path)
|
||||
|
||||
with monkeypatch.context() as m:
|
||||
m.chdir(tmp_path)
|
||||
# Make sure it can read the attr statically without evaluating the module
|
||||
assert expand.read_attr('pkg.sub.VERSION') == '0.1.1'
|
||||
values = expand.read_attr('lib.mod.VALUES', {'lib': 'pkg/sub'})
|
||||
|
||||
assert values['a'] == 0
|
||||
assert values['b'] == {42}
|
||||
|
||||
# Make sure the same APIs work outside cwd
|
||||
assert expand.read_attr('pkg.sub.VERSION', root_dir=tmp_path) == '0.1.1'
|
||||
values = expand.read_attr('lib.mod.VALUES', {'lib': 'pkg/sub'}, tmp_path)
|
||||
assert values['c'] == (0, 1, 1)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"example",
|
||||
[
|
||||
"VERSION: str\nVERSION = '0.1.1'\nraise SystemExit(1)\n",
|
||||
"VERSION: str = '0.1.1'\nraise SystemExit(1)\n",
|
||||
],
|
||||
)
|
||||
def test_read_annotated_attr(self, tmp_path, example):
|
||||
files = {
|
||||
"pkg/__init__.py": "",
|
||||
"pkg/sub/__init__.py": example,
|
||||
}
|
||||
write_files(files, tmp_path)
|
||||
# Make sure this attribute can be read statically
|
||||
assert expand.read_attr('pkg.sub.VERSION', root_dir=tmp_path) == '0.1.1'
|
||||
|
||||
def test_import_order(self, tmp_path):
|
||||
"""
|
||||
Sometimes the import machinery will import the parent package of a nested
|
||||
module, which triggers side-effects and might create problems (see issue #3176)
|
||||
|
||||
``read_attr`` should bypass these limitations by resolving modules statically
|
||||
(via ast.literal_eval).
|
||||
"""
|
||||
files = {
|
||||
"src/pkg/__init__.py": "from .main import func\nfrom .about import version",
|
||||
"src/pkg/main.py": "import super_complicated_dep\ndef func(): return 42",
|
||||
"src/pkg/about.py": "version = '42'",
|
||||
}
|
||||
write_files(files, tmp_path)
|
||||
attr_desc = "pkg.about.version"
|
||||
package_dir = {"": "src"}
|
||||
# `import super_complicated_dep` should not run, otherwise the build fails
|
||||
assert expand.read_attr(attr_desc, package_dir, tmp_path) == "42"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'package_dir, file, module, return_value',
|
||||
[
|
||||
({"": "src"}, "src/pkg/main.py", "pkg.main", 42),
|
||||
({"pkg": "lib"}, "lib/main.py", "pkg.main", 13),
|
||||
({}, "single_module.py", "single_module", 70),
|
||||
({}, "flat_layout/pkg.py", "flat_layout.pkg", 836),
|
||||
],
|
||||
)
|
||||
def test_resolve_class(monkeypatch, tmp_path, package_dir, file, module, return_value):
|
||||
monkeypatch.setattr(sys, "modules", {}) # reproducibility
|
||||
files = {file: f"class Custom:\n def testing(self): return {return_value}"}
|
||||
write_files(files, tmp_path)
|
||||
cls = expand.resolve_class(f"{module}.Custom", package_dir, tmp_path)
|
||||
assert cls().testing() == return_value
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'args, pkgs',
|
||||
[
|
||||
({"where": ["."], "namespaces": False}, {"pkg", "other"}),
|
||||
({"where": [".", "dir1"], "namespaces": False}, {"pkg", "other", "dir2"}),
|
||||
({"namespaces": True}, {"pkg", "other", "dir1", "dir1.dir2"}),
|
||||
({}, {"pkg", "other", "dir1", "dir1.dir2"}), # default value for `namespaces`
|
||||
],
|
||||
)
|
||||
def test_find_packages(tmp_path, args, pkgs):
|
||||
files = {
|
||||
"pkg/__init__.py",
|
||||
"other/__init__.py",
|
||||
"dir1/dir2/__init__.py",
|
||||
}
|
||||
write_files({k: "" for k in files}, tmp_path)
|
||||
|
||||
package_dir = {}
|
||||
kwargs = {"root_dir": tmp_path, "fill_package_dir": package_dir, **args}
|
||||
where = kwargs.get("where", ["."])
|
||||
assert set(expand.find_packages(**kwargs)) == pkgs
|
||||
for pkg in pkgs:
|
||||
pkg_path = find_package_path(pkg, package_dir, tmp_path)
|
||||
assert os.path.exists(pkg_path)
|
||||
|
||||
# Make sure the same APIs work outside cwd
|
||||
where = [
|
||||
str((tmp_path / p).resolve()).replace(os.sep, "/") # ensure posix-style paths
|
||||
for p in args.pop("where", ["."])
|
||||
]
|
||||
|
||||
assert set(expand.find_packages(where=where, **args)) == pkgs
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"files, where, expected_package_dir",
|
||||
[
|
||||
(["pkg1/__init__.py", "pkg1/other.py"], ["."], {}),
|
||||
(["pkg1/__init__.py", "pkg2/__init__.py"], ["."], {}),
|
||||
(["src/pkg1/__init__.py", "src/pkg1/other.py"], ["src"], {"": "src"}),
|
||||
(["src/pkg1/__init__.py", "src/pkg2/__init__.py"], ["src"], {"": "src"}),
|
||||
(
|
||||
["src1/pkg1/__init__.py", "src2/pkg2/__init__.py"],
|
||||
["src1", "src2"],
|
||||
{"pkg1": "src1/pkg1", "pkg2": "src2/pkg2"},
|
||||
),
|
||||
(
|
||||
["src/pkg1/__init__.py", "pkg2/__init__.py"],
|
||||
["src", "."],
|
||||
{"pkg1": "src/pkg1"},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_fill_package_dir(tmp_path, files, where, expected_package_dir):
|
||||
write_files({k: "" for k in files}, tmp_path)
|
||||
pkg_dir = {}
|
||||
kwargs = {"root_dir": tmp_path, "fill_package_dir": pkg_dir, "namespaces": False}
|
||||
pkgs = expand.find_packages(where=where, **kwargs)
|
||||
assert set(pkg_dir.items()) == set(expected_package_dir.items())
|
||||
for pkg in pkgs:
|
||||
pkg_path = find_package_path(pkg, pkg_dir, tmp_path)
|
||||
assert os.path.exists(pkg_path)
|
||||
+396
@@ -0,0 +1,396 @@
|
||||
import re
|
||||
from configparser import ConfigParser
|
||||
from inspect import cleandoc
|
||||
|
||||
import jaraco.path
|
||||
import pytest
|
||||
import tomli_w
|
||||
from path import Path
|
||||
|
||||
import setuptools # noqa: F401 # force distutils.core to be patched
|
||||
from setuptools.config.pyprojecttoml import (
|
||||
_ToolsTypoInMetadata,
|
||||
apply_configuration,
|
||||
expand_configuration,
|
||||
read_configuration,
|
||||
validate,
|
||||
)
|
||||
from setuptools.dist import Distribution
|
||||
from setuptools.errors import OptionError
|
||||
|
||||
import distutils.core
|
||||
|
||||
EXAMPLE = """
|
||||
[project]
|
||||
name = "myproj"
|
||||
keywords = ["some", "key", "words"]
|
||||
dynamic = ["version", "readme"]
|
||||
requires-python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
dependencies = [
|
||||
'importlib-metadata>=0.12;python_version<"3.8"',
|
||||
'importlib-resources>=1.0;python_version<"3.7"',
|
||||
'pathlib2>=2.3.3,<3;python_version < "3.4" and sys.platform != "win32"',
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
docs = [
|
||||
"sphinx>=3",
|
||||
"sphinx-argparse>=0.2.5",
|
||||
"sphinx-rtd-theme>=0.4.3",
|
||||
]
|
||||
testing = [
|
||||
"pytest>=1",
|
||||
"coverage>=3,<5",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
exec = "pkg.__main__:exec"
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.setuptools]
|
||||
package-dir = {"" = "src"}
|
||||
zip-safe = true
|
||||
platforms = ["any"]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
|
||||
[tool.setuptools.cmdclass]
|
||||
sdist = "pkg.mod.CustomSdist"
|
||||
|
||||
[tool.setuptools.dynamic.version]
|
||||
attr = "pkg.__version__.VERSION"
|
||||
|
||||
[tool.setuptools.dynamic.readme]
|
||||
file = ["README.md"]
|
||||
content-type = "text/markdown"
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
"*" = ["*.txt"]
|
||||
|
||||
[tool.setuptools.data-files]
|
||||
"data" = ["_files/*.txt"]
|
||||
|
||||
[tool.distutils.sdist]
|
||||
formats = "gztar"
|
||||
|
||||
[tool.distutils.bdist_wheel]
|
||||
universal = true
|
||||
"""
|
||||
|
||||
|
||||
def create_example(path, pkg_root):
|
||||
files = {
|
||||
"pyproject.toml": EXAMPLE,
|
||||
"README.md": "hello world",
|
||||
"_files": {
|
||||
"file.txt": "",
|
||||
},
|
||||
}
|
||||
packages = {
|
||||
"pkg": {
|
||||
"__init__.py": "",
|
||||
"mod.py": "class CustomSdist: pass",
|
||||
"__version__.py": "VERSION = (3, 10)",
|
||||
"__main__.py": "def exec(): print('hello')",
|
||||
},
|
||||
}
|
||||
|
||||
assert pkg_root # Meta-test: cannot be empty string.
|
||||
|
||||
if pkg_root == ".":
|
||||
files = {**files, **packages}
|
||||
# skip other files: flat-layout will raise error for multi-package dist
|
||||
else:
|
||||
# Use this opportunity to ensure namespaces are discovered
|
||||
files[pkg_root] = {**packages, "other": {"nested": {"__init__.py": ""}}}
|
||||
|
||||
jaraco.path.build(files, prefix=path)
|
||||
|
||||
|
||||
def verify_example(config, path, pkg_root):
|
||||
pyproject = path / "pyproject.toml"
|
||||
pyproject.write_text(tomli_w.dumps(config), encoding="utf-8")
|
||||
expanded = expand_configuration(config, path)
|
||||
expanded_project = expanded["project"]
|
||||
assert read_configuration(pyproject, expand=True) == expanded
|
||||
assert expanded_project["version"] == "3.10"
|
||||
assert expanded_project["readme"]["text"] == "hello world"
|
||||
assert "packages" in expanded["tool"]["setuptools"]
|
||||
if pkg_root == ".":
|
||||
# Auto-discovery will raise error for multi-package dist
|
||||
assert set(expanded["tool"]["setuptools"]["packages"]) == {"pkg"}
|
||||
else:
|
||||
assert set(expanded["tool"]["setuptools"]["packages"]) == {
|
||||
"pkg",
|
||||
"other",
|
||||
"other.nested",
|
||||
}
|
||||
assert expanded["tool"]["setuptools"]["include-package-data"] is True
|
||||
assert "" in expanded["tool"]["setuptools"]["package-data"]
|
||||
assert "*" not in expanded["tool"]["setuptools"]["package-data"]
|
||||
assert expanded["tool"]["setuptools"]["data-files"] == [
|
||||
("data", ["_files/file.txt"])
|
||||
]
|
||||
|
||||
|
||||
def test_read_configuration(tmp_path):
|
||||
create_example(tmp_path, "src")
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
|
||||
config = read_configuration(pyproject, expand=False)
|
||||
assert config["project"].get("version") is None
|
||||
assert config["project"].get("readme") is None
|
||||
|
||||
verify_example(config, tmp_path, "src")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pkg_root, opts",
|
||||
[
|
||||
(".", {}),
|
||||
("src", {}),
|
||||
("lib", {"packages": {"find": {"where": ["lib"]}}}),
|
||||
],
|
||||
)
|
||||
def test_discovered_package_dir_with_attr_directive_in_config(tmp_path, pkg_root, opts):
|
||||
create_example(tmp_path, pkg_root)
|
||||
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
|
||||
config = read_configuration(pyproject, expand=False)
|
||||
assert config["project"].get("version") is None
|
||||
assert config["project"].get("readme") is None
|
||||
config["tool"]["setuptools"].pop("packages", None)
|
||||
config["tool"]["setuptools"].pop("package-dir", None)
|
||||
|
||||
config["tool"]["setuptools"].update(opts)
|
||||
verify_example(config, tmp_path, pkg_root)
|
||||
|
||||
|
||||
ENTRY_POINTS = {
|
||||
"console_scripts": {"a": "mod.a:func"},
|
||||
"gui_scripts": {"b": "mod.b:func"},
|
||||
"other": {"c": "mod.c:func [extra]"},
|
||||
}
|
||||
|
||||
|
||||
class TestEntryPoints:
|
||||
def write_entry_points(self, tmp_path):
|
||||
entry_points = ConfigParser()
|
||||
entry_points.read_dict(ENTRY_POINTS)
|
||||
with open(tmp_path / "entry-points.txt", "w", encoding="utf-8") as f:
|
||||
entry_points.write(f)
|
||||
|
||||
def pyproject(self, dynamic=None):
|
||||
project = {"dynamic": dynamic or ["scripts", "gui-scripts", "entry-points"]}
|
||||
tool = {"dynamic": {"entry-points": {"file": "entry-points.txt"}}}
|
||||
return {"project": project, "tool": {"setuptools": tool}}
|
||||
|
||||
def test_all_listed_in_dynamic(self, tmp_path):
|
||||
self.write_entry_points(tmp_path)
|
||||
expanded = expand_configuration(self.pyproject(), tmp_path)
|
||||
expanded_project = expanded["project"]
|
||||
assert len(expanded_project["scripts"]) == 1
|
||||
assert expanded_project["scripts"]["a"] == "mod.a:func"
|
||||
assert len(expanded_project["gui-scripts"]) == 1
|
||||
assert expanded_project["gui-scripts"]["b"] == "mod.b:func"
|
||||
assert len(expanded_project["entry-points"]) == 1
|
||||
assert expanded_project["entry-points"]["other"]["c"] == "mod.c:func [extra]"
|
||||
|
||||
@pytest.mark.parametrize("missing_dynamic", ("scripts", "gui-scripts"))
|
||||
def test_scripts_not_listed_in_dynamic(self, tmp_path, missing_dynamic):
|
||||
self.write_entry_points(tmp_path)
|
||||
dynamic = {"scripts", "gui-scripts", "entry-points"} - {missing_dynamic}
|
||||
|
||||
msg = f"defined outside of `pyproject.toml`:.*{missing_dynamic}"
|
||||
with pytest.raises(OptionError, match=re.compile(msg, re.S)):
|
||||
expand_configuration(self.pyproject(dynamic), tmp_path)
|
||||
|
||||
|
||||
class TestClassifiers:
|
||||
def test_dynamic(self, tmp_path):
|
||||
# Let's create a project example that has dynamic classifiers
|
||||
# coming from a txt file.
|
||||
create_example(tmp_path, "src")
|
||||
classifiers = cleandoc(
|
||||
"""
|
||||
Framework :: Flask
|
||||
Programming Language :: Haskell
|
||||
"""
|
||||
)
|
||||
(tmp_path / "classifiers.txt").write_text(classifiers, encoding="utf-8")
|
||||
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
config = read_configuration(pyproject, expand=False)
|
||||
dynamic = config["project"]["dynamic"]
|
||||
config["project"]["dynamic"] = list({*dynamic, "classifiers"})
|
||||
dynamic_config = config["tool"]["setuptools"]["dynamic"]
|
||||
dynamic_config["classifiers"] = {"file": "classifiers.txt"}
|
||||
|
||||
# When the configuration is expanded,
|
||||
# each line of the file should be an different classifier.
|
||||
validate(config, pyproject)
|
||||
expanded = expand_configuration(config, tmp_path)
|
||||
|
||||
assert set(expanded["project"]["classifiers"]) == {
|
||||
"Framework :: Flask",
|
||||
"Programming Language :: Haskell",
|
||||
}
|
||||
|
||||
def test_dynamic_without_config(self, tmp_path):
|
||||
config = """
|
||||
[project]
|
||||
name = "myproj"
|
||||
version = '42'
|
||||
dynamic = ["classifiers"]
|
||||
"""
|
||||
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
pyproject.write_text(cleandoc(config), encoding="utf-8")
|
||||
with pytest.raises(OptionError, match="No configuration .* .classifiers."):
|
||||
read_configuration(pyproject)
|
||||
|
||||
def test_dynamic_readme_from_setup_script_args(self, tmp_path):
|
||||
config = """
|
||||
[project]
|
||||
name = "myproj"
|
||||
version = '42'
|
||||
dynamic = ["readme"]
|
||||
"""
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
pyproject.write_text(cleandoc(config), encoding="utf-8")
|
||||
dist = Distribution(attrs={"long_description": "42"})
|
||||
# No error should occur because of missing `readme`
|
||||
dist = apply_configuration(dist, pyproject)
|
||||
assert dist.metadata.long_description == "42"
|
||||
|
||||
def test_dynamic_without_file(self, tmp_path):
|
||||
config = """
|
||||
[project]
|
||||
name = "myproj"
|
||||
version = '42'
|
||||
dynamic = ["classifiers"]
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
classifiers = {file = ["classifiers.txt"]}
|
||||
"""
|
||||
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
pyproject.write_text(cleandoc(config), encoding="utf-8")
|
||||
with pytest.warns(UserWarning, match="File .*classifiers.txt. cannot be found"):
|
||||
expanded = read_configuration(pyproject)
|
||||
assert "classifiers" not in expanded["project"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"example",
|
||||
(
|
||||
"""
|
||||
[project]
|
||||
name = "myproj"
|
||||
version = "1.2"
|
||||
|
||||
[my-tool.that-disrespect.pep518]
|
||||
value = 42
|
||||
""",
|
||||
),
|
||||
)
|
||||
def test_ignore_unrelated_config(tmp_path, example):
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
pyproject.write_text(cleandoc(example), encoding="utf-8")
|
||||
|
||||
# Make sure no error is raised due to 3rd party configs in pyproject.toml
|
||||
assert read_configuration(pyproject) is not None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"example, error_msg",
|
||||
[
|
||||
(
|
||||
"""
|
||||
[project]
|
||||
name = "myproj"
|
||||
version = "1.2"
|
||||
requires = ['pywin32; platform_system=="Windows"' ]
|
||||
""",
|
||||
"configuration error: .project. must not contain ..requires.. properties",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_invalid_example(tmp_path, example, error_msg):
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
pyproject.write_text(cleandoc(example), encoding="utf-8")
|
||||
|
||||
pattern = re.compile(f"invalid pyproject.toml.*{error_msg}.*", re.M | re.S)
|
||||
with pytest.raises(ValueError, match=pattern):
|
||||
read_configuration(pyproject)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config", ("", "[tool.something]\nvalue = 42"))
|
||||
def test_empty(tmp_path, config):
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
pyproject.write_text(config, encoding="utf-8")
|
||||
|
||||
# Make sure no error is raised
|
||||
assert read_configuration(pyproject) == {}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config", ("[project]\nname = 'myproj'\nversion='42'\n",))
|
||||
def test_include_package_data_by_default(tmp_path, config):
|
||||
"""Builds with ``pyproject.toml`` should consider ``include-package-data=True`` as
|
||||
default.
|
||||
"""
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
pyproject.write_text(config, encoding="utf-8")
|
||||
|
||||
config = read_configuration(pyproject)
|
||||
assert config["tool"]["setuptools"]["include-package-data"] is True
|
||||
|
||||
|
||||
def test_include_package_data_in_setuppy(tmp_path):
|
||||
"""Builds with ``pyproject.toml`` should consider ``include_package_data`` set in
|
||||
``setup.py``.
|
||||
|
||||
See https://github.com/pypa/setuptools/issues/3197#issuecomment-1079023889
|
||||
"""
|
||||
files = {
|
||||
"pyproject.toml": "[project]\nname = 'myproj'\nversion='42'\n",
|
||||
"setup.py": "__import__('setuptools').setup(include_package_data=False)",
|
||||
}
|
||||
jaraco.path.build(files, prefix=tmp_path)
|
||||
|
||||
with Path(tmp_path):
|
||||
dist = distutils.core.run_setup("setup.py", {}, stop_after="config")
|
||||
|
||||
assert dist.get_name() == "myproj"
|
||||
assert dist.get_version() == "42"
|
||||
assert dist.include_package_data is False
|
||||
|
||||
|
||||
def test_warn_tools_typo(tmp_path):
|
||||
"""Test that the common ``tools.setuptools`` typo in ``pyproject.toml`` issues a warning
|
||||
|
||||
See https://github.com/pypa/setuptools/issues/4150
|
||||
"""
|
||||
config = """
|
||||
[build-system]
|
||||
requires = ["setuptools"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "myproj"
|
||||
version = '42'
|
||||
|
||||
[tools.setuptools]
|
||||
packages = ["package"]
|
||||
"""
|
||||
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
pyproject.write_text(cleandoc(config), encoding="utf-8")
|
||||
|
||||
with pytest.warns(_ToolsTypoInMetadata):
|
||||
read_configuration(pyproject)
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
from inspect import cleandoc
|
||||
|
||||
import pytest
|
||||
from jaraco import path
|
||||
|
||||
from setuptools.config.pyprojecttoml import apply_configuration
|
||||
from setuptools.dist import Distribution
|
||||
|
||||
|
||||
def test_dynamic_dependencies(tmp_path):
|
||||
files = {
|
||||
"requirements.txt": "six\n # comment\n",
|
||||
"pyproject.toml": cleandoc(
|
||||
"""
|
||||
[project]
|
||||
name = "myproj"
|
||||
version = "1.0"
|
||||
dynamic = ["dependencies"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.setuptools.dynamic.dependencies]
|
||||
file = ["requirements.txt"]
|
||||
"""
|
||||
),
|
||||
}
|
||||
path.build(files, prefix=tmp_path)
|
||||
dist = Distribution()
|
||||
dist = apply_configuration(dist, tmp_path / "pyproject.toml")
|
||||
assert dist.install_requires == ["six"]
|
||||
|
||||
|
||||
def test_dynamic_optional_dependencies(tmp_path):
|
||||
files = {
|
||||
"requirements-docs.txt": "sphinx\n # comment\n",
|
||||
"pyproject.toml": cleandoc(
|
||||
"""
|
||||
[project]
|
||||
name = "myproj"
|
||||
version = "1.0"
|
||||
dynamic = ["optional-dependencies"]
|
||||
|
||||
[tool.setuptools.dynamic.optional-dependencies.docs]
|
||||
file = ["requirements-docs.txt"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
"""
|
||||
),
|
||||
}
|
||||
path.build(files, prefix=tmp_path)
|
||||
dist = Distribution()
|
||||
dist = apply_configuration(dist, tmp_path / "pyproject.toml")
|
||||
assert dist.extras_require == {"docs": ["sphinx"]}
|
||||
|
||||
|
||||
def test_mixed_dynamic_optional_dependencies(tmp_path):
|
||||
"""
|
||||
Test that if PEP 621 was loosened to allow mixing of dynamic and static
|
||||
configurations in the case of fields containing sub-fields (groups),
|
||||
things would work out.
|
||||
"""
|
||||
files = {
|
||||
"requirements-images.txt": "pillow~=42.0\n # comment\n",
|
||||
"pyproject.toml": cleandoc(
|
||||
"""
|
||||
[project]
|
||||
name = "myproj"
|
||||
version = "1.0"
|
||||
dynamic = ["optional-dependencies"]
|
||||
|
||||
[project.optional-dependencies]
|
||||
docs = ["sphinx"]
|
||||
|
||||
[tool.setuptools.dynamic.optional-dependencies.images]
|
||||
file = ["requirements-images.txt"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
"""
|
||||
),
|
||||
}
|
||||
|
||||
path.build(files, prefix=tmp_path)
|
||||
|
||||
# Test that the mix-and-match doesn't currently validate.
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
with pytest.raises(ValueError, match="project.optional-dependencies"):
|
||||
apply_configuration(Distribution(), pyproject)
|
||||
|
||||
# Explicitly disable the validation and try again, to see that the mix-and-match
|
||||
# result would be correct.
|
||||
dist = Distribution()
|
||||
dist = apply_configuration(dist, pyproject, ignore_option_errors=True)
|
||||
assert dist.extras_require == {"docs": ["sphinx"], "images": ["pillow~=42.0"]}
|
||||
@@ -0,0 +1,969 @@
|
||||
import configparser
|
||||
import contextlib
|
||||
import inspect
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
from packaging.requirements import InvalidRequirement
|
||||
|
||||
from setuptools.config.setupcfg import ConfigHandler, read_configuration
|
||||
from setuptools.dist import Distribution, _Distribution
|
||||
from setuptools.warnings import SetuptoolsDeprecationWarning
|
||||
|
||||
from ..textwrap import DALS
|
||||
|
||||
from distutils.errors import DistutilsFileError, DistutilsOptionError
|
||||
|
||||
|
||||
class ErrConfigHandler(ConfigHandler):
|
||||
"""Erroneous handler. Fails to implement required methods."""
|
||||
|
||||
section_prefix = "**err**"
|
||||
|
||||
|
||||
def make_package_dir(name, base_dir, ns=False):
|
||||
dir_package = base_dir
|
||||
for dir_name in name.split('/'):
|
||||
dir_package = dir_package.mkdir(dir_name)
|
||||
init_file = None
|
||||
if not ns:
|
||||
init_file = dir_package.join('__init__.py')
|
||||
init_file.write('')
|
||||
return dir_package, init_file
|
||||
|
||||
|
||||
def fake_env(
|
||||
tmpdir, setup_cfg, setup_py=None, encoding='ascii', package_path='fake_package'
|
||||
):
|
||||
if setup_py is None:
|
||||
setup_py = 'from setuptools import setup\nsetup()\n'
|
||||
|
||||
tmpdir.join('setup.py').write(setup_py)
|
||||
config = tmpdir.join('setup.cfg')
|
||||
config.write(setup_cfg.encode(encoding), mode='wb')
|
||||
|
||||
package_dir, init_file = make_package_dir(package_path, tmpdir)
|
||||
|
||||
init_file.write(
|
||||
'VERSION = (1, 2, 3)\n'
|
||||
'\n'
|
||||
'VERSION_MAJOR = 1'
|
||||
'\n'
|
||||
'def get_version():\n'
|
||||
' return [3, 4, 5, "dev"]\n'
|
||||
'\n'
|
||||
)
|
||||
|
||||
return package_dir, config
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def get_dist(tmpdir, kwargs_initial=None, parse=True):
|
||||
kwargs_initial = kwargs_initial or {}
|
||||
|
||||
with tmpdir.as_cwd():
|
||||
dist = Distribution(kwargs_initial)
|
||||
dist.script_name = 'setup.py'
|
||||
parse and dist.parse_config_files()
|
||||
|
||||
yield dist
|
||||
|
||||
|
||||
def test_parsers_implemented():
|
||||
with pytest.raises(NotImplementedError):
|
||||
handler = ErrConfigHandler(None, {}, False, Mock())
|
||||
handler.parsers
|
||||
|
||||
|
||||
class TestConfigurationReader:
|
||||
def test_basic(self, tmpdir):
|
||||
_, config = fake_env(
|
||||
tmpdir,
|
||||
'[metadata]\n'
|
||||
'version = 10.1.1\n'
|
||||
'keywords = one, two\n'
|
||||
'\n'
|
||||
'[options]\n'
|
||||
'scripts = bin/a.py, bin/b.py\n',
|
||||
)
|
||||
config_dict = read_configuration('%s' % config)
|
||||
assert config_dict['metadata']['version'] == '10.1.1'
|
||||
assert config_dict['metadata']['keywords'] == ['one', 'two']
|
||||
assert config_dict['options']['scripts'] == ['bin/a.py', 'bin/b.py']
|
||||
|
||||
def test_no_config(self, tmpdir):
|
||||
with pytest.raises(DistutilsFileError):
|
||||
read_configuration('%s' % tmpdir.join('setup.cfg'))
|
||||
|
||||
def test_ignore_errors(self, tmpdir):
|
||||
_, config = fake_env(
|
||||
tmpdir,
|
||||
'[metadata]\nversion = attr: none.VERSION\nkeywords = one, two\n',
|
||||
)
|
||||
with pytest.raises(ImportError):
|
||||
read_configuration('%s' % config)
|
||||
|
||||
config_dict = read_configuration('%s' % config, ignore_option_errors=True)
|
||||
|
||||
assert config_dict['metadata']['keywords'] == ['one', 'two']
|
||||
assert 'version' not in config_dict['metadata']
|
||||
|
||||
config.remove()
|
||||
|
||||
|
||||
class TestMetadata:
|
||||
def test_basic(self, tmpdir):
|
||||
fake_env(
|
||||
tmpdir,
|
||||
'[metadata]\n'
|
||||
'version = 10.1.1\n'
|
||||
'description = Some description\n'
|
||||
'long_description_content_type = text/something\n'
|
||||
'long_description = file: README\n'
|
||||
'name = fake_name\n'
|
||||
'keywords = one, two\n'
|
||||
'provides = package, package.sub\n'
|
||||
'license = otherlic\n'
|
||||
'download_url = http://test.test.com/test/\n'
|
||||
'maintainer_email = test@test.com\n',
|
||||
)
|
||||
|
||||
tmpdir.join('README').write('readme contents\nline2')
|
||||
|
||||
meta_initial = {
|
||||
# This will be used so `otherlic` won't replace it.
|
||||
'license': 'BSD 3-Clause License',
|
||||
}
|
||||
|
||||
with get_dist(tmpdir, meta_initial) as dist:
|
||||
metadata = dist.metadata
|
||||
|
||||
assert metadata.version == '10.1.1'
|
||||
assert metadata.description == 'Some description'
|
||||
assert metadata.long_description_content_type == 'text/something'
|
||||
assert metadata.long_description == 'readme contents\nline2'
|
||||
assert metadata.provides == ['package', 'package.sub']
|
||||
assert metadata.license == 'BSD 3-Clause License'
|
||||
assert metadata.name == 'fake_name'
|
||||
assert metadata.keywords == ['one', 'two']
|
||||
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(
|
||||
tmpdir,
|
||||
'[metadata]\nlong_description = file: README.rst, CHANGES.rst\n\n',
|
||||
)
|
||||
|
||||
tmpdir.join('README.rst').write('readme contents\nline2')
|
||||
tmpdir.join('CHANGES.rst').write('changelog contents\nand stuff')
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.metadata.long_description == (
|
||||
'readme contents\nline2\nchangelog contents\nand stuff'
|
||||
)
|
||||
|
||||
def test_file_sandboxed(self, tmpdir):
|
||||
tmpdir.ensure("README")
|
||||
project = tmpdir.join('depth1', 'depth2')
|
||||
project.ensure(dir=True)
|
||||
fake_env(project, '[metadata]\nlong_description = file: ../../README\n')
|
||||
|
||||
with get_dist(project, parse=False) as dist:
|
||||
with pytest.raises(DistutilsOptionError):
|
||||
dist.parse_config_files() # file: out of sandbox
|
||||
|
||||
def test_aliases(self, tmpdir):
|
||||
fake_env(
|
||||
tmpdir,
|
||||
'[metadata]\n'
|
||||
'author_email = test@test.com\n'
|
||||
'home_page = http://test.test.com/test/\n'
|
||||
'summary = Short summary\n'
|
||||
'platform = a, b\n'
|
||||
'classifier =\n'
|
||||
' Framework :: Django\n'
|
||||
' Programming Language :: Python :: 3.5\n',
|
||||
)
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
metadata = dist.metadata
|
||||
assert metadata.author_email == 'test@test.com'
|
||||
assert metadata.url == 'http://test.test.com/test/'
|
||||
assert metadata.description == 'Short summary'
|
||||
assert metadata.platforms == ['a', 'b']
|
||||
assert metadata.classifiers == [
|
||||
'Framework :: Django',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
]
|
||||
|
||||
def test_multiline(self, tmpdir):
|
||||
fake_env(
|
||||
tmpdir,
|
||||
'[metadata]\n'
|
||||
'name = fake_name\n'
|
||||
'keywords =\n'
|
||||
' one\n'
|
||||
' two\n'
|
||||
'classifiers =\n'
|
||||
' Framework :: Django\n'
|
||||
' Programming Language :: Python :: 3.5\n',
|
||||
)
|
||||
with get_dist(tmpdir) as dist:
|
||||
metadata = dist.metadata
|
||||
assert metadata.keywords == ['one', 'two']
|
||||
assert metadata.classifiers == [
|
||||
'Framework :: Django',
|
||||
'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):
|
||||
package_dir, config = fake_env(
|
||||
tmpdir, '[metadata]\nversion = attr: fake_package.VERSION\n'
|
||||
)
|
||||
|
||||
sub_a = package_dir.mkdir('subpkg_a')
|
||||
sub_a.join('__init__.py').write('')
|
||||
sub_a.join('mod.py').write('VERSION = (2016, 11, 26)')
|
||||
|
||||
sub_b = package_dir.mkdir('subpkg_b')
|
||||
sub_b.join('__init__.py').write('')
|
||||
sub_b.join('mod.py').write(
|
||||
'import third_party_module\nVERSION = (2016, 11, 26)'
|
||||
)
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.metadata.version == '1.2.3'
|
||||
|
||||
config.write('[metadata]\nversion = attr: fake_package.get_version\n')
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.metadata.version == '3.4.5.dev'
|
||||
|
||||
config.write('[metadata]\nversion = attr: fake_package.VERSION_MAJOR\n')
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.metadata.version == '1'
|
||||
|
||||
config.write('[metadata]\nversion = attr: fake_package.subpkg_a.mod.VERSION\n')
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.metadata.version == '2016.11.26'
|
||||
|
||||
config.write('[metadata]\nversion = attr: fake_package.subpkg_b.mod.VERSION\n')
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.metadata.version == '2016.11.26'
|
||||
|
||||
def test_version_file(self, tmpdir):
|
||||
_, config = fake_env(
|
||||
tmpdir, '[metadata]\nversion = file: fake_package/version.txt\n'
|
||||
)
|
||||
tmpdir.join('fake_package', 'version.txt').write('1.2.3\n')
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.metadata.version == '1.2.3'
|
||||
|
||||
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
|
||||
|
||||
def test_version_with_package_dir_simple(self, tmpdir):
|
||||
_, config = fake_env(
|
||||
tmpdir,
|
||||
'[metadata]\n'
|
||||
'version = attr: fake_package_simple.VERSION\n'
|
||||
'[options]\n'
|
||||
'package_dir =\n'
|
||||
' = src\n',
|
||||
package_path='src/fake_package_simple',
|
||||
)
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.metadata.version == '1.2.3'
|
||||
|
||||
def test_version_with_package_dir_rename(self, tmpdir):
|
||||
_, config = fake_env(
|
||||
tmpdir,
|
||||
'[metadata]\n'
|
||||
'version = attr: fake_package_rename.VERSION\n'
|
||||
'[options]\n'
|
||||
'package_dir =\n'
|
||||
' fake_package_rename = fake_dir\n',
|
||||
package_path='fake_dir',
|
||||
)
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.metadata.version == '1.2.3'
|
||||
|
||||
def test_version_with_package_dir_complex(self, tmpdir):
|
||||
_, config = fake_env(
|
||||
tmpdir,
|
||||
'[metadata]\n'
|
||||
'version = attr: fake_package_complex.VERSION\n'
|
||||
'[options]\n'
|
||||
'package_dir =\n'
|
||||
' fake_package_complex = src/fake_dir\n',
|
||||
package_path='src/fake_dir',
|
||||
)
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.metadata.version == '1.2.3'
|
||||
|
||||
def test_unknown_meta_item(self, tmpdir):
|
||||
fake_env(tmpdir, '[metadata]\nname = fake_name\nunknown = some\n')
|
||||
with get_dist(tmpdir, parse=False) as dist:
|
||||
dist.parse_config_files() # Skip unknown.
|
||||
|
||||
def test_usupported_section(self, tmpdir):
|
||||
fake_env(tmpdir, '[metadata.some]\nkey = val\n')
|
||||
with get_dist(tmpdir, parse=False) as dist:
|
||||
with pytest.raises(DistutilsOptionError):
|
||||
dist.parse_config_files()
|
||||
|
||||
def test_classifiers(self, tmpdir):
|
||||
expected = set([
|
||||
'Framework :: Django',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
])
|
||||
|
||||
# From file.
|
||||
_, config = fake_env(tmpdir, '[metadata]\nclassifiers = file: classifiers\n')
|
||||
|
||||
tmpdir.join('classifiers').write(
|
||||
'Framework :: Django\n'
|
||||
'Programming Language :: Python :: 3\n'
|
||||
'Programming Language :: Python :: 3.5\n'
|
||||
)
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert set(dist.metadata.classifiers) == expected
|
||||
|
||||
# From list notation
|
||||
config.write(
|
||||
'[metadata]\n'
|
||||
'classifiers =\n'
|
||||
' Framework :: Django\n'
|
||||
' Programming Language :: Python :: 3\n'
|
||||
' Programming Language :: Python :: 3.5\n'
|
||||
)
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert set(dist.metadata.classifiers) == expected
|
||||
|
||||
def test_interpolation(self, tmpdir):
|
||||
fake_env(tmpdir, '[metadata]\ndescription = %(message)s\n')
|
||||
with pytest.raises(configparser.InterpolationMissingOptionError):
|
||||
with get_dist(tmpdir):
|
||||
pass
|
||||
|
||||
def test_non_ascii_1(self, tmpdir):
|
||||
fake_env(tmpdir, '[metadata]\ndescription = éàïôñ\n', encoding='utf-8')
|
||||
with get_dist(tmpdir):
|
||||
pass
|
||||
|
||||
def test_non_ascii_3(self, tmpdir):
|
||||
fake_env(tmpdir, '\n# -*- coding: invalid\n')
|
||||
with get_dist(tmpdir):
|
||||
pass
|
||||
|
||||
def test_non_ascii_4(self, tmpdir):
|
||||
fake_env(
|
||||
tmpdir,
|
||||
'# -*- coding: utf-8\n[metadata]\ndescription = éàïôñ\n',
|
||||
encoding='utf-8',
|
||||
)
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.metadata.description == 'éàïôñ'
|
||||
|
||||
def test_not_utf8(self, tmpdir):
|
||||
"""
|
||||
Config files encoded not in UTF-8 will fail
|
||||
"""
|
||||
fake_env(
|
||||
tmpdir,
|
||||
'# vim: set fileencoding=iso-8859-15 :\n'
|
||||
'[metadata]\n'
|
||||
'description = éàïôñ\n',
|
||||
encoding='iso-8859-15',
|
||||
)
|
||||
with pytest.raises(UnicodeDecodeError):
|
||||
with get_dist(tmpdir):
|
||||
pass
|
||||
|
||||
def test_warn_dash_deprecation(self, tmpdir):
|
||||
# warn_dash_deprecation() is a method in setuptools.dist
|
||||
# remove this test and the method when no longer needed
|
||||
fake_env(
|
||||
tmpdir,
|
||||
'[metadata]\n'
|
||||
'author-email = test@test.com\n'
|
||||
'maintainer_email = foo@foo.com\n',
|
||||
)
|
||||
msg = "Usage of dash-separated 'author-email' will not be supported"
|
||||
with pytest.warns(SetuptoolsDeprecationWarning, match=msg):
|
||||
with get_dist(tmpdir) as dist:
|
||||
metadata = dist.metadata
|
||||
|
||||
assert metadata.author_email == 'test@test.com'
|
||||
assert metadata.maintainer_email == 'foo@foo.com'
|
||||
|
||||
def test_make_option_lowercase(self, tmpdir):
|
||||
# remove this test and the method make_option_lowercase() in setuptools.dist
|
||||
# when no longer needed
|
||||
fake_env(tmpdir, '[metadata]\nName = foo\ndescription = Some description\n')
|
||||
msg = "Usage of uppercase key 'Name' in 'metadata' will not be supported"
|
||||
with pytest.warns(SetuptoolsDeprecationWarning, match=msg):
|
||||
with get_dist(tmpdir) as dist:
|
||||
metadata = dist.metadata
|
||||
|
||||
assert metadata.name == 'foo'
|
||||
assert metadata.description == 'Some description'
|
||||
|
||||
|
||||
class TestOptions:
|
||||
def test_basic(self, tmpdir):
|
||||
fake_env(
|
||||
tmpdir,
|
||||
'[options]\n'
|
||||
'zip_safe = True\n'
|
||||
'include_package_data = yes\n'
|
||||
'package_dir = b=c, =src\n'
|
||||
'packages = pack_a, pack_b.subpack\n'
|
||||
'namespace_packages = pack1, pack2\n'
|
||||
'scripts = bin/one.py, bin/two.py\n'
|
||||
'eager_resources = bin/one.py, bin/two.py\n'
|
||||
'install_requires = docutils>=0.3; pack ==1.1, ==1.3; hey\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'
|
||||
'python_requires = >=1.0, !=2.8\n'
|
||||
'py_modules = module1, module2\n',
|
||||
)
|
||||
deprec = pytest.warns(SetuptoolsDeprecationWarning, match="namespace_packages")
|
||||
with deprec, get_dist(tmpdir) as dist:
|
||||
assert dist.zip_safe
|
||||
assert dist.include_package_data
|
||||
assert dist.package_dir == {'': 'src', 'b': 'c'}
|
||||
assert dist.packages == ['pack_a', 'pack_b.subpack']
|
||||
assert dist.namespace_packages == ['pack1', 'pack2']
|
||||
assert dist.scripts == ['bin/one.py', 'bin/two.py']
|
||||
assert dist.dependency_links == ([
|
||||
'http://some.com/here/1',
|
||||
'http://some.com/there/2',
|
||||
])
|
||||
assert dist.install_requires == ([
|
||||
'docutils>=0.3',
|
||||
'pack==1.1,==1.3',
|
||||
'hey',
|
||||
])
|
||||
assert dist.setup_requires == ([
|
||||
'docutils>=0.3',
|
||||
'spack ==1.1, ==1.3',
|
||||
'there',
|
||||
])
|
||||
assert dist.python_requires == '>=1.0, !=2.8'
|
||||
assert dist.py_modules == ['module1', 'module2']
|
||||
|
||||
def test_multiline(self, tmpdir):
|
||||
fake_env(
|
||||
tmpdir,
|
||||
'[options]\n'
|
||||
'package_dir = \n'
|
||||
' b=c\n'
|
||||
' =src\n'
|
||||
'packages = \n'
|
||||
' pack_a\n'
|
||||
' pack_b.subpack\n'
|
||||
'namespace_packages = \n'
|
||||
' pack1\n'
|
||||
' pack2\n'
|
||||
'scripts = \n'
|
||||
' bin/one.py\n'
|
||||
' bin/two.py\n'
|
||||
'eager_resources = \n'
|
||||
' bin/one.py\n'
|
||||
' bin/two.py\n'
|
||||
'install_requires = \n'
|
||||
' docutils>=0.3\n'
|
||||
' pack ==1.1, ==1.3\n'
|
||||
' hey\n'
|
||||
'setup_requires = \n'
|
||||
' docutils>=0.3\n'
|
||||
' spack ==1.1, ==1.3\n'
|
||||
' there\n'
|
||||
'dependency_links = \n'
|
||||
' http://some.com/here/1\n'
|
||||
' http://some.com/there/2\n',
|
||||
)
|
||||
deprec = pytest.warns(SetuptoolsDeprecationWarning, match="namespace_packages")
|
||||
with deprec, get_dist(tmpdir) as dist:
|
||||
assert dist.package_dir == {'': 'src', 'b': 'c'}
|
||||
assert dist.packages == ['pack_a', 'pack_b.subpack']
|
||||
assert dist.namespace_packages == ['pack1', 'pack2']
|
||||
assert dist.scripts == ['bin/one.py', 'bin/two.py']
|
||||
assert dist.dependency_links == ([
|
||||
'http://some.com/here/1',
|
||||
'http://some.com/there/2',
|
||||
])
|
||||
assert dist.install_requires == ([
|
||||
'docutils>=0.3',
|
||||
'pack==1.1,==1.3',
|
||||
'hey',
|
||||
])
|
||||
assert dist.setup_requires == ([
|
||||
'docutils>=0.3',
|
||||
'spack ==1.1, ==1.3',
|
||||
'there',
|
||||
])
|
||||
|
||||
def test_package_dir_fail(self, tmpdir):
|
||||
fake_env(tmpdir, '[options]\npackage_dir = a b\n')
|
||||
with get_dist(tmpdir, parse=False) as dist:
|
||||
with pytest.raises(DistutilsOptionError):
|
||||
dist.parse_config_files()
|
||||
|
||||
def test_package_data(self, tmpdir):
|
||||
fake_env(
|
||||
tmpdir,
|
||||
'[options.package_data]\n'
|
||||
'* = *.txt, *.rst\n'
|
||||
'hello = *.msg\n'
|
||||
'\n'
|
||||
'[options.exclude_package_data]\n'
|
||||
'* = fake1.txt, fake2.txt\n'
|
||||
'hello = *.dat\n',
|
||||
)
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.package_data == {
|
||||
'': ['*.txt', '*.rst'],
|
||||
'hello': ['*.msg'],
|
||||
}
|
||||
assert dist.exclude_package_data == {
|
||||
'': ['fake1.txt', 'fake2.txt'],
|
||||
'hello': ['*.dat'],
|
||||
}
|
||||
|
||||
def test_packages(self, tmpdir):
|
||||
fake_env(tmpdir, '[options]\npackages = find:\n')
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.packages == ['fake_package']
|
||||
|
||||
def test_find_directive(self, tmpdir):
|
||||
dir_package, config = fake_env(tmpdir, '[options]\npackages = find:\n')
|
||||
|
||||
dir_sub_one, _ = make_package_dir('sub_one', dir_package)
|
||||
dir_sub_two, _ = make_package_dir('sub_two', dir_package)
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert set(dist.packages) == set([
|
||||
'fake_package',
|
||||
'fake_package.sub_two',
|
||||
'fake_package.sub_one',
|
||||
])
|
||||
|
||||
config.write(
|
||||
'[options]\n'
|
||||
'packages = find:\n'
|
||||
'\n'
|
||||
'[options.packages.find]\n'
|
||||
'where = .\n'
|
||||
'include =\n'
|
||||
' fake_package.sub_one\n'
|
||||
' two\n'
|
||||
)
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.packages == ['fake_package.sub_one']
|
||||
|
||||
config.write(
|
||||
'[options]\n'
|
||||
'packages = find:\n'
|
||||
'\n'
|
||||
'[options.packages.find]\n'
|
||||
'exclude =\n'
|
||||
' fake_package.sub_one\n'
|
||||
)
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert set(dist.packages) == set(['fake_package', 'fake_package.sub_two'])
|
||||
|
||||
def test_find_namespace_directive(self, tmpdir):
|
||||
dir_package, config = fake_env(
|
||||
tmpdir, '[options]\npackages = find_namespace:\n'
|
||||
)
|
||||
|
||||
dir_sub_one, _ = make_package_dir('sub_one', dir_package)
|
||||
dir_sub_two, _ = make_package_dir('sub_two', dir_package, ns=True)
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert set(dist.packages) == {
|
||||
'fake_package',
|
||||
'fake_package.sub_two',
|
||||
'fake_package.sub_one',
|
||||
}
|
||||
|
||||
config.write(
|
||||
'[options]\n'
|
||||
'packages = find_namespace:\n'
|
||||
'\n'
|
||||
'[options.packages.find]\n'
|
||||
'where = .\n'
|
||||
'include =\n'
|
||||
' fake_package.sub_one\n'
|
||||
' two\n'
|
||||
)
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.packages == ['fake_package.sub_one']
|
||||
|
||||
config.write(
|
||||
'[options]\n'
|
||||
'packages = find_namespace:\n'
|
||||
'\n'
|
||||
'[options.packages.find]\n'
|
||||
'exclude =\n'
|
||||
' fake_package.sub_one\n'
|
||||
)
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert set(dist.packages) == {'fake_package', 'fake_package.sub_two'}
|
||||
|
||||
def test_extras_require(self, tmpdir):
|
||||
fake_env(
|
||||
tmpdir,
|
||||
'[options.extras_require]\n'
|
||||
'pdf = ReportLab>=1.2; RXP\n'
|
||||
'rest = \n'
|
||||
' docutils>=0.3\n'
|
||||
' pack ==1.1, ==1.3\n',
|
||||
)
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.extras_require == {
|
||||
'pdf': ['ReportLab>=1.2', 'RXP'],
|
||||
'rest': ['docutils>=0.3', 'pack==1.1,==1.3'],
|
||||
}
|
||||
assert set(dist.metadata.provides_extras) == {'pdf', 'rest'}
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"config",
|
||||
[
|
||||
"[options.extras_require]\nfoo = bar;python_version<'3'",
|
||||
"[options.extras_require]\nfoo = bar;os_name=='linux'",
|
||||
"[options.extras_require]\nfoo = bar;python_version<'3'\n",
|
||||
"[options.extras_require]\nfoo = bar;os_name=='linux'\n",
|
||||
"[options]\ninstall_requires = bar;python_version<'3'",
|
||||
"[options]\ninstall_requires = bar;os_name=='linux'",
|
||||
"[options]\ninstall_requires = bar;python_version<'3'\n",
|
||||
"[options]\ninstall_requires = bar;os_name=='linux'\n",
|
||||
],
|
||||
)
|
||||
def test_raises_accidental_env_marker_misconfig(self, config, tmpdir):
|
||||
fake_env(tmpdir, config)
|
||||
match = (
|
||||
r"One of the parsed requirements in `(install_requires|extras_require.+)` "
|
||||
"looks like a valid environment marker.*"
|
||||
)
|
||||
with pytest.raises(InvalidRequirement, match=match):
|
||||
with get_dist(tmpdir) as _:
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"config",
|
||||
[
|
||||
"[options.extras_require]\nfoo = bar;python_version<3",
|
||||
"[options.extras_require]\nfoo = bar;python_version<3\n",
|
||||
"[options]\ninstall_requires = bar;python_version<3",
|
||||
"[options]\ninstall_requires = bar;python_version<3\n",
|
||||
],
|
||||
)
|
||||
def test_warn_accidental_env_marker_misconfig(self, config, tmpdir):
|
||||
fake_env(tmpdir, config)
|
||||
match = (
|
||||
r"One of the parsed requirements in `(install_requires|extras_require.+)` "
|
||||
"looks like a valid environment marker.*"
|
||||
)
|
||||
with pytest.warns(SetuptoolsDeprecationWarning, match=match):
|
||||
with get_dist(tmpdir) as _:
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"config",
|
||||
[
|
||||
"[options.extras_require]\nfoo =\n bar;python_version<'3'",
|
||||
"[options.extras_require]\nfoo = bar;baz\nboo = xxx;yyy",
|
||||
"[options.extras_require]\nfoo =\n bar;python_version<'3'\n",
|
||||
"[options.extras_require]\nfoo = bar;baz\nboo = xxx;yyy\n",
|
||||
"[options.extras_require]\nfoo =\n bar\n python_version<3\n",
|
||||
"[options]\ninstall_requires =\n bar;python_version<'3'",
|
||||
"[options]\ninstall_requires = bar;baz\nboo = xxx;yyy",
|
||||
"[options]\ninstall_requires =\n bar;python_version<'3'\n",
|
||||
"[options]\ninstall_requires = bar;baz\nboo = xxx;yyy\n",
|
||||
"[options]\ninstall_requires =\n bar\n python_version<3\n",
|
||||
],
|
||||
)
|
||||
@pytest.mark.filterwarnings("error::setuptools.SetuptoolsDeprecationWarning")
|
||||
def test_nowarn_accidental_env_marker_misconfig(self, config, tmpdir, recwarn):
|
||||
fake_env(tmpdir, config)
|
||||
num_warnings = len(recwarn)
|
||||
with get_dist(tmpdir) as _:
|
||||
pass
|
||||
# The examples are valid, no warnings shown
|
||||
assert len(recwarn) == num_warnings
|
||||
|
||||
def test_dash_preserved_extras_require(self, tmpdir):
|
||||
fake_env(tmpdir, '[options.extras_require]\nfoo-a = foo\nfoo_b = test\n')
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.extras_require == {'foo-a': ['foo'], 'foo_b': ['test']}
|
||||
|
||||
def test_entry_points(self, tmpdir):
|
||||
_, config = fake_env(
|
||||
tmpdir,
|
||||
'[options.entry_points]\n'
|
||||
'group1 = point1 = pack.module:func, '
|
||||
'.point2 = pack.module2:func_rest [rest]\n'
|
||||
'group2 = point3 = pack.module:func2\n',
|
||||
)
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.entry_points == {
|
||||
'group1': [
|
||||
'point1 = pack.module:func',
|
||||
'.point2 = pack.module2:func_rest [rest]',
|
||||
],
|
||||
'group2': ['point3 = pack.module:func2'],
|
||||
}
|
||||
|
||||
expected = (
|
||||
'[blogtool.parsers]\n'
|
||||
'.rst = some.nested.module:SomeClass.some_classmethod[reST]\n'
|
||||
)
|
||||
|
||||
tmpdir.join('entry_points').write(expected)
|
||||
|
||||
# From file.
|
||||
config.write('[options]\nentry_points = file: entry_points\n')
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.entry_points == expected
|
||||
|
||||
def test_case_sensitive_entry_points(self, tmpdir):
|
||||
_, config = fake_env(
|
||||
tmpdir,
|
||||
'[options.entry_points]\n'
|
||||
'GROUP1 = point1 = pack.module:func, '
|
||||
'.point2 = pack.module2:func_rest [rest]\n'
|
||||
'group2 = point3 = pack.module:func2\n',
|
||||
)
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.entry_points == {
|
||||
'GROUP1': [
|
||||
'point1 = pack.module:func',
|
||||
'.point2 = pack.module2:func_rest [rest]',
|
||||
],
|
||||
'group2': ['point3 = pack.module:func2'],
|
||||
}
|
||||
|
||||
def test_data_files(self, tmpdir):
|
||||
fake_env(
|
||||
tmpdir,
|
||||
'[options.data_files]\n'
|
||||
'cfg =\n'
|
||||
' a/b.conf\n'
|
||||
' c/d.conf\n'
|
||||
'data = e/f.dat, g/h.dat\n',
|
||||
)
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
expected = [
|
||||
('cfg', ['a/b.conf', 'c/d.conf']),
|
||||
('data', ['e/f.dat', 'g/h.dat']),
|
||||
]
|
||||
assert sorted(dist.data_files) == sorted(expected)
|
||||
|
||||
def test_data_files_globby(self, tmpdir):
|
||||
fake_env(
|
||||
tmpdir,
|
||||
'[options.data_files]\n'
|
||||
'cfg =\n'
|
||||
' a/b.conf\n'
|
||||
' c/d.conf\n'
|
||||
'data = *.dat\n'
|
||||
'icons = \n'
|
||||
' *.ico\n'
|
||||
'audio = \n'
|
||||
' *.wav\n'
|
||||
' sounds.db\n',
|
||||
)
|
||||
|
||||
# Create dummy files for glob()'s sake:
|
||||
tmpdir.join('a.dat').write('')
|
||||
tmpdir.join('b.dat').write('')
|
||||
tmpdir.join('c.dat').write('')
|
||||
tmpdir.join('a.ico').write('')
|
||||
tmpdir.join('b.ico').write('')
|
||||
tmpdir.join('c.ico').write('')
|
||||
tmpdir.join('beep.wav').write('')
|
||||
tmpdir.join('boop.wav').write('')
|
||||
tmpdir.join('sounds.db').write('')
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
expected = [
|
||||
('cfg', ['a/b.conf', 'c/d.conf']),
|
||||
('data', ['a.dat', 'b.dat', 'c.dat']),
|
||||
('icons', ['a.ico', 'b.ico', 'c.ico']),
|
||||
('audio', ['beep.wav', 'boop.wav', 'sounds.db']),
|
||||
]
|
||||
assert sorted(dist.data_files) == sorted(expected)
|
||||
|
||||
def test_python_requires_simple(self, tmpdir):
|
||||
fake_env(
|
||||
tmpdir,
|
||||
DALS(
|
||||
"""
|
||||
[options]
|
||||
python_requires=>=2.7
|
||||
"""
|
||||
),
|
||||
)
|
||||
with get_dist(tmpdir) as dist:
|
||||
dist.parse_config_files()
|
||||
|
||||
def test_python_requires_compound(self, tmpdir):
|
||||
fake_env(
|
||||
tmpdir,
|
||||
DALS(
|
||||
"""
|
||||
[options]
|
||||
python_requires=>=2.7,!=3.0.*
|
||||
"""
|
||||
),
|
||||
)
|
||||
with get_dist(tmpdir) as dist:
|
||||
dist.parse_config_files()
|
||||
|
||||
def test_python_requires_invalid(self, tmpdir):
|
||||
fake_env(
|
||||
tmpdir,
|
||||
DALS(
|
||||
"""
|
||||
[options]
|
||||
python_requires=invalid
|
||||
"""
|
||||
),
|
||||
)
|
||||
with pytest.raises(Exception):
|
||||
with get_dist(tmpdir) as dist:
|
||||
dist.parse_config_files()
|
||||
|
||||
def test_cmdclass(self, tmpdir):
|
||||
module_path = Path(tmpdir, "src/custom_build.py") # auto discovery for src
|
||||
module_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
module_path.write_text(
|
||||
"from distutils.core import Command\nclass CustomCmd(Command): pass\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
setup_cfg = """
|
||||
[options]
|
||||
cmdclass =
|
||||
customcmd = custom_build.CustomCmd
|
||||
"""
|
||||
fake_env(tmpdir, inspect.cleandoc(setup_cfg))
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
cmdclass = dist.cmdclass['customcmd']
|
||||
assert cmdclass.__name__ == "CustomCmd"
|
||||
assert cmdclass.__module__ == "custom_build"
|
||||
assert module_path.samefile(inspect.getfile(cmdclass))
|
||||
|
||||
def test_requirements_file(self, tmpdir):
|
||||
fake_env(
|
||||
tmpdir,
|
||||
DALS(
|
||||
"""
|
||||
[options]
|
||||
install_requires = file:requirements.txt
|
||||
[options.extras_require]
|
||||
colors = file:requirements-extra.txt
|
||||
"""
|
||||
),
|
||||
)
|
||||
|
||||
tmpdir.join('requirements.txt').write('\ndocutils>=0.3\n\n')
|
||||
tmpdir.join('requirements-extra.txt').write('colorama')
|
||||
|
||||
with get_dist(tmpdir) as dist:
|
||||
assert dist.install_requires == ['docutils>=0.3']
|
||||
assert dist.extras_require == {'colors': ['colorama']}
|
||||
|
||||
|
||||
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
|
||||
# _Distribution.__init__ ().
|
||||
#
|
||||
# It's possible distutils calls out to various keyword
|
||||
# implementations (i.e. distutils.setup_keywords entry points)
|
||||
# that may set a range of variables.
|
||||
#
|
||||
# This wraps distutil's Distribution.__init__ and simulates
|
||||
# pbr or something else setting these values.
|
||||
def _fake_distribution_init(self, dist, attrs):
|
||||
saved_dist_init(dist, attrs)
|
||||
# see self._DISTUTILS_UNSUPPORTED_METADATA
|
||||
dist.metadata.long_description_content_type = 'text/something'
|
||||
# Test overwrite setup() args
|
||||
dist.metadata.project_urls = {
|
||||
'Link One': 'https://example.com/one/',
|
||||
'Link Two': 'https://example.com/two/',
|
||||
}
|
||||
|
||||
@patch.object(_Distribution, '__init__', autospec=True)
|
||||
def test_external_setters(self, mock_parent_init, tmpdir):
|
||||
mock_parent_init.side_effect = self._fake_distribution_init
|
||||
|
||||
dist = Distribution(attrs={'project_urls': {'will_be': 'ignored'}})
|
||||
|
||||
assert dist.metadata.long_description_content_type == 'text/something'
|
||||
assert dist.metadata.project_urls == {
|
||||
'Link One': 'https://example.com/one/',
|
||||
'Link Two': 'https://example.com/two/',
|
||||
}
|
||||
Reference in New Issue
Block a user