build: consolidate packaging on pyproject.toml and correct the dependency set
The project carried three sets of packaging metadata — pyproject.toml [project], setup.cfg [options] and setup.py kwargs — declaring different dependencies. pyproject.toml wins under any modern backend, so the wheel was correct, but the other two said "Pillow, numpy" and reading either gave the wrong answer about what the library needs. Reduce setup.cfg to its [flake8] section and setup.py to a setup() shim, each pointing at pyproject.toml. Correct the authoritative list while consolidating: - flask was a runtime dependency but is imported only by the fixture HTTP server in tests. Every user was installing Flask, Jinja2, Werkzeug, click, itsdangerous and blinker for nothing. - ebooklib was a runtime dependency and is never imported by the library; epub_reader.py uses zipfile + xml.etree directly. Only the tests use it, to build EPUB fixtures. - requests was declared required but concrete/image.py imports it lazily and degrades gracefully when absent, so it belongs in an extra. - requires-python said >=3.6, which cannot be true: the package uses dataclasses and `from __future__ import annotations`, both 3.7+, and CI tests 3.10/3.12/3.13. Runtime install drops from 7 direct dependencies to 4. Adds test, remote-images and dev extras, and a CI step that installs into an empty venv with only the runtime deps and imports every subpackage — this class of defect is only caught by installing what you ship. Verified: runtime-only install imports all subpackages; `.[test]` runs the full suite, 853 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+20
-8
@@ -24,17 +24,29 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Verify declared dependencies are sufficient
|
||||
run: |
|
||||
# A clean venv with ONLY the declared runtime deps. If an import here
|
||||
# fails, install_requires is incomplete and a real `pip install
|
||||
# pyWebLayout` would fail the same way for a user.
|
||||
python -m venv /tmp/clean-install
|
||||
/tmp/clean-install/bin/pip install --upgrade pip
|
||||
/tmp/clean-install/bin/pip install .
|
||||
/tmp/clean-install/bin/python -c "
|
||||
import pyWebLayout.concrete, pyWebLayout.abstract
|
||||
import pyWebLayout.io.readers.epub_reader
|
||||
import pyWebLayout.io.readers.html_extraction
|
||||
import pyWebLayout.layout.ereader_manager
|
||||
print('clean install imports OK')
|
||||
"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
# Install package in development mode
|
||||
pip install -e .
|
||||
# Install test dependencies if they exist
|
||||
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
|
||||
if [ -f requirements/test.txt ]; then pip install -r requirements/test.txt; fi
|
||||
# Install common test packages
|
||||
pip install pytest pytest-cov flake8 coverage-badge interrogate
|
||||
|
||||
# Install package in development mode, with the declared dev extra.
|
||||
# Test dependencies belong in setup.cfg, not in an ad-hoc pip line.
|
||||
pip install -e '.[dev]'
|
||||
|
||||
- name: Download initial failed badges
|
||||
run: |
|
||||
echo "Downloading initial failed badges..."
|
||||
|
||||
+45
-25
@@ -18,7 +18,7 @@ finding is already specced, it is cross-referenced rather than restated.
|
||||
| [R1](#r1--the-process-pool-crashes-on-python-314) | The process pool crashes on Python 3.14 | Critical | New; raises priority of S12 |
|
||||
| [R2](#r2--the-test-suite-hangs-at-interpreter-exit) | Test suite hangs at interpreter exit | High | New; same root cause as R1 |
|
||||
| [R3](#r3--font-scaling-destroys-hyperlinks) | Font scaling destroys hyperlinks | High | New |
|
||||
| [R4](#r4--declared-dependencies-are-incomplete) | Declared dependencies are incomplete | High | New |
|
||||
| [R4](#r4--three-packaging-configs-that-disagree) | Three packaging configs that disagree | Medium | New (corrected) |
|
||||
| [R5](#r5--monkey-patched-page-methods-with-a-conflicting-signature) | Monkey-patched `Page` methods with a conflicting signature | Medium | New |
|
||||
| [R6](#r6--dead-duck-typing-cluster-in-pagepy) | Dead duck-typing cluster in `page.py` | Low | New; extends S10.3 |
|
||||
| [R7](#r7--two-orphaned-subsystems) | Two orphaned subsystems | Low | New |
|
||||
@@ -79,8 +79,9 @@ At `c5c61a3`, in a clean venv on Python 3.14.6:
|
||||
833 passed, 2 skipped, 24 subtests passed in 11.51s
|
||||
```
|
||||
|
||||
(Two further failures in `test_concrete_image.py` are environmental — the review
|
||||
venv lacked `requests`. See R4, which is the same underlying problem.)
|
||||
(The 2 skips were environmental — the review venv lacked `requests`, so the URL
|
||||
image tests skipped. With the `test` extra from R4 installed the suite reports
|
||||
`853 passed, 24 subtests passed in 13.53s`.)
|
||||
|
||||
The suite then **hangs indefinitely** rather than exiting. See R2.
|
||||
|
||||
@@ -267,40 +268,59 @@ This is strictly a stopgap — it keeps the per-page allocation cost.
|
||||
|
||||
---
|
||||
|
||||
## R4 — Declared dependencies are incomplete
|
||||
## R4 — Three packaging configs that disagree
|
||||
|
||||
**Severity: high. A clean `pip install pyWebLayout` fails on first import.**
|
||||
**Severity: medium.** *Corrected: the original review claimed a clean install
|
||||
fails on first import. It does not — see below.*
|
||||
|
||||
### Problem
|
||||
|
||||
[setup.cfg](../setup.cfg) declares:
|
||||
The project carries **three** sets of packaging metadata:
|
||||
|
||||
```
|
||||
install_requires =
|
||||
Pillow
|
||||
numpy
|
||||
```
|
||||
| File | Declares |
|
||||
|------|----------|
|
||||
| `pyproject.toml` `[project]` | Pillow, numpy, pyphen, beautifulsoup4, flask, ebooklib, requests |
|
||||
| `setup.cfg` `[options]` | Pillow, numpy |
|
||||
| `setup.py` `setup(...)` kwargs | Pillow, numpy |
|
||||
|
||||
The library also imports `pyphen` ([abstract/inline.py](../pyWebLayout/abstract/inline.py)),
|
||||
`bs4` ([io/readers/html_extraction.py](../pyWebLayout/io/readers/html_extraction.py))
|
||||
and `ebooklib` ([io/readers/epub_reader.py](../pyWebLayout/io/readers/epub_reader.py)).
|
||||
`import pyWebLayout.concrete` fails without `pyphen`.
|
||||
`pyproject.toml`'s `[project]` table wins under any modern build backend, so the
|
||||
shipped wheel is correct and `pip install pyWebLayout` works. The `setup.cfg` and
|
||||
`setup.py` copies are dead, contradictory, and actively misleading — reading
|
||||
either one gives the wrong answer about what the library needs.
|
||||
|
||||
There is no declared test extra either; `tests/concrete/test_concrete_image.py`
|
||||
needs `requests`, which nothing declares.
|
||||
The authoritative list is itself wrong in the other direction:
|
||||
|
||||
- **`flask` is a runtime dependency.** It is imported only by
|
||||
`tests/abstract/test_abstract_blocks.py`, as a fixture HTTP server. Every user
|
||||
installs Flask, Jinja2, Werkzeug, click, itsdangerous and blinker for nothing.
|
||||
- **`ebooklib` is a runtime dependency and is never imported by the library.**
|
||||
`epub_reader.py` uses `zipfile` + `xml.etree` directly. Only the *tests* use
|
||||
ebooklib, to build EPUB fixtures.
|
||||
- **`requests` is declared required but is optional.** `concrete/image.py:100-111`
|
||||
imports it lazily and degrades to an error message on the image when absent.
|
||||
- **`requires-python = ">=3.6"` is false.** The package uses dataclasses (3.7+)
|
||||
and `from __future__ import annotations` (3.7+); CI tests 3.10, 3.12 and 3.13.
|
||||
|
||||
Net effect: a runtime install pulls 7 direct dependencies where 4 are needed.
|
||||
|
||||
### Action
|
||||
|
||||
- Add `pyphen`, `beautifulsoup4` and `ebooklib` to `install_requires`, with lower
|
||||
bounds.
|
||||
- Add an `[options.extras_require] test =` entry covering `pytest` and `requests`.
|
||||
- Add a CI job that installs the built wheel into an empty environment and runs
|
||||
`python -c "import pyWebLayout.concrete, pyWebLayout.io.readers.epub_reader"`.
|
||||
This class of defect is only ever caught by installing what you ship.
|
||||
- Consolidate on `pyproject.toml`. Reduce `setup.cfg` to its `[flake8]` section
|
||||
and `setup.py` to a `setup()` shim, each with a comment saying where metadata
|
||||
lives.
|
||||
- Runtime deps: Pillow, numpy, pyphen, beautifulsoup4. Move flask, werkzeug,
|
||||
ebooklib and requests into a `test` extra; add a `remote-images` extra for
|
||||
requests; add a `dev` extra composing them.
|
||||
- Set `requires-python = ">=3.10"` to match the CI matrix, and add version
|
||||
classifiers.
|
||||
- Add a CI step that installs the package into an empty venv with **only**
|
||||
declared runtime deps and imports every top-level subpackage. This class of
|
||||
defect is only caught by installing what you ship — and it is what would have
|
||||
caught the original misreading.
|
||||
|
||||
### Files
|
||||
|
||||
`setup.cfg`, `setup.py`, CI configuration
|
||||
`pyproject.toml`, `setup.cfg`, `setup.py`, `.gitea/workflows/ci.yml`
|
||||
|
||||
---
|
||||
|
||||
@@ -477,7 +497,7 @@ positions round-trip through tables correctly (S8 already notes this dependency)
|
||||
## Recommended order
|
||||
|
||||
```
|
||||
R4 ── packaging; independent, minutes, unblocks clean CI
|
||||
R4 ── packaging; independent, minutes, unblocks clean CI [done]
|
||||
S12 ── delete the process pool; resolves R1 and R2 with it
|
||||
R3 ── font scaling loses links; independent, user-visible
|
||||
R5 ── delete the monkey patch; minutes
|
||||
|
||||
+37
-5
@@ -4,24 +4,56 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pyWebLayout"
|
||||
version = "0.1.1"
|
||||
description = "A Python library for HTML-like layout and rendering"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.6"
|
||||
requires-python = ">=3.10"
|
||||
license = {file = "LICENSE"}
|
||||
authors = [
|
||||
{name = "Duncan Tourolle", email = "duncan@tourolle.paris"}
|
||||
]
|
||||
dynamic = ["version"]
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
]
|
||||
dependencies = [
|
||||
"Pillow",
|
||||
"numpy",
|
||||
"pyphen",
|
||||
"beautifulsoup4",
|
||||
"flask",
|
||||
"ebooklib",
|
||||
"requests"
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://gitea.tourolle.paris/pyWebLayout"
|
||||
|
||||
[project.optional-dependencies]
|
||||
# Loading images from http(s) URLs. concrete.image imports requests lazily and
|
||||
# degrades to an error message on the image when it is absent, so it is not a
|
||||
# hard requirement.
|
||||
remote-images = ["requests"]
|
||||
test = [
|
||||
"pytest>=6.0",
|
||||
"pytest-cov",
|
||||
"flask", # fixture HTTP server in tests/abstract/test_abstract_blocks.py
|
||||
"werkzeug", # make_server, same fixture
|
||||
"ebooklib", # builds EPUB fixtures; the reader itself uses zipfile + ElementTree
|
||||
"requests", # exercises the remote-images path
|
||||
]
|
||||
dev = [
|
||||
"pyWebLayout[test,remote-images]",
|
||||
"flake8",
|
||||
"coverage-badge",
|
||||
"interrogate",
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
include = ["pyWebLayout*"]
|
||||
|
||||
[tool.coverage.run]
|
||||
source = ["pyWebLayout"]
|
||||
branch = true
|
||||
|
||||
@@ -1,26 +1,6 @@
|
||||
[metadata]
|
||||
name = pyWebLayout
|
||||
version = 0.1.1
|
||||
author = Duncan Tourolle
|
||||
author_email = duncan@tourolle.paris
|
||||
description = A Python library for HTML-like layout and rendering
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
url = https://gitea.tourolle.paris/pyWebLayout
|
||||
classifiers =
|
||||
Programming Language :: Python :: 3
|
||||
License :: OSI Approved :: MIT License
|
||||
Operating System :: OS Independent
|
||||
|
||||
[options]
|
||||
packages = find:
|
||||
python_requires = >=3.6
|
||||
install_requires =
|
||||
Pillow
|
||||
numpy
|
||||
|
||||
[options.packages.find]
|
||||
include = pyWebLayout*
|
||||
# Packaging metadata lives in pyproject.toml ([project]), which takes
|
||||
# precedence over anything declared here. This file keeps only tool config
|
||||
# that has nowhere better to live.
|
||||
|
||||
[flake8]
|
||||
exclude =
|
||||
|
||||
@@ -1,32 +1,10 @@
|
||||
from setuptools import setup, find_packages
|
||||
"""Shim for legacy `python setup.py` invocations.
|
||||
|
||||
setup(
|
||||
name="pyWebLayout",
|
||||
version="0.1.1",
|
||||
packages=find_packages(),
|
||||
install_requires=[
|
||||
"Pillow",
|
||||
"numpy",
|
||||
],
|
||||
extras_require={
|
||||
"test": [
|
||||
"coverage>=5.0",
|
||||
],
|
||||
"dev": [
|
||||
"coverage>=5.0",
|
||||
"pytest>=6.0",
|
||||
],
|
||||
},
|
||||
author="Duncan Tourolle",
|
||||
author_email="duncan@tourolle.paris",
|
||||
description="A Python library for HTML-like layout and rendering",
|
||||
long_description=open("README.md").read(),
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://gitea.tourolle.paris/pyWebLayout",
|
||||
classifiers=[
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
python_requires=">=3.6",
|
||||
)
|
||||
All packaging metadata lives in setup.cfg. Keeping a second copy here was an
|
||||
active hazard: keyword arguments passed to setup() override setup.cfg, so the
|
||||
two could disagree silently and the setup.py copy would win.
|
||||
"""
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
setup()
|
||||
|
||||
Reference in New Issue
Block a user