Skip to content

Commit c2b7827

Browse files
committed
LCORE-1958: Add Spectral OpenAPI linting (verify + CI)
Add .spectral.yaml, make lint-openapi (optional npx locally), wire into verify, GitHub workflow to regenerate-check docs/openapi.json and run Spectral, and CONTRIBUTING prerequisites for Node. Extend contributing doc with Spectral section.
1 parent a0ad26f commit c2b7827

5 files changed

Lines changed: 78 additions & 0 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# OpenAPI: regenerate check (Spectral + committed docs/openapi.json drift).
2+
# - scripts/generate_openapi_schema.py builds the spec from FastAPI (app.main).
3+
# - CI fails if docs/openapi.json does not match generator output (run locally:
4+
# uv run scripts/generate_openapi_schema.py docs/openapi.json).
5+
name: OpenAPI (Spectral)
6+
7+
on:
8+
- push
9+
- pull_request
10+
11+
jobs:
12+
spectral:
13+
runs-on: ubuntu-latest
14+
permissions:
15+
contents: read
16+
pull-requests: read
17+
steps:
18+
- uses: actions/checkout@v4
19+
- name: Install uv
20+
uses: astral-sh/setup-uv@v5
21+
with:
22+
python-version: "3.12"
23+
- name: Install dependencies
24+
# Same pattern as local dev (CONTRIBUTING.md): dev + llslibdev for a full app import.
25+
run: uv sync --group dev --group llslibdev
26+
- name: Install PDM
27+
# scripts/generate_openapi_schema.py asserts OpenAPI info.version matches `pdm show --version`.
28+
run: uv pip install pdm
29+
- name: Verify docs/openapi.json matches generator
30+
run: |
31+
set -euo pipefail
32+
uv run python scripts/generate_openapi_schema.py /tmp/openapi-generated.json
33+
if ! diff -u docs/openapi.json /tmp/openapi-generated.json; then
34+
echo "::error::docs/openapi.json is out of date. Regenerate with: uv run scripts/generate_openapi_schema.py docs/openapi.json"
35+
exit 1
36+
fi
37+
- name: Setup Node.js
38+
uses: actions/setup-node@v4
39+
with:
40+
node-version: "22"
41+
- name: Spectral lint
42+
run: npx --yes @stoplight/spectral-cli@6 lint docs/openapi.json --fail-severity error --display-only-failures

.spectral.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Spectral (OpenAPI) — https://meta.stoplight.io/docs/spectral/
2+
#
3+
# Full Stoplight OAS ruleset. `oas3-valid-media-example` is off: examples in
4+
# docs/openapi.json are often partial and produced ~600 warnings with little
5+
# signal until examples are aligned with schemas. Re-enable when tightening
6+
# examples (set to "warn" or "error").
7+
extends: spectral:oas
8+
rules:
9+
oas3-valid-media-example: off

