Skip to content

Commit 3905554

Browse files
Merge pull request #6 from block/feat/art-850/enable-gha-openapi-spec-update
feat: upgrade codegen, refresh models, and add OpenAPI drift check
2 parents 985b228 + 219a27c commit 3905554

File tree

427 files changed

+2105
-950
lines changed

Some content is hidden

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

427 files changed

+2105
-950
lines changed

.githooks/commit-msg

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/sh
2+
3+
commit_msg=$(head -1 "$1")
4+
5+
if ! echo "$commit_msg" | grep -qE '^(feat|fix|chore|docs|ci|test|refactor|perf|build|style|revert)(\(.+\))?(!)?: .+'; then
6+
echo "ERROR: Commit message must follow Conventional Commits format"
7+
echo ""
8+
echo " <type>[optional scope][!]: <description>"
9+
echo ""
10+
echo " Types: feat, fix, chore, docs, ci, test, refactor, perf, build, style, revert"
11+
echo ""
12+
echo " Examples:"
13+
echo " feat: add dashboard endpoints"
14+
echo " fix(models): handle null chart_settings"
15+
echo " feat!: rename VantageSDK to VantageClient"
16+
echo ""
17+
exit 1
18+
fi
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Check OpenAPI Spec
2+
3+
on:
4+
schedule:
5+
- cron: '0 9 * * *' # Daily at 9am UTC
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: write
10+
pull-requests: write
11+
12+
jobs:
13+
check-spec:
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
18+
19+
- name: Install the latest version of uv
20+
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
21+
with:
22+
version: "latest"
23+
enable-cache: true
24+
25+
- name: Fetch latest spec and regenerate models
26+
run: |
27+
curl -sS https://api.vantage.sh/v2/oas_v3.json -o openapi_spec.json
28+
uv run datamodel-codegen
29+
30+
- name: Check for differences
31+
id: diff
32+
run: |
33+
if [[ -n "$(git status --porcelain --untracked-files=all -- vantage_sdk/models/gen_models/)" ]]; then
34+
echo "changed=true" >> "$GITHUB_OUTPUT"
35+
else
36+
echo "changed=false" >> "$GITHUB_OUTPUT"
37+
fi
38+
39+
- name: Create pull request
40+
if: steps.diff.outputs.changed == 'true'
41+
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7
42+
with:
43+
commit-message: "chore: update OpenAPI spec and regenerate models"
44+
title: "chore: update OpenAPI spec and regenerate models"
45+
body: |
46+
The upstream Vantage OpenAPI spec has changed. This PR updates `openapi_spec.json` and regenerates the models in `vantage_sdk/models/gen_models/`.
47+
48+
Please review the generated model changes for any breaking impacts.
49+
branch: chore/update-openapi-spec
50+
delete-branch: true

.github/workflows/python-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
branches: '*'
88

99
jobs:
10-
tests:
10+
python-test:
1111
runs-on: ubuntu-latest
1212

1313
steps:

CONTRIBUTORS.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22

33
## Development Setup
44

