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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ This resolves [#548](https://github.com/JarryShaw/PyPCAPKit/issues/548), which r
#### Changed

- **a breaking change to** `requires-python`: it is `>=3.8`, so pip refuses an older interpreter at install, as "requires a different Python". The sources use 3.8 syntax, and the `bpc-*` backport tools that converted it for 3.6 and 3.7 are no longer maintained, so the conversion goes: the `bpc-*` requirements, `refactor()` and the `setup.py` build commands that ran it. The 3.7 KTLO (*keep the lights on*) legs go with it, and `coverage-comment.yml` lists no install failure as by design any more, which closes [#2040](https://github.com/JarryShaw/PyPCAPKit/issues/2040) ([#2051](https://github.com/JarryShaw/PyPCAPKit/issues/2051), [#2053](https://github.com/JarryShaw/PyPCAPKit/pull/2053)).
- `setup.py` is gone, and `pyproject.toml` is the only build configuration. With the 3.8 floor it was a Python 2 guard and a bare `setup()`: the wheel's files are identical without it, `pip install -e .` goes through PEP 660, pip still refuses 3.7 as "requires a different Python", and on 2.7 it fails on the `setuptools` build requirement before it would reach `setup.py`. `examples/benchmark/Dockerfile` stops copying it, and a test fails should `setup.py` return or a `COPY` there name a file the repository does not track. Dependabot now fetches `pyproject.toml` in `setup.py`'s place and passes it through unstubbed; the static `readme` stays, as it does not depend on what a stub would supply ([#2061](https://github.com/JarryShaw/PyPCAPKit/issues/2061), [#2066](https://github.com/JarryShaw/PyPCAPKit/pull/2066)).
- renames with no compatibility alias left behind: `examples/sample` and `examples/samples` -- one letter apart, holding different things -- are now `examples/captures` and `examples/generators`.
- the README is a landing page now, and Markdown rather than reStructuredText. `README.rst` became a much shorter `README.md`, keeping what a reader arriving from PyPI or a search result needs (what the library is, why it exists rather than Scapy or DPKT, how to install it, a worked example, where the documentation lives). **Module Structure**, **Engine Comparison**, **Engine support by Python version**, **Test Environment**, **Test Results** and **Installation Notes** were already duplicated in fuller form in `docs/source/index.rst`, so they are linked rather than restated; the blocks that existed nowhere else moved: **Testing** is now `docs/source/contributing/testing.rst`, and the `pipenv` and `make setup` block joined the Installation section of `docs/source/index.rst`. Requested by the project owner, and a deliberate exception to the reStructuredText convention, for the README only, since its renderers are GitHub and PyPI rather than Sphinx. `setup.py` reads `README.md` and declares `text/markdown`, and `MANIFEST.in` gains `include README.md`, which is belt-and-braces rather than load-bearing: setuptools' `sdist` ships a `README.md` regardless. Of the `include` lines only `CHANGELOG.md` is load-bearing, measured in the [#631](https://github.com/JarryShaw/PyPCAPKit/pull/631) entry below. `examples/benchmark/Dockerfile` copies `README.md`, while the `README.md` that `examples/benchmark/benchmark.py` names is the benchmark suite's own ([#619](https://github.com/JarryShaw/PyPCAPKit/pull/619)).
- `CODE_OF_CONDUCT.md` moves from Contributor Covenant 1.4 to 3.0, at the owner's request. The text is the canonical 3.0 Markdown fetched from https://www.contributor-covenant.org/version/3/0/code_of_conduct/code_of_conduct.md rather than transcribed. Some things needed deciding. 3.0's `[NOTE` placeholders are filled or removed: the reporting channel names `jarryshaw@icloud.com` (the contact 1.4 carried and `SECURITY.md` uses as email fallback) plus GitHub's report-abuse form for a report about the maintainer, which a single-maintainer project cannot otherwise cover; the enforcement placeholder is an instruction to the adopter and is removed. 3.0's plural "Community Moderators" become the singular maintainer, this repository having no moderators. The enforcement ladder (Warning, Temporarily Limited Activities, Temporary Suspension, Permanent Ban) is a suggestion and is **kept**, each rung mapping to a lever one person holds on GitHub. 3.0 is CC BY-SA 4.0 where 1.4 carried no licence notice, so the attribution names version 3.0, links the permanent `version/3/0/` URL, carries the CC BY-SA 4.0 notice, indicates changes were made as BY requires, and says the share-alike term covers this document only (the code remains BSD-3-Clause, `LICENSE` untouched). The ladder renders as nested list items, checked against GitHub's Markdown API; [#613](https://github.com/JarryShaw/PyPCAPKit/pull/613) had to repair the 1.4 file when a stray list marker collapsed it into one nested item ([#624](https://github.com/JarryShaw/PyPCAPKit/pull/624)).
Expand Down
10 changes: 10 additions & 0 deletions docs/source/changelog/1.5.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2963,6 +2963,16 @@ Changed
the lights on*) legs go with it, and ``coverage-comment.yml`` lists no install
failure as by design any more, which closes :issue:`2040` (:issue:`2051`,
:pr:`2053`).
* ``setup.py`` is gone, and ``pyproject.toml`` is the only build configuration. With
the 3.8 floor it was a Python 2 guard and a bare ``setup()``: the wheel's files are
identical without it, ``pip install -e .`` goes through PEP 660, pip still refuses
3.7 as "requires a different Python", and on 2.7 it fails on the ``setuptools``
build requirement before it would reach ``setup.py``.
``examples/benchmark/Dockerfile`` stops copying it, and a test fails should
``setup.py`` return or a ``COPY`` there name a file the repository does not track.
Dependabot now fetches ``pyproject.toml`` in ``setup.py``'s place and passes it
through unstubbed; the static ``readme`` stays, as it does not depend on what a stub
would supply (:issue:`2061`, :pr:`2066`).
* renames with no compatibility alias left behind: ``examples/sample`` and
``examples/samples`` -- one letter apart, holding different things -- are now
``examples/captures`` and ``examples/generators``.
Expand Down
2 changes: 1 addition & 1 deletion examples/benchmark/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ RUN set -eux; \
rm -f /tmp/pypcap-install.log

# `pcapkit` last, because it is the thing under test and changes on every commit.
COPY pyproject.toml setup.py MANIFEST.in README.md ./
COPY pyproject.toml MANIFEST.in README.md ./
COPY pcapkit ./pcapkit

# --no-deps so the pinned requirements above stay authoritative: `pcapkit`
Expand Down
8 changes: 5 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ build-backend = "setuptools.build_meta"

[project]
name = "pypcapkit"
# ``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.
# ``readme`` is static (#1968). There is no ``setup.py`` (#2061), so
# ``fetch_project_file`` in Dependabot's Python file fetcher fetches
# ``pyproject.toml`` in its place, and its Pipfile updater writes that file as is:
# only a ``setup.py`` is stubbed. A static ``readme`` keeps the Pipfile's editable
# ``pypcapkit`` from depending on what a stub would supply.
dynamic = [ "version" ]
authors = [
{ name="Jarry Shaw", email="jarryshaw@icloud.com" },
Expand Down
21 changes: 0 additions & 21 deletions setup.py

This file was deleted.

2 changes: 1 addition & 1 deletion tests/_tiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ def is_unit_tier(path: 'pathlib.Path | str') -> 'bool':
_IMPORT_TEXT = re.compile(rf'(?:\b(?:import|from)|-m)\s+{PROJECT_PIN_PACKAGE}(?:\.\w+)*(?![\w/.])')
#: A string that is wholly a relative path, e.g. ``util/ci_timing.py`` or ``tests/cli``.
_PATH_TEXT = re.compile(r'[\w.-]+(?:/[\w.-]+)*/?\Z')
#: A string that is wholly a dotted module name, e.g. ``tests.project.test_setup``.
#: A string that is wholly a dotted module name, e.g. ``tests.project.test_no_setup_py_2061``.
_DOTTED_NAME = re.compile(r'[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)+\Z')

#: What a module asks of the running interpreter when it branches on it:
Expand Down
15 changes: 8 additions & 7 deletions tests/project/test_actions_pinned_1968.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
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.
declares its readme statically, as Dependabot now fetches that file in place
of :file:`setup.py` and passes it through unstubbed.
* :class:`TestPyUpOpensNoUpdates` checks :file:`.pyup.yml` turns PyUp's update
pull requests off, as Dependabot opens them.

Expand Down Expand Up @@ -203,12 +203,13 @@ def test_every_entry_is_weekly_and_names_one_kind_of_directory(self) -> None:


class TestDependabotCanBuildTheEditable(unittest.TestCase):
"""The Pipfile's editable ``pypcapkit`` builds under Dependabot's stub :file:`setup.py`.
"""The Pipfile's editable ``pypcapkit`` reads its readme from :file:`pyproject.toml`.

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.
There is no :file:`setup.py` (#2061), so ``fetch_project_file`` in
Dependabot's Python file fetcher fetches :file:`pyproject.toml` in its place,
and the Pipfile updater writes it unstubbed: ``SetupFileSanitizer`` runs only
on a :file:`setup.py`. A static ``readme`` does not depend on what a stub
would supply.
"""

def setUp(self) -> None:
Expand Down
116 changes: 116 additions & 0 deletions tests/project/test_no_setup_py_2061.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
"""#2061: there is no :file:`setup.py`, and the benchmark image copies only files that exist.

``pyproject.toml`` declares everything, and the 3.8 floor (#2051) left
:file:`setup.py` a Python 2 guard and a bare ``setup()``, so it went.

* :class:`TestNoSetupPy` fails if one comes back at the repository root.
* :class:`TestBenchmarkDockerfileCopies` checks every source a ``COPY`` in
:file:`examples/benchmark/Dockerfile` names is in the build context, the
repository root that ``run.sh`` passes. Its ``COPY`` of the packaging files
named :file:`setup.py` too, and a missing source fails only at ``docker
build``, which no CI job runs.

"""

from __future__ import annotations

import pathlib
import shlex
import shutil
import subprocess # nosec: B404
import unittest

#: Test tier (#1958): reads the repository's files and asks git which it tracks.
__tier__ = 'LATEST'

ROOT = pathlib.Path(__file__).resolve().parents[2]
DOCKERFILE = ROOT / 'examples' / 'benchmark' / 'Dockerfile'


def instructions(text: 'str') -> 'list[str]':
"""The Dockerfile's instructions, each with its continuation lines joined."""
result = [] # type: list[str]
pending = ''
for line in text.splitlines():
stripped = line.strip()
if not pending and (not stripped or stripped.startswith('#')):
continue
if pending and stripped.startswith('#'): # a comment inside a continuation
continue
if stripped.endswith('\\'):
pending += stripped[:-1] + ' '
continue
result.append(pending + stripped)
pending = ''
if pending:
result.append(pending)
return result


def copy_sources(text: 'str') -> 'list[str]':
"""Every source path a build-context ``COPY`` names, in order.

A ``COPY --from=...`` reads another stage or image, not the context, so it
is left out; so are flags and the destination.

"""
sources = [] # type: list[str]
for instruction in instructions(text):
keyword, _, rest = instruction.partition(' ')
if keyword.upper() != 'COPY':
continue
words = shlex.split(rest)
if any(word.startswith('--from=') for word in words):
continue
operands = [word for word in words if not word.startswith('--')]
sources.extend(operands[:-1])
return sources


def tracked() -> 'set[str] | None':
"""Every path git tracks, or :data:`None` when this is not a checkout."""
if shutil.which('git') is None or not (ROOT / '.git').exists():
return None
result = subprocess.run(['git', 'ls-files', '-z'], cwd=ROOT, # nosec
capture_output=True, check=True)
return {name for name in result.stdout.decode('utf-8').split('\0') if name}


class TestNoSetupPy(unittest.TestCase):
"""``pyproject.toml`` is the only build configuration."""

def test_there_is_no_setup_py(self) -> None:
self.assertFalse((ROOT / 'setup.py').exists(),
'setup.py is back; pyproject.toml declares everything (#2061)')


class TestBenchmarkDockerfileCopies(unittest.TestCase):
"""Every ``COPY`` source in the benchmark image is in the build context."""

def setUp(self) -> None:
if not DOCKERFILE.is_file(): # pragma: no cover - an sdist does not ship it
self.skipTest('examples/benchmark/Dockerfile is not in this tree')
self.sources = copy_sources(DOCKERFILE.read_text(encoding='utf-8'))

def test_the_parser_finds_the_packaging_copy(self) -> None:
"""A parser that found nothing would pass the check below vacuously."""
for name in ('pyproject.toml', 'MANIFEST.in', 'README.md', 'pcapkit'):
with self.subTest(name=name):
self.assertIn(name, self.sources)

def test_every_source_exists(self) -> None:
"""Tracked, on a checkout: ``docker build`` runs from a fresh clone too."""
files = tracked()
for source in self.sources:
with self.subTest(source=source):
if files is None:
self.assertTrue((ROOT / source).exists())
else:
prefix = source.rstrip('/') + '/'
self.assertTrue(source in files or any(name.startswith(prefix) for name in files),
f'{source} is not tracked, so a COPY of it fails the build')


if __name__ == '__main__':
unittest.main()
57 changes: 0 additions & 57 deletions tests/project/test_setup.py

This file was deleted.

2 changes: 1 addition & 1 deletion util/classify_changes.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
A file is docs iff :func:`is_docs` holds: it is under ``docs/`` or ends in
``.md``, and is not under one of :data:`CODE_DIRS` -- so a Markdown file in one
of those (a PR template, a test fixture) is code, and so is every other file
(``pyproject.toml``, ``setup.py``, ...). The diff is ``HEAD^1..HEAD``, a pull
(``pyproject.toml``, ``Makefile``, ...). The diff is ``HEAD^1..HEAD``, a pull
request's merge commit against its first parent, which is exactly what merging
would add; ``--no-renames`` lists both sides of a rename, so moving a code file
under ``docs/`` is still a code change.
Expand Down
Loading