From c62b6fdadedd9c6a6c223f17f46621da741ed4ce Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Sun, 13 Dec 2020 07:31:03 -0800 Subject: [PATCH 01/44] List sources for most pbr projects. Refs #7 --- dowsing/check_source_mapping.py | 4 +- dowsing/setuptools/__init__.py | 14 +++++ dowsing/setuptools/setup_and_metadata.py | 11 ++++ dowsing/tests/setuptools.py | 73 +++++++++++++++++++++++- dowsing/types.py | 6 ++ 5 files changed, 106 insertions(+), 2 deletions(-) diff --git a/dowsing/check_source_mapping.py b/dowsing/check_source_mapping.py index 9d0c292..789732f 100644 --- a/dowsing/check_source_mapping.py +++ b/dowsing/check_source_mapping.py @@ -70,7 +70,9 @@ async def main(packages: List[str]) -> None: ) md_blob = "".join(sorted(f"{f}\n" for f in metadata.source_mapping.keys())) - if md_blob == wheel_blob: + if metadata.source_mapping == {}: + print(f"{package_name}: empty dict") + elif md_blob == wheel_blob: print(f"{package_name}: ok") elif md_blob in ("", "?.py\n"): print(f"{package_name}: COMPLETELY MISSING") diff --git a/dowsing/setuptools/__init__.py b/dowsing/setuptools/__init__.py index 57bb7cb..aac7890 100644 --- a/dowsing/setuptools/__init__.py +++ b/dowsing/setuptools/__init__.py @@ -43,6 +43,20 @@ def get_metadata(self) -> Distribution: if getattr(d2, k): setattr(d1, k, getattr(d2, k)) + # This is the bare minimum to get pbr projects to show as having any + # sources. I don't want to use pbr.util.cfg_to_args because it appears + # to import and run arbitrary code. + if d1.pbr: + where = "." + if d1.pbr__files__packages_root: + d1.package_dir = {"": d1.pbr__files__packages_root} + where = d1.pbr__files__packages_root + + if d1.pbr__files__packages: + d1.packages = d1.pbr__files__packages + else: + d1.packages = FindPackages(where, (), ("*",)) # type: ignore + # package_dir can both add and remove components, see docs # https://docs.python.org/2/distutils/setupscript.html#listing-whole-packages package_dir: Mapping[str, str] = d1.package_dir diff --git a/dowsing/setuptools/setup_and_metadata.py b/dowsing/setuptools/setup_and_metadata.py index 9c44087..dea96f4 100644 --- a/dowsing/setuptools/setup_and_metadata.py +++ b/dowsing/setuptools/setup_and_metadata.py @@ -222,4 +222,15 @@ SetupCfg("options.packages.find", "include", writer_cls=ListCommaWriter), sample_value=None, ), + ConfigField("pbr", SetupCfg("--unused--", "--unused--"), sample_value=None,), + ConfigField( + "pbr__files__packages_root", + SetupCfg("files", "packages_root"), + sample_value=None, + ), + ConfigField( + "pbr__files__packages", + SetupCfg("files", "packages", writer_cls=ListCommaWriter), + sample_value=None, + ), ] diff --git a/dowsing/tests/setuptools.py b/dowsing/tests/setuptools.py index 5295975..4d3f548 100644 --- a/dowsing/tests/setuptools.py +++ b/dowsing/tests/setuptools.py @@ -1,5 +1,6 @@ import unittest from pathlib import Path +from typing import Dict, Optional import volatile @@ -63,10 +64,18 @@ def test_setup_py(self) -> None: ("setuptools", "wheel", "def"), r.get_requires_for_build_wheel() ) - def _read(self, data: str, src_dir: str = ".") -> Distribution: + def _read( + self, + data: str, + src_dir: str = ".", + extra_files: Optional[Dict[str, str]] = None, + ) -> Distribution: with volatile.dir() as d: sp = Path(d, "setup.py") sp.write_text(data) + if extra_files: + for k, v in extra_files.items(): + Path(d, k).write_text(v) Path(d, src_dir, "pkg").mkdir(parents=True) Path(d, src_dir, "pkg", "__init__.py").touch() Path(d, src_dir, "pkg", "sub").mkdir() @@ -232,3 +241,65 @@ def test_invalid_packages(self) -> None: ) # TODO wish this were None self.assertEqual(d.source_mapping, {}) + + def test_pbr_properly_enabled(self) -> None: + d = self._read( + """\ +from setuptools import setup + +setup( + setup_requires=['pbr>=1.9', 'setuptools>=17.1'], + pbr=True, +)""", + extra_files={ + "setup.cfg": """\ +[metadata] +name = pbr +author = OpenStack Foundation + +[files] +packages = + pkg +""" + }, + ) + self.assertEqual( + d.source_mapping, + { + "pkg/__init__.py": "pkg/__init__.py", + "pkg/sub/__init__.py": "pkg/sub/__init__.py", + "pkg/tests/__init__.py": "pkg/tests/__init__.py", + }, + ) + + def test_pbr_properly_enabled_src(self) -> None: + d = self._read( + """\ +from setuptools import setup + +setup( + setup_requires=['pbr>=1.9', 'setuptools>=17.1'], + pbr=True, +)""", + src_dir="src", + extra_files={ + "setup.cfg": """\ +[metadata] +name = pbr +author = OpenStack Foundation + +[files] +packages = + pkg +packages_root = src +""" + }, + ) + self.assertEqual( + d.source_mapping, + { + "pkg/__init__.py": "src/pkg/__init__.py", + "pkg/sub/__init__.py": "src/pkg/sub/__init__.py", + "pkg/tests/__init__.py": "src/pkg/tests/__init__.py", + }, + ) diff --git a/dowsing/types.py b/dowsing/types.py index 349c456..045cf77 100644 --- a/dowsing/types.py +++ b/dowsing/types.py @@ -60,6 +60,9 @@ class Distribution(pkginfo.distribution.Distribution): # type: ignore find_packages_exclude: Sequence[str] = () find_packages_include: Sequence[str] = ("*",) source_mapping: Optional[Mapping[str, str]] = None + pbr: Optional[bool] = None + pbr__files__packages_root: Optional[str] = None + pbr__files__packages: Optional[str] = None def _getHeaderAttrs(self) -> Sequence[Tuple[str, str, bool]]: # Until I invent a metadata version to include this, do so @@ -80,6 +83,9 @@ def _getHeaderAttrs(self) -> Sequence[Tuple[str, str, bool]]: ("X-Packages-Dict", "packages_dict", False), ("X-Py-Modules", "py_modules", True), ("X-Entry-Points", "entry_points", False), + ("X-Pbr", "pbr", False), + ("X-pbr__files__packages_root", "pbr__files__packages_root", False), + ("X-pbr__files__packages", "pbr__files__packages", True), ) def asdict(self) -> Dict[str, Any]: From bf7299209fc29708f578fbef6d68fefd04384b31 Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Sun, 13 Dec 2020 10:15:43 -0800 Subject: [PATCH 02/44] Also consider pbr-looking projects. This simple heuristic works for pbr itself without complex setup.py-matching. --- dowsing/setuptools/__init__.py | 2 +- dowsing/tests/setuptools.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/dowsing/setuptools/__init__.py b/dowsing/setuptools/__init__.py index aac7890..4de386b 100644 --- a/dowsing/setuptools/__init__.py +++ b/dowsing/setuptools/__init__.py @@ -46,7 +46,7 @@ def get_metadata(self) -> Distribution: # This is the bare minimum to get pbr projects to show as having any # sources. I don't want to use pbr.util.cfg_to_args because it appears # to import and run arbitrary code. - if d1.pbr: + if d1.pbr or (d1.pbr__files__packages and not d1.packages): where = "." if d1.pbr__files__packages_root: d1.package_dir = {"": d1.pbr__files__packages_root} diff --git a/dowsing/tests/setuptools.py b/dowsing/tests/setuptools.py index 4d3f548..31d7f14 100644 --- a/dowsing/tests/setuptools.py +++ b/dowsing/tests/setuptools.py @@ -303,3 +303,31 @@ def test_pbr_properly_enabled_src(self) -> None: "pkg/tests/__init__.py": "src/pkg/tests/__init__.py", }, ) + + def test_pbr_improperly_enabled(self) -> None: + # pbr itself is something like this. + d = self._read( + """\ +from setuptools import setup + +setup()""", + extra_files={ + "setup.cfg": """\ +[metadata] +name = pbr +author = OpenStack Foundation + +[files] +packages = + pkg +""" + }, + ) + self.assertEqual( + d.source_mapping, + { + "pkg/__init__.py": "pkg/__init__.py", + "pkg/sub/__init__.py": "pkg/sub/__init__.py", + "pkg/tests/__init__.py": "pkg/tests/__init__.py", + }, + ) From 01fffc8d42c21e503a80e59c7193cf18fcf7b784 Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Sat, 13 Mar 2021 07:37:23 -0800 Subject: [PATCH 03/44] Change dowsing.pep517 to also show source_mapping --- dowsing/pep517.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dowsing/pep517.py b/dowsing/pep517.py index 26a7e99..73d30e2 100644 --- a/dowsing/pep517.py +++ b/dowsing/pep517.py @@ -74,10 +74,12 @@ def _default(obj: Any) -> Any: def main(path: Path) -> None: + metadata = get_metadata(path) d = { "get_requires_for_build_sdist": get_requires_for_build_sdist(path), "get_requires_for_build_wheel": get_requires_for_build_wheel(path), - "get_metadata": get_metadata(path).asdict(), + "get_metadata": metadata.asdict(), + "source_mapping": metadata.source_mapping, } print(json.dumps(d, default=_default)) From c5ca8ec55f040fa29c1d151831efd9827f8229ac Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Sat, 13 Mar 2021 07:37:44 -0800 Subject: [PATCH 04/44] Support flit modules, not just packages Fixes #24 --- dowsing/flit.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/dowsing/flit.py b/dowsing/flit.py index 45c6a93..76b1514 100644 --- a/dowsing/flit.py +++ b/dowsing/flit.py @@ -35,9 +35,13 @@ def get_metadata(self) -> Distribution: d.project_urls["Homepage"] = v continue elif k == "module": - k = "packages" - v = find_packages(self.path.as_posix(), include=(f"{v}.*")) - d.packages_dict = {i: i.replace(".", "/") for i in v} + if (self.path / f"{v}.py").exists(): + k = "py_modules" + v = [v] + else: + k = "packages" + v = find_packages(self.path.as_posix(), include=(f"{v}.*")) + d.packages_dict = {i: i.replace(".", "/") for i in v} elif k == "description-file": k = "description" v = f"file: {v}" From 968e2de2ab543d95d0ab3e1c727051a486745024 Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Sat, 13 Mar 2021 07:47:51 -0800 Subject: [PATCH 05/44] setup.py parsing: support list/str addition Fixes #25 --- dowsing/setuptools/setup_py_parsing.py | 10 ++++++++++ dowsing/tests/setuptools.py | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/dowsing/setuptools/setup_py_parsing.py b/dowsing/setuptools/setup_py_parsing.py index 8d9819b..9efcde2 100644 --- a/dowsing/setuptools/setup_py_parsing.py +++ b/dowsing/setuptools/setup_py_parsing.py @@ -281,6 +281,16 @@ def evaluate_in_scope(self, item: cst.CSTNode, scope: Any) -> Any: else: # LOG.warning(f"Omit2 {type(item.slice[0].slice)!r}") return "??" + elif isinstance(item, cst.BinaryOperation): + lhs = self.evaluate_in_scope(item.left, scope) + rhs = self.evaluate_in_scope(item.right, scope) + if isinstance(item.operator, cst.Add): + try: + return lhs + rhs + except Exception: + return "??" + else: + return "??" else: # LOG.warning(f"Omit1 {type(item)!r}") return "??" diff --git a/dowsing/tests/setuptools.py b/dowsing/tests/setuptools.py index 31d7f14..0b13207 100644 --- a/dowsing/tests/setuptools.py +++ b/dowsing/tests/setuptools.py @@ -331,3 +331,16 @@ def test_pbr_improperly_enabled(self) -> None: "pkg/tests/__init__.py": "pkg/tests/__init__.py", }, ) + + def test_add_items(self) -> None: + d = self._read( + """\ +from setuptools import setup +a = "aaaa" +p = ["a", "b", "c"] +setup(name = a + "1111", packages=[] + p, classifiers=a + p) + """ + ) + self.assertEqual(d.name, "aaaa1111") + self.assertEqual(d.packages, ["a", "b", "c"]) + self.assertEqual(d.classifiers, "??") From 5cfa546a69545186e99b8e21f58fa6e8eb36ab12 Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Sat, 13 Mar 2021 07:53:16 -0800 Subject: [PATCH 06/44] Support py_modules with dots in them Fixes #22 --- dowsing/types.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dowsing/types.py b/dowsing/types.py index 045cf77..ffc89b7 100644 --- a/dowsing/types.py +++ b/dowsing/types.py @@ -106,6 +106,7 @@ def _source_mapping(self, root: Path) -> Optional[Dict[str, str]]: for m in self.py_modules: if m == "?": return None + m = m.replace(".", "/") d[f"{m}.py"] = f"{m}.py" try: From 8fa493d225fd5908fa84064262a4acf736c68683 Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Sat, 13 Mar 2021 07:58:41 -0800 Subject: [PATCH 07/44] Ignore falsy items in packages list Fixes #20 --- dowsing/setuptools/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dowsing/setuptools/__init__.py b/dowsing/setuptools/__init__.py index 4de386b..b3ffcaf 100644 --- a/dowsing/setuptools/__init__.py +++ b/dowsing/setuptools/__init__.py @@ -99,7 +99,8 @@ def mangle(package: str) -> str: elif d1.packages != "??": assert isinstance(d1.packages, (list, tuple)) for p in d1.packages: - d1.packages_dict[p] = mangle(p) + if p: + d1.packages_dict[p] = mangle(p) d1.source_mapping = d1._source_mapping(self.path) return d1 From 7aad2e47c6bd71eb5dcd400a9121f04d3a80bd86 Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Sat, 13 Mar 2021 07:50:04 -0800 Subject: [PATCH 08/44] Update changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b7ccc0..c566f50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## v0.9.0b2 + +* `source_mapping` bugfixes + * `packages` being an empty string (#20) + * `py_modules` containing dots (#22) + * Flit modules instead of packages (#24) + * `setup.py` parsing addition operator (#25) + ## v0.9.0b1 * Includes package data in `source_mapping` all the time. From cbede183e43a08474648dabb0b92f40c9c941698 Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Thu, 1 Apr 2021 07:25:56 -0700 Subject: [PATCH 09/44] Update skel 2021-04-01 --- Makefile | 6 ++---- requirements-dev.txt | 5 +++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 4c166f6..1050aed 100644 --- a/Makefile +++ b/Makefile @@ -21,13 +21,11 @@ test: .PHONY: format format: - python -m isort --recursive -y $(SOURCES) - python -m black $(SOURCES) + python -m ufmt format $(SOURCES) .PHONY: lint lint: - python -m isort --recursive --diff $(SOURCES) - python -m black --check $(SOURCES) + python -m ufmt check $(SOURCES) python -m flake8 $(SOURCES) mypy --strict dowsing diff --git a/requirements-dev.txt b/requirements-dev.txt index 0479c95..b5f7505 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,9 +1,10 @@ -black==19.10b0 +black==20.8b1 coverage==4.5.4 flake8==3.7.9 -isort==4.3.21 mypy==0.750 tox==3.14.1 twine==3.1.1 +ufmt==1.1 +usort==0.6.3 volatile==2.1.0 wheel==0.33.6 From 72f6af54070bf295db2fe5ff7048f06bd5150e93 Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Thu, 1 Apr 2021 07:27:24 -0700 Subject: [PATCH 10/44] `make format` with new black --- dowsing/setuptools/setup_and_metadata.py | 20 ++++++++++++++++---- dowsing/tests/setuptools_metadata.py | 9 ++++++--- dowsing/tests/setuptools_types.py | 10 ++++++++-- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/dowsing/setuptools/setup_and_metadata.py b/dowsing/setuptools/setup_and_metadata.py index dea96f4..bd7a598 100644 --- a/dowsing/setuptools/setup_and_metadata.py +++ b/dowsing/setuptools/setup_and_metadata.py @@ -41,14 +41,22 @@ ), ConfigField("author", SetupCfg("metadata", "author"), Metadata("Author")), ConfigField( - "author_email", SetupCfg("metadata", "author_email"), Metadata("Author-email"), + "author_email", + SetupCfg("metadata", "author_email"), + Metadata("Author-email"), + ), + ConfigField( + "license", + SetupCfg("metadata", "license"), + Metadata("License"), ), - ConfigField("license", SetupCfg("metadata", "license"), Metadata("License"),), # TODO licence (alternate spelling) # TODO license_file, license_files (setuptools-specific) ConfigField("url", SetupCfg("metadata", "url"), Metadata("Home-page")), ConfigField( - "description", SetupCfg("metadata", "description"), Metadata("Summary"), + "description", + SetupCfg("metadata", "description"), + Metadata("Summary"), ), ConfigField( "long_description", @@ -222,7 +230,11 @@ SetupCfg("options.packages.find", "include", writer_cls=ListCommaWriter), sample_value=None, ), - ConfigField("pbr", SetupCfg("--unused--", "--unused--"), sample_value=None,), + ConfigField( + "pbr", + SetupCfg("--unused--", "--unused--"), + sample_value=None, + ), ConfigField( "pbr__files__packages_root", SetupCfg("files", "packages_root"), diff --git a/dowsing/tests/setuptools_metadata.py b/dowsing/tests/setuptools_metadata.py index d1ac9b5..6c226b5 100644 --- a/dowsing/tests/setuptools_metadata.py +++ b/dowsing/tests/setuptools_metadata.py @@ -80,13 +80,16 @@ def test_arg_mapping(self) -> None: name = field.get_distribution_key() self.assertNotEqual( - getattr(setup_py_dist, name), None, + getattr(setup_py_dist, name), + None, ) self.assertEqual( - foo, getattr(setup_py_dist, name), + foo, + getattr(setup_py_dist, name), ) self.assertEqual( - foo, getattr(setup_cfg_dist, name), + foo, + getattr(setup_cfg_dist, name), ) if field.metadata: diff --git a/dowsing/tests/setuptools_types.py b/dowsing/tests/setuptools_types.py index b73cf8d..dc51813 100644 --- a/dowsing/tests/setuptools_types.py +++ b/dowsing/tests/setuptools_types.py @@ -19,7 +19,10 @@ class WriterTest(unittest.TestCase): @parameterized.expand( # type: ignore - [(False,), (True,),] + [ + (False,), + (True,), + ] ) def test_bool_writer(self, arg: bool) -> None: c = ConfigFile() @@ -32,7 +35,10 @@ def test_bool_writer(self, arg: bool) -> None: self.assertEqual(str(arg).lower(), rcp["a"]["b"]) @parameterized.expand( # type: ignore - [("hello",), ("a\nb\nc",),] + [ + ("hello",), + ("a\nb\nc",), + ] ) def test_str_writer(self, arg: str) -> None: c = ConfigFile() From b7af6977b9b29169aec958a2d956d8c6a26aa570 Mon Sep 17 00:00:00 2001 From: John Reese Date: Mon, 6 Dec 2021 16:52:32 -0800 Subject: [PATCH 11/44] Add provides_extra metadata to types --- dowsing/types.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dowsing/types.py b/dowsing/types.py index ffc89b7..cefbdae 100644 --- a/dowsing/types.py +++ b/dowsing/types.py @@ -63,6 +63,7 @@ class Distribution(pkginfo.distribution.Distribution): # type: ignore pbr: Optional[bool] = None pbr__files__packages_root: Optional[str] = None pbr__files__packages: Optional[str] = None + provides_extra: Optional[Sequence[str]] = () def _getHeaderAttrs(self) -> Sequence[Tuple[str, str, bool]]: # Until I invent a metadata version to include this, do so From aebd317869c9bae824d4d504ec68b8b939795333 Mon Sep 17 00:00:00 2001 From: John Reese Date: Mon, 6 Dec 2021 17:14:00 -0800 Subject: [PATCH 12/44] Enhance package compatibility Handle more edge cases, adding support for parsing setup.py from fastai/fastprogress, dirsync, and plotly. --- dowsing/setuptools/__init__.py | 6 ++++-- dowsing/setuptools/setup_py_parsing.py | 20 +++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/dowsing/setuptools/__init__.py b/dowsing/setuptools/__init__.py index b3ffcaf..3864fee 100644 --- a/dowsing/setuptools/__init__.py +++ b/dowsing/setuptools/__init__.py @@ -96,8 +96,10 @@ def mangle(package: str) -> str: d1.find_packages_include, ): d1.packages_dict[p] = mangle(p) - elif d1.packages != "??": - assert isinstance(d1.packages, (list, tuple)) + elif d1.packages not in ("??", "????"): + assert isinstance( + d1.packages, (list, tuple) + ), f"{d1.packages!r} is not a list/tuple" for p in d1.packages: if p: d1.packages_dict[p] = mangle(p) diff --git a/dowsing/setuptools/setup_py_parsing.py b/dowsing/setuptools/setup_py_parsing.py index 9efcde2..8b0766a 100644 --- a/dowsing/setuptools/setup_py_parsing.py +++ b/dowsing/setuptools/setup_py_parsing.py @@ -141,7 +141,13 @@ def visit_Call(self, node: cst.Call) -> Optional[bool]: # TODO sometimes there is more than one setup call, we might # prioritize/merge... if any( - q.name in ("setuptools.setup", "distutils.core.setup", "setup3lib") + q.name + in ( + "setuptools.setup", + "distutils.core.setup", + "setup3lib", + "skbuild.setup", + ) for q in names ): self.found_setup = True @@ -177,7 +183,8 @@ def evaluate_in_scope(self, item: cst.CSTNode, scope: Any) -> Any: if isinstance(item, cst.SimpleString): return item.evaluated_value - # TODO int/float/etc + elif isinstance(item, (cst.Integer, cst.Float)): + return int(item.value) elif isinstance(item, cst.Name) and item.value in self.BOOL_NAMES: return self.BOOL_NAMES[item.value] elif isinstance(item, cst.Name): @@ -277,7 +284,14 @@ def evaluate_in_scope(self, item: cst.CSTNode, scope: Any) -> Any: # TODO: Figure out why this is Sequence if isinstance(item.slice[0].slice, cst.Index): rhs = self.evaluate_in_scope(item.slice[0].slice.value, scope) - return lhs.get(rhs, "??") + try: + if isinstance(lhs, dict): + return lhs.get(rhs, "??") + else: + return lhs[rhs] + except Exception: + return "??" + else: # LOG.warning(f"Omit2 {type(item.slice[0].slice)!r}") return "??" From 2b57fcb99bb9dc997444c7897ea01eee5ca1a346 Mon Sep 17 00:00:00 2001 From: John Reese Date: Mon, 6 Dec 2021 18:08:06 -0800 Subject: [PATCH 13/44] Simple dependabot config --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..55dbe85 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "monthly" From 7fcd5f2534080babfe0935cd5ff35c064b829d73 Mon Sep 17 00:00:00 2001 From: John Reese Date: Mon, 6 Dec 2021 18:51:41 -0800 Subject: [PATCH 14/44] Fix #33: Try reading long_description from payload body Updates the test_arg_mapping case to handle setuptools >= 57, which writes the long_description field as the payload/body of PKG-INFO, skipping the previous `Description:` field entirely. This results in the missing key from the Message object, so the test then expects to read long_description from the payload. --- dowsing/tests/setuptools_metadata.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dowsing/tests/setuptools_metadata.py b/dowsing/tests/setuptools_metadata.py index 6c226b5..10893e8 100644 --- a/dowsing/tests/setuptools_metadata.py +++ b/dowsing/tests/setuptools_metadata.py @@ -96,6 +96,12 @@ def test_arg_mapping(self) -> None: a = setup_py_info.get_all(field.metadata.key) b = setup_cfg_info.get_all(field.metadata.key) + # setuptools>=57 writes long_description to the body/payload + # of PKG-INFO, and skips the description field entirely. + if field.keyword == "long_description" and a is None: + a = setup_py_info.get_payload() + b = setup_cfg_info.get_payload() + # install_requires gets written out to *.egg-info/requires.txt # instead if field.keyword != "install_requires": From f072c484a3181580b0bf0c5c5e9da5fe4c7a0c39 Mon Sep 17 00:00:00 2001 From: John Reese Date: Thu, 20 Jan 2022 20:52:13 -0800 Subject: [PATCH 15/44] Initial support for PEP 621 metadata with Flit This adds a `Pep621Reader` class that pulls metadata from the [project] table of pyproject.toml. The `FlitReader` class uses this as a basis for reading metadata, and is also updated to not fail if the flit table is missing. --- dowsing/flit.py | 24 ++++++++--------- dowsing/pep621.py | 49 +++++++++++++++++++++++++++++++++ dowsing/tests/__init__.py | 2 ++ dowsing/tests/flit.py | 45 +++++++++++++++++++++++++++++-- dowsing/tests/pep621.py | 57 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 163 insertions(+), 14 deletions(-) create mode 100644 dowsing/pep621.py create mode 100644 dowsing/tests/pep621.py diff --git a/dowsing/flit.py b/dowsing/flit.py index 76b1514..a61c632 100644 --- a/dowsing/flit.py +++ b/dowsing/flit.py @@ -4,10 +4,11 @@ import tomlkit from setuptools import find_packages -from .types import BaseReader, Distribution +from .pep621 import Pep621Reader +from .types import Distribution -class FlitReader(BaseReader): +class FlitReader(Pep621Reader): def __init__(self, path: Path): self.path = path @@ -21,12 +22,12 @@ def get_metadata(self) -> Distribution: pyproject = self.path / "pyproject.toml" doc = tomlkit.parse(pyproject.read_text()) - d = Distribution() - d.metadata_version = "2.1" - d.project_urls = {} - d.entry_points = {} + d = self.get_pep621_metadata() + d.entry_points = dict(d.entry_points) or {} - for k, v in doc["tool"]["flit"]["metadata"].items(): + flit = doc.get("tool", {}).get("flit", {}) + metadata = flit.get("metadata", {}) + for k, v in metadata.items(): # TODO description-file -> long_description # TODO home-page -> urls # TODO requires -> requires_dist @@ -52,10 +53,10 @@ def get_metadata(self) -> Distribution: if k2 in d: setattr(d, k2, v) - for k, v in doc["tool"]["flit"]["metadata"].get("urls", {}).items(): + for k, v in metadata.get("urls", {}).items(): d.project_urls[k] = v - for k, v in doc["tool"]["flit"].get("scripts", {}).items(): + for k, v in flit.get("scripts", {}).items(): d.entry_points[k] = v # TODO extras-require @@ -72,8 +73,7 @@ def _get_requires(self) -> Sequence[str]: https://github.com/takluyver/flit/issues/141 """ - pyproject = self.path / "pyproject.toml" - doc = tomlkit.parse(pyproject.read_text()) - seq = doc["tool"]["flit"]["metadata"].get("requires", ()) + dist = self.get_metadata() + seq = dist.requires_dist assert isinstance(seq, (list, tuple)) return seq diff --git a/dowsing/pep621.py b/dowsing/pep621.py new file mode 100644 index 0000000..b5e9082 --- /dev/null +++ b/dowsing/pep621.py @@ -0,0 +1,49 @@ +import tomlkit +from setuptools import find_packages + +from .types import BaseReader, Distribution + + +class Pep621Reader(BaseReader): + def get_pep621_metadata(self) -> Distribution: + pyproject = self.path / "pyproject.toml" + doc = tomlkit.parse(pyproject.read_text()) + + d = Distribution() + d.metadata_version = "2.1" + d.project_urls = {} + d.entry_points = {} + d.requires_dist = [] + d.packages = [] + d.packages_dict = {} + + table = doc.get("project", None) + if table: + for k, v in table.items(): + if k == "name": + if (self.path / f"{v}.py").exists(): + d.py_modules = [v] + else: + d.packages = find_packages( + self.path.as_posix(), include=(f"{v}.*") + ) + d.packages_dict = {i: i.replace(".", "/") for i in d.packages} + elif k == "license": + if "text" in v: + v = v["text"] + elif "file" in v: + v = f"file: {v['file']}" + else: + raise ValueError("no known license field values") + elif k == "dependencies": + k = "requires_dist" + elif k == "optional-dependencies": + pass + elif k == "urls": + d.project_urls.update(v) + + k2 = k.replace("-", "_") + if k2 in d: + setattr(d, k2, v) + + return d diff --git a/dowsing/tests/__init__.py b/dowsing/tests/__init__.py index 7eb139d..80335e6 100644 --- a/dowsing/tests/__init__.py +++ b/dowsing/tests/__init__.py @@ -2,6 +2,7 @@ from .flit import FlitReaderTest from .maturin import MaturinReaderTest from .pep517 import Pep517Test +from .pep621 import Pep621ReaderTest from .poetry import PoetryReaderTest from .setuptools import SetuptoolsReaderTest from .setuptools_metadata import SetupArgsTest @@ -12,6 +13,7 @@ "FlitReaderTest", "MaturinReaderTest", "Pep517Test", + "Pep621ReaderTest", "PoetryReaderTest", "SetuptoolsReaderTest", "WriterTest", diff --git a/dowsing/tests/flit.py b/dowsing/tests/flit.py index ae15ad6..a36448d 100644 --- a/dowsing/tests/flit.py +++ b/dowsing/tests/flit.py @@ -23,8 +23,8 @@ def test_simplest(self) -> None: # handle missing metadata appropriately. r = FlitReader(dp) - self.assertEqual((), r.get_requires_for_build_sdist()) - self.assertEqual((), r.get_requires_for_build_wheel()) + self.assertEqual([], r.get_requires_for_build_sdist()) + self.assertEqual([], r.get_requires_for_build_wheel()) md = r.get_metadata() self.assertEqual("Name", md.name) @@ -69,3 +69,44 @@ def test_normal(self) -> None: }, md.asdict(), ) + + def test_pep621(self) -> None: + with volatile.dir() as d: + dp = Path(d) + (dp / "pyproject.toml").write_text( + """\ +[build-system] +requires = ["flit_core >=2,<4"] +build-backend = "flit_core.buildapi" + +[project] +name = "foo" +dependencies = ["abc", "def"] + +[project.urls] +Foo = "https://" +""" + ) + (dp / "foo").mkdir() + (dp / "foo" / "tests").mkdir() + (dp / "foo" / "__init__.py").write_text("") + (dp / "foo" / "tests" / "__init__.py").write_text("") + + r = FlitReader(dp) + # Notably these do not include flit itself; that's handled by + # dowsing.pep517 + self.assertEqual(["abc", "def"], r.get_requires_for_build_sdist()) + self.assertEqual(["abc", "def"], r.get_requires_for_build_wheel()) + md = r.get_metadata() + self.assertEqual("foo", md.name) + self.assertEqual( + { + "metadata_version": "2.1", + "name": "foo", + "packages": ["foo", "foo.tests"], + "packages_dict": {"foo": "foo", "foo.tests": "foo/tests"}, + "requires_dist": ["abc", "def"], + "project_urls": {"Foo": "https://"}, + }, + md.asdict(), + ) diff --git a/dowsing/tests/pep621.py b/dowsing/tests/pep621.py new file mode 100644 index 0000000..3b3ab8b --- /dev/null +++ b/dowsing/tests/pep621.py @@ -0,0 +1,57 @@ +import unittest +from pathlib import Path + +import volatile + +from ..pep621 import Pep621Reader + + +class Pep621ReaderTest(unittest.TestCase): + def test_simplest(self) -> None: + with volatile.dir() as d: + dp = Path(d) + (dp / "pyproject.toml").write_text( + """\ +[project] +name = "Name" +""" + ) + + r = Pep621Reader(dp) + md = r.get_pep621_metadata() + self.assertEqual("Name", md.name) + + def test_normal(self) -> None: + with volatile.dir() as d: + dp = Path(d) + (dp / "pyproject.toml").write_text( + """\ +[project] +name = "foo" +dependencies = ["abc", "def"] +license = {text = "MIT"} + +[project.urls] +Foo = "https://" +""" + ) + (dp / "foo").mkdir() + (dp / "foo" / "tests").mkdir() + (dp / "foo" / "__init__.py").write_text("") + (dp / "foo" / "tests" / "__init__.py").write_text("") + + r = Pep621Reader(dp) + md = r.get_pep621_metadata() + self.assertEqual("foo", md.name) + self.assertEqual( + { + "metadata_version": "2.1", + "name": "foo", + "license": "MIT", + "packages": ["foo", "foo.tests"], + "packages_dict": {"foo": "foo", "foo.tests": "foo/tests"}, + "requires_dist": ["abc", "def"], + "project_urls": {"Foo": "https://"}, + }, + md.asdict(), + ) From ceb4048da1c31bdfdf107d2d29f6331f61b0e86b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jan 2022 00:10:05 -0800 Subject: [PATCH 16/44] Bump wheel from 0.33.6 to 0.37.1 (#40) --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 19d9292..8abc6bd 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,5 +8,5 @@ twine==3.1.1 ufmt==1.1 usort==0.6.3 volatile==2.1.0 -wheel==0.33.6 +wheel==0.37.1 honesty==0.3.0a1 From 323d7c054342cd7fae0c50bec2c51c0e8f7e2e05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jan 2022 00:10:50 -0800 Subject: [PATCH 17/44] Bump click from 7.1.2 to 8.0.3 (#38) --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 8abc6bd..33d6001 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ black==20.8b1 -click==7.1.2 +click==8.0.3 coverage==4.5.4 flake8==3.7.9 mypy==0.750 From f863a431ac4de83ccde37d304690415dc6ca0102 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jan 2022 00:29:07 -0800 Subject: [PATCH 18/44] Bump tox from 3.14.1 to 3.24.5 (#44) --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 33d6001..b6ebc27 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,7 +3,7 @@ click==8.0.3 coverage==4.5.4 flake8==3.7.9 mypy==0.750 -tox==3.14.1 +tox==3.24.5 twine==3.1.1 ufmt==1.1 usort==0.6.3 From d5cddeff6d0118ed210d7dedb91c89c8acc89012 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jan 2022 00:38:26 -0800 Subject: [PATCH 19/44] Bump black from 20.8b1 to 21.12b0 (#35) Bumps [black](https://github.com/psf/black) from 20.8b1 to 21.12b0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits) --- updated-dependencies: - dependency-name: black dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index b6ebc27..b6d9817 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -black==20.8b1 +black==21.12b0 click==8.0.3 coverage==4.5.4 flake8==3.7.9 From a2b87845e5ff9a042cbd7d23fbcae6be1a9e42c4 Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Wed, 26 Jan 2022 10:13:06 -0800 Subject: [PATCH 20/44] mypy can pass on 3.9 now --- .github/workflows/build.yml | 1 - dowsing/setuptools/__init__.py | 2 +- dowsing/setuptools/setup_py_parsing.py | 4 ++-- requirements-dev.txt | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b8394ac..8209f9e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,7 +34,6 @@ jobs: run: make test - name: Lint run: make lint - if: ${{ matrix.python-version != '3.9' }} check-deps: runs-on: ${{ matrix.os }} diff --git a/dowsing/setuptools/__init__.py b/dowsing/setuptools/__init__.py index 3864fee..ce8452e 100644 --- a/dowsing/setuptools/__init__.py +++ b/dowsing/setuptools/__init__.py @@ -61,7 +61,7 @@ def get_metadata(self) -> Distribution: # https://docs.python.org/2/distutils/setupscript.html#listing-whole-packages package_dir: Mapping[str, str] = d1.package_dir # If there was an error, we might have written "??" - if package_dir != "??": + if package_dir != "??": # type: ignore if not package_dir: package_dir = {"": "."} diff --git a/dowsing/setuptools/setup_py_parsing.py b/dowsing/setuptools/setup_py_parsing.py index 8b0766a..2a61bba 100644 --- a/dowsing/setuptools/setup_py_parsing.py +++ b/dowsing/setuptools/setup_py_parsing.py @@ -92,7 +92,7 @@ def __init__(self, filename: str) -> None: class SetupCallTransformer(cst.CSTTransformer): - METADATA_DEPENDENCIES = (ScopeProvider, ParentNodeProvider, QualifiedNameProvider) # type: ignore + METADATA_DEPENDENCIES = (ScopeProvider, ParentNodeProvider, QualifiedNameProvider) def __init__( self, @@ -124,7 +124,7 @@ def leave_Call( class SetupCallAnalyzer(cst.CSTVisitor): - METADATA_DEPENDENCIES = (ScopeProvider, ParentNodeProvider, QualifiedNameProvider) # type: ignore + METADATA_DEPENDENCIES = (ScopeProvider, ParentNodeProvider, QualifiedNameProvider) # TODO names resulting from other than 'from setuptools import setup' # TODO wrapper funcs that modify args diff --git a/requirements-dev.txt b/requirements-dev.txt index b6d9817..074221c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,7 +2,7 @@ black==21.12b0 click==8.0.3 coverage==4.5.4 flake8==3.7.9 -mypy==0.750 +mypy==0.931 tox==3.24.5 twine==3.1.1 ufmt==1.1 From 0a318d430d685a67c0563f8296ef4cb7008d78ad Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Wed, 26 Jan 2022 10:13:41 -0800 Subject: [PATCH 21/44] Bump some deps, including usort with minor format change --- dowsing/check_source_mapping.py | 2 +- requirements-dev.txt | 4 ++-- requirements.txt | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dowsing/check_source_mapping.py b/dowsing/check_source_mapping.py index 789732f..efe38f3 100644 --- a/dowsing/check_source_mapping.py +++ b/dowsing/check_source_mapping.py @@ -6,7 +6,7 @@ from honesty.archive import extract_and_get_names from honesty.cache import Cache from honesty.cmdline import select_versions, wrap_async -from honesty.releases import FileType, async_parse_index +from honesty.releases import async_parse_index, FileType from moreorless.click import echo_color_unified_diff from dowsing.pep517 import get_metadata diff --git a/requirements-dev.txt b/requirements-dev.txt index 074221c..c6faaca 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,8 +5,8 @@ flake8==3.7.9 mypy==0.931 tox==3.24.5 twine==3.1.1 -ufmt==1.1 -usort==0.6.3 +ufmt==1.3.1.post1 +usort==1.0.0 volatile==2.1.0 wheel==0.37.1 honesty==0.3.0a1 diff --git a/requirements.txt b/requirements.txt index 578b979..74c5df2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ highlighter==0.1.1 imperfect==0.3.0 LibCST==0.3.12 -tomlkit==0.7.0 -pkginfo==1.5.0.1 +tomlkit==0.7.2 +pkginfo==1.8.1 From 0980cadc3ee1409d736a54394379bedd9bb1f852 Mon Sep 17 00:00:00 2001 From: John Reese Date: Wed, 26 Jan 2022 21:03:04 -0800 Subject: [PATCH 22/44] Upgrade to newest tomlkit, fix type issues --- dowsing/maturin.py | 6 ++++-- dowsing/pep517.py | 15 ++++++++------- dowsing/poetry.py | 9 +++++---- requirements.txt | 2 +- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/dowsing/maturin.py b/dowsing/maturin.py index 4576b34..299ea51 100644 --- a/dowsing/maturin.py +++ b/dowsing/maturin.py @@ -25,7 +25,8 @@ def get_metadata(self) -> Distribution: cargo = self.path / "Cargo.toml" doc = tomlkit.parse(cargo.read_text()) - for k, v in doc["package"].items(): + package = doc.get("package", {}) + for k, v in package.items(): if k == "name": d.name = v elif k == "version": @@ -39,7 +40,8 @@ def get_metadata(self) -> Distribution: # homepage # readme (filename) - for k, v in doc["package"]["metadata"]["maturin"].items(): + maturin = package.get("metadata", {}).get("maturin", {}) + for k, v in maturin.items(): if k == "requires-python": d.requires_python = v elif k == "classifier": diff --git a/dowsing/pep517.py b/dowsing/pep517.py index 73d30e2..ef17b11 100644 --- a/dowsing/pep517.py +++ b/dowsing/pep517.py @@ -26,13 +26,14 @@ def get_backend(path: Path) -> Tuple[List[str], BaseReader]: requires: List[str] = [] if pyproject.exists(): doc = tomlkit.parse(pyproject.read_text()) - if "build-system" in doc: - # 1b. include any build-system requires - if "requires" in doc["build-system"]: - requires.extend(doc["build-system"]["requires"]) - if "build-backend" in doc["build-system"]: - backend = doc["build-system"]["build-backend"] - # TODO backend-path + table = doc.get("build-system", {}) + + # 1b. include any build-system requires + if "requires" in table: + requires.extend(table["requires"]) + if "build-backend" in table: + backend = table["build-backend"] + # TODO backend-path try: backend_path = KNOWN_BACKENDS[backend] diff --git a/dowsing/poetry.py b/dowsing/poetry.py index 37b6157..ebb5a72 100644 --- a/dowsing/poetry.py +++ b/dowsing/poetry.py @@ -42,7 +42,8 @@ def get_metadata(self) -> Distribution: d.packages = [] d.packages_dict = {} - for k, v in doc["tool"]["poetry"].items(): + poetry = doc.get("tool", {}).get("poetry", {}) + for k, v in poetry.items(): if k in ("homepage", "repository", "documentation"): d.project_urls[k] = v elif k == "packages": @@ -64,16 +65,16 @@ def get_metadata(self) -> Distribution: d.packages_dict[p] = p.replace(".", "/") d.packages.append(p) - for k, v in doc["tool"]["poetry"].get("dependencies", {}).items(): + for k, v in poetry.get("dependencies", {}).items(): if k == "python": pass # TODO translate to requires_python else: d.requires_dist.append(k) # TODO something with version - for k, v in doc["tool"]["poetry"].get("urls", {}).items(): + for k, v in poetry.get("urls", {}).items(): d.project_urls[k] = v - for k, v in doc["tool"]["poetry"].get("scripts", {}).items(): + for k, v in poetry.get("scripts", {}).items(): d.entry_points[k] = v d.source_mapping = d._source_mapping(self.path) diff --git a/requirements.txt b/requirements.txt index 74c5df2..bccf3e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ highlighter==0.1.1 imperfect==0.3.0 LibCST==0.3.12 -tomlkit==0.7.2 +tomlkit==0.8.0 pkginfo==1.8.1 From e49c4a27a6b7d3941fef35eb1f7cc17c37456a22 Mon Sep 17 00:00:00 2001 From: John Reese Date: Wed, 26 Jan 2022 21:07:33 -0800 Subject: [PATCH 23/44] Add dataclasses for 3.6 --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index bccf3e8..09e6376 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ imperfect==0.3.0 LibCST==0.3.12 tomlkit==0.8.0 pkginfo==1.8.1 +dataclasses==0.8; python_version<"3.7" From 86884a044002e7347b1d19d48aec888a4764357f Mon Sep 17 00:00:00 2001 From: John Reese Date: Thu, 27 Jan 2022 05:39:48 +0000 Subject: [PATCH 24/44] Run mypy against python 3.7 --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index e6b7df7..0bda0bf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -48,6 +48,8 @@ use_parentheses = True [mypy] ignore_missing_imports = True +python_version = 3.7 +strict = True [tox:tox] envlist = py36, py37, py38 From 74e6599943c700845067039836fdf555e18dbd1b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Feb 2022 11:57:56 -0800 Subject: [PATCH 25/44] Bump tomlkit from 0.8.0 to 0.9.0 (#48) Bumps [tomlkit](https://github.com/sdispater/tomlkit) from 0.8.0 to 0.9.0. - [Release notes](https://github.com/sdispater/tomlkit/releases) - [Changelog](https://github.com/sdispater/tomlkit/blob/master/CHANGELOG.md) - [Commits](https://github.com/sdispater/tomlkit/compare/0.8.0...0.9.0) --- updated-dependencies: - dependency-name: tomlkit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 09e6376..d972937 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ highlighter==0.1.1 imperfect==0.3.0 LibCST==0.3.12 -tomlkit==0.8.0 +tomlkit==0.9.0 pkginfo==1.8.1 dataclasses==0.8; python_version<"3.7" From 2b658be5e64294a2e29b5483eab70ce6b71d60cb Mon Sep 17 00:00:00 2001 From: Brendan Gerrity Date: Fri, 4 Feb 2022 10:36:32 -0500 Subject: [PATCH 26/44] set python variable to use 3 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9103317..adb21fc 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PYTHON?=python +PYTHON?=python3 SOURCES=dowsing setup.py .PHONY: venv From a97cd507d64599a84fe9a944ee373fd9bb89d9cb Mon Sep 17 00:00:00 2001 From: Brendan Gerrity Date: Fri, 4 Feb 2022 11:05:37 -0500 Subject: [PATCH 27/44] add jupyter packaging as backend --- dowsing/pep517.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dowsing/pep517.py b/dowsing/pep517.py index ef17b11..57ee59a 100644 --- a/dowsing/pep517.py +++ b/dowsing/pep517.py @@ -11,6 +11,7 @@ KNOWN_BACKENDS: Dict[str, str] = { "setuptools.build_meta:__legacy__": "dowsing.setuptools:SetuptoolsReader", "setuptools.build_meta": "dowsing.setuptools:SetuptoolsReader", + "jupyter_packaging.build_api": "dowsing.setuptools:SetuptoolsReader", "flit_core.buildapi": "dowsing.flit:FlitReader", "flit.buildapi": "dowsing.flit:FlitReader", "maturin": "dowsing.maturin:MaturinReader", From 4c6fa30d419bdea3aaf6fb8936ad40888d4641f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Feb 2022 20:27:43 -0800 Subject: [PATCH 28/44] Bump black from 21.12b0 to 22.1.0 (#51) Bumps [black](https://github.com/psf/black) from 21.12b0 to 22.1.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits/22.1.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c6faaca..b3e704c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -black==21.12b0 +black==22.1.0 click==8.0.3 coverage==4.5.4 flake8==3.7.9 From 36441b60a2070dd0dea157a91253aabe1c7e207e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Feb 2022 20:27:54 -0800 Subject: [PATCH 29/44] Bump honesty from 0.3.0a1 to 0.3.0a2 (#50) Bumps [honesty](https://github.com/python-packaging/honesty) from 0.3.0a1 to 0.3.0a2. - [Release notes](https://github.com/python-packaging/honesty/releases) - [Changelog](https://github.com/python-packaging/honesty/blob/main/CHANGELOG.md) - [Commits](https://github.com/python-packaging/honesty/compare/v0.3.0a1...v0.3.0a2) --- updated-dependencies: - dependency-name: honesty dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index b3e704c..6df737c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,4 +9,4 @@ ufmt==1.3.1.post1 usort==1.0.0 volatile==2.1.0 wheel==0.37.1 -honesty==0.3.0a1 +honesty==0.3.0a2 From f2ad1becb10fab631dc5143ba9547eb478cbf90e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Feb 2022 20:28:14 -0800 Subject: [PATCH 30/44] Bump usort from 1.0.0 to 1.0.1 (#47) Bumps [usort](https://github.com/facebookexperimental/usort) from 1.0.0 to 1.0.1. - [Release notes](https://github.com/facebookexperimental/usort/releases) - [Changelog](https://github.com/facebookexperimental/usort/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebookexperimental/usort/compare/v1.0.0...v1.0.1) --- updated-dependencies: - dependency-name: usort dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 6df737c..0d90b9b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,7 +6,7 @@ mypy==0.931 tox==3.24.5 twine==3.1.1 ufmt==1.3.1.post1 -usort==1.0.0 +usort==1.0.1 volatile==2.1.0 wheel==0.37.1 honesty==0.3.0a2 From ac4db2f7cb1c04c87ea539a8c3106ea9c8942219 Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Sat, 23 Nov 2024 18:33:40 -0800 Subject: [PATCH 31/44] Make tests work with the current version of setuptools Previously was failing because of multiple top-level packages. --- dowsing/setuptools/setup_and_metadata.py | 2 +- dowsing/tests/setuptools_metadata.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/dowsing/setuptools/setup_and_metadata.py b/dowsing/setuptools/setup_and_metadata.py index bd7a598..ff64c4c 100644 --- a/dowsing/setuptools/setup_and_metadata.py +++ b/dowsing/setuptools/setup_and_metadata.py @@ -175,7 +175,7 @@ ConfigField( "packages", SetupCfg("options", "packages", writer_cls=ListCommaWriter), - sample_value=["a", "b"], + sample_value=["a"], ), ConfigField( "package_dir", diff --git a/dowsing/tests/setuptools_metadata.py b/dowsing/tests/setuptools_metadata.py index 10893e8..9d36509 100644 --- a/dowsing/tests/setuptools_metadata.py +++ b/dowsing/tests/setuptools_metadata.py @@ -31,13 +31,13 @@ def egg_info(files: Dict[str, str]) -> Tuple[Message, Distribution]: os.chdir(d) sys.stdout = io.StringIO() - dist = run_setup(f"setup.py", ["egg_info"]) + dist = run_setup("setup.py", ["egg_info"]) finally: os.chdir(cwd) sys.stdout = stdout sources = list(Path(d).rglob("PKG-INFO")) - assert len(sources) == 1 + assert len(sources) == 1, sources with open(sources[0]) as f: parser = email.parser.Parser() @@ -63,7 +63,6 @@ def test_arg_mapping(self) -> None: "setup.py": "from setuptools import setup\n" f"setup({field.keyword}={foo!r})\n", "a/__init__.py": "", - "b/__init__.py": "", } ) @@ -74,7 +73,6 @@ def test_arg_mapping(self) -> None: f"{field.cfg.key} = {cfg_format_foo}\n", "setup.py": "from setuptools import setup\n" "setup()\n", "a/__init__.py": "", - "b/__init__.py": "", } ) From 1057a840afc4bddea4939491aa59eed40f68f35a Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Sat, 23 Nov 2024 18:59:22 -0800 Subject: [PATCH 32/44] Upgrade testing dependencies, fix types --- dowsing/_demo_pep517.py | 13 +++++++------ dowsing/flit.py | 5 ++++- dowsing/pep621.py | 6 ++++-- dowsing/poetry.py | 8 +++++--- dowsing/tests/setuptools_metadata.py | 8 ++++---- dowsing/types.py | 8 ++++---- requirements-dev.txt | 22 +++++++++++----------- requirements.txt | 9 ++++----- setup.cfg | 4 ++-- 9 files changed, 45 insertions(+), 38 deletions(-) diff --git a/dowsing/_demo_pep517.py b/dowsing/_demo_pep517.py index 967d9c7..e66cb8b 100644 --- a/dowsing/_demo_pep517.py +++ b/dowsing/_demo_pep517.py @@ -1,6 +1,7 @@ """ For testing, dump the requirements that we find using the pep517 project. """ + import json import sys @@ -19,12 +20,12 @@ def main(path: str) -> None: d = {} with BuildEnvironment() as env: env.pip_install(requires) - d[ - "get_requires_for_build_sdist" - ] = requires + hooks.get_requires_for_build_sdist(None) - d[ - "get_requires_for_build_wheel" - ] = requires + hooks.get_requires_for_build_wheel(None) + d["get_requires_for_build_sdist"] = ( + requires + hooks.get_requires_for_build_sdist(None) + ) + d["get_requires_for_build_wheel"] = ( + requires + hooks.get_requires_for_build_wheel(None) + ) print(json.dumps(d)) diff --git a/dowsing/flit.py b/dowsing/flit.py index a61c632..095aa29 100644 --- a/dowsing/flit.py +++ b/dowsing/flit.py @@ -24,6 +24,9 @@ def get_metadata(self) -> Distribution: d = self.get_pep621_metadata() d.entry_points = dict(d.entry_points) or {} + d.project_urls = list(d.project_urls) + + assert isinstance(d.project_urls, list) flit = doc.get("tool", {}).get("flit", {}) metadata = flit.get("metadata", {}) @@ -33,7 +36,7 @@ def get_metadata(self) -> Distribution: # TODO requires -> requires_dist # TODO tool.flit.metadata.urls if k == "home-page": - d.project_urls["Homepage"] = v + d.project_urls.append("Homepage={v}") continue elif k == "module": if (self.path / f"{v}.py").exists(): diff --git a/dowsing/pep621.py b/dowsing/pep621.py index b5e9082..8915e2e 100644 --- a/dowsing/pep621.py +++ b/dowsing/pep621.py @@ -11,12 +11,14 @@ def get_pep621_metadata(self) -> Distribution: d = Distribution() d.metadata_version = "2.1" - d.project_urls = {} + d.project_urls = [] d.entry_points = {} d.requires_dist = [] d.packages = [] d.packages_dict = {} + assert isinstance(d.project_urls, list) + table = doc.get("project", None) if table: for k, v in table.items(): @@ -40,7 +42,7 @@ def get_pep621_metadata(self) -> Distribution: elif k == "optional-dependencies": pass elif k == "urls": - d.project_urls.update(v) + d.project_urls.extend(v) k2 = k.replace("-", "_") if k2 in d: diff --git a/dowsing/poetry.py b/dowsing/poetry.py index ebb5a72..f6dc977 100644 --- a/dowsing/poetry.py +++ b/dowsing/poetry.py @@ -36,16 +36,18 @@ def get_metadata(self) -> Distribution: d = Distribution() d.metadata_version = "2.1" - d.project_urls = {} + d.project_urls = [] d.entry_points = {} d.requires_dist = [] d.packages = [] d.packages_dict = {} + assert isinstance(d.project_urls, list) + poetry = doc.get("tool", {}).get("poetry", {}) for k, v in poetry.items(): if k in ("homepage", "repository", "documentation"): - d.project_urls[k] = v + d.project_urls.append(f"{k}={v}") elif k == "packages": # TODO improve and add tests; this works for tf2_utils and # poetry itself but include can be a glob and there are excludes @@ -72,7 +74,7 @@ def get_metadata(self) -> Distribution: d.requires_dist.append(k) # TODO something with version for k, v in poetry.get("urls", {}).items(): - d.project_urls[k] = v + d.project_urls.append(f"{k}={v}") for k, v in poetry.get("scripts", {}).items(): d.entry_points[k] = v diff --git a/dowsing/tests/setuptools_metadata.py b/dowsing/tests/setuptools_metadata.py index 9d36509..3e3c0ac 100644 --- a/dowsing/tests/setuptools_metadata.py +++ b/dowsing/tests/setuptools_metadata.py @@ -43,8 +43,8 @@ def egg_info(files: Dict[str, str]) -> Tuple[Message, Distribution]: parser = email.parser.Parser() info = parser.parse(f) reader = SetuptoolsReader(Path(d)) - dist = reader.get_metadata() - return info, dist + dist = reader.get_metadata() # type: ignore[assignment] + return info, dist # type: ignore[return-value] # These tests do not increase coverage, and just verify that we have the right @@ -97,8 +97,8 @@ def test_arg_mapping(self) -> None: # setuptools>=57 writes long_description to the body/payload # of PKG-INFO, and skips the description field entirely. if field.keyword == "long_description" and a is None: - a = setup_py_info.get_payload() - b = setup_cfg_info.get_payload() + a = setup_py_info.get_payload() # type: ignore[assignment] + b = setup_cfg_info.get_payload() # type: ignore[assignment] # install_requires gets written out to *.egg-info/requires.txt # instead diff --git a/dowsing/types.py b/dowsing/types.py index cefbdae..7c93096 100644 --- a/dowsing/types.py +++ b/dowsing/types.py @@ -37,9 +37,8 @@ def get_metadata(self) -> "Distribution": DEFAULT_EMPTY_DICT: Mapping[str, Any] = MappingProxyType({}) -# TODO: pkginfo isn't typed, and is doing to require a yak-shave to send a PR -# since it's on launchpad. -class Distribution(pkginfo.distribution.Distribution): # type: ignore + +class Distribution(pkginfo.distribution.Distribution): # These are not actually part of the metadata, see PEP 566 setup_requires: Sequence[str] = () tests_require: Sequence[str] = () @@ -68,7 +67,8 @@ class Distribution(pkginfo.distribution.Distribution): # type: ignore def _getHeaderAttrs(self) -> Sequence[Tuple[str, str, bool]]: # Until I invent a metadata version to include this, do so # unconditionally. - return tuple(super()._getHeaderAttrs()) + ( + # Stubs are wrong, this does too exist. + return tuple(super()._getHeaderAttrs()) + ( # type: ignore[misc] ("X-Setup-Requires", "setup_requires", True), ("X-Tests-Require", "tests_require", True), ("???", "extras_require", False), diff --git a/requirements-dev.txt b/requirements-dev.txt index 0d90b9b..fc8019c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,12 +1,12 @@ -black==22.1.0 -click==8.0.3 -coverage==4.5.4 -flake8==3.7.9 -mypy==0.931 -tox==3.24.5 -twine==3.1.1 -ufmt==1.3.1.post1 -usort==1.0.1 +black==24.10.0 +click==8.1.7 +coverage==7.6.8 +flake8==7.1.1 +mypy==1.13.0 +tox==4.23.2 +twine==5.1.1 +ufmt==2.8.0 +usort==1.0.8.post1 volatile==2.1.0 -wheel==0.37.1 -honesty==0.3.0a2 +wheel==0.45.1 +honesty==0.3.0b1 diff --git a/requirements.txt b/requirements.txt index d972937..7afca69 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ -highlighter==0.1.1 +highlighter==0.2.0 imperfect==0.3.0 -LibCST==0.3.12 -tomlkit==0.9.0 -pkginfo==1.8.1 -dataclasses==0.8; python_version<"3.7" +LibCST==1.5.1 +tomlkit==0.13.2 +pkginfo==1.11.2 diff --git a/setup.cfg b/setup.cfg index 0bda0bf..d123a00 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,7 +16,7 @@ packages = setup_requires = setuptools_scm setuptools >= 38.3.0 -python_requires = >=3.6 +python_requires = >=3.7 install_requires = highlighter>=0.1.1 imperfect>=0.1.0 @@ -48,7 +48,7 @@ use_parentheses = True [mypy] ignore_missing_imports = True -python_version = 3.7 +python_version = 3.8 strict = True [tox:tox] From 1a562021a1119a2ea1c04455474a65a85e10d521 Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Sat, 23 Nov 2024 19:02:38 -0800 Subject: [PATCH 33/44] Modernize GH Actions --- .github/workflows/build.yml | 69 ++++++++++++++++++++++--------------- Makefile | 2 +- requirements-dev.txt | 12 ------- setup.cfg | 16 +++++++++ 4 files changed, 58 insertions(+), 41 deletions(-) delete mode 100644 requirements-dev.txt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8209f9e..5d02cb9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,52 +9,65 @@ on: - v* pull_request: +env: + UV_SYSTEM_PYTHON: 1 + jobs: - dowsing: + test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - python-version: ["3.6", "3.7", "3.8", "3.9"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] os: [macOS-latest, ubuntu-latest, windows-latest] steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Set Up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - uses: astral-sh/setup-uv@v3 - name: Install run: | - python -m pip install --upgrade pip - make setup - pip install -U . + uv pip install -e .[test] - name: Test run: make test - name: Lint - run: make lint - - check-deps: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - python-version: ["3.6", "3.7", "3.8", "3.9"] - os: [ubuntu-latest] + run: | + uv pip install -e .[test,dev] + make lint + if: ${{ matrix.python-version != '3.9' && matrix.python-version != '3.8' }} + build: + needs: test + runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v1 - - name: Set Up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: - python-version: ${{ matrix.python-version }} + python-version: "3.12" + - uses: astral-sh/setup-uv@v3 - name: Install - run: | - python -m pip install --upgrade pip - pip install 'pessimist>=0.8.0' - echo 'importall>=0.2.1' > importall.txt - - name: Check Deps - run: | - python -m pessimist --requirements=importall.txt --fast -c 'importall --root=. --exclude=tests,_demo_pep517.py,check_source_mapping.py dowsing' . + run: uv pip install build + - name: Build + run: python -m build + - name: Upload + uses: actions/upload-artifact@v3 + with: + name: sdist + path: dist + + publish: + needs: build + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v3 + with: + name: sdist + path: dist + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/Makefile b/Makefile index adb21fc..da69f1f 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ venv: .PHONY: setup setup: - python -m pip install -U -r requirements.txt -r requirements-dev.txt + python -m pip install -Ue .[dev,test] .PHONY: test test: diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index fc8019c..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,12 +0,0 @@ -black==24.10.0 -click==8.1.7 -coverage==7.6.8 -flake8==7.1.1 -mypy==1.13.0 -tox==4.23.2 -twine==5.1.1 -ufmt==2.8.0 -usort==1.0.8.post1 -volatile==2.1.0 -wheel==0.45.1 -honesty==0.3.0b1 diff --git a/setup.cfg b/setup.cfg index d123a00..8f00d86 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,6 +24,22 @@ install_requires = tomlkit>=0.2.0 pkginfo>=1.4.2 +[options.extras_require] +dev = + black==24.10.0 + click==8.1.7 + flake8==7.1.1 + mypy==1.13.0 + tox==4.23.2 + twine==5.1.1 + ufmt==2.8.0 + usort==1.0.8.post1 + volatile==2.1.0 + wheel==0.45.1 + honesty==0.3.0b1 +test = + coverage >= 6 + [check] metadata = true strict = true From f50cb665ada67e07b5f035a1714e0a11a931b7a7 Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Sat, 23 Nov 2024 19:15:17 -0800 Subject: [PATCH 34/44] Change tests to match --- dowsing/flit.py | 2 +- dowsing/pep621.py | 2 +- dowsing/tests/flit.py | 4 ++-- dowsing/tests/pep621.py | 2 +- dowsing/tests/poetry.py | 8 ++++---- setup.cfg | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dowsing/flit.py b/dowsing/flit.py index 095aa29..60907d0 100644 --- a/dowsing/flit.py +++ b/dowsing/flit.py @@ -57,7 +57,7 @@ def get_metadata(self) -> Distribution: setattr(d, k2, v) for k, v in metadata.get("urls", {}).items(): - d.project_urls[k] = v + d.project_urls.append(f"{k}={v}") for k, v in flit.get("scripts", {}).items(): d.entry_points[k] = v diff --git a/dowsing/pep621.py b/dowsing/pep621.py index 8915e2e..4a956b2 100644 --- a/dowsing/pep621.py +++ b/dowsing/pep621.py @@ -42,7 +42,7 @@ def get_pep621_metadata(self) -> Distribution: elif k == "optional-dependencies": pass elif k == "urls": - d.project_urls.extend(v) + d.project_urls.extend([f"{x}={y}" for x, y in v.items()]) k2 = k.replace("-", "_") if k2 in d: diff --git a/dowsing/tests/flit.py b/dowsing/tests/flit.py index a36448d..98a15b0 100644 --- a/dowsing/tests/flit.py +++ b/dowsing/tests/flit.py @@ -65,7 +65,7 @@ def test_normal(self) -> None: "packages": ["foo", "foo.tests"], "packages_dict": {"foo": "foo", "foo.tests": "foo/tests"}, "requires_dist": ["abc", "def"], - "project_urls": {"Foo": "https://"}, + "project_urls": ["Foo=https://"], }, md.asdict(), ) @@ -106,7 +106,7 @@ def test_pep621(self) -> None: "packages": ["foo", "foo.tests"], "packages_dict": {"foo": "foo", "foo.tests": "foo/tests"}, "requires_dist": ["abc", "def"], - "project_urls": {"Foo": "https://"}, + "project_urls": ["Foo=https://"], }, md.asdict(), ) diff --git a/dowsing/tests/pep621.py b/dowsing/tests/pep621.py index 3b3ab8b..99069b9 100644 --- a/dowsing/tests/pep621.py +++ b/dowsing/tests/pep621.py @@ -51,7 +51,7 @@ def test_normal(self) -> None: "packages": ["foo", "foo.tests"], "packages_dict": {"foo": "foo", "foo.tests": "foo/tests"}, "requires_dist": ["abc", "def"], - "project_urls": {"Foo": "https://"}, + "project_urls": ["Foo=https://"], }, md.asdict(), ) diff --git a/dowsing/tests/poetry.py b/dowsing/tests/poetry.py index ae8b2d4..f8a1f9e 100644 --- a/dowsing/tests/poetry.py +++ b/dowsing/tests/poetry.py @@ -41,10 +41,10 @@ def test_basic(self) -> None: self.assertEqual("1.5.2", md.version) self.assertEqual("BSD-3-Clause", md.license) self.assertEqual( - { - "homepage": "http://example.com", - "Bug Tracker": "https://github.com/python-poetry/poetry/issues", - }, + [ + "homepage=http://example.com", + "Bug Tracker=https://github.com/python-poetry/poetry/issues", + ], md.project_urls, ) self.assertEqual(["Not a real classifier"], md.classifiers) diff --git a/setup.cfg b/setup.cfg index 8f00d86..cf0b25f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,11 +34,11 @@ dev = twine==5.1.1 ufmt==2.8.0 usort==1.0.8.post1 - volatile==2.1.0 wheel==0.45.1 honesty==0.3.0b1 test = coverage >= 6 + volatile==2.1.0 [check] metadata = true From 2b08a16c85f1218d0d6459681ad21bc195adefe0 Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Sat, 23 Nov 2024 19:17:32 -0800 Subject: [PATCH 35/44] Bring in setuptools in runtime deps --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index cf0b25f..622ebc9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,6 +23,7 @@ install_requires = LibCST>=0.3.7 tomlkit>=0.2.0 pkginfo>=1.4.2 + setuptools >= 38.3.0 [options.extras_require] dev = From 77a421449b8db787cb420f76347e340379562545 Mon Sep 17 00:00:00 2001 From: Amethyst Reese Date: Mon, 21 Oct 2024 17:24:49 -0700 Subject: [PATCH 36/44] Support PEP 639 style license metadata --- dowsing/pep621.py | 4 +++- dowsing/tests/pep621.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/dowsing/pep621.py b/dowsing/pep621.py index 4a956b2..47697f8 100644 --- a/dowsing/pep621.py +++ b/dowsing/pep621.py @@ -31,7 +31,9 @@ def get_pep621_metadata(self) -> Distribution: ) d.packages_dict = {i: i.replace(".", "/") for i in d.packages} elif k == "license": - if "text" in v: + if isinstance(v, str): + pass # PEP 639 proposes `license = "MIT"` style metadata + elif "text" in v: v = v["text"] elif "file" in v: v = f"file: {v['file']}" diff --git a/dowsing/tests/pep621.py b/dowsing/tests/pep621.py index 99069b9..408f3ae 100644 --- a/dowsing/tests/pep621.py +++ b/dowsing/tests/pep621.py @@ -55,3 +55,19 @@ def test_normal(self) -> None: }, md.asdict(), ) + + def test_pep639(self) -> None: + with volatile.dir() as d: + dp = Path(d) + (dp / "pyproject.toml").write_text( + """\ +[project] +name = "Name" +license = "MIT" +""" + ) + + r = Pep621Reader(dp) + md = r.get_pep621_metadata() + self.assertEqual("Name", md.name) + self.assertEqual("MIT", md.license) From 6927be9a814cc6061838374b438d32ee3d4b3e2b Mon Sep 17 00:00:00 2001 From: John Reese Date: Mon, 7 Feb 2022 19:49:29 -0800 Subject: [PATCH 37/44] More robust evaluation for self-referential names Tracks the `target_name` that we are recursively evaluating, and short-circuits evaluation if we attempt to evaluate that name again. Also allows the assignment evaluation to try multiple assignments until either a real value is found, or all assignments are exhausted. Also prevents combining lhs and rhs of binary addition if either side is the `"??"` sentinel value. Fixes #56 --- dowsing/setuptools/setup_py_parsing.py | 40 +++++++++++++++++++------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/dowsing/setuptools/setup_py_parsing.py b/dowsing/setuptools/setup_py_parsing.py index 2a61bba..00e2f7f 100644 --- a/dowsing/setuptools/setup_py_parsing.py +++ b/dowsing/setuptools/setup_py_parsing.py @@ -178,7 +178,9 @@ def visit_Call(self, node: cst.Call) -> Optional[bool]: BOOL_NAMES = {"True": True, "False": False, "None": None} PRETEND_ARGV = ["setup.py", "bdist_wheel"] - def evaluate_in_scope(self, item: cst.CSTNode, scope: Any) -> Any: + def evaluate_in_scope( + self, item: cst.CSTNode, scope: Any, target_name: str = "" + ) -> Any: qnames = self.get_metadata(QualifiedNameProvider, item) if isinstance(item, cst.SimpleString): @@ -224,13 +226,23 @@ def evaluate_in_scope(self, item: cst.CSTNode, scope: Any) -> Any: # module scope isn't in the dict return "??" - return self.evaluate_in_scope(gp.value, scope) + # we have recursed, likey due to `x = x + y` assignment or similar + # self-referential evaluation + if target_name and target_name == name: + return "??" + + # keep trying assignments until we get something other than ?? + result = self.evaluate_in_scope(gp.value, scope, name) + if result and result != "??": + return result + # give up + return "??" elif isinstance(item, (cst.Tuple, cst.List)): lst = [] for el in item.elements: lst.append( self.evaluate_in_scope( - el.value, self.get_metadata(ScopeProvider, el) + el.value, self.get_metadata(ScopeProvider, el), target_name ) ) if isinstance(item, cst.Tuple): @@ -248,10 +260,10 @@ def evaluate_in_scope(self, item: cst.CSTNode, scope: Any) -> Any: for arg in item.args: if isinstance(arg.keyword, cst.Name): args[names.index(arg.keyword.value)] = self.evaluate_in_scope( - arg.value, scope + arg.value, scope, target_name ) else: - args[i] = self.evaluate_in_scope(arg.value, scope) + args[i] = self.evaluate_in_scope(arg.value, scope, target_name) i += 1 # TODO clear ones that are still default @@ -264,7 +276,9 @@ def evaluate_in_scope(self, item: cst.CSTNode, scope: Any) -> Any: d = {} for arg in item.args: if isinstance(arg.keyword, cst.Name): - d[arg.keyword.value] = self.evaluate_in_scope(arg.value, scope) + d[arg.keyword.value] = self.evaluate_in_scope( + arg.value, scope, target_name + ) # TODO something with **kwargs return d elif isinstance(item, cst.Dict): @@ -272,18 +286,20 @@ def evaluate_in_scope(self, item: cst.CSTNode, scope: Any) -> Any: for el2 in item.elements: if isinstance(el2, cst.DictElement): d[self.evaluate_in_scope(el2.key, scope)] = self.evaluate_in_scope( - el2.value, scope + el2.value, scope, target_name ) return d elif isinstance(item, cst.Subscript): - lhs = self.evaluate_in_scope(item.value, scope) + lhs = self.evaluate_in_scope(item.value, scope, target_name) if isinstance(lhs, str): # A "??" entry, propagate return "??" # TODO: Figure out why this is Sequence if isinstance(item.slice[0].slice, cst.Index): - rhs = self.evaluate_in_scope(item.slice[0].slice.value, scope) + rhs = self.evaluate_in_scope( + item.slice[0].slice.value, scope, target_name + ) try: if isinstance(lhs, dict): return lhs.get(rhs, "??") @@ -296,8 +312,10 @@ def evaluate_in_scope(self, item: cst.CSTNode, scope: Any) -> Any: # LOG.warning(f"Omit2 {type(item.slice[0].slice)!r}") return "??" elif isinstance(item, cst.BinaryOperation): - lhs = self.evaluate_in_scope(item.left, scope) - rhs = self.evaluate_in_scope(item.right, scope) + lhs = self.evaluate_in_scope(item.left, scope, target_name) + rhs = self.evaluate_in_scope(item.right, scope, target_name) + if lhs == "??" or rhs == "??": + return "??" if isinstance(item.operator, cst.Add): try: return lhs + rhs From 9ae3ac377e10d325f419ba82e1f6bce2523eefc7 Mon Sep 17 00:00:00 2001 From: John Reese Date: Tue, 8 Feb 2022 23:26:48 -0800 Subject: [PATCH 38/44] Improved assignment handling with sorting --- dowsing/setuptools/setup_py_parsing.py | 98 ++++++++++++++++++-------- dowsing/tests/setuptools.py | 29 ++++++++ 2 files changed, 97 insertions(+), 30 deletions(-) diff --git a/dowsing/setuptools/setup_py_parsing.py b/dowsing/setuptools/setup_py_parsing.py index 00e2f7f..ebf7b61 100644 --- a/dowsing/setuptools/setup_py_parsing.py +++ b/dowsing/setuptools/setup_py_parsing.py @@ -8,7 +8,12 @@ from typing import Any, Dict, Optional import libcst as cst -from libcst.metadata import ParentNodeProvider, QualifiedNameProvider, ScopeProvider +from libcst.metadata import ( + ParentNodeProvider, + PositionProvider, + QualifiedNameProvider, + ScopeProvider, +) from ..types import Distribution from .setup_and_metadata import SETUP_ARGS @@ -124,7 +129,12 @@ def leave_Call( class SetupCallAnalyzer(cst.CSTVisitor): - METADATA_DEPENDENCIES = (ScopeProvider, ParentNodeProvider, QualifiedNameProvider) + METADATA_DEPENDENCIES = ( + ScopeProvider, + ParentNodeProvider, + QualifiedNameProvider, + PositionProvider, + ) # TODO names resulting from other than 'from setuptools import setup' # TODO wrapper funcs that modify args @@ -179,7 +189,7 @@ def visit_Call(self, node: cst.Call) -> Optional[bool]: PRETEND_ARGV = ["setup.py", "bdist_wheel"] def evaluate_in_scope( - self, item: cst.CSTNode, scope: Any, target_name: str = "" + self, item: cst.CSTNode, scope: Any, target_name: str = "", target_line: int = 0 ) -> Any: qnames = self.get_metadata(QualifiedNameProvider, item) @@ -192,19 +202,30 @@ def evaluate_in_scope( elif isinstance(item, cst.Name): name = item.value assignments = scope[name] - for a in assignments: - # TODO: Only assignments "before" this node matter if in the - # same scope; really if we had a call graph and walked the other - # way, we could have a better idea of what has already happened. - + assignment_nodes = sorted( + ( + (self.get_metadata(PositionProvider, a.node).start.line, a.node) + for a in assignments + if a.node + ), + reverse=True, + ) + # Walk assignments from bottom to top, evaluating them recursively. + # When recursing, only look at assignments above the "target line". + for lineno, node in assignment_nodes: # Assign( # targets=[AssignTarget(target=Name(value="v"))], # value=SimpleString(value="'x'"), # ) # TODO or an import... # TODO builtins have BuiltinAssignment + + # we have recursed, likey due to `x = x + y` assignment or similar + # self-referential evaluation, and can't + if target_name and target_name == name and lineno >= target_line: + continue + try: - node = a.node if node: parent = self.get_metadata(ParentNodeProvider, node) if parent: @@ -214,27 +235,27 @@ def evaluate_in_scope( else: raise KeyError except (KeyError, AttributeError): - return "??" - - # This presumes a single assignment - if not isinstance(gp, cst.Assign) or len(gp.targets) != 1: - return "??" # TooComplicated(repr(gp)) + continue try: scope = self.get_metadata(ScopeProvider, gp) except KeyError: # module scope isn't in the dict - return "??" + continue - # we have recursed, likey due to `x = x + y` assignment or similar - # self-referential evaluation - if target_name and target_name == name: - return "??" + # This presumes a single assignment + if isinstance(gp, cst.Assign) and len(gp.targets) == 1: + result = self.evaluate_in_scope(gp.value, scope, name, lineno) + elif isinstance(parent, cst.AugAssign): + result = self.evaluate_in_scope(parent, scope, name, lineno) + else: + # too complicated? + continue # keep trying assignments until we get something other than ?? - result = self.evaluate_in_scope(gp.value, scope, name) - if result and result != "??": + if result != "??": return result + # give up return "??" elif isinstance(item, (cst.Tuple, cst.List)): @@ -242,7 +263,10 @@ def evaluate_in_scope( for el in item.elements: lst.append( self.evaluate_in_scope( - el.value, self.get_metadata(ScopeProvider, el), target_name + el.value, + self.get_metadata(ScopeProvider, el), + target_name, + target_line, ) ) if isinstance(item, cst.Tuple): @@ -260,10 +284,12 @@ def evaluate_in_scope( for arg in item.args: if isinstance(arg.keyword, cst.Name): args[names.index(arg.keyword.value)] = self.evaluate_in_scope( - arg.value, scope, target_name + arg.value, scope, target_name, target_line ) else: - args[i] = self.evaluate_in_scope(arg.value, scope, target_name) + args[i] = self.evaluate_in_scope( + arg.value, scope, target_name, target_line + ) i += 1 # TODO clear ones that are still default @@ -277,7 +303,7 @@ def evaluate_in_scope( for arg in item.args: if isinstance(arg.keyword, cst.Name): d[arg.keyword.value] = self.evaluate_in_scope( - arg.value, scope, target_name + arg.value, scope, target_name, target_line ) # TODO something with **kwargs return d @@ -286,11 +312,11 @@ def evaluate_in_scope( for el2 in item.elements: if isinstance(el2, cst.DictElement): d[self.evaluate_in_scope(el2.key, scope)] = self.evaluate_in_scope( - el2.value, scope, target_name + el2.value, scope, target_name, target_line ) return d elif isinstance(item, cst.Subscript): - lhs = self.evaluate_in_scope(item.value, scope, target_name) + lhs = self.evaluate_in_scope(item.value, scope, target_name, target_line) if isinstance(lhs, str): # A "??" entry, propagate return "??" @@ -298,7 +324,7 @@ def evaluate_in_scope( # TODO: Figure out why this is Sequence if isinstance(item.slice[0].slice, cst.Index): rhs = self.evaluate_in_scope( - item.slice[0].slice.value, scope, target_name + item.slice[0].slice.value, scope, target_name, target_line ) try: if isinstance(lhs, dict): @@ -312,8 +338,8 @@ def evaluate_in_scope( # LOG.warning(f"Omit2 {type(item.slice[0].slice)!r}") return "??" elif isinstance(item, cst.BinaryOperation): - lhs = self.evaluate_in_scope(item.left, scope, target_name) - rhs = self.evaluate_in_scope(item.right, scope, target_name) + lhs = self.evaluate_in_scope(item.left, scope, target_name, target_line) + rhs = self.evaluate_in_scope(item.right, scope, target_name, target_line) if lhs == "??" or rhs == "??": return "??" if isinstance(item.operator, cst.Add): @@ -323,6 +349,18 @@ def evaluate_in_scope( return "??" else: return "??" + elif isinstance(item, cst.AugAssign): + lhs = self.evaluate_in_scope(item.target, scope, target_name, target_line) + rhs = self.evaluate_in_scope(item.value, scope, target_name, target_line) + if lhs == "??" or rhs == "??": + return "??" + if isinstance(item.operator, cst.AddAssign): + try: + return lhs + rhs + except Exception: + return "??" + else: + return "??" else: # LOG.warning(f"Omit1 {type(item)!r}") return "??" diff --git a/dowsing/tests/setuptools.py b/dowsing/tests/setuptools.py index 0b13207..320c5cc 100644 --- a/dowsing/tests/setuptools.py +++ b/dowsing/tests/setuptools.py @@ -344,3 +344,32 @@ def test_add_items(self) -> None: self.assertEqual(d.name, "aaaa1111") self.assertEqual(d.packages, ["a", "b", "c"]) self.assertEqual(d.classifiers, "??") + + def test_self_reference_assignments(self) -> None: + d = self._read( + """\ +from setuptools import setup + +version = "base" +name = "foo" +name += "bar" +version = version + ".suffix" + +classifiers = [ + "123", + "abc", +] + +if True: + classifiers = classifiers + ["xyz"] + +setup( + name=name, + version=version, + classifiers=classifiers, +) + """ + ) + self.assertEqual(d.name, "foobar") + self.assertEqual(d.version, "base.suffix") + self.assertListEqual(d.classifiers, ["123", "abc", "xyz"]) From 1a80f10487a3f8ffec93cb060d756ea63d1df0c0 Mon Sep 17 00:00:00 2001 From: John Reese Date: Tue, 8 Feb 2022 23:53:58 -0800 Subject: [PATCH 39/44] Don't track names, just line numbers --- dowsing/setuptools/setup_py_parsing.py | 47 ++++++++++++++------------ dowsing/tests/setuptools.py | 20 +++++++++++ 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/dowsing/setuptools/setup_py_parsing.py b/dowsing/setuptools/setup_py_parsing.py index ebf7b61..8774e6d 100644 --- a/dowsing/setuptools/setup_py_parsing.py +++ b/dowsing/setuptools/setup_py_parsing.py @@ -189,7 +189,7 @@ def visit_Call(self, node: cst.Call) -> Optional[bool]: PRETEND_ARGV = ["setup.py", "bdist_wheel"] def evaluate_in_scope( - self, item: cst.CSTNode, scope: Any, target_name: str = "", target_line: int = 0 + self, item: cst.CSTNode, scope: Any, target_line: int = 0 ) -> Any: qnames = self.get_metadata(QualifiedNameProvider, item) @@ -211,20 +211,26 @@ def evaluate_in_scope( reverse=True, ) # Walk assignments from bottom to top, evaluating them recursively. - # When recursing, only look at assignments above the "target line". for lineno, node in assignment_nodes: + + # When recursing, only look at assignments above the "target line". + if target_line and lineno >= target_line: + continue + # Assign( # targets=[AssignTarget(target=Name(value="v"))], # value=SimpleString(value="'x'"), # ) + # + # AugAssign( + # target=Name(value="v"), + # operator=AddAssign(...), + # value=SimpleString(value="'x'"), + # ) + # # TODO or an import... # TODO builtins have BuiltinAssignment - # we have recursed, likey due to `x = x + y` assignment or similar - # self-referential evaluation, and can't - if target_name and target_name == name and lineno >= target_line: - continue - try: if node: parent = self.get_metadata(ParentNodeProvider, node) @@ -245,9 +251,9 @@ def evaluate_in_scope( # This presumes a single assignment if isinstance(gp, cst.Assign) and len(gp.targets) == 1: - result = self.evaluate_in_scope(gp.value, scope, name, lineno) + result = self.evaluate_in_scope(gp.value, scope, lineno) elif isinstance(parent, cst.AugAssign): - result = self.evaluate_in_scope(parent, scope, name, lineno) + result = self.evaluate_in_scope(parent, scope, lineno) else: # too complicated? continue @@ -265,7 +271,6 @@ def evaluate_in_scope( self.evaluate_in_scope( el.value, self.get_metadata(ScopeProvider, el), - target_name, target_line, ) ) @@ -284,12 +289,10 @@ def evaluate_in_scope( for arg in item.args: if isinstance(arg.keyword, cst.Name): args[names.index(arg.keyword.value)] = self.evaluate_in_scope( - arg.value, scope, target_name, target_line + arg.value, scope, target_line ) else: - args[i] = self.evaluate_in_scope( - arg.value, scope, target_name, target_line - ) + args[i] = self.evaluate_in_scope(arg.value, scope, target_line) i += 1 # TODO clear ones that are still default @@ -303,7 +306,7 @@ def evaluate_in_scope( for arg in item.args: if isinstance(arg.keyword, cst.Name): d[arg.keyword.value] = self.evaluate_in_scope( - arg.value, scope, target_name, target_line + arg.value, scope, target_line ) # TODO something with **kwargs return d @@ -312,11 +315,11 @@ def evaluate_in_scope( for el2 in item.elements: if isinstance(el2, cst.DictElement): d[self.evaluate_in_scope(el2.key, scope)] = self.evaluate_in_scope( - el2.value, scope, target_name, target_line + el2.value, scope, target_line ) return d elif isinstance(item, cst.Subscript): - lhs = self.evaluate_in_scope(item.value, scope, target_name, target_line) + lhs = self.evaluate_in_scope(item.value, scope, target_line) if isinstance(lhs, str): # A "??" entry, propagate return "??" @@ -324,7 +327,7 @@ def evaluate_in_scope( # TODO: Figure out why this is Sequence if isinstance(item.slice[0].slice, cst.Index): rhs = self.evaluate_in_scope( - item.slice[0].slice.value, scope, target_name, target_line + item.slice[0].slice.value, scope, target_line ) try: if isinstance(lhs, dict): @@ -338,8 +341,8 @@ def evaluate_in_scope( # LOG.warning(f"Omit2 {type(item.slice[0].slice)!r}") return "??" elif isinstance(item, cst.BinaryOperation): - lhs = self.evaluate_in_scope(item.left, scope, target_name, target_line) - rhs = self.evaluate_in_scope(item.right, scope, target_name, target_line) + lhs = self.evaluate_in_scope(item.left, scope, target_line) + rhs = self.evaluate_in_scope(item.right, scope, target_line) if lhs == "??" or rhs == "??": return "??" if isinstance(item.operator, cst.Add): @@ -350,8 +353,8 @@ def evaluate_in_scope( else: return "??" elif isinstance(item, cst.AugAssign): - lhs = self.evaluate_in_scope(item.target, scope, target_name, target_line) - rhs = self.evaluate_in_scope(item.value, scope, target_name, target_line) + lhs = self.evaluate_in_scope(item.target, scope, target_line) + rhs = self.evaluate_in_scope(item.value, scope, target_line) if lhs == "??" or rhs == "??": return "??" if isinstance(item.operator, cst.AddAssign): diff --git a/dowsing/tests/setuptools.py b/dowsing/tests/setuptools.py index 320c5cc..5e99c7b 100644 --- a/dowsing/tests/setuptools.py +++ b/dowsing/tests/setuptools.py @@ -373,3 +373,23 @@ def test_self_reference_assignments(self) -> None: self.assertEqual(d.name, "foobar") self.assertEqual(d.version, "base.suffix") self.assertListEqual(d.classifiers, ["123", "abc", "xyz"]) + + def test_circular_references(self) -> None: + d = self._read( + """\ +from setuptools import setup + +name = "foo" + +foo = bar +bar = version +version = foo + +setup( + name=name, + version=version, +) + """ + ) + self.assertEqual(d.name, "foo") + self.assertEqual(d.version, "??") From 1b7b694e88fd483914acf20ba8273b56840e15ef Mon Sep 17 00:00:00 2001 From: John Reese Date: Tue, 8 Feb 2022 23:59:50 -0800 Subject: [PATCH 40/44] x=x test case --- dowsing/tests/setuptools.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dowsing/tests/setuptools.py b/dowsing/tests/setuptools.py index 5e99c7b..3bf1119 100644 --- a/dowsing/tests/setuptools.py +++ b/dowsing/tests/setuptools.py @@ -385,6 +385,8 @@ def test_circular_references(self) -> None: bar = version version = foo +classifiers = classifiers + setup( name=name, version=version, @@ -393,3 +395,4 @@ def test_circular_references(self) -> None: ) self.assertEqual(d.name, "foo") self.assertEqual(d.version, "??") + self.assertEqual(d.classifiers, ()) From e42a849b0602768408b52474d0af6525506a4f8f Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Sat, 23 Nov 2024 19:31:30 -0800 Subject: [PATCH 41/44] Add test confirming builtin redefinition is ok (see pr comment) --- dowsing/tests/setuptools.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/dowsing/tests/setuptools.py b/dowsing/tests/setuptools.py index 3bf1119..5ba2a8c 100644 --- a/dowsing/tests/setuptools.py +++ b/dowsing/tests/setuptools.py @@ -396,3 +396,24 @@ def test_circular_references(self) -> None: self.assertEqual(d.name, "foo") self.assertEqual(d.version, "??") self.assertEqual(d.classifiers, ()) + + def test_redefines_builtin(self) -> None: + d = self._read( + """\ +import setuptools +with open("CREDITS.txt", "r", encoding="utf-8") as fp: + credits = fp.read() + +long_desc = "a" + credits + "b" +name = "foo" + +kwargs = dict( + long_description = long_desc, + name = name, +) + +setuptools.setup(**kwargs) +""" + ) + self.assertEqual(d.name, "foo") + self.assertEqual(d.description, "??") From 10a837a691e4093ad0160fd99a3571ce1aac64d9 Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Sat, 23 Nov 2024 19:37:48 -0800 Subject: [PATCH 42/44] Fix typing glitch --- dowsing/tests/setuptools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dowsing/tests/setuptools.py b/dowsing/tests/setuptools.py index 5ba2a8c..88840a7 100644 --- a/dowsing/tests/setuptools.py +++ b/dowsing/tests/setuptools.py @@ -372,7 +372,7 @@ def test_self_reference_assignments(self) -> None: ) self.assertEqual(d.name, "foobar") self.assertEqual(d.version, "base.suffix") - self.assertListEqual(d.classifiers, ["123", "abc", "xyz"]) + self.assertSequenceEqual(d.classifiers, ["123", "abc", "xyz"]) def test_circular_references(self) -> None: d = self._read( From be7dee696fcecb7778820974e801f3345e9115c7 Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Sat, 23 Nov 2024 19:36:10 -0800 Subject: [PATCH 43/44] Update tox config --- setup.cfg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index 622ebc9..db662a1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -69,15 +69,15 @@ python_version = 3.8 strict = True [tox:tox] -envlist = py36, py37, py38 +envlist = py{38,39,310,311,312,313}-tests [testenv] -deps = -rrequirements-dev.txt -whitelist_externals = make +deps = .[test] +allowlist_externals = make commands = make test setenv = - py{36,37,38}: COVERAGE_FILE={envdir}/.coverage + tests: COVERAGE_FILE={envdir}/.coverage [flake8] ignore = E203, E231, E266, E302, E501, W503 From 71a65988f29cb143c399f986ae56f4195fb9e860 Mon Sep 17 00:00:00 2001 From: Tim Hatch Date: Sat, 23 Nov 2024 19:47:56 -0800 Subject: [PATCH 44/44] Update changelog to get ready for release --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c566f50..8ff4ed4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## v0.9.0b3 + +* Support PEP 639 style metadata (#76) +* Support more `setup.py` assignments (#57) +* 3.12 compat (depends on setuptools) +* Fix tests to work on modern Python + ## v0.9.0b2 * `source_mapping` bugfixes