From 86675b9306ac3565d5617c526c668e573c6a27ed Mon Sep 17 00:00:00 2001 From: Sam Welborn Date: Fri, 10 Jul 2026 01:20:32 -0400 Subject: [PATCH 1/5] add prek for precommit hooks using these for generating clients --- pyproject.toml | 1 + uv.lock | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index ccc44f9..c806855 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ dev = [ "ruff>=0.15.17", "types-pyyaml>=6.0.12.20260518", "testcontainers[postgres]>=4.14.2", + "prek>=0.4.8", ] [build-system] diff --git a/uv.lock b/uv.lock index b43a893..f8f3a0c 100644 --- a/uv.lock +++ b/uv.lock @@ -604,6 +604,7 @@ dependencies = [ dev = [ { name = "mpi4py" }, { name = "mypy" }, + { name = "prek" }, { name = "pytest" }, { name = "pytest-aiohttp" }, { name = "pytest-asyncio" }, @@ -634,6 +635,7 @@ requires-dist = [ dev = [ { name = "mpi4py", specifier = ">=4.1.2" }, { name = "mypy", specifier = ">=2.1.0" }, + { name = "prek", specifier = ">=0.4.8" }, { name = "pytest", specifier = ">=9.0.3" }, { name = "pytest-aiohttp", specifier = ">=1.1.0" }, { name = "pytest-asyncio", specifier = ">=1.4.0" }, @@ -874,6 +876,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "prek" +version = "0.4.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/46/e436a6eb9fdb4d3fd08d0ab7fdba19fe03a9e994ec810de57869b853bd8e/prek-0.4.8.tar.gz", hash = "sha256:d15d8bef72ab7b02c7dc01458ac9e05b3131534492b5ce9bb11c4f6f636fa868", size = 494570, upload-time = "2026-07-04T12:05:10.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/78/b4149c8913ced2e42debb49e261c4788a1ce431e84226921c2e1a7ea8545/prek-0.4.8-py3-none-linux_armv6l.whl", hash = "sha256:1f8f8cdc65836b571824c965daebb81b449f7e4a43894c58621f5708d5a185ed", size = 5668955, upload-time = "2026-07-04T12:04:41.588Z" }, + { url = "https://files.pythonhosted.org/packages/76/5f/7f54a0087b6b2f1751aeb41266d9c15e66fd0055492814798ab818cd0414/prek-0.4.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:bce1798e96d9e3a6e6abf435da7107e81452f69edb3ca7c6f90a457355ea46e2", size = 6030947, upload-time = "2026-07-04T12:04:43.8Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d6/f2829fc3902920c36b764a386fa303e71a8219dac25cb3827c575e84199a/prek-0.4.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ab3a52db17254d701c3cebb7eea58c8230aa7c1959aacfd5b5f25de18edb15d1", size = 5572593, upload-time = "2026-07-04T12:04:45.763Z" }, + { url = "https://files.pythonhosted.org/packages/74/8c/c5589955bcd5e3e33b67d8bc3110818cecac82a38fd6bc8b5dfdc5de421c/prek-0.4.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:b3fcfd620523bbc3f51a21d7cd63449f659b9e2cf3582de12dd5949e23227b8f", size = 5847150, upload-time = "2026-07-04T12:04:47.419Z" }, + { url = "https://files.pythonhosted.org/packages/2d/9d/1f2dc91bdb79d2c4714b27eac9477a51490fba5b4731330dbbebc76bd345/prek-0.4.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42e65bc8425e9d7f1691a13ca1da2e07807d1ba76c35740833354b945131689e", size = 5573738, upload-time = "2026-07-04T12:04:49.125Z" }, + { url = "https://files.pythonhosted.org/packages/81/29/69a7b58e16ecbc5f3989bf4b028018d11a82dcdd320b93d6588d72f32aa7/prek-0.4.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f578492a8e0c9bc6b4bf6dfbba8716f647d4cd0769bf10ad6cf336e3096fd392", size = 5981054, upload-time = "2026-07-04T12:04:50.842Z" }, + { url = "https://files.pythonhosted.org/packages/63/cc/9b9850a60c22ed18c7755ebd2d72c6eefb37fac58149d09f6adc4691c2cf/prek-0.4.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4335f9d5beb123a3884a7fe34f57c9f0828f4fbb7666beab4298833459b104f", size = 6751350, upload-time = "2026-07-04T12:04:52.529Z" }, + { url = "https://files.pythonhosted.org/packages/01/e5/c425aa7272b430630119e6757def3a2007555ba8cbeb2630e0448e7a8b7f/prek-0.4.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18a8747df9c602e052881d3efb14dd7f7d62a59bd7277ae5171c9e7661d59d84", size = 6243881, upload-time = "2026-07-04T12:04:54.703Z" }, + { url = "https://files.pythonhosted.org/packages/1c/da/accd3ad07fd2891d3c2777eb42435439fdf11982c51d60f087c0b6b6e102/prek-0.4.8-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:4db639db481d5f854eff9b3d2108889e613b8c15868bcf6bdd777c7cee577436", size = 5848846, upload-time = "2026-07-04T12:04:56.402Z" }, + { url = "https://files.pythonhosted.org/packages/15/00/3477704635249f21f5f98ce444cd7690c2aa9dc8d146a045db88ef2cd8c5/prek-0.4.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:c3890a6f92316d2cf44eb50584e8d2b23a596dd70487022e61186a71a2ac0900", size = 5713942, upload-time = "2026-07-04T12:04:58.311Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e6/3ca4fabaebeadc976d9a92d1d9130674265355ea3b728418bad61583b097/prek-0.4.8-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:fc7e15c24c591a37c6ffce5b25a021b16c299ac2649f183d812b67d665cd6551", size = 5554725, upload-time = "2026-07-04T12:04:59.96Z" }, + { url = "https://files.pythonhosted.org/packages/a5/46/2ab6aaaeff0cedb8955b2e4032071c8712382bdd423bb849718c3720180d/prek-0.4.8-py3-none-musllinux_1_1_i686.whl", hash = "sha256:36fe721704ff0c7624c1167639e23a5fe658bfd38c314f487219c9afd1eeb733", size = 5838595, upload-time = "2026-07-04T12:05:01.861Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8b/91398f2b6cd1629d5d8ca8c85b08eca500814a374313b0193f4aaf6ab6c4/prek-0.4.8-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:162e544abc394a8124f3a4ad68efee116bad09440e679dbd1675177335c2a432", size = 6357222, upload-time = "2026-07-04T12:05:03.845Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2a/ce5cbfaad36866134a21754640a05ecdba641fcd7ad15aa74cf3443f34f6/prek-0.4.8-py3-none-win32.whl", hash = "sha256:2602e46c8c5da7dfa69f60fcf88c2b57132ac623f49fb08bfb3094298c5f07e3", size = 5354388, upload-time = "2026-07-04T12:05:05.587Z" }, + { url = "https://files.pythonhosted.org/packages/df/03/3bc908bc5f7e430315553e47dfa055f19923a3888f9afe4da19f244b5cbf/prek-0.4.8-py3-none-win_amd64.whl", hash = "sha256:7cb22da60bee41b89c4978c0bea7126a3c0ccc003dae6748cf29b53947815edc", size = 5748221, upload-time = "2026-07-04T12:05:07.559Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a7/4295e6d5f5028171dfeb115ad38ab76bf3fe0c8df91b70d73c79aa760a94/prek-0.4.8-py3-none-win_arm64.whl", hash = "sha256:da70057f577b15d4bd121bf9dd29ee205fd4b4d75a0cafba062e84d7e8b4378b", size = 5574425, upload-time = "2026-07-04T12:05:09.595Z" }, +] + [[package]] name = "propcache" version = "0.5.2" From 2e44a0b68c595553e4a5811b0d85709ec4693e71 Mon Sep 17 00:00:00 2001 From: Sam Welborn Date: Fri, 10 Jul 2026 02:41:32 -0400 Subject: [PATCH 2/5] add clients precommit hook --- .pre-commit-config.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..4b4a79b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +repos: + - repo: local + hooks: + - id: openapi-clients + name: Regenerate OpenAPI clients on backend schema changes + description: >- + Dumps the v2 app's OpenAPI schema and, only if it changed, + regenerates the Python (client/) and frontend (frontend/src/client) + clients. Fails the commit so the regenerated files can be reviewed + and staged. + entry: make clients + language: system + files: ^src/lclstream_api/v2/.*\.py$ + exclude: ^src/lclstream_api/v2/alembic/versions/ + pass_filenames: false From 9aab1536bace3fba55fd9fa802e3f60ceca27beb Mon Sep 17 00:00:00 2001 From: Sam Welborn Date: Fri, 10 Jul 2026 02:42:16 -0400 Subject: [PATCH 3/5] add makefile with client generation --- Makefile | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3336720 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +.PHONY: openapi client-python client-frontend clients lint-frontend + +openapi: + uv run python scripts/dump_openapi.py + +lint-frontend: + cd frontend && bun run lint + +client-python: openapi + bash client/regenerate.sh + +client-frontend: openapi + cd frontend && bun run generate-client + $(MAKE) lint-frontend + +# Regenerates openapi.json and, only if its content actually changed +# relative to git HEAD, regenerates the Python + frontend clients. Used by +# the local prek hook (see .pre-commit-config.yaml) so most commits (which +# don't change the schema) skip the expensive Docker/bun regeneration steps +# entirely. CI bypasses this gate and calls client-python/client-frontend +# directly for an unconditional full regen (see openapi-clients.yml). +clients: openapi + @if git diff --quiet -- openapi.json; then \ + exit 0; \ + fi; \ + echo "openapi.json changed — regenerating python + frontend clients..."; \ + $(MAKE) -s client-python client-frontend; \ + echo ""; \ + echo "The OpenAPI schema changed, so the Python and frontend clients were"; \ + echo "regenerated. Review the diff, then:"; \ + echo ""; \ + echo " git add openapi.json client/src/lclstream_api_client/_generated frontend/src/client"; \ + echo " git commit"; \ + exit 1 From 6a15b77a6d314f683c374e2f567d108c908a4cca Mon Sep 17 00:00:00 2001 From: Sam Welborn Date: Fri, 10 Jul 2026 02:49:56 -0400 Subject: [PATCH 4/5] add CI for checking client drift --- .github/workflows/openapi-clients.yml | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/openapi-clients.yml diff --git a/.github/workflows/openapi-clients.yml b/.github/workflows/openapi-clients.yml new file mode 100644 index 0000000..e289a2a --- /dev/null +++ b/.github/workflows/openapi-clients.yml @@ -0,0 +1,46 @@ +name: OpenAPI Clients + +on: + push: + branches: [main] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + check-clients: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Install uv + uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + with: + enable-cache: true + + - name: Install python deps + run: uv sync --frozen + + - name: Install bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + + - name: Install frontend deps + working-directory: frontend + run: bun install --frozen-lockfile + + - name: Regenerate clients + run: make client-python client-frontend + + # we check full regen here because it may be that someone manually edited or versioning changed on the generators + - name: Fail if generated clients are stale + run: | + if ! git diff --exit-code -- openapi.json client/src/lclstream_api_client/_generated frontend/src/client; then + echo "::error::Generated OpenAPI clients are out of date. Run 'make clients' locally, review the diff, and commit the result." + exit 1 + fi From ac82d67f3675f201d073ad859bd0331d30150819 Mon Sep 17 00:00:00 2001 From: Sam Welborn Date: Fri, 10 Jul 2026 11:02:28 -0400 Subject: [PATCH 5/5] clear up /tmp files not able to be rm in CI --- client/regenerate.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/client/regenerate.sh b/client/regenerate.sh index 7738c8f..f7233db 100755 --- a/client/regenerate.sh +++ b/client/regenerate.sh @@ -13,6 +13,7 @@ uv run python scripts/dump_openapi.py GENERATOR_IMAGE="openapitools/openapi-generator-cli@sha256:da552b8d0add9fd3cd89ce836d55b433c8cf467b287dd50c62bbd4482c03f677" docker run --rm \ + --user "$(id -u):$(id -g)" \ -v "$REPO_ROOT/openapi.json:/spec.json:ro" \ -v "$GENERATOR_CONFIG:/config.yaml:ro" \ -v "$TMP_DIR:/out" \