Skip to content

Commit 75dfae5

Browse files
authored
Consolidate shared Sphinx infra into 4 workspace packages (#5)
Extracts duplicated Sphinx documentation configuration, extensions, and theme from 14+ git-pull repositories into a UV workspace monorepo with 4 independent packages. A single `merge_sphinx_config()` call replaces ~300 lines of per-project conf.py boilerplate with ~10. **Packages:** - **gp-sphinx** — config coordinator: `merge_sphinx_config()`, `make_linkcode_resolve()`, shared defaults for 13 extensions - **sphinx-argparse-neo** — modern argparse CLI doc extension (was duplicated across 4 repos) - **sphinx-fonts** — Fontsource CDN font extension with build-time download and caching (was duplicated across 7 repos) - **sphinx-gptheme** — Furo child theme with custom templates, CSS variables, SPA navigation, and sidebar **Config consolidation:** autodoc, napoleon, copybutton, MyST, intersphinx, rediraffe, pygments, font families, and HTML paths are all shared defaults. `issue_url_tpl` and `ogp_*` are auto-computed from `source_repository` and `docs_url`. `sphinx.ext.linkcode` is auto-added when `linkcode_resolve` is passed as an override. **PEP 561 typing:** All 4 packages ship py.typed markers with strict mypy. TypedDicts replace `dict[str, Any]` for theme options, font config, footer icons, and extension setup returns. Argparse fields narrowed from `Any` to `object`. **Code quality:** NullHandler on library loggers, stdlib namespace imports, `next()` safety with explicit ValueError, working doctests replacing commented-out examples, dead code removal.
2 parents a182045 + 7151c0a commit 75dfae5

67 files changed

Lines changed: 13727 additions & 354 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/docs.yml

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ jobs:
1414
build:
1515
runs-on: ubuntu-latest
1616
environment: docs
17-
strategy:
18-
matrix:
19-
python-version: ['3.14']
2017
steps:
2118
- uses: actions/checkout@v6
2219

@@ -25,47 +22,45 @@ jobs:
2522
id: changes
2623
with:
2724
filters: |
28-
root_docs:
29-
- CHANGES
30-
- README.*
31-
docs:
25+
publishable:
26+
- 'README.*'
27+
- 'CHANGES'
3228
- 'docs/**'
33-
- 'examples/**'
34-
python_files:
35-
- 'src/gp_sphinx/**/*.py'
36-
- pyproject.toml
37-
- uv.lock
38-
39-
- name: Should publish
40-
# if: steps.changes.outputs.docs == 'true' || steps.changes.outputs.root_docs == 'true' || steps.changes.outputs.python_files == 'true'
41-
run: echo "PUBLISH=$(echo true)" >> $GITHUB_ENV
29+
- 'packages/**'
30+
- 'scripts/ci/**'
31+
- '.github/workflows/docs.yml'
32+
- '.github/workflows/tests.yml'
33+
- 'pyproject.toml'
34+
- 'uv.lock'
35+
36+
- name: Set up Python 3.14
37+
if: steps.changes.outputs.publishable == 'true'
38+
uses: actions/setup-python@v6
39+
with:
40+
python-version: "3.14"
4241

4342
- name: Install uv
43+
if: steps.changes.outputs.publishable == 'true'
4444
uses: astral-sh/setup-uv@v7
45-
if: env.PUBLISH == 'true'
4645
with:
4746
enable-cache: true
4847

49-
- name: Set up Python ${{ matrix.python-version }}
50-
if: env.PUBLISH == 'true'
51-
run: uv python install ${{ matrix.python-version }}
52-
53-
- name: Install dependencies
54-
if: env.PUBLISH == 'true'
55-
run: uv sync --all-extras --dev
48+
- name: Install workspace dependencies
49+
if: steps.changes.outputs.publishable == 'true'
50+
run: uv sync --all-packages --all-extras --group dev
5651

5752
- name: Install just
58-
if: env.PUBLISH == 'true'
53+
if: steps.changes.outputs.publishable == 'true'
5954
uses: extractions/setup-just@v3
6055

61-
- name: Print python versions
62-
if: env.PUBLISH == 'true'
56+
- name: Print Python versions
57+
if: steps.changes.outputs.publishable == 'true'
6358
run: |
6459
python -V
6560
uv run python -V
6661
6762
- name: Cache sphinx fonts
68-
if: env.PUBLISH == 'true'
63+
if: steps.changes.outputs.publishable == 'true'
6964
uses: actions/cache@v5
7065
with:
7166
path: ~/.cache/sphinx-fonts
@@ -74,32 +69,31 @@ jobs:
7469
sphinx-fonts-
7570
7671
- name: Build documentation
77-
if: env.PUBLISH == 'true'
78-
run: |
79-
cd docs && just html
72+
if: steps.changes.outputs.publishable == 'true'
73+
run: uv run sphinx-build -W -b html docs docs/_build/html
8074

8175
- name: Configure AWS Credentials
82-
if: env.PUBLISH == 'true'
76+
if: steps.changes.outputs.publishable == 'true'
8377
uses: aws-actions/configure-aws-credentials@v6
8478
with:
8579
role-to-assume: ${{ secrets.GP_SPHINX_DOCS_ROLE_ARN }}
8680
aws-region: us-east-1
8781

8882
- name: Push documentation to S3
89-
if: env.PUBLISH == 'true'
83+
if: steps.changes.outputs.publishable == 'true'
9084
run: |
9185
aws s3 sync docs/_build/html "s3://${{ secrets.GP_SPHINX_DOCS_BUCKET }}" \
9286
--delete --follow-symlinks
9387
9488
- name: Invalidate CloudFront
95-
if: env.PUBLISH == 'true'
89+
if: steps.changes.outputs.publishable == 'true'
9690
run: |
9791
aws cloudfront create-invalidation \
9892
--distribution-id "${{ secrets.GP_SPHINX_DOCS_DISTRIBUTION }}" \
9993
--paths "/index.html" "/objects.inv" "/searchindex.js"
10094
10195
- name: Purge cache on Cloudflare
102-
if: env.PUBLISH == 'true'
96+
if: steps.changes.outputs.publishable == 'true'
10397
uses: jakejarvis/cloudflare-purge-action@v0.3.0
10498
env:
10599
CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }}