CONTRIBUTING.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
* [Pre-commit hook settings](#pre-commit-hook-settings)
1919
* [Code coverage measurement](#code-coverage-measurement)
2020
* [Linters](#linters)
21+
* [OpenAPI (Spectral)](#openapi-spectral)
2122
* [Type hints checks](#type-hints-checks)
2223
* [Ruff](#ruff)
2324
* [Pylint](#pylint)
@@ -48,6 +49,7 @@
4849
- git
4950
- Python 3.12 or 3.13
5051
- pip
52+
- **Node.js** (18 or newer; **npm** and **`npx`** ship with Node). **`make verify`** runs **`lint-openapi`**, which calls **`npx --yes @stoplight/spectral-cli@6`** (see `Makefile`). If **`npx`** is not installed, **`lint-openapi` is skipped** with a message so **`make verify` still succeeds** locally; install Node to run the OpenAPI check. **CI** always runs Spectral (see `.github/workflows/openapi_spectral.yaml`).
5153

5254
The development requires at least [Python 3.12](https://docs.python.org/3/whatsnew/3.12.html) due to significant improvement on performance, optimizations which benefit modern ML, AI, LLM, NL stacks, and improved asynchronous processing capabilities. It is also possible to use Python 3.13.
5355

@@ -57,6 +59,7 @@ The development requires at least [Python 3.12](https://docs.python.org/3/whatsn
5759

5860
1. `pip install --user uv`
5961
1. `uv --version` -- should return no error
62+
1. Install [Node.js](https://nodejs.org/en/download) (LTS is fine) or use your OS package manager, e.g. Fedora: `sudo dnf install nodejs`, macOS with [Homebrew](https://brew.sh/): `brew install node`. Confirm `node --version` and `npx --version` work. CI uses Node 22 for Spectral (see `.github/workflows/openapi_spectral.yaml`).
6063

6164

6265

@@ -217,6 +220,10 @@ Code coverage reports are generated in JSON and also in format compatible with [
217220

218221
_Black_, _Ruff_, Pyright, _Pylint_, __Pydocstyle__, __Mypy__, and __Bandit__ tools are used as linters. There are a bunch of linter rules enabled for this repository. All of them are specified in `pyproject.toml`, such as in sections `[tool.ruff]` and `[tool.pylint."MESSAGES CONTROL"]`. Some specific rules can be disabled using `ignore` parameter (empty now).
219222

223+
### OpenAPI (Spectral)
224+
225+
OpenAPI is linted with [Spectral](https://stoplight.io/open-api/) via **`npx --yes @stoplight/spectral-cli@6`** in the **`lint-openapi`** target (`make lint-openapi`, part of **`make verify`**). If **`npx`** is missing, **`lint-openapi`** skips Spectral locally; install **Node.js** to run it (see [Prerequisites](#prerequisites) and [Tooling installation](#tooling-installation)). **CI** always runs the Spectral step. If you introduce a **new** router tag (`APIRouter(tags=[...])`), you must also extend the global tag list in `src/app/main.py` and regenerate `docs/openapi.json`. See **[docs/contributing/openapi-tags-and-spectral.md](docs/contributing/openapi-tags-and-spectral.md)**.
226+
220227

221228
### Type hints checks
222229

Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,21 @@ docstyle: ## Check the docstring style using Docstyle checker
113113
ruff: ## Check source code using Ruff linter
114114
uv run ruff check src tests --per-file-ignores=tests/*:S101 --per-file-ignores=scripts/*:S101
115115

116+
lint-openapi: ## Lint docs/openapi.json (Spectral OAS ruleset; fail on error)
117+
@if command -v npx >/dev/null 2>&1; then \
118+
npx --yes @stoplight/spectral-cli@6 lint docs/openapi.json --fail-severity error --display-only-failures; \
119+
else \
120+
echo "lint-openapi: skipping Spectral (npx not found). Install Node.js for OpenAPI lint locally; CI still runs it."; \
121+
fi
122+
116123
verify: ## Run all linters
117124
$(MAKE) black
118125
$(MAKE) pylint
119126
$(MAKE) pyright
120127
$(MAKE) ruff
121128
$(MAKE) docstyle
122129
$(MAKE) check-types
130+
$(MAKE) lint-openapi
123131

124132
distribution-archives: ## Generate distribution archives to be uploaded into Python registry
125133
rm -rf dist

docs/contributing/openapi-tags-and-spectral.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,15 @@ The schema generator **`scripts/generate_openapi_schema.py`** passes **`tags=app
1313
```bash
1414
uv run scripts/generate_openapi_schema.py docs/openapi.json
1515
```
16+
17+
## Linting (`make lint-openapi`)
18+
19+
Spectral is configured in **`.spectral.yaml`** (extends `spectral:oas`). Run:
20+
21+
```bash
22+
make lint-openapi
23+
```
24+
25+
This is part of **`make verify`**. If **`npx`** is not on your **`PATH`**, the Makefile **skips** Spectral and prints a short message so **`make verify`** can still pass; install Node.js to run the check locally. **CI** (`.github/workflows/openapi_spectral.yaml`) always runs Spectral. Failures are driven by **error**-severity rules.
26+
27+
The rule **`oas3-valid-media-example`** (examples must match schemas) is **turned off** in **`.spectral.yaml`** because the generated spec carries many partial examples and produced hundreds of noisy warnings. Turn it back on when examples are brought in line with schemas.

0 commit comments

Comments
 (0)