Skip to content

Commit

Permalink
Test pyproject.toml config has the same effect as setup.cfg
Browse files Browse the repository at this point in the history
  • Loading branch information
abravalheri committed Feb 1, 2022
1 parent 43e3436 commit c6907cb
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ setuptools.egg-info
.idea/
.pytest_cache/
.mypy_cache/
/setuptools/tests/config/downloads
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ testing =
build[virtualenv]
filelock>=3.4.0
pip_run>=8.8
ini2toml[lite]>=0.6.1

testing-integration =
pytest
Expand All @@ -79,7 +80,6 @@ testing-integration =
build[virtualenv]
filelock>=3.4.0


docs =
# upstream
sphinx
Expand Down
23 changes: 23 additions & 0 deletions setuptools/tests/config/setupcfg_examples.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# ====================================================================
# 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/tqdm/tqdm/raw/fc69d5dcf578f7c7986fa76841a6b793f813df35/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
117 changes: 117 additions & 0 deletions setuptools/tests/config/test_apply_pyprojecttoml.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""Make sure that applying the configuration from pyproject.toml is equivalent to
applying a similar configuration from setup.cfg
"""
import io
import re
from pathlib import Path
from urllib.request import urlopen
from unittest.mock import Mock

import pytest
from ini2toml.api import Translator

import setuptools # noqa ensure monkey patch to metadata
from setuptools.dist import Distribution
from setuptools.config import setupcfg, pyprojecttoml
from setuptools.config import expand


EXAMPLES = (Path(__file__).parent / "setupcfg_examples.txt").read_text()
EXAMPLE_URLS = [x for x in EXAMPLES.splitlines() if not x.startswith("#")]
DOWNLOAD_DIR = Path(__file__).parent / "downloads"


@pytest.mark.parametrize("url", EXAMPLE_URLS)
@pytest.mark.filterwarnings("ignore")
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, DOWNLOAD_DIR)
pyproject_example = Path(tmp_path, "pyproject.toml")
toml_config = Translator().translate(setupcfg_example.read_text(), "setup.cfg")
pyproject_example.write_text(toml_config)

dist_toml = pyprojecttoml.apply_configuration(Distribution(), pyproject_example)
dist_cfg = setupcfg.apply_configuration(Distribution(), 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)):
if (
"testing" in dist_toml.extras_require
and "testing" not in dist_cfg.extras_require
):
# ini2toml can automatically convert `tests_require` to `testing` extra
dist_toml.extras_require.pop("testing")
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


NAME_REMOVE = ("http://", "https://", "github.com/", "/raw/")


def retrieve_file(url, download_dir):
file_name = url.strip()
for part in NAME_REMOVE:
file_name = file_name.replace(part, '').strip().strip('/:').strip()
file_name = re.sub(r"[^\-_\.\w\d]+", "_", file_name)
path = Path(download_dir, file_name)
if not path.exists():
download_dir.mkdir(exist_ok=True, parents=True)
download(url, path)
return path


def download(url, dest):
with urlopen(url) as f:
data = f.read()

with open(dest, "wb") as f:
f.write(data)

assert Path(dest).exists()


def core_metadata(dist) -> str:
buffer = io.StringIO()
dist.metadata.write_pkg_file(buffer)
value = "\n".join(buffer.getvalue().strip().splitlines())

# ---- DIFF NORMALISATION ----
# PEP 621 is very particular about author/maintainer metadata conversion, so skip
value = re.sub(r"^(Author|Maintainer)(-email)?:.*$", "", value, flags=re.M)
# May be redundant with Home-page
value = re.sub(r"^Project-URL: Homepage,.*$", "", value, flags=re.M)
# May be missing in original (relying on default) but backfilled in the TOML
value = re.sub(r"^Description-Content-Type:.*$", "", value, flags=re.M)
# ini2toml can automatically convert `tests_require` to `testing` extra
value = value.replace("Provides-Extra: testing\n", "")
# Remove empty lines
value = re.sub(r"^\s*$", "", value, flags=re.M)
value = re.sub(r"^\n", "", value, flags=re.M)

return value

0 comments on commit c6907cb

Please sign in to comment.