.github/workflows/release.yml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
permissions:
9+
contents: read
10+
id-token: write
11+
attestations: write
12+
13+
jobs:
14+
publish:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v6
18+
19+
- name: Set up Python 3.14
20+
uses: actions/setup-python@v6
21+
with:
22+
python-version: "3.14"
23+
24+
- name: Install uv
25+
uses: astral-sh/setup-uv@v7
26+
with:
27+
enable-cache: true
28+
29+
- name: Resolve release metadata
30+
id: release
31+
run: |
32+
python - <<'PY' "${GITHUB_REF_NAME}" >> "${GITHUB_OUTPUT}"
33+
import json
34+
import pathlib
35+
import sys
36+
37+
sys.path.insert(0, str(pathlib.Path("scripts/ci").resolve()))
38+
import package_tools
39+
40+
metadata = package_tools.release_metadata(sys.argv[1])
41+
for key, value in metadata.items():
42+
print(f"{key}={value}")
43+
PY
44+
45+
- name: Install workspace dependencies
46+
run: uv sync --all-packages --all-extras --group dev
47+
48+
- name: Validate lockstep versions
49+
run: uv run python scripts/ci/package_tools.py check-versions
50+
51+
- name: Build publishable packages
52+
run: |
53+
rm -rf dist
54+
mkdir -p dist
55+
while IFS= read -r package; do
56+
uv build --package "$package" --out-dir dist
57+
done < <(uv run python scripts/ci/package_tools.py print-packages)
58+
59+
- name: Validate distributions with twine
60+
run: uvx twine check dist/*
61+
62+
- name: Smoke test publishable packages
63+
run: |
64+
while IFS= read -r package; do
65+
uv run python scripts/ci/package_tools.py smoke "$package" --dist-dir dist
66+
done < <(uv run python scripts/ci/package_tools.py print-packages)
67+
68+
- name: Publish packages
69+
uses: pypa/gh-action-pypi-publish@release/v1
70+
with:
71+
attestations: true
72+
packages-dir: dist/
73+
skip-existing: true

.github/workflows/tests.yml

Lines changed: 107 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,33 @@
1-
name: tests
1+
name: ci
22

3-
on: [push, pull_request]
3+
on:
4+
push:
5+
pull_request:
46

57
jobs:
6-
build:
7-
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
8-
8+
qa:
99
runs-on: ubuntu-latest
1010
strategy:
11+
fail-fast: false
1112
matrix:
12-
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
13+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
1314
steps:
1415
- uses: actions/checkout@v6
1516

17+
- name: Set up Python ${{ matrix.python-version }}
18+
uses: actions/setup-python@v6
19+
with:
20+
python-version: ${{ matrix.python-version }}
21+
1622
- name: Install uv
1723
uses: astral-sh/setup-uv@v7
1824
with:
1925
enable-cache: true
2026

21-
- name: Set up Python ${{ matrix.python-version }}
22-
run: uv python install ${{ matrix.python-version }}
23-
24-
- name: Install dependencies
25-
run: uv sync --all-extras --dev
27+
- name: Install workspace dependencies
28+
run: uv sync --all-packages --all-extras --group dev
2629

27-
- name: Print python and pytest versions
30+
- name: Print Python versions
2831
run: |
2932
python -V
3033
uv run python -V
@@ -36,49 +39,118 @@ jobs:
3639
- name: Format with ruff format
3740
run: uv run ruff format . --check
3841

39-
- name: Lint with mypy
42+
- name: Type check with mypy
4043
run: uv run mypy .
4144

45+
- name: Validate package runtime versions
46+
if: matrix.python-version == '3.14'
47+
run: uv run python scripts/ci/package_tools.py check-versions
48+
4249
- name: Test with pytest
43-
run: uv run py.test --cov=./ --cov-report=xml
50+
if: matrix.python-version != '3.14'
51+
run: uv run pytest
52+
53+
- name: Test with pytest and coverage
54+
if: matrix.python-version == '3.14'
55+
run: uv run pytest --cov=./ --cov-report=xml
4456

4557
- uses: codecov/codecov-action@v6
58+
if: matrix.python-version == '3.14'
4659
with:
4760
token: ${{ secrets.CODECOV_TOKEN }}
4861

49-
release:
62+
docs:
5063
runs-on: ubuntu-latest
51-
needs: build
52-
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
53-
permissions:
54-
id-token: write
55-
attestations: write
64+
needs: qa
65+
steps:
66+
- uses: actions/checkout@v6
5667

57-
strategy:
58-
matrix:
59-
python-version: ['3.14']
68+
- name: Set up Python 3.14
69+
uses: actions/setup-python@v6
70+
with:
71+
python-version: "3.14"
72+
73+
- name: Install uv
74+
uses: astral-sh/setup-uv@v7
75+
with:
76+
enable-cache: true
77+
78+
- name: Install workspace dependencies
79+
run: uv sync --all-packages --all-extras --group dev
6080

81+
- name: Build documentation with warnings as errors
82+
run: uv run sphinx-build -W -b html docs docs/_build/html
83+
84+
packages:
85+
runs-on: ubuntu-latest
86+
needs: qa
6187
steps:
6288
- uses: actions/checkout@v6
6389

90+
- name: Set up Python 3.14
91+
uses: actions/setup-python@v6
92+
with:
93+
python-version: "3.14"
94+
6495
- name: Install uv
6596
uses: astral-sh/setup-uv@v7
6697
with:
6798
enable-cache: true
6899

69-
- name: Set up Python ${{ matrix.python-version }}
70-
run: uv python install ${{ matrix.python-version }}
100+
- name: Install workspace dependencies
101+
run: uv sync --all-packages --all-extras --group dev
71102

72-
- name: Install dependencies
73-
run: uv sync --all-extras --dev
103+
- name: Validate package runtime versions
104+
run: uv run python scripts/ci/package_tools.py check-versions
74105

75-
- name: Build package
76-
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
77-
run: uv build
106+
- name: Build all workspace packages
107+
run: uv build --all-packages --out-dir dist --clear
78108

79-
- name: Publish package
80-
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
81-
uses: pypa/gh-action-pypi-publish@release/v1
109+
- name: Validate distributions with twine
110+
run: uvx twine check dist/*
111+
112+
- name: Upload package distributions
113+
uses: actions/upload-artifact@v7
82114
with:
83-
attestations: true
84-
skip-existing: true
115+
name: dist
116+
path: dist/
117+
118+
smoke:
119+
runs-on: ubuntu-latest
120+
needs: packages
121+
strategy:
122+
fail-fast: false
123+
matrix:
124+
target:
125+
- root-install
126+
- gp-sphinx
127+
- sphinx-gptheme
128+
- sphinx-fonts
129+
- sphinx-argparse-neo
130+
steps:
131+
- uses: actions/checkout@v6
132+
133+
- name: Set up Python 3.14
134+
uses: actions/setup-python@v6
135+
with:
136+
python-version: "3.14"
137+
138+
- name: Install uv
139+
uses: astral-sh/setup-uv@v7
140+
with:
141+
enable-cache: true
142+
143+
- name: Download package distributions
144+
if: matrix.target != 'root-install'
145+
uses: actions/download-artifact@v8
146+
with:
147+
name: dist
148+
path: dist
149+
150+
- name: Smoke test root bootstrap install
151+
if: matrix.target == 'root-install'
152+
run: python scripts/ci/package_tools.py smoke root-install
153+
154+
- name: Smoke test built package artifact
155+
if: matrix.target != 'root-install'
156+
run: python scripts/ci/package_tools.py smoke "${{ matrix.target }}" --dist-dir dist

AGENTS.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,10 @@ This project uses:
3838

3939
```bash
4040
# Install dependencies
41-
uv pip install --editable .
42-
uv pip sync
41+
uv sync --all-packages
4342

4443
# Install with development dependencies
45-
uv pip install --editable . -G dev
44+
uv sync --all-packages --all-extras --group dev
4645
```
4746

4847
### Running Tests

0 commit comments

Comments
 (0)