Skip to content

Migrate Graphene → Strawberry (Phases 0–5, combined)#95

Merged
chrisbc merged 17 commits into
deploy-testfrom
migrate/strawberry-combined
Jun 24, 2026
Merged

Migrate Graphene → Strawberry (Phases 0–5, combined)#95
chrisbc merged 17 commits into
deploy-testfrom
migrate/strawberry-combined

Conversation

@chrisbc

@chrisbc chrisbc commented Jun 24, 2026

Copy link
Copy Markdown
Member

Lands the full Graphene → Strawberry / FastAPI / Mangum migration on deploy-test as one merge → one test-stage deploy (the stack's intermediate phases aren't independently deployable — runbook G7). Consolidates the per-phase stack #88#94 + the current deploy-test (#81/#82/#84) + a CI mypy fix.

Epic: GNS-Science/nshm-toshi-api#359 · Tracking: #86 · Log: docs/MIGRATION_LOG.md

What's included (per-phase detail in #88#94)

  • 0 poetry→uv, SDL baseline, query corpus (test + kororaa)
  • 1 FastAPI/Mangum app + container entry swap (drop serverless-wsgi)
  • 2a PynamoDB → pydantic + boto3 (wrapper API preserved)
  • 2b schema port — SDL byte-identical to schema.legacy.graphql
  • 3 all runtime resolvers — 11 differential-parity checks (in-process vs Graphene)
  • 4 deploy/CI/deps hardening (360 MB image, pip-audit clean)
  • 5 cutover plan (docs/PHASE5_CUTOVER.md)

CI fix folded in

poetry2uv had set follow_untyped_imports = true, making mypy type-check into the legacy graphene/solvis code (29 errors) — restored the original lenient posture + a SetOperationEnum TYPE_CHECKING stub. Whole package mypy-clean (30 files).

Verification (local, on the merged tree)

  • mypy clean · SDL parity byte-identical · 88 passed / 10 skipped · ruff clean
  • amd64 container builds (360 MB); Mangum handler + full schema import in-image
  • deploy-test is a clean ancestor (no merge conflict)

After merge

Closes #88#94 (landed via this PR). Merging deploys to the test stage → run cli_ab_test -A prod -B test; prod promote is a separate deploy-test → main step (see PHASE5_CUTOVER.md).

chrisbc added 17 commits June 24, 2026 11:27
… sibling)

Captures the repo-specific inventory and runbook deltas before code:
- PynamoDB surface is a single wrapped model (BinaryLargeObject)
- repo ships its own cross-stage differential validator (cli_ab_test)
  -> reuse for Phase 5 instead of building drive_live.py
- still on poetry; yarn age gate missing; container entry is the
  serverless-wsgi handler.py workaround (-> Mangum)
- both serverless-s3-local + serverless-dynamodb load at boot (§4.7)

Epic: GNS-Science/nshm-toshi-api#359
chore: migrate Poetry → uv, flake8/black/isort → ruff
- add tools/dump_legacy_sdl.py (stdout->stderr guard; pyvista warns on import)
- commit schema.legacy.graphql (449 lines, 29 types)
- log: poetry2uv verified green (ruff + 62 tests); Phase 0 SDL done

