Skip to content

Commit ceafd8d

Browse files
authored
Merge pull request #116 from DigitalHarborFoundation/docs/fix-peewee-attributes-issue-86
Fix peewee model attributes missing from generated docs (#86)
2 parents 54dacbc + 396f776 commit ceafd8d

4 files changed

Lines changed: 116 additions & 17 deletions

File tree

.github/workflows/github-pages.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ jobs:
3636
uses: astral-sh/setup-uv@v5
3737
with:
3838
version: "latest"
39+
enable-cache: true
3940

4041
- name: "Set up Python"
4142
uses: actions/setup-python@v5

.github/workflows/validate.yaml

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ jobs:
1515
uses: astral-sh/setup-uv@v5
1616
with:
1717
version: "latest"
18+
enable-cache: true
1819

1920
- name: "Set up Python"
2021
uses: actions/setup-python@v5
@@ -29,4 +30,34 @@ jobs:
2930
run: |
3031
uv run python -m unittest discover -s tests.unit
3132
env:
32-
CURRENT_BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
33+
CURRENT_BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
34+
35+
docs:
36+
name: Build docs
37+
runs-on: ubuntu-latest
38+
39+
steps:
40+
- name: Check out code
41+
uses: actions/checkout@v4
42+
43+
- name: Install uv
44+
uses: astral-sh/setup-uv@v5
45+
with:
46+
version: "latest"
47+
enable-cache: true
48+
49+
- name: "Set up Python"
50+
uses: actions/setup-python@v5
51+
with:
52+
python-version-file: ".python-version"
53+
54+
- name: Install docs dependencies
55+
run: |
56+
uv sync --group docs
57+
58+
# Builds the same way github-pages.yml deploys, so doc-build breakage
59+
# (and, with -W, doc warnings) is caught on PRs instead of only on the
60+
# deploy-to-main step. This job does not deploy.
61+
- name: Build docs
62+
run: |
63+
make html

DEVELOPMENT.md

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,21 +149,69 @@ uv run python -m flexeval --eval_name {eval_suite_name}
149149

150150
## Documentation
151151

152-
We use Sphinx to generate the documentation website.
152+
We use [Sphinx](https://www.sphinx-doc.org/) to generate the [documentation
153+
website](https://digitalharborfoundation.github.io/FlexEval). The source lives in
154+
`docs/`, the configuration is `docs/conf.py`, and the relevant build targets are in
155+
the `Makefile`. The site is rebuilt and deployed to GitHub Pages on every push to
156+
`main` by the [`github-pages`](.github/workflows/github-pages.yml) workflow.
153157

154-
All of the relevant directives are in the `Makefile`.
158+
The API reference is generated automatically by `autodoc`/`autosummary` from the
159+
source code (see the `:recursive:` `autosummary` directive in `docs/api.rst`), so
160+
new modules and classes show up without any manual wiring.
155161

156-
Develop the documentation website locally:
162+
### Building the docs locally
163+
164+
First make sure the docs dependencies are installed (they live in the `docs`
165+
dependency group; `uv sync --all-groups` installs them too):
157166

158167
```bash
159-
make docclean docautobuild
168+
uv sync --group docs
169+
```
170+
171+
Build the site once:
172+
173+
```bash
174+
make html
175+
```
176+
177+
The output is written to `build/html/`; open `build/html/index.html` in a browser to
178+
view it.
179+
180+
For an interactive workflow, use `sphinx-autobuild`, which rebuilds on save and serves
181+
the site at <http://127.0.0.1:8000>:
182+
183+
```bash
184+
make docautobuild
160185
```
161186

162-
You can also just build the site directly:
187+
### Debugging the build
188+
189+
The generated API stubs (`docs/generated/`) and the rendered output (`build/`) are
190+
**cached** between builds. `autosummary` will not regenerate a stub that already
191+
exists, so when you change `docs/conf.py` (especially `autodoc`/`autosummary`
192+
options) or restructure modules, an incremental build can show stale results. Force a
193+
clean rebuild:
194+
163195
```bash
196+
make docclean # removes build/ and docs/generated/
164197
make html
198+
# or chain them, e.g. for autobuild:
199+
make docclean docautobuild
165200
```
166201

202+
Sphinx prints `WARNING:` lines during the build and ends with a summary
203+
(e.g. `build succeeded, N warnings.`). Warnings are worth scanning — they flag broken
204+
cross-references, malformed docstrings, and members that failed to render.
205+
206+
A note on the API reference: FlexEval's database models (`src/flexeval/classes/`) are
207+
[peewee](https://docs.peewee-orm.com/) models, and peewee's metaclass rewrites
208+
field definitions into descriptors and adds generated members (a per-model
209+
`DoesNotExist` exception and a `<fk>_id` alias for every foreign key). The
210+
`skip_peewee_internals` hook in `docs/conf.py` hides that generated noise while
211+
keeping the real fields, and `inherited-members: False` keeps inherited peewee/pydantic
212+
machinery out of the reference. If model attributes stop appearing on a generated page,
213+
that hook (and the `autodoc_default_options`) is the place to look.
214+
167215
## Releasing a new version
168216

169217
The package version lives in `src/flexeval/__about__.py` (`pyproject.toml` reads it

docs/conf.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from pathlib import Path
99
from packaging.version import parse as parse_version
1010

11+
import peewee as pw
12+
1113
import flexeval
1214

1315
sys.path.append(os.path.abspath("."))
@@ -168,6 +170,12 @@ def linkcode_resolve(domain, info):
168170
nb_merge_streams = True
169171

170172
autosummary_generate = True
173+
# Don't let numpydoc inject its own per-class "Methods"/"Attributes" summary
174+
# tables. They duplicate the member documentation autodoc already renders below,
175+
# and for our peewee models they list every inherited peewee.Model method
176+
# (save, select, bulk_create, ...) as noise. Disabling this also avoids the
177+
# "stub file not found" warnings those tables' :toctree: would otherwise emit.
178+
numpydoc_show_class_members = False
171179
autodoc_typehints = "signature"
172180
autodoc_default_options = {
173181
"members": True,
@@ -178,18 +186,29 @@ def linkcode_resolve(domain, info):
178186
}
179187

180188

181-
def skip_inherited_members(app, what, name, obj, skip, options):
182-
# Skip members if they are inherited (not defined on the class itself)
183-
if what == "class":
184-
# The object is the class being documented
185-
cls = obj
186-
if hasattr(cls, "__dict__"):
187-
# If the member name is NOT in the class dict, it's inherited
188-
if name not in cls.__dict__:
189-
return True # skip inherited member
190-
return skip # otherwise use default behavior
189+
def skip_peewee_internals(app, what, name, obj, skip, options):
190+
"""Hide peewee-generated noise from the API docs.
191+
192+
peewee's model metaclass adds two kinds of members to every model class
193+
that aren't useful in the generated reference:
194+
195+
- a per-model ``DoesNotExist`` exception (e.g. ``MetricDoesNotExist``), and
196+
- a ``<fk>_id`` alias for every foreign key (e.g. ``dataset_id`` alongside
197+
``dataset``). The alias shares the same ``Field`` object as the FK, whose
198+
``.name`` is the FK field name, so we can detect it by name mismatch.
199+
200+
Genuine fields and methods are left untouched. (Inherited members are
201+
excluded separately via ``inherited-members: False`` below — note that
202+
peewee's per-model ``DoesNotExist`` and ``_id`` accessors are defined on the
203+
model class itself, not inherited, which is why they need explicit skipping.)
204+
"""
205+
if name == "DoesNotExist":
206+
return True
207+
if isinstance(obj, pw.ForeignKeyField) and name != obj.name:
208+
return True
209+
return skip
191210

192211

193212
def setup(app):
194-
app.connect("autodoc-skip-member", skip_inherited_members)
213+
app.connect("autodoc-skip-member", skip_peewee_internals)
195214
app.connect("builder-inited", vignettes.generate_custom_stubs)

0 commit comments

Comments
 (0)