5-
Install the sdk locally and run tests when you finish making changes:
5+
```bash
6+
just setup
7+
```
8+
9+
This configures git hooks that enforce [Conventional Commits](https://www.conventionalcommits.org/) on commit messages (see [Releases](#releases)).
10+
11+
To run all checks (format, lint, typecheck, test):
612

713
```bash
814
uv run nox
@@ -78,6 +84,19 @@ Re-record cassettes when:
7884
- An API response schema changes (use `just test-vcr-rewrite tests/test_main.py::test_affected_test`)
7985
- A fixture changes the request payload (field values, resource names, etc.)
8086

87+
## Releases
88+
89+
Releases are automated via [release-please](https://github.com/googleapis/release-please). PR titles must use [Conventional Commits](https://www.conventionalcommits.org/) prefixes, and PRs must be squash-merged so the title becomes the commit message.
90+
91+
| Prefix | Version bump | Example |
92+
|---|---|---|
93+
| `fix:` | Patch (1.1.1 -> 1.1.2) | `fix: handle null chart_settings in cost reports` |
94+
| `feat:` | Minor (1.1.1 -> 1.2.0) | `feat: add dashboard endpoints` |
95+
| `feat!:` | Major (1.1.1 -> 2.0.0) | `feat!: rename VantageSDK to VantageClient` |
96+
| `chore:`, `docs:`, `ci:` | No release | `chore: update dev dependencies` |
97+
98+
When commits land on `main`, release-please opens (or updates) a Release PR that bumps the version in `pyproject.toml` and updates `CHANGELOG.md`. Merging that Release PR triggers the publish to PyPI. Do not manually edit the version in `pyproject.toml`.
99+
81100
## Regenerating Models
82101

83102
`vantage_sdk/models/gen_models/` is generated using the `datamodel-codegen` tool. All codegen configuration lives in `pyproject.toml` under `[tool.datamodel-codegen]`.

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ This SDK is a Python client built around the [Vantage API], providing a streamli
3232

3333
```python
3434
from vantage_sdk import VantageSDK
35-
from vantage_sdk.models import CreateCostReport
35+
from vantage_sdk.models import (
36+
CreateCostReport,
37+
CreateCostReportChartType,
38+
CreateCostReportDateBin,
39+
)
3640

3741
vantage = VantageSDK(vantage_api_key)
3842

@@ -49,9 +53,8 @@ cost_report = CreateCostReport(
4953
previous_period_end_date="2024-01-31",
5054
start_date="2024-02-01",
5155
end_date="2024-02-28",
52-
date_interval=DateInterval.this_month,
53-
chart_type=ChartType.line,
54-
date_bin=DateBin.cumulative
56+
chart_type=CreateCostReportChartType.line,
57+
date_bin=CreateCostReportDateBin.cumulative,
5558
)
5659

5760
response = vantage.create_cost_report(cost_report)

justfile

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,21 @@ typecheck-tests:
3737

3838
check: format lint typecheck test
3939

40+
# --- Setup ---
41+
42+
setup:
43+
git config core.hooksPath .githooks
44+
4045
# --- Code Generation ---
4146

4247
generate-models:
4348
uv run datamodel-codegen \
4449
--url https://api.vantage.sh/v2/oas_v3.json \
45-
--output model.py
50+
--output vantage_sdk/models/gen_models/
51+
52+
fetch-spec:
53+
curl -sS https://api.vantage.sh/v2/oas_v3.json -o openapi_spec.json
54+
55+
regenerate-models: fetch-spec
56+
rm -rf vantage_sdk/models/gen_models/
57+
uv run datamodel-codegen

pyproject.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ dependencies = [
2424
"pydantic-settings>=2.7.1",
2525
"pydantic-extra-types>=2.10.2",
2626
"httpx>=0.28.1",
27-
"pendulum>=3.0.0",
2827
]
2928

3029
[dependency-groups]
@@ -34,7 +33,7 @@ dev = [
3433
"ruff>=0.12.0",
3534
"nox>=2025.05.01",
3635
"pytest-mock>=3.14.0",
37-
"datamodel-code-generator[http,ruff]>=0.53.0",
36+
"datamodel-code-generator[http,ruff]>=0.55.0",
3837
"pytest-timeout>=2.4.0",
3938
"basedpyright>=1.29.0",
4039
]
@@ -104,11 +103,14 @@ field-constraints = true
104103
use-annotated = true
105104
strict-nullable = true
106105
use-default-kwarg = true
106+
set-default-enum-member = true
107+
allow-population-by-field-name = true
107108
naming-strategy = "primary-first"
108109
parent-scoped-naming = true
109110
all-exports-scope = "recursive"
110111
all-exports-collision-strategy = "minimal-prefix"
111112
openapi-scopes = ["schemas", "paths", "parameters"]
113+
disable-timestamp = true
112114
formatters = ["ruff-format", "ruff-check"]
113115

114116
[tool.ruff.lint]
@@ -161,4 +163,4 @@ quote-style = "double"
161163

162164
[tool.pyproject-fmt]
163165
column_width = 120
164-
indent = 4
166+
indent = 4
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
interactions:
2+
- request:
3+
body: ''
4+
headers:
5+
Accept:
6+
- application/json
7+
Accept-Encoding:
8+
- gzip, deflate
9+
Connection:
10+
- keep-alive
11+
Content-Type:
12+
- application/json
13+
Host:
14+
- api.vantage.sh
15+
User-Agent:
16+
- python-httpx/0.28.1
17+
method: GET
18+
uri: https://api.vantage.sh/v2/users?page=1
19+
response:
20+
body:
21+
string: '{"links":{"self":"https://api.vantage.sh/v2/users?page=1","first":"https://api.vantage.sh/v2/users?page=1","next":null,"last":"https://api.vantage.sh/v2/users?page=1","prev":null},"users":[{"token":"usr_3b8e93c36c581811","name":"Block
22+
Open Source","email":"blockopensource@gmail.com","role":"Owner","default_dashboard_token":null,"last_seen_at":"2025-11-25"}]}'
23+
headers:
24+
Access-Control-Allow-Origin:
25+
- '*'
26+
Access-Control-Request-Method:
27+
- '*'
28+
Cache-Control:
29+
- max-age=0, private, must-revalidate
30+
Connection:
31+
- keep-alive
32+
Content-Security-Policy:
33+
- 'default-src ''self'' https:; font-src ''self'' https: data: https://fonts.gstatic.com;
34+
img-src ''self'' https: data:; object-src ''none''; script-src ''self'' https:
35+
''unsafe-inline''; style-src ''self'' https: https://fonts.googleapis.com
36+
''unsafe-inline''; connect-src ''self'' https: https://browser-intake-datadoghq.com;
37+
report-uri https://browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pub6a4daf9be897fccfebb14341baa5d70c&dd-evp-origin=content-security-policy&ddsource=csp-report'
38+
Content-Type:
39+
- application/json
40+
ETag:
41+
- W/"f25d06f5a4e978f3373b4424ed03435e"
42+
Set-Cookie:
43+
- _stratus_session=LUF1z%2BSIWHEFDGBa9rZFdsIVVvOqFjYT3qfgEzd0N7IcHL8pK%2FXqRjUFaWn01IQe%2BW%2F08LcO46sTQrWOZltgpL5ifNJQFUMy5g2JeOenn2PE9MPrUzkvY38j73tU%2FmTvl3omju0NgYdV%2F2YAczChgYY0kqJFfHSK6fXMUHrSBw%2B6DcZ3U%2FX3ZUM0nMOQBgXdwSW46M7wods%3D--cvXgs%2BI4F%2BxY2fVd--61tp8gkQszvf4D0WKVyoDw%3D%3D;
44+
path=/; secure; HttpOnly; SameSite=Lax
45+
Strict-Transport-Security:
46+
- max-age=63072000; includeSubDomains
47+
Vary:
48+
- Accept-Encoding
49+
X-Rate-Limit-Limit:
50+
- '1000'
51+
X-Rate-Limit-Remaining:
52+
- '991'
53+
X-Rate-Limit-Reset:
54+
- '1773342605'
55+
X-Runtime:
56+
- '0.051649'
57+
content-length:
58+
- '360'
59+
status:
60+
code: 200
61+
message: OK
62+
- request:
63+
body: '{}'
64+
headers:
65+
Accept:
66+
- application/json
67+
Accept-Encoding:
68+
- gzip, deflate
69+
Connection:
70+
- keep-alive
71+
Content-Length:
72+
- '2'
73+
Content-Type:
74+
- application/json
75+
Cookie:
76+
- _stratus_session=LUF1z%2BSIWHEFDGBa9rZFdsIVVvOqFjYT3qfgEzd0N7IcHL8pK%2FXqRjUFaWn01IQe%2BW%2F08LcO46sTQrWOZltgpL5ifNJQFUMy5g2JeOenn2PE9MPrUzkvY38j73tU%2FmTvl3omju0NgYdV%2F2YAczChgYY0kqJFfHSK6fXMUHrSBw%2B6DcZ3U%2FX3ZUM0nMOQBgXdwSW46M7wods%3D--cvXgs%2BI4F%2BxY2fVd--61tp8gkQszvf4D0WKVyoDw%3D%3D
77+
Host:
78+
- api.vantage.sh
79+
User-Agent:
80+
- python-httpx/0.28.1
81+
method: PUT
82+
uri: https://api.vantage.sh/v2/users/usr_3b8e93c36c581811
83+
response:
84+
body:
85+
string: '{"token":"usr_3b8e93c36c581811","name":"Block Open Source","email":"blockopensource@gmail.com","role":"Owner","default_dashboard_token":null,"last_seen_at":"2025-11-25"}'
86+
headers:
87+
Access-Control-Allow-Origin:
88+
- '*'
89+
Access-Control-Request-Method:
90+
- '*'
91+
Cache-Control:
92+
- max-age=0, private, must-revalidate
93+
Connection:
94+
- keep-alive
95+
Content-Security-Policy:
96+
- 'default-src ''self'' https:; font-src ''self'' https: data: https://fonts.gstatic.com;
97+
img-src ''self'' https: data:; object-src ''none''; script-src ''self'' https:
98+
''unsafe-inline''; style-src ''self'' https: https://fonts.googleapis.com
99+
''unsafe-inline''; connect-src ''self'' https: https://browser-intake-datadoghq.com;
100+
report-uri https://browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pub6a4daf9be897fccfebb14341baa5d70c&dd-evp-origin=content-security-policy&ddsource=csp-report'
101+
Content-Type:
102+
- application/json
103+
ETag:
104+
- W/"7efcead6f0b62066feb211c8d4a6f709"
105+
Set-Cookie:
106+
- _stratus_session=24Qe4NLMltYtIyRoAte0R22jxIibxUDcM1NuZSplqlYQ4B8EMLseVSHkFXf3LsB2EXI%2Fyo9d6ve6sCplXhH4Q5jojWtl%2FkizyBJOYnd%2FzH9H4MOVc7f%2BerYbNkZ1ujnkdL7BK9jYvPPKYoW501su8Np%2BR3vwyLGAtPUarMaTV2l%2BCRXwieR%2F0PcSgcVUreQGhyUyxBwsAYA%3D--Wd7T0TuUk%2Bj7tMCz--VYBX43iS64r6d5YrX5pGAQ%3D%3D;
107+
path=/; secure; HttpOnly; SameSite=Lax
108+
Strict-Transport-Security:
109+
- max-age=63072000; includeSubDomains
110+
Vary:
111+
- Accept-Encoding
112+
X-Rate-Limit-Limit:
113+
- '1000'
114+
X-Rate-Limit-Remaining:
115+
- '990'
116+
X-Rate-Limit-Reset:
117+
- '1773342605'
118+
X-Runtime:
119+
- '0.062798'
120+
content-length:
121+
- '169'
122+
status:
123+
code: 200
124+
message: OK
125+
version: 1

tests/conftest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,14 @@ def pytest_configure(config) -> None:
138138
"""Register custom markers"""
139139
config.addinivalue_line("markers", "slow: mark test as a long running test")
140140
config.addinivalue_line("markers", "live: mark test as requiring the live API")
141+
config.addinivalue_line("markers", "vcr_only: mark test as requiring VCR cassettes (skipped against live API)")
141142

142143
def pytest_collection_modifyitems(items):
143144
use_vcr = settings.vcr_enabled
144145
if not use_vcr:
146+
for item in items:
147+
if item.get_closest_marker("vcr_only"):
148+
item.add_marker(pytest.mark.skip(reason="Test requires VCR cassettes, not runnable against live API"))
145149
return
146150
for item in items:
147151
item.add_marker(pytest.mark.vcr)

0 commit comments

Comments
 (0)