Refs #86
- 11 real queries lifted from the test suite into fixtures/corpus/*.graphql
- test_corpus_replay.py validates each against schema_root (no fixtures needed);
  re-points to the Strawberry schema later to catch field/type drift
- kororaa UI queries to follow (real frontend traffic)

Refs #86
- vendor 3 pure-solvis kororaa Relay ops, transformed from gateway shape
  (SOLVIS_<f> -> <f>, on Query -> on QueryRoot); validated against schema
- adds coverage the tests lacked: locations_by_id + radius_geojson(style:),
  rich rupture node fields (fault_surfaces, magnitude, rate_weighted_mean, ...)
- ComboRuptureMap* skipped (multi-API: also fetch KORORAA_textual_content)
- log the stale-checkout lesson: kororaa was 9mo behind and showed retired
  inversion_solution/analyse_solution usage -> false parity alarm

Refs #86
- dev.yml: drop the pull_request branches:[main,deploy-test] filter so
  stacked migration PRs get CI (runbook Trap #14)
- .yarnrc.yml: now tracked (was untracked); add npmMinimalAgeGate 7d +
  preapproved project scopes (runbook §4.6); yarn install --immutable OK

Refs #86
- add strawberry/fastapi/mangum/pydantic (legacy graphene/flask kept until cutover)
- app.py: FastAPI + GraphQLRouter + Mangum + CORS
- strawberry_schema.py: minimal QueryRoot.about at SDL parity
  (auto_camel_case=False, root type QueryRoot to match legacy + kororaa corpus)
- serverless.yml: image.command -> solvis_graphql_api.app.handler; drop
  serverless-wsgi plugin + custom.wsgi
- Dockerfile: CMD -> app.handler; drop bogus /bin/bash -c entrypoint
- verified: amd64 docker build OK + handler imports in-image; uvicorn {about}
  byte-matches legacy; full legacy suite 77 passed

Refs #86
…r API)

- BinaryLargeObjectModel(pynamodb.Model) -> pydantic BaseModel + thin boto3
  DynamoDB CRUD; public BinaryLargeObject wrapper API unchanged so cli +
  composite_solution.cached are untouched
- object_meta stored as a JSON string (JSONAttribute parity; ints not Decimals)
- migrate()/drop_tables()/tables preserved (create_table wait -> boto3 waiter)
- drop pynamodb dependency; uv re-locked (164 pkgs)
- verified: moto contract 4/4, full suite 77 passed, ruff + mypy clean

Refs #86
…rity

- strawberry_schema.py: full 29-type schema; tools/schema_parity.py gate confirms
  byte-identical vs schema.legacy.graphql
- custom Node interface (id: ID!, not strawberry.relay/GlobalID); QueryRoot root
  type; auto_camel_case=False; relay PageInfo camelCase via name=
- nullability (X|None for nullable-with-default), UNSET vs =null args, and the
  partial 3-key style-arg default all matched
- resolvers reuse the graphene-free compute helpers; light query-root resolvers
  verified vs legacy output
- corpus replay re-pointed at the Strawberry schema (15 pass); add strawberry
  mypy plugin (new code mypy-clean)
- archive-dependent runtime resolvers stubbed -> Phase 3 (need fixtures + tests)

Refs #86
- test_strawberry_parity.py: run each query vs Graphene AND Strawberry, assert
  identical data (in-process analogue of cli_ab_test)
- implement CompositeRuptureDetail computed fields (magnitude/area/length/
  rake_mean/rate_*) via rupture_detail; fix get_parent_fault_names shape
- harness caught 2 bugs the SDL gate can't: relay global-id encoding (id ->
  to_global_id, Model C1) and float opacity defaults (1.0 not 1); switch the
  style-arg default to a mapping (instance default breaks graphql-core coercion)
- SDL parity still byte-identical; suite 85 passed; ruff + mypy(new) clean

Refs #86
- reuse legacy paginated_filtered_ruptures via a dict adapter (_legacy_filter)
  that exposes the dict+attr access it expects; convert the graphene connection
  to the Strawberry RuptureDetailConnection
- fix: filter_set_options default must be a plain mapping, not an input instance
  (an instance default trips strawberry argument coercion) - same class of fix
  as the style-arg default; SDL parity preserved
- test_filter_ruptures_parity: pagination + cursors + node global-id + magnitude
  all byte-match legacy (note: legacy can't serve pageInfo.hasPreviousPage)

Refs #86
- CompositeRuptureSections: delegate every resolver to the legacy graphene
  static resolvers (aggregates/geojson/MFD/colour reused verbatim) via a graphene
  root built from the input; colour-scale normalisation converted to string value
- node(): legacy has no get_node -> null on both schemas (parity-correct stub)
- mypy: None-safe _fso_dict, edges/normalisation narrowing, type:ignore on
  untyped graphene constructors -> new code clean
- 11 differential parity checks (full surface) + suite 88 passed; SDL byte-identical

Refs #86
- container: amd64 build green with migrated deps; 360MB (< 500MB aim, no
  multi-stage); Mangum handler + full schema import in-image
- pip-audit runtime deps: no known vulnerabilities
- plugin chain (s3rver + serverless-dynamodb) loads; no resolutions needed
- drop dead serverless-wsgi npm plugin + stale requirements_* scripts;
  yarn lockfile updated, --immutable clean (§4.6)
- memory stays 2096; CI already on -uv test+deploy workflows

Refs #86
- docs/PHASE5_CUTOVER.md: in-place replacement, rollback (image-only, no new
  write shapes), differential validation via the built-in cli_ab_test
  (prod-vs-test, covers the full migrated surface), promote + 30-min watch,
  post-healthy legacy-cleanup scope
- reuse cli_ab_test (no drive_live.py needed); in-process parity already locked
  by test_strawberry_parity.py
- actual deploy/promote needs AWS creds + prod go-ahead

Refs #86
…ationEnum type stub

CI gates on mypy. poetry2uv added follow_untyped_imports=true, making mypy
type-check into the legacy graphene + solvis code (29 errors). Restore the
original lenient posture (ignore_missing_imports only); add a TYPE_CHECKING
stub enum for SetOperationEnum so it stays a valid type without follow.
Whole package mypy-clean (30 files); parity + 11 differential tests + ruff green.
@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.04202% with 89 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.86%. Comparing base (9091d0e) to head (3334e10).

Files with missing lines Patch % Lines
solvis_graphql_api/strawberry_schema.py 89.83% 29 Missing and 8 partials ⚠️
solvis_graphql_api/app.py 0.00% 17 Missing ⚠️
solvis_graphql_api/tools/schema_parity.py 0.00% 16 Missing ⚠️
solvis_graphql_api/data_store/model.py 86.79% 4 Missing and 3 partials ⚠️
solvis_graphql_api/tools/dump_legacy_sdl.py 0.00% 5 Missing ⚠️
...i/composite_solution/composite_rupture_sections.py 75.00% 3 Missing ⚠️
solvis_graphql_api/color_scale/color_scale.py 75.00% 2 Missing ⚠️
solvis_graphql_api/composite_solution/cached.py 91.66% 2 Missing ⚠️
Additional details and impacted files
@@               Coverage Diff               @@
##           deploy-test      #95      +/-   ##
===============================================
- Coverage        95.79%   91.86%   -3.94%     
===============================================
  Files               20       24       +4     
  Lines             1142     1548     +406     
  Branches            54       71      +17     
===============================================
+ Hits              1094     1422     +328     
- Misses              34      102      +68     
- Partials            14       24      +10     
Flag Coverage Δ
unittests 91.86% <85.04%> (-3.94%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant