|
| 1 | +# Contributing |
| 2 | + |
| 3 | +Thanks for looking. gp-libs is pre-1.0 (`0.0.x`); a minor version bump may |
| 4 | +still carry a breaking change. Bug reports with a reproduction, and notes on |
| 5 | +where the documentation misled you, are the most useful contributions right |
| 6 | +now. |
| 7 | + |
| 8 | +How this project writes prose — README, `CHANGES`, release notes, commit |
| 9 | +messages, docstrings, source comments, and log messages — is set out |
| 10 | +separately in [WRITING.md](WRITING.md). Read that before changing any of it. |
| 11 | +The constraints every change is held to, and the map of what is where, are in |
| 12 | +[AGENTS.md](../AGENTS.md). |
| 13 | + |
| 14 | +## Getting set up |
| 15 | + |
| 16 | +Install [git], [uv], and [just]. |
| 17 | + |
| 18 | +```console |
| 19 | +$ git clone https://github.com/git-pull/gp-libs.git |
| 20 | +``` |
| 21 | + |
| 22 | +```console |
| 23 | +$ cd gp-libs |
| 24 | +``` |
| 25 | + |
| 26 | +```console |
| 27 | +$ uv sync --all-extras --dev |
| 28 | +``` |
| 29 | + |
| 30 | +[git]: https://git-scm.com/ |
| 31 | +[uv]: https://github.com/astral-sh/uv |
| 32 | +[just]: https://just.systems/ |
| 33 | + |
| 34 | +## The gates |
| 35 | + |
| 36 | +Format: |
| 37 | + |
| 38 | +```console |
| 39 | +$ uv run ruff format . |
| 40 | +``` |
| 41 | + |
| 42 | +Lint: |
| 43 | + |
| 44 | +```console |
| 45 | +$ uv run ruff check . --fix --show-fixes |
| 46 | +``` |
| 47 | + |
| 48 | +Type-check — strict mypy across `src/` and `tests/`: |
| 49 | + |
| 50 | +```console |
| 51 | +$ uv run mypy . |
| 52 | +``` |
| 53 | + |
| 54 | +Test: |
| 55 | + |
| 56 | +```console |
| 57 | +$ uv run pytest |
| 58 | +``` |
| 59 | + |
| 60 | +**Imports.** Namespace stdlib imports — `import enum`, not |
| 61 | +`from enum import Enum` — so a call site reads `enum.Enum`. Third-party |
| 62 | +packages may use `from X import Y`. For typing, `import typing as t` and |
| 63 | +access via the namespace: `t.NamedTuple`, `t.TYPE_CHECKING`. Every file |
| 64 | +starts with `from __future__ import annotations`; ruff's isort |
| 65 | +`required-imports` (`pyproject.toml`) enforces that one, the rest is |
| 66 | +convention ruff does not check. |
| 67 | + |
| 68 | +Documentation is a gate, not a courtesy. Examples in `src/*.py` docstrings |
| 69 | +and under `docs/` are executed by `uv run pytest`, because `testpaths` in |
| 70 | +`pyproject.toml` lists `tests`, `docs`, and `src`; `README.md` is not in |
| 71 | +`testpaths`, so its three `>>> ` examples do not currently run under CI. |
| 72 | +There is no separate doctest step for the paths that are collected — a |
| 73 | +green `pytest` is the proof. Which blocks qualify, and the one mistake that |
| 74 | +silently removes a test, are in |
| 75 | +[WRITING.md](WRITING.md#documented-examples-that-run). |
| 76 | + |
| 77 | +Before claiming a test or a gate works, show it failing. A gate that has |
| 78 | +never been red is an assumption. |
| 79 | + |
| 80 | +CI (`.github/workflows/tests.yml`) runs the equivalent of the four commands |
| 81 | +above (`ruff check .`, `ruff format . --check`, `mypy .`, |
| 82 | +`py.test --cov=./ --cov-report=xml`) across a matrix of Python 3.10-3.14, |
| 83 | +docutils 0.20 and 0.22.4, and pytest 8 and 9 — gp-libs supports a wider |
| 84 | +version span than most consumers of it, because everything downstream |
| 85 | +depends on this collector staying compatible. |
| 86 | + |
| 87 | +## Tests |
| 88 | + |
| 89 | +Write tests as standalone functions, not classes — no `class TestFoo:` |
| 90 | +groupings. Use descriptive function names and file organization instead. |
| 91 | + |
| 92 | +Prefer fixtures from `tests/conftest.py` over `monkeypatch` and |
| 93 | +`unittest.mock` when one exists; document in the test docstring why a |
| 94 | +standard fixture was not used for an exceptional case. Use `tmp_path` |
| 95 | +(`pathlib.Path`) over `tempfile`, and `monkeypatch` over `unittest.mock` |
| 96 | +when you do need one. |
| 97 | + |
| 98 | +`tests/conftest.py` also provides the Sphinx `app_params`/`make_app_params` |
| 99 | +fixtures (via `pytest_plugins = ["sphinx.testing.fixtures", "pytester"]`) |
| 100 | +for tests that build a throwaway Sphinx app — used by |
| 101 | +`tests/test_linkify_issues.py` and the plugin-suppression tests. |
| 102 | +`tests/regressions/` holds one file per historical bug, named for its |
| 103 | +issue. |
| 104 | + |
| 105 | +Run continuously with [pytest-watcher]: |
| 106 | + |
| 107 | +```console |
| 108 | +$ just start |
| 109 | +``` |
| 110 | + |
| 111 | +Or with [entr(1)] when you want a shell-only watcher: |
| 112 | + |
| 113 | +```console |
| 114 | +$ just watch-test |
| 115 | +``` |
| 116 | + |
| 117 | +[pytest-watcher]: https://github.com/olzhasar/pytest-watcher |
| 118 | +[entr(1)]: http://eradman.com/entrproject/ |
| 119 | + |
| 120 | +## Documentation |
| 121 | + |
| 122 | +Build the docs: |
| 123 | + |
| 124 | +```console |
| 125 | +$ just build-docs |
| 126 | +``` |
| 127 | + |
| 128 | +Start the default preview server, which watches for file changes: |
| 129 | + |
| 130 | +```console |
| 131 | +$ just start-docs |
| 132 | +``` |
| 133 | + |
| 134 | +From inside `docs/`, the local `docs/justfile` has finer-grained recipes: |
| 135 | +`just html` builds once, `just serve` serves the built output, `just watch` |
| 136 | +rebuilds on change, `just dev` watches and serves together, and |
| 137 | +`just design` disables incremental builds while you edit static assets. |
| 138 | + |
| 139 | +`docs/conf.py` also enables `sphinx.ext.doctest` with a |
| 140 | +`doctest_global_setup` that imports `is_allowed_version` and |
| 141 | +`pytest_ignore_collect`, so the `{doctest}` blocks under `docs/` are valid |
| 142 | +input to Sphinx's own doctest builder as well as to pytest. Run it with |
| 143 | +`just -f docs/justfile doctest`. Neither `tests.yml` nor `docs.yml` invokes |
| 144 | +that recipe — it is a manual, local-only check, not a CI gate. `just |
| 145 | +build-docs` is what CI runs, and it is also the only thing that catches a |
| 146 | +broken `{ref}`/`{doc}` cross-reference; the doctests do not. |
| 147 | + |
| 148 | +## Releasing |
| 149 | + |
| 150 | +gp-libs is pre-1.0: minor version bumps may include breaking changes. [uv] |
| 151 | +handles virtualenv creation, package requirements, versioning, building, |
| 152 | +and publishing — there is no `setup.py` or requirements file. |
| 153 | + |
| 154 | +1. Update `CHANGES` with release notes. |
| 155 | +2. Bump the version in `src/gp_libs.py` and `pyproject.toml`. |
| 156 | +3. Create the release commit: |
| 157 | + |
| 158 | + ```console |
| 159 | + $ git commit -m 'Tag v0.1.1' |
| 160 | + ``` |
| 161 | + |
| 162 | +4. Push the branch for review: |
| 163 | + |
| 164 | + ```console |
| 165 | + $ git push |
| 166 | + ``` |
| 167 | + |
| 168 | +5. After review, the release owner creates and pushes the tag. Never |
| 169 | + create tags. Never push tags. The owner handles tagging and tag pushes, |
| 170 | + because a tag triggers the publish workflow. See |
| 171 | + [Release commits](WRITING.md#release-commits). |
| 172 | + |
| 173 | +## Pull requests |
| 174 | + |
| 175 | +One subject per pull request. Unrelated cleanup found along the way |
| 176 | +belongs in its own commit, and usually in its own pull request. |
| 177 | + |
| 178 | +Discuss a substantial change via an issue before making it. |
| 179 | + |
| 180 | +Commit format is in [WRITING.md](WRITING.md#commits). |
| 181 | + |
| 182 | +## Decorum |
| 183 | + |
| 184 | +- Participants will be tolerant of opposing views. |
| 185 | +- Participants must ensure that their language and actions are free of |
| 186 | + personal attacks and disparaging personal remarks. |
| 187 | +- When interpreting the words and actions of others, participants should |
| 188 | + always assume good intentions. |
| 189 | +- Behaviour which can be reasonably considered harassment will not be |
| 190 | + tolerated. |
| 191 | + |
| 192 | +Based on [Ruby's Community Conduct Guideline](https://www.ruby-lang.org/en/conduct/). |
| 193 | + |
| 194 | +## Security |
| 195 | + |
| 196 | +Please do not open a public issue for a vulnerability. Report it privately |
| 197 | +through GitHub: |
| 198 | +<https://github.com/git-pull/gp-libs/security/advisories/new>. |
0 commit comments