Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
# Left out (#1968): conda/requirements.txt, which Conda Update's `Refresh the
# platform pins` and `Setup Conda Distribution` steps write, and the benchmark's
# image digests, which its Dockerfile takes from examples/benchmark/python-images.txt.
# The pip entry's `exclude-paths` keeps conda/requirements.txt out: the `/` job
# otherwise reads requirements files one directory down, conda/ included.

version: 2
updates:
Expand All @@ -15,6 +17,8 @@ updates:
- "/"
- "/docs" # Pins Sphinx and sphinx-autodoc-typehints deliberately, bumps included
- "/examples/benchmark" # Nothing else updates the benchmark's pins
exclude-paths: # Matched against the path from the repository root
- "conda/requirements.txt"
schedule:
interval: "weekly"

Expand Down
6 changes: 3 additions & 3 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ prune typings
# ``adding license file 'LICENSE'`` in the build log, twice, in a build with this
# line deleted.
#
# ``setup.py`` does read ``README.md`` unguarded for the ``long_description``
# (``setup.py:31-36``), and it reads it from wherever ``setup.py`` is executing --
# which during ``pip install`` of an sdist is the unpacked sdist, not a checkout.
# ``pyproject.toml``'s ``readme`` does read ``README.md`` unguarded for the long
# description, and it reads it from wherever the build is running -- which
# during ``pip install`` of an sdist is the unpacked sdist, not a checkout.
# So the failure mode is real in principle; it just cannot be reached by removing
# this line, because the README is in the sdist regardless.
#
Expand Down
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ build-backend = "setuptools.build_meta"

