Add Sphinx documentation (#30)#56
Merged
Merged
Conversation
Structured with Diataxis: a tutorial, four how-to guides, two explanation pages, and a reference for every fixture, the portal marker, and the fixtures_factory API. The reference is generated from the docstrings by sphinx-autodoc2 rather than sphinx.ext.autodoc. Two things make that necessary: - @pytest.fixture returns a FixtureFunctionDefinition, not a function. sphinx.ext.autodoc introspects live objects and cannot document our fixtures. sphinx-autodoc2 analyses the source statically, so the decorator is irrelevant to it. - Our docstrings are Markdown. sphinx.ext.autodoc hands docstrings to the reStructuredText parser, which mangles them. autodoc2 parses them as MyST. Because the analysis is static, the docs build never imports the package and needs no Plone at all. It gets its own small virtualenv from docs/requirements.txt and takes seconds, not minutes. make docs builds with -W, so warnings are errors.
The job deliberately skips 'make install'. Because sphinx-autodoc2 analyses the source statically and never imports the package, the documentation builds without Plone. A cold build -- creating the virtualenv and installing every dependency from scratch -- takes under four seconds, against roughly three minutes for a test job. make docs runs sphinx with -W, so any warning fails the build.
The 'requirements*.txt' rule exists for the mxdev-generated requirements-mxdev.txt at the repository root, but it also swallowed docs/requirements.txt, which is hand-written and must be committed. The docs CI job failed with 'No rule to make target docs/requirements.txt' because the file was simply not in the checkout.
The documentation assumed the reader already had a testing.py with layers: MY_ADDON_FUNCTIONAL_TESTING simply appeared out of nowhere in the conftest examples. Explaining the layer machinery is out of scope here, but leaving it unexplained is not acceptable either. The tutorial and the setup how-to now state the prerequisite plainly and point at plone.app.testing and plone.testing, which own that machinery. The explanation page now says that layers come from your add-on's testing.py and that pytest-plone consumes rather than replaces them. Links go to the READMEs on GitHub. Those are the current source of truth: the Plone 6 documentation has no backend testing chapter at all, and the only rendered HTML lives on the legacy 5.docs.plone.org domain.
Reported in #16, where it was never actually answered: widening a working function-scoped fixture to scope='class' and reading functional_class['portal'] raises KeyError: 'portal'. The layer sets its portal key in testSetUp, which zope.pytestlayer invokes only for the function-scoped fixture. At class scope it never runs, so the key does not exist. Changing a pytest scope is normally a drop-in change, which is precisely why this bites. The explanation page now covers why the two models do not line up, and the how-to guide warns against it at the point where a reader is tempted to do it -- they should use portal_class or functional_portal_class, which drive that lifecycle themselves. Verified: the KeyError reproduces exactly as described.
Move the documentation dependencies from docs/requirements.txt into a [docs] optional-dependency group and build with the project's own environment (-e .[test,docs]). conf.py reads the version from pytest_plone.__version__ and gains a logo, favicon, sitemap, OpenGraph tags, a 404 page, and a live-reload docs-livehtml Makefile target. Extract the docs build into a reusable .github/workflows/docs.yml that builds the HTML and deploys to GitHub Pages on main, and add Vale prose linting (.vale.ini, docs/_styles, docs-vale target).
The Makefile selects the Plone constraints from the PLONE_VERSION environment variable (defaulting to 6.1.4). The docs refactor had renamed the CI env vars to lowercase, so make install stopped seeing them and every job silently installed against 6.1.4 — which pins lxml 5.4.0, a version with no Python 3.14 wheel, breaking the 3.14 test job. Restore the uppercase env var names so each matrix job installs against its own Plone version, and pass PLONE_VERSION/PYTHON_VERSION into the docs workflow's install step.
In CI only, the requirements-mxdev.txt recipe drops the docs extra from mx.ini (installing .[test] instead of .[test,docs]) unless CI_DOCS is set, so the test and coverage jobs skip the heavy documentation dependencies. mx.ini is edited only for the mxdev run and restored right after, and local runs (CI unset) are never touched. The docs workflow sets CI_DOCS=1 so its build keeps Sphinx and the rest of the docs stack.
Make Vale lint only the documentation sources, not the generated docs/_build output, which was doubling every alert. Add the false-positive terms flagged by Vale to the vocabulary (finalizer, teardown, reimplement, and the plone.*/zope.* package names), tighten the spaced em-dashes to comply with the Microsoft style, code-format pytest-plone in the two headings, and reword the em-dashes glued to inline code that Vale cannot otherwise accept. The docs workflow now runs the Vale check (check-vale: true); it fails only on errors, so the remaining style warnings are shown but non-blocking.
Add the app_class and functional_app_class fixtures to the reference, plus a Distribution section covering create_site and its supporting distribution_name, answers, site_logo, site_owner_name, and site_owner_password fixtures, and list them all in the overview table.
ericof
approved these changes
Jul 13, 2026
ericof
left a comment
Member
There was a problem hiding this comment.
I've added new fixtures and did use make install to make my life easier ;-)
Member
|
@jensens Thank you for this |
This was referenced Jul 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements #30. Design discussed in this comment.
Important
Merge #55 first. Once the docstrings become the source of truth for the reference, every error in them gets published verbatim — and I confirmed that concretely: built against
mainwithout #55, the generated reference containsisinstance(fti, IDexterityFTI)(alwaysFalse),toc.getTerm(undefined name), anddef test_fti(self, ...)(fails withfixture 'self' not found). With #55 merged in locally, the same build renders the corrected examples. This PR is otherwise independent and does not conflict.Why
sphinx-autodoc2and notsphinx.ext.autodocTwo properties of this package rule out the obvious choice:
@pytest.fixturedoes not return a function. It returns aFixtureFunctionDefinition.sphinx.ext.autodocintrospects live objects, soautofunctioncannot document a single one of our fixtures.sphinx.ext.autodochands docstrings to the reStructuredText parser. MyST-parser's own documentation states it is not compatible with MyST docstrings.sphinx-autodoc2analyses the source statically. The decorator becomes irrelevant, andautodoc2_docstring_parser_regexesparses the docstrings as MyST. Both problems disappear rather than getting worked around.Two configuration gotchas are recorded in
docs/conf.pybecause they are not obvious:autodoc2_packages[…]["path"]must be relative toconf.py, and thefieldlistMyST extension is required or:param x:renders as literal text.The docs build needs no Plone
This falls out of the static analysis, and it is the nicest consequence.
The docs environment installs from
docs/requirements.txt— Sphinx, the theme, autodoc2 — and deliberately does not installpytest-ploneitself, because doing so would drag inProducts.CMFPlone[test]and the rest of the runtime dependencies for no benefit.A cold
make docs, creating the virtualenv and installing everything from scratch, takes 3.8 seconds. The CI job skipsmake installentirely. It also means Read the Docs would not have to install Plone, which is worth weighing when we settle the hosting question.Contents
Diataxis, all four quadrants:
tutorials/first-test.mdhow-to/explanation/reference/autodoc2-objectper fixture), theportalmarker, thefixtures_factoryAPI.reference/fixtures.mdis the hybrid the Plone docs standard asks for: the grouping and the overview table are written by hand, because autodoc cannot produce navigation; the facts come from the docstrings.Verification
make docsbuilds with-W(warnings are errors): 0 warnings.ruff format --diffandruff check --diffclean.Not in this PR
🤖 Generated with Claude Code