Skip to content

Commit 50b7d19

Browse files
mfosterwclaude
andauthored
Fix outdated documentation and incorrect docstrings (#293)
* Fix outdated documentation and incorrect docstrings - Update docs/webiscite.rst to reflect current architecture: GithubWebhookView and PullRequestHandler replace the removed github_hook, process_pull, and pr_opened identifiers - Fix managers.py docstrings: correct parameter names and return descriptions for create_from_github on both managers; add missing docstrings to get_queryset and annotate_user_vote; add module docstring - Fix webhooks.py docstrings: update module docstring to class-based view; fix _validate_header and _validate_signature return type descriptions - Add missing module docstring to activitypub/models.py Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add docs Claude Code skill Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c9ce2c9 commit 50b7d19

5 files changed

Lines changed: 163 additions & 24 deletions

File tree

.claude/skills/docs/SKILL.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
---
2+
name: docs
3+
description: Update, review, or build the Sphinx documentation for Democrasite
4+
disable-model-invocation: true
5+
argument-hint: "[what to update or check, e.g. 'review for accuracy' or 'add activitypub section']"
6+
---
7+
8+
## Task
9+
$ARGUMENTS
10+
11+
## Documentation structure
12+
13+
```
14+
docs/
15+
├── conf.py — Sphinx config (theme, extensions, Django setup)
16+
├── index.rst — Master toctree (README, CONTRIBUTING, howto, users, webiscite, api/)
17+
├── README.rst — Project overview (mirrors root README)
18+
├── CONTRIBUTING.rst — Dev setup and contribution guide
19+
├── howto.rst — Instructions for building and writing docs
20+
├── users.rst — Users app overview (minimal; relies on autodoc)
21+
├── webiscite.rst — Core app narrative (pipeline, bill lifecycle)
22+
└── api/ — Auto-generated RST files (do not edit manually)
23+
├── democrasite.rst
24+
├── democrasite.webiscite.rst
25+
├── democrasite.webiscite.models.rst
26+
├── democrasite.webiscite.managers.rst
27+
├── democrasite.webiscite.tasks.rst
28+
├── democrasite.webiscite.webhooks.rst
29+
├── democrasite.webiscite.constitution.rst
30+
├── democrasite.webiscite.views.rst
31+
├── democrasite.webiscite.api.rst
32+
├── democrasite.users.rst
33+
├── democrasite.activitypub.rst
34+
└── … (one file per module)
35+
```
36+
37+
## Key make commands
38+
39+
All run from the `docs/` directory (or with `make -C docs <target>`):
40+
41+
| Command | What it does |
42+
|---|---|
43+
| `make apidocs` | Regenerate all `docs/api/*.rst` from source using sphinx-apidoc. Run this whenever Python modules are added or removed. |
44+
| `make livehtml` | Build docs and serve with live reload at http://localhost:9000. Requires the docs Docker container (`docker compose -f docker-compose.docs.yml up`). |
45+
| `make clean` | Remove `_build/` and `api/` directories for a fresh build. |
46+
| `make html` | One-off HTML build into `_build/html/`. |
47+
48+
To serve with live reload locally (in Docker):
49+
```
50+
docker compose -f docker-compose.docs.yml up
51+
```
52+
53+
To regenerate API docs locally (sphinx-apidoc must be installed):
54+
```
55+
make -C docs apidocs
56+
```
57+
58+
## What is auto-generated vs. manual
59+
60+
- **`docs/api/`** — fully auto-generated by `make apidocs` (sphinx-apidoc). Never edit these files directly; edit the Python docstrings instead, then re-run `make apidocs`.
61+
- **`docs/webiscite.rst`** and **`docs/users.rst`** — manually maintained narrative docs. Keep these in sync with code changes (especially `webiscite.rst` for the PR pipeline and bill lifecycle).
62+
- **`docs/CONTRIBUTING.rst`** and **`docs/howto.rst`** — manually maintained.
63+
64+
## Docstring style
65+
66+
Use Google-style docstrings (supported via the Napoleon extension). Example:
67+
68+
```python
69+
def my_function(arg: int) -> str:
70+
"""One-line summary.
71+
72+
Longer description if needed.
73+
74+
Args:
75+
arg: Description of the argument.
76+
77+
Returns:
78+
Description of the return value.
79+
80+
Raises:
81+
ValueError: When and why.
82+
"""
83+
```
84+
85+
Line length: 88 characters (enforced by ruff). Wrap long docstring lines to stay within this limit.
86+
87+
## Steps for common tasks
88+
89+
### Reviewing docs for accuracy
90+
1. Read `docs/webiscite.rst` and cross-check function/class references against actual source code
91+
2. Check that docstrings in `democrasite/webiscite/managers.py`, `models.py`, `tasks.py`, `webhooks.py`, and `constitution.py` have correct parameter names and return descriptions
92+
3. Run `make apidocs` if modules have been added or removed since the last regeneration
93+
4. Run `just lint` to confirm no ruff line-length violations were introduced
94+
95+
### Adding or updating narrative docs
96+
1. Edit the relevant `.rst` file in `docs/` (not under `docs/api/`)
97+
2. Use Sphinx cross-references for code identifiers:
98+
- `:class:\`~democrasite.webiscite.models.Bill\`` — class link
99+
- `:func:\`~democrasite.webiscite.tasks.submit_bill\`` — function link
100+
- `:meth:\`~democrasite.webiscite.webhooks.PullRequestHandler.opened\`` — method link
101+
3. Verify the build: `docker compose -f docker-compose.docs.yml up`
102+
103+
### Adding a new Python module
104+
1. Write a module-level docstring at the top of the file
105+
2. Run `make apidocs` to regenerate `docs/api/` — this creates the new RST file and adds it to the relevant package toctree automatically
106+
3. Commit the updated `docs/api/` files alongside the new module
107+
108+
### Updating docs after refactoring
109+
1. Update any cross-references in `docs/webiscite.rst` or `docs/users.rst` that point to renamed/removed identifiers
110+
2. Update affected docstrings in source files
111+
3. Run `make apidocs` if the module structure changed (files added/removed)
112+
4. Run `just lint` to verify no line-length violations
113+
114+
## Published docs
115+
- ReadTheDocs URL: https://cookiestocracy.readthedocs.io/en/latest/
116+
- Builds automatically on push to the main branch

democrasite/activitypub/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Models for the activitypub app."""
2+
13
from django.contrib.auth import get_user_model
24
from django.db import models
35
from django.urls import reverse

democrasite/webiscite/managers.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Managers for the webiscite app models."""
2+
13
from logging import WARNING
24
from typing import TYPE_CHECKING
35
from typing import Any
@@ -16,16 +18,14 @@
1618

1719
class PullRequestManager[T](models.Manager):
1820
def create_from_github(self, pr: dict[str, Any]) -> T:
19-
"""Create a :class:`~democrasite.webiscite.models.PullRequest` and optionally a
20-
:class:`~democrasite.webiscite.models.Bill` instance from a GitHub pull request
21+
"""Create or update a :class:`~democrasite.webiscite.models.PullRequest` from
22+
a GitHub pull request payload
2123
2224
Args:
23-
pr_args: The parameters for the ``PullRequest``
24-
bill_args: The parameters for the ``Bill`` or None
25+
pr: The pull request data from the GitHub API
2526
2627
Returns:
27-
A tuple containing the new or updated pull request and new bill instance, if
28-
applicable
28+
The new or updated pull request instance
2929
"""
3030
pull_request, created = self.update_or_create(
3131
number=pr["number"],
@@ -47,6 +47,12 @@ def create_from_github(self, pr: dict[str, Any]) -> T:
4747

4848
class BillManager[T](models.Manager):
4949
def get_queryset(self):
50+
"""Return a queryset with pull_request pre-fetched and vote percentages added.
51+
52+
All Bill querysets include ``total_votes``, ``yes_percent``, and
53+
``no_percent``
54+
annotations, and are ordered by creation date.
55+
"""
5056
return (
5157
super()
5258
.get_queryset()
@@ -80,6 +86,16 @@ def get_queryset(self):
8086
def annotate_user_vote(
8187
self, user: User, queryset: models.QuerySet["Bill"] | None = None
8288
):
89+
"""Annotate a queryset with the given user's vote on each bill.
90+
91+
Args:
92+
user: The user whose vote to annotate
93+
queryset: The queryset to annotate; defaults to ``self.get_queryset()``
94+
95+
Returns:
96+
The queryset annotated with a ``user_vote`` field
97+
(``True``/``False``/``None``)
98+
"""
8399
if queryset is None:
84100
queryset = self.get_queryset()
85101

democrasite/webiscite/webhooks.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Views for processing webhooks.
22
3-
Each service that sends webhooks should have its own function-based view."""
3+
Each service that sends webhooks should have its own class-based view."""
44

55
import hmac
66
import json
@@ -151,10 +151,10 @@ def _validate_header(headers: request.HttpHeaders) -> HttpResponseBadRequest | N
151151
"""Validate the headers of a request from a webhook
152152
153153
Args:
154-
headers (dict): The headers from the request to validate
154+
headers: The headers from the request to validate
155155
156156
Returns:
157-
str: Error message if the headers are invalid, otherwise an empty string
157+
A bad request response if required headers are missing, otherwise ``None``
158158
"""
159159
header_signature = headers.get("x-hub-signature-256")
160160
if header_signature is None:
@@ -177,11 +177,12 @@ def _validate_signature(
177177
"""Validate the signature of a request from a webhook
178178
179179
Args:
180-
header_signature (str): The signature from the request to validate
181-
request_body (bytes): The body of the request to validate
180+
header_signature: The HMAC signature from the request headers
181+
request_body: The raw body of the request to validate
182182
183183
Returns:
184-
str: Error message if the signature is invalid, otherwise an empty string
184+
An error response if the signature is invalid or uses an unsupported
185+
digest, otherwise ``None``
185186
"""
186187
digest_name, signature = header_signature.split("=")
187188
if digest_name != "sha256":

docs/webiscite.rst

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,27 @@ Pull Requests
1515
Pull Request Processing Pipeline
1616
--------------------------------
1717

18-
When a pull request is created on `GitHub`_, a `webhook`_ makes a request to
19-
the GitHub :func:`webhook view <democrasite.webiscite.webhooks.github_hook>`.
18+
When a pull request is created on `GitHub`_, a `webhook`_ makes a POST request
19+
to the :class:`~democrasite.webiscite.webhooks.GithubWebhookView`, which
20+
validates the request signature and dispatches to the appropriate handler.
2021

21-
This method parses the data from the request and calls
22-
:func:`~democrasite.webiscite.tasks.process_pull`
23-
to handle the request. In the event a pull request was opened or reopened,
24-
:func:`~democrasite.webiscite.tasks.pr_opened` is called.
22+
For pull request events, the request is handled by
23+
:class:`~democrasite.webiscite.webhooks.PullRequestHandler`. When a pull
24+
request is opened or reopened, its
25+
:meth:`~democrasite.webiscite.webhooks.PullRequestHandler.opened` method
26+
creates a :class:`~democrasite.webiscite.models.PullRequest` instance.
2527

2628
If the user who created the pull request has a democrasite account, a new
2729
:class:`~democrasite.webiscite.models.Bill`
2830
is created with the information from the pull request and made visible on the
29-
homepage immediately.
30-
31-
A task to execute :func:`~democrasite.webiscite.tasks.submit_bill`
32-
is also put in the celery queue to execute once the voting period ends. The
33-
function verifies that the pull request is open and unedited and then counts
34-
the votes for and against that Bill.
31+
homepage immediately. A :class:`~django_celery_beat.models.PeriodicTask` is
32+
also scheduled to execute :func:`~democrasite.webiscite.tasks.submit_bill`
33+
once the voting period ends.
34+
35+
:func:`~democrasite.webiscite.tasks.submit_bill` verifies that the pull
36+
request is still open and that its SHA has not changed since the bill was
37+
created (i.e. the pull request has not been edited), then counts the votes for
38+
and against that Bill.
3539

3640
If the votes for the Bill pass the threshold, the pull request is merged into
3741
the master branch on Github and automatically deployed, officially making it

0 commit comments

Comments
 (0)