[project]
name = "pypcapkit"
dynamic = [ "version", "readme" ]
# ``readme`` is static, not supplied by ``setup.py`` (#1968): Dependabot replaces
# ``setup.py`` with a stub that passes no long description, so a dynamic one fails
# to build the Pipfile's editable ``pypcapkit`` and every pip update job with it.
dynamic = [ "version" ]
authors = [
{ name="Jarry Shaw", email="jarryshaw@icloud.com" },
]
Expand Down Expand Up @@ -58,6 +61,7 @@ license = { text="BSD 3-Clause License" }
# besides. The ``PCAP_CT`` extra needs 3.10 regardless.
requires-python = ">=3.6, <4"
description = "PyPCAPKit: comprehensive network packet analysis library"
readme = { file = "README.md", content-type = "text/markdown" }
keywords = [ "network", "pcap", "packet" ]
classifiers = [
"Development Status :: 5 - Production/Stable",
Expand Down
15 changes: 2 additions & 13 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,6 @@
logger.addHandler(handler)


def get_long_description() -> "str":
"""Extract description from README.md, for PyPI's usage."""
readme = os.path.join(os.path.dirname(os.path.abspath(__file__)), "README.md")
with open(readme, encoding="utf-8") as file:
long_description = file.read()
return long_description


def refactor(path: "str") -> "None":
"""Refactor code."""
import subprocess # nosec: B404
Expand Down Expand Up @@ -160,8 +152,5 @@ def run(self) -> "None":
# above, so the tree it archives is already converted before the wheel is
# written, and ``dist_dir`` holds only the finished wheels (#1773).

setup(
cmdclass=cmdclass,
long_description=get_long_description(),
long_description_content_type="text/markdown",
)
# The long description is ``pyproject.toml``'s static ``readme`` (#1968).
setup(cmdclass=cmdclass)
42 changes: 41 additions & 1 deletion tests/project/test_actions_pinned_1968.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@
* :class:`TestDependabotCoversEveryManifest` checks the owner's ruling on #1968:
``pip`` over the root, :file:`docs` and :file:`examples/benchmark`, and
neither :file:`conda` nor a ``docker`` entry, as Dependabot cannot update
either.
either; and the root job's ``exclude-paths`` skips
:file:`conda/requirements.txt`, which it would otherwise read.
* :class:`TestDependabotCanBuildTheEditable` checks :file:`pyproject.toml`
declares its readme statically, as Dependabot's stub :file:`setup.py` supplies
none.
* :class:`TestPyUpOpensNoUpdates` checks :file:`.pyup.yml` turns PyUp's update
pull requests off, as Dependabot opens them.

Expand All @@ -31,6 +35,11 @@
import unittest
from typing import TYPE_CHECKING

try:
import tomllib
except ImportError: # pragma: no cover
tomllib = None # type: ignore[assignment]

from tests import _workflows
from tests._tiers import ROOT

Expand Down Expand Up @@ -168,6 +177,16 @@ def test_conda_is_not_watched(self) -> None:
with self.subTest(ecosystem=ecosystem):
self.assertNotIn('/conda', directories(entry))

def test_the_root_job_skips_the_conda_requirements(self) -> None:
"""The ``/`` job reads requirements files one directory down, :file:`conda` included.

dependabot-core's ``SharedFileFetcher.req_files_for_dir`` fetches them, and
``FileFetchers::Base.filter_excluded`` drops an entry whose path from the
repository root matches ``exclude-paths``; without it the job checked
:file:`conda/requirements.txt`'s cryptography pin.
"""
self.assertIn('conda/requirements.txt', updates()['pip'].get('exclude-paths') or [])

def test_there_is_no_docker_entry(self) -> None:
"""Dependabot reads a ``FROM`` that names its image; the benchmark's is an ``ARG``.

Expand All @@ -183,6 +202,27 @@ def test_every_entry_is_weekly_and_names_one_kind_of_directory(self) -> None:
self.assertEqual(len({'directory', 'directories'} & set(entry)), 1)


class TestDependabotCanBuildTheEditable(unittest.TestCase):
"""The Pipfile's editable ``pypcapkit`` builds under Dependabot's stub :file:`setup.py`.

Dependabot's ``SetupFileSanitizer`` replaces :file:`setup.py` with
``setup(name=..., version="0.0.1", ...)``, which passes no long
description. A ``readme`` left in ``dynamic`` then fails the build, and every
root pip update with it.
"""

def setUp(self) -> None:
if tomllib is None: # pragma: no cover
self.skipTest('tomllib needs Python 3.11')
self.project = tomllib.loads((ROOT / 'pyproject.toml').read_text(encoding='utf-8'))['project']

def test_the_readme_is_not_dynamic(self) -> None:
self.assertNotIn('readme', self.project.get('dynamic', []))

def test_the_readme_is_the_markdown_file(self) -> None:
self.assertEqual(self.project['readme'], {'file': 'README.md', 'content-type': 'text/markdown'})


class TestPyUpOpensNoUpdates(unittest.TestCase):
"""PyUp's update pull requests are off, and nothing in its file turns one back on."""

Expand Down
22 changes: 13 additions & 9 deletions tests/project/test_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,19 @@ def fake_setup(*args, **kwargs):
spec.loader.exec_module(module)
return module, captured, loaded

def test_get_long_description_reads_readme(self) -> None:
module, _, loaded = self._load_setup_module()
try:
description = module.get_long_description()
finally:
for name in loaded + ['project_setup']:
sys.modules.pop(name, None)

self.assertIn('PyPCAPKit', description)
def test_setup_passes_no_long_description(self) -> None:
"""``pyproject.toml``'s static ``readme`` is the long description (#1968).

Dependabot replaces this file with a stub that passes none, so a long
description supplied here leaves ``readme`` dynamic and the stub's build
failing.
"""
_, captured, loaded = self._load_setup_module()
for name in loaded + ['project_setup']:
sys.modules.pop(name, None)

self.assertNotIn('long_description', captured['kwargs'])
self.assertNotIn('long_description_content_type', captured['kwargs'])

def test_refactor_runs_no_backports_on_modern_python(self) -> None:
module, _, loaded = self._load_setup_module()
Expand Down
Loading