fastapi-breakgate 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastapi_breakgate-1.0.1/.gitignore +19 -0
- fastapi_breakgate-1.0.1/CHANGELOG.md +28 -0
- fastapi_breakgate-1.0.1/LICENSE +21 -0
- fastapi_breakgate-1.0.1/PKG-INFO +191 -0
- fastapi_breakgate-1.0.1/README.md +145 -0
- fastapi_breakgate-1.0.1/action/action.yml +118 -0
- fastapi_breakgate-1.0.1/examples/demo_app/__init__.py +1 -0
- fastapi_breakgate-1.0.1/examples/demo_app/main.py +47 -0
- fastapi_breakgate-1.0.1/examples/demo_app/openapi.snapshot.json +224 -0
- fastapi_breakgate-1.0.1/examples/demo_app/pyproject.toml +10 -0
- fastapi_breakgate-1.0.1/pyproject.toml +139 -0
- fastapi_breakgate-1.0.1/src/fastapi_contract/__init__.py +26 -0
- fastapi_breakgate-1.0.1/src/fastapi_contract/__main__.py +4 -0
- fastapi_breakgate-1.0.1/src/fastapi_contract/__version__.py +1 -0
- fastapi_breakgate-1.0.1/src/fastapi_contract/check.py +167 -0
- fastapi_breakgate-1.0.1/src/fastapi_contract/cli.py +295 -0
- fastapi_breakgate-1.0.1/src/fastapi_contract/comment.py +101 -0
- fastapi_breakgate-1.0.1/src/fastapi_contract/config.py +161 -0
- fastapi_breakgate-1.0.1/src/fastapi_contract/diff/__init__.py +5 -0
- fastapi_breakgate-1.0.1/src/fastapi_contract/diff/classifier.py +779 -0
- fastapi_breakgate-1.0.1/src/fastapi_contract/diff/oasdiff.py +160 -0
- fastapi_breakgate-1.0.1/src/fastapi_contract/diff/policy.py +85 -0
- fastapi_breakgate-1.0.1/src/fastapi_contract/doctor.py +251 -0
- fastapi_breakgate-1.0.1/src/fastapi_contract/export.py +283 -0
- fastapi_breakgate-1.0.1/src/fastapi_contract/intentional.py +109 -0
- fastapi_breakgate-1.0.1/src/fastapi_contract/models.py +33 -0
- fastapi_breakgate-1.0.1/src/fastapi_contract/py.typed +0 -0
- fastapi_breakgate-1.0.1/src/fastapi_contract/pytest_plugin.py +98 -0
- fastapi_breakgate-1.0.1/tests/conftest.py +115 -0
- fastapi_breakgate-1.0.1/tests/test_check_intentional_cli.py +160 -0
- fastapi_breakgate-1.0.1/tests/test_comment_oasdiff_config.py +163 -0
- fastapi_breakgate-1.0.1/tests/test_diff_classifier.py +172 -0
- fastapi_breakgate-1.0.1/tests/test_export_and_doctor.py +102 -0
- fastapi_breakgate-1.0.1/tests/test_integration_e2e.py +341 -0
- fastapi_breakgate-1.0.1/tests/test_more_coverage.py +283 -0
- fastapi_breakgate-1.0.1/tests/test_pytest_plugin.py +24 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
.eggs/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.pytest_cache/
|
|
8
|
+
.mypy_cache/
|
|
9
|
+
.ruff_cache/
|
|
10
|
+
.coverage
|
|
11
|
+
htmlcov/
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
.env
|
|
15
|
+
*.snapshot.json.bak
|
|
16
|
+
contract-report.md
|
|
17
|
+
contract-comment.md
|
|
18
|
+
openapi.json
|
|
19
|
+
.DS_Store
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## 1.0.1 — 2026-11-15
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- PyPI distribution name is **`fastapi-breakgate`** (`fastapi-contract` was already taken on PyPI)
|
|
10
|
+
- CLI entry points: `fastapi-breakgate` (primary) and `fastapi-contract` (alias)
|
|
11
|
+
- GitHub repo: `prakash888kp/fastapi-breakgate`
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Resolve nested OpenAPI `$ref` enums (FastAPI component schemas) during property comparison
|
|
16
|
+
- Diff component-level enum add/remove so policy (`additive-friendly` vs `strict`) applies correctly
|
|
17
|
+
- Expanded integration E2E coverage for additive/breaking/intentional/CLI/pytest-plugin flows
|
|
18
|
+
|
|
19
|
+
## 1.0.0 — 2026-11-15
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Production-ready `fastapi-contract` package with CLI, pytest plugin, and GitHub Action
|
|
24
|
+
- Reliable OpenAPI export ladder (live / factory / snapshot) with `doctor`
|
|
25
|
+
- Pure-Python OpenAPI breaking-change classifier with `additive-friendly` and `strict` policies
|
|
26
|
+
- Optional oasdiff integration when the binary is available
|
|
27
|
+
- Intentional breaking-change protocol (label/title marker + changelog section)
|
|
28
|
+
- PR markdown reports, SemVer bump hints, and GitHub Actions annotations
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 prakash888kp / fastapi-breakgate contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: fastapi-breakgate
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Production merge gate for FastAPI: export live OpenAPI, detect breaking changes, enforce intentional-break protocol.
|
|
5
|
+
Project-URL: Homepage, https://github.com/prakash888kp/fastapi-breakgate
|
|
6
|
+
Project-URL: Documentation, https://github.com/prakash888kp/fastapi-breakgate#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/prakash888kp/fastapi-breakgate
|
|
8
|
+
Project-URL: Issues, https://github.com/prakash888kp/fastapi-breakgate/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/prakash888kp/fastapi-breakgate/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: prakash888kp <prakash888kp@users.noreply.github.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: api-compatibility,breaking-changes,ci,contract-testing,fastapi,openapi
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Framework :: FastAPI
|
|
17
|
+
Classifier: Framework :: Pytest
|
|
18
|
+
Classifier: Intended Audience :: Developers
|
|
19
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Classifier: Programming Language :: Python :: 3
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
27
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
28
|
+
Classifier: Topic :: Software Development :: Testing
|
|
29
|
+
Classifier: Typing :: Typed
|
|
30
|
+
Requires-Python: >=3.10
|
|
31
|
+
Requires-Dist: fastapi>=0.110
|
|
32
|
+
Requires-Dist: rich>=13.7
|
|
33
|
+
Requires-Dist: tomli>=2.0.1; python_version < '3.11'
|
|
34
|
+
Requires-Dist: typer>=0.12
|
|
35
|
+
Requires-Dist: typing-extensions>=4.8; python_version < '3.11'
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
38
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
39
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
40
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
42
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
43
|
+
Requires-Dist: twine>=5.0; extra == 'dev'
|
|
44
|
+
Provides-Extra: oasdiff
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
|
|
47
|
+
# fastapi-breakgate
|
|
48
|
+
|
|
49
|
+
**Production merge gate for FastAPI.** Export live OpenAPI in CI, fail on accidental breaking changes, and make intentional breaks an explicit, changelog-backed review event.
|
|
50
|
+
|
|
51
|
+
> Status: **Beta (v1.0.1)** — core gate is solid; dogfood on real apps welcome.
|
|
52
|
+
> Not a fuzzer (that’s Schemathesis). Not a visual OpenAPI browser. A **hard merge gate** with FastAPI-aware DX.
|
|
53
|
+
|
|
54
|
+
> **Note:** The PyPI name is `fastapi-breakgate` because `fastapi-contract` is already taken by an unrelated project. The import package remains `fastapi_contract`.
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install fastapi-breakgate
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
CLI commands (either entry point works):
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
fastapi-breakgate --help
|
|
66
|
+
fastapi-contract --help
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 5-minute setup
|
|
70
|
+
|
|
71
|
+
### 1. Configure
|
|
72
|
+
|
|
73
|
+
```toml
|
|
74
|
+
# pyproject.toml
|
|
75
|
+
[tool.fastapi_contract]
|
|
76
|
+
app = "myapp.main:app"
|
|
77
|
+
policy = "additive-friendly" # or "strict"
|
|
78
|
+
fail_on = "breaking"
|
|
79
|
+
snapshot_path = "openapi.snapshot.json"
|
|
80
|
+
intentional_label = "breaking-change"
|
|
81
|
+
intentional_title_marker = "[breaking-change]"
|
|
82
|
+
changelog_required_heading = "### Breaking"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 2. Commit a base snapshot
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
fastapi-breakgate doctor --app myapp.main:app
|
|
89
|
+
fastapi-breakgate snapshot --app myapp.main:app
|
|
90
|
+
git add openapi.snapshot.json && git commit -m "chore: add OpenAPI contract snapshot"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3. Gate PRs (GitHub Action)
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
# .github/workflows/contract.yml
|
|
97
|
+
name: API contract
|
|
98
|
+
on:
|
|
99
|
+
pull_request:
|
|
100
|
+
jobs:
|
|
101
|
+
contract:
|
|
102
|
+
runs-on: ubuntu-latest
|
|
103
|
+
steps:
|
|
104
|
+
- uses: actions/checkout@v4
|
|
105
|
+
- uses: actions/setup-python@v5
|
|
106
|
+
with:
|
|
107
|
+
python-version: "3.12"
|
|
108
|
+
- name: Install project
|
|
109
|
+
run: pip install -e ".[dev]"
|
|
110
|
+
- uses: prakash888kp/fastapi-breakgate/action@v1
|
|
111
|
+
with:
|
|
112
|
+
app: myapp.main:app
|
|
113
|
+
fail-on: breaking
|
|
114
|
+
policy: additive-friendly
|
|
115
|
+
install-command: ""
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Or call the CLI directly:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
pip install fastapi-breakgate
|
|
122
|
+
fastapi-breakgate check --app myapp.main:app --annotations
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## CLI
|
|
126
|
+
|
|
127
|
+
| Command | Purpose |
|
|
128
|
+
|---------|---------|
|
|
129
|
+
| `fastapi-breakgate doctor` | Diagnose import/export readiness |
|
|
130
|
+
| `fastapi-breakgate export -o openapi.json` | Export live OpenAPI |
|
|
131
|
+
| `fastapi-breakgate snapshot` | Write committed snapshot |
|
|
132
|
+
| `fastapi-breakgate diff --base-file openapi.snapshot.json` | Classify changes |
|
|
133
|
+
| `fastapi-breakgate check` | CI entrypoint (exit 1 on accidental breaks) |
|
|
134
|
+
| `fastapi-breakgate comment -o pr.md` | Render PR markdown |
|
|
135
|
+
|
|
136
|
+
## Intentional breaking changes
|
|
137
|
+
|
|
138
|
+
Accidental breaks fail CI. Planned breaks must complete the protocol:
|
|
139
|
+
|
|
140
|
+
1. PR label `breaking-change` **or** title contains `[breaking-change]`
|
|
141
|
+
2. `CHANGELOG.md` includes `### Breaking` with at least one bullet
|
|
142
|
+
|
|
143
|
+
Then the check passes with a visible warning and SemVer **major** suggestion.
|
|
144
|
+
|
|
145
|
+
## Pytest plugin
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
def test_openapi_contract(fastapi_contract):
|
|
149
|
+
fastapi_contract.assert_compatible()
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pytest --fastapi-contract-app=myapp.main:app --fastapi-contract-base=openapi.snapshot.json
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Policy
|
|
157
|
+
|
|
158
|
+
| Change | `additive-friendly` (default) | `strict` |
|
|
159
|
+
|--------|-------------------------------|----------|
|
|
160
|
+
| Add optional response field | safe | safe |
|
|
161
|
+
| Add response enum value | **safe** | breaking |
|
|
162
|
+
| Remove path / field | breaking | breaking |
|
|
163
|
+
| Add required request field | breaking | breaking |
|
|
164
|
+
|
|
165
|
+
## Export reliability
|
|
166
|
+
|
|
167
|
+
Real apps often need DB/env at import time. Strategy ladder:
|
|
168
|
+
|
|
169
|
+
1. **Live** import with stub/default env + optional `.env.contract`
|
|
170
|
+
2. **Factory** (`create_app()`) when configured/detected
|
|
171
|
+
3. **Snapshot** fallback in `export_mode = "auto"` with an explicit warning
|
|
172
|
+
|
|
173
|
+
Run `fastapi-breakgate doctor` when CI import fails.
|
|
174
|
+
|
|
175
|
+
## Development
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
pip install -e ".[dev]"
|
|
179
|
+
export PYTHONPATH=src:examples
|
|
180
|
+
python -m coverage run -m pytest
|
|
181
|
+
python -m coverage report --fail-under=75
|
|
182
|
+
ruff check src tests
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Release / PyPI
|
|
186
|
+
|
|
187
|
+
Tagging `vX.Y.Z` (must match `pyproject.toml`) runs CI tests, publishes to PyPI via Trusted Publishing, and creates a GitHub Release.
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# fastapi-breakgate
|
|
2
|
+
|
|
3
|
+
**Production merge gate for FastAPI.** Export live OpenAPI in CI, fail on accidental breaking changes, and make intentional breaks an explicit, changelog-backed review event.
|
|
4
|
+
|
|
5
|
+
> Status: **Beta (v1.0.1)** — core gate is solid; dogfood on real apps welcome.
|
|
6
|
+
> Not a fuzzer (that’s Schemathesis). Not a visual OpenAPI browser. A **hard merge gate** with FastAPI-aware DX.
|
|
7
|
+
|
|
8
|
+
> **Note:** The PyPI name is `fastapi-breakgate` because `fastapi-contract` is already taken by an unrelated project. The import package remains `fastapi_contract`.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install fastapi-breakgate
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
CLI commands (either entry point works):
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
fastapi-breakgate --help
|
|
20
|
+
fastapi-contract --help
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 5-minute setup
|
|
24
|
+
|
|
25
|
+
### 1. Configure
|
|
26
|
+
|
|
27
|
+
```toml
|
|
28
|
+
# pyproject.toml
|
|
29
|
+
[tool.fastapi_contract]
|
|
30
|
+
app = "myapp.main:app"
|
|
31
|
+
policy = "additive-friendly" # or "strict"
|
|
32
|
+
fail_on = "breaking"
|
|
33
|
+
snapshot_path = "openapi.snapshot.json"
|
|
34
|
+
intentional_label = "breaking-change"
|
|
35
|
+
intentional_title_marker = "[breaking-change]"
|
|
36
|
+
changelog_required_heading = "### Breaking"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 2. Commit a base snapshot
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
fastapi-breakgate doctor --app myapp.main:app
|
|
43
|
+
fastapi-breakgate snapshot --app myapp.main:app
|
|
44
|
+
git add openapi.snapshot.json && git commit -m "chore: add OpenAPI contract snapshot"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 3. Gate PRs (GitHub Action)
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
# .github/workflows/contract.yml
|
|
51
|
+
name: API contract
|
|
52
|
+
on:
|
|
53
|
+
pull_request:
|
|
54
|
+
jobs:
|
|
55
|
+
contract:
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
steps:
|
|
58
|
+
- uses: actions/checkout@v4
|
|
59
|
+
- uses: actions/setup-python@v5
|
|
60
|
+
with:
|
|
61
|
+
python-version: "3.12"
|
|
62
|
+
- name: Install project
|
|
63
|
+
run: pip install -e ".[dev]"
|
|
64
|
+
- uses: prakash888kp/fastapi-breakgate/action@v1
|
|
65
|
+
with:
|
|
66
|
+
app: myapp.main:app
|
|
67
|
+
fail-on: breaking
|
|
68
|
+
policy: additive-friendly
|
|
69
|
+
install-command: ""
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Or call the CLI directly:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install fastapi-breakgate
|
|
76
|
+
fastapi-breakgate check --app myapp.main:app --annotations
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## CLI
|
|
80
|
+
|
|
81
|
+
| Command | Purpose |
|
|
82
|
+
|---------|---------|
|
|
83
|
+
| `fastapi-breakgate doctor` | Diagnose import/export readiness |
|
|
84
|
+
| `fastapi-breakgate export -o openapi.json` | Export live OpenAPI |
|
|
85
|
+
| `fastapi-breakgate snapshot` | Write committed snapshot |
|
|
86
|
+
| `fastapi-breakgate diff --base-file openapi.snapshot.json` | Classify changes |
|
|
87
|
+
| `fastapi-breakgate check` | CI entrypoint (exit 1 on accidental breaks) |
|
|
88
|
+
| `fastapi-breakgate comment -o pr.md` | Render PR markdown |
|
|
89
|
+
|
|
90
|
+
## Intentional breaking changes
|
|
91
|
+
|
|
92
|
+
Accidental breaks fail CI. Planned breaks must complete the protocol:
|
|
93
|
+
|
|
94
|
+
1. PR label `breaking-change` **or** title contains `[breaking-change]`
|
|
95
|
+
2. `CHANGELOG.md` includes `### Breaking` with at least one bullet
|
|
96
|
+
|
|
97
|
+
Then the check passes with a visible warning and SemVer **major** suggestion.
|
|
98
|
+
|
|
99
|
+
## Pytest plugin
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
def test_openapi_contract(fastapi_contract):
|
|
103
|
+
fastapi_contract.assert_compatible()
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
pytest --fastapi-contract-app=myapp.main:app --fastapi-contract-base=openapi.snapshot.json
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Policy
|
|
111
|
+
|
|
112
|
+
| Change | `additive-friendly` (default) | `strict` |
|
|
113
|
+
|--------|-------------------------------|----------|
|
|
114
|
+
| Add optional response field | safe | safe |
|
|
115
|
+
| Add response enum value | **safe** | breaking |
|
|
116
|
+
| Remove path / field | breaking | breaking |
|
|
117
|
+
| Add required request field | breaking | breaking |
|
|
118
|
+
|
|
119
|
+
## Export reliability
|
|
120
|
+
|
|
121
|
+
Real apps often need DB/env at import time. Strategy ladder:
|
|
122
|
+
|
|
123
|
+
1. **Live** import with stub/default env + optional `.env.contract`
|
|
124
|
+
2. **Factory** (`create_app()`) when configured/detected
|
|
125
|
+
3. **Snapshot** fallback in `export_mode = "auto"` with an explicit warning
|
|
126
|
+
|
|
127
|
+
Run `fastapi-breakgate doctor` when CI import fails.
|
|
128
|
+
|
|
129
|
+
## Development
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pip install -e ".[dev]"
|
|
133
|
+
export PYTHONPATH=src:examples
|
|
134
|
+
python -m coverage run -m pytest
|
|
135
|
+
python -m coverage report --fail-under=75
|
|
136
|
+
ruff check src tests
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Release / PyPI
|
|
140
|
+
|
|
141
|
+
Tagging `vX.Y.Z` (must match `pyproject.toml`) runs CI tests, publishes to PyPI via Trusted Publishing, and creates a GitHub Release.
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
name: fastapi-breakgate
|
|
2
|
+
description: Fail PRs on accidental FastAPI OpenAPI breaking changes.
|
|
3
|
+
author: prakash888kp
|
|
4
|
+
branding:
|
|
5
|
+
icon: shield
|
|
6
|
+
color: green
|
|
7
|
+
|
|
8
|
+
inputs:
|
|
9
|
+
app:
|
|
10
|
+
description: FastAPI target as module:attr (e.g. myapp.main:app)
|
|
11
|
+
required: true
|
|
12
|
+
fail-on:
|
|
13
|
+
description: breaking | risky | any
|
|
14
|
+
required: false
|
|
15
|
+
default: breaking
|
|
16
|
+
policy:
|
|
17
|
+
description: additive-friendly | strict
|
|
18
|
+
required: false
|
|
19
|
+
default: additive-friendly
|
|
20
|
+
base-file:
|
|
21
|
+
description: Path to base OpenAPI JSON (defaults to openapi.snapshot.json)
|
|
22
|
+
required: false
|
|
23
|
+
default: ""
|
|
24
|
+
working-directory:
|
|
25
|
+
description: Directory containing the FastAPI project
|
|
26
|
+
required: false
|
|
27
|
+
default: "."
|
|
28
|
+
python-version:
|
|
29
|
+
description: Python version for the runner setup (informational; use setup-python before)
|
|
30
|
+
required: false
|
|
31
|
+
default: "3.12"
|
|
32
|
+
install-command:
|
|
33
|
+
description: Command to install project dependencies before the check
|
|
34
|
+
required: false
|
|
35
|
+
default: "pip install -e ."
|
|
36
|
+
pr-title:
|
|
37
|
+
description: Override PR title for intentional protocol (defaults to github.event.pull_request.title)
|
|
38
|
+
required: false
|
|
39
|
+
default: ""
|
|
40
|
+
pr-labels:
|
|
41
|
+
description: Comma-separated labels (defaults to PR labels)
|
|
42
|
+
required: false
|
|
43
|
+
default: ""
|
|
44
|
+
report-path:
|
|
45
|
+
description: Where to write the markdown report
|
|
46
|
+
required: false
|
|
47
|
+
default: contract-report.md
|
|
48
|
+
|
|
49
|
+
outputs:
|
|
50
|
+
report-path:
|
|
51
|
+
description: Path to the generated markdown report
|
|
52
|
+
value: ${{ inputs.report-path }}
|
|
53
|
+
conclusion:
|
|
54
|
+
description: success or failure
|
|
55
|
+
value: ${{ steps.check.outputs.conclusion }}
|
|
56
|
+
|
|
57
|
+
runs:
|
|
58
|
+
using: composite
|
|
59
|
+
steps:
|
|
60
|
+
- name: Install fastapi-breakgate
|
|
61
|
+
shell: bash
|
|
62
|
+
working-directory: ${{ inputs.working-directory }}
|
|
63
|
+
run: |
|
|
64
|
+
python -m pip install --upgrade pip
|
|
65
|
+
python -m pip install "fastapi-breakgate>=1.0.1"
|
|
66
|
+
if [ -n "${{ inputs.install-command }}" ]; then
|
|
67
|
+
${{ inputs.install-command }}
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
- name: Resolve PR metadata
|
|
71
|
+
id: meta
|
|
72
|
+
shell: bash
|
|
73
|
+
run: |
|
|
74
|
+
TITLE="${{ inputs.pr-title }}"
|
|
75
|
+
if [ -z "$TITLE" ]; then
|
|
76
|
+
TITLE="${{ github.event.pull_request.title }}"
|
|
77
|
+
fi
|
|
78
|
+
LABELS="${{ inputs.pr-labels }}"
|
|
79
|
+
if [ -z "$LABELS" ]; then
|
|
80
|
+
LABELS=$(python - <<'PY'
|
|
81
|
+
import json, os
|
|
82
|
+
event_path = os.environ.get("GITHUB_EVENT_PATH")
|
|
83
|
+
labels = []
|
|
84
|
+
if event_path and os.path.isfile(event_path):
|
|
85
|
+
with open(event_path, encoding="utf-8") as f:
|
|
86
|
+
event = json.load(f)
|
|
87
|
+
for label in event.get("pull_request", {}).get("labels", []) or []:
|
|
88
|
+
name = label.get("name")
|
|
89
|
+
if name:
|
|
90
|
+
labels.append(name)
|
|
91
|
+
print(",".join(labels))
|
|
92
|
+
PY
|
|
93
|
+
)
|
|
94
|
+
fi
|
|
95
|
+
echo "title=$TITLE" >> "$GITHUB_OUTPUT"
|
|
96
|
+
echo "labels=$LABELS" >> "$GITHUB_OUTPUT"
|
|
97
|
+
|
|
98
|
+
- name: Run contract check
|
|
99
|
+
id: check
|
|
100
|
+
shell: bash
|
|
101
|
+
working-directory: ${{ inputs.working-directory }}
|
|
102
|
+
env:
|
|
103
|
+
FAC_PR_TITLE: ${{ steps.meta.outputs.title }}
|
|
104
|
+
FAC_PR_LABELS: ${{ steps.meta.outputs.labels }}
|
|
105
|
+
run: |
|
|
106
|
+
set +e
|
|
107
|
+
ARGS=(check --app "${{ inputs.app }}" --fail-on "${{ inputs.fail-on }}" --policy "${{ inputs.policy }}" --report "${{ inputs.report-path }}" --annotations)
|
|
108
|
+
if [ -n "${{ inputs.base-file }}" ]; then
|
|
109
|
+
ARGS+=(--base-file "${{ inputs.base-file }}")
|
|
110
|
+
fi
|
|
111
|
+
fastapi-breakgate "${ARGS[@]}"
|
|
112
|
+
CODE=$?
|
|
113
|
+
if [ "$CODE" -eq 0 ]; then
|
|
114
|
+
echo "conclusion=success" >> "$GITHUB_OUTPUT"
|
|
115
|
+
else
|
|
116
|
+
echo "conclusion=failure" >> "$GITHUB_OUTPUT"
|
|
117
|
+
fi
|
|
118
|
+
exit "$CODE"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Demo FastAPI application used in docs and local smoke tests."""
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Minimal FastAPI app demonstrating fastapi-contract."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from fastapi import FastAPI
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Status(str, Enum):
|
|
13
|
+
ACTIVE = "active"
|
|
14
|
+
INACTIVE = "inactive"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ItemCreate(BaseModel):
|
|
18
|
+
name: str = Field(..., min_length=1)
|
|
19
|
+
status: Status = Status.ACTIVE
|
|
20
|
+
note: Optional[str] = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Item(BaseModel):
|
|
24
|
+
id: int
|
|
25
|
+
name: str
|
|
26
|
+
status: Status
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def create_app() -> FastAPI:
|
|
30
|
+
app = FastAPI(title="Demo API", version="1.0.0")
|
|
31
|
+
|
|
32
|
+
@app.get("/health")
|
|
33
|
+
def health() -> dict[str, str]:
|
|
34
|
+
return {"status": "ok"}
|
|
35
|
+
|
|
36
|
+
@app.get("/items/{item_id}", response_model=Item)
|
|
37
|
+
def get_item(item_id: int) -> Item:
|
|
38
|
+
return Item(id=item_id, name="demo", status=Status.ACTIVE)
|
|
39
|
+
|
|
40
|
+
@app.post("/items", response_model=Item, status_code=201)
|
|
41
|
+
def create_item(payload: ItemCreate) -> Item:
|
|
42
|
+
return Item(id=1, name=payload.name, status=payload.status)
|
|
43
|
+
|
|
44
|
+
return app
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
app = create_app()
|