Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: test

on:
push:
branches: [master]
pull_request:

jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
# Builds the wheel, installs it, byte-compiles the tree and imports every
# module. See scripts/verify_import.py.
- run: make verify

# Fail if the committed client is out of date with the committed schema — i.e.
# someone changed openapi.json without re-running the generator. Regenerates
# from the committed schema, not the live one, so this stays a check on the
# commit rather than a second copy of the sync job.
generate-drift:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
# The generator's post_hooks shell out to ruff. Without it on PATH they
# are skipped with a warning, and --fail-on-warning then fails the build.
- run: uv tool install ruff
- run: make generate
- name: Check for drift
run: |
if ! git diff --quiet; then
echo "::error::Generated client is out of date. Run 'make generate' and commit the result."
git diff --stat
exit 1
fi
9 changes: 6 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,17 @@ generate: openapi.json
--overwrite \
--fail-on-warning
python3 scripts/mark_deprecated.py openapi.json $(PACKAGE)
# Committed too, but recreated here because generate removes the package
# first. Without it type checkers ignore every annotation in the SDK.
touch $(PACKAGE)/py.typed

# Python has no compile step, so we install the wheel and import it. That at
# least runs every generated module once.
# Python has no compile step, so we install the wheel and exercise it. See
# scripts/verify_import.py for why importing the package alone isn't enough.
verify:
uv build
uv venv --clear .venv-verify
uv pip install --quiet --python .venv-verify dist/*.whl
.venv-verify/bin/python -c "import incident_io; from incident_io.api.incidents_v2 import incidents_v2_list; print('import ok')"
.venv-verify/bin/python scripts/verify_import.py

clean:
rm -rf $(PACKAGE) dist build .venv-verify .ruff_cache
Empty file added incident_io/py.typed
Empty file.
39 changes: 39 additions & 0 deletions scripts/verify_import.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Import every generated module, and byte-compile the ones that can't be imported.

Run against the installed wheel, not the working tree, so this checks what we would
actually publish.

`import incident_io` only pulls in the client and, transitively, the model graph — about
four fifths of the package. Every module under `incident_io.api` is left out, because
`api/__init__.py` imports nothing and each operation lives in its own module. Those are
the modules callers actually reach for, and a wheel ships source rather than bytecode, so
a name error in one of them would go out undetected.
"""

import compileall
import importlib
import pkgutil
import sys

import incident_io

package_root = incident_io.__path__[0]

# Catches syntax errors anywhere in the tree, including any module that import alone would
# never reach. quiet=1 prints errors but not filenames.
if not compileall.compile_dir(package_root, quiet=1, force=True):
sys.exit("verify: byte-compilation failed")


def _raise(name: str) -> None:
"""walk_packages swallows import errors while discovering subpackages unless it is
given an onerror. Swallowing them is exactly the failure this script exists to catch."""
raise ImportError(f"failed to import {name}")


count = 0
for module in pkgutil.walk_packages(incident_io.__path__, "incident_io.", onerror=_raise):
importlib.import_module(module.name)
count += 1

print(f"verify: imported {count} modules")
Loading