Publish local runs to analytics_dev on the real database - #18
Open
lassebenni wants to merge 5 commits into
Open
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates local development publishing to target the real backend Postgres database by default (writing into analytics_dev), and adds a provenance “stamp” onto published tables so teams can identify which warehouse schema last populated the shared dev mart.
Changes:
- Extend backend DB setup to include an
analytics_devschema and a dedicatedanalytics_dev_userrole with constrained privileges. - Add optional
sourcestamping to the publish swap so the final table gets aCOMMENT ON TABLElikefrom <source> at <timestamp>. - Update Airflow/local documentation and defaults to publish to
analytics_devwith the dev role and TLS required.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/db-setup.py | Adds analytics_dev schema/role and updates the privilege layout to enforce the dev/prod boundary via grants. |
| data/src/publishing/sync.py | Adds optional source parameter and stamps the published table via COMMENT ON TABLE after the swap. |
| data/tests/publishing/test_sync.py | Adds tests asserting the stamp is written and occurs after the rename/swap. |
| data/README.md | Documents new publish schema/user settings and explains shared analytics_dev behavior + stamp visibility. |
| data/airflow/dags/pipeline_dag.py | Publishes to a configurable schema and supplies the source string for stamping. |
| data/.env.example | Updates local env guidance to use the real DB, analytics_dev_user, analytics_dev, and TLS required. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| "fct_postings", | ||
| columns, | ||
| rows, | ||
| source=f"{Warehouse.from_env().catalog}.{setting('DBT_SCHEMA')}", |
Comment on lines
318
to
+320
| | `DBT_SCHEMA` | `dev_yourname` | `analytics` | | ||
| | `BACKEND_PG_HOST` | your own Postgres in Docker | the backend's database | | ||
| | `BACKEND_PG_PUBLISH_SCHEMA` | `analytics_dev` | `analytics` | | ||
| | `BACKEND_PG_USER` | `analytics_dev_user` | `analytics_user` | |
This was referenced Aug 13, 2026
added 5 commits
August 13, 2026 22:30
…duction may not Local development published to a Postgres in Docker, which simulates the one thing that most reliably breaks on the first scheduled run: connecting to a real database. TLS, the firewall and the grants all went untested until production tested them. So trainees now publish to the real database, into their own schema. Three schemas, and the boundary is permissions rather than naming: analytics_user full on analytics, reads app analytics_dev_user full on analytics_dev, reads app and analytics app_user full on app, reads both analytics_dev_user cannot write analytics, so an .env still pointing at production fails on the drop with a permission error instead of replacing what the backend serves. analytics_user cannot even read analytics_dev, so nothing production builds can come to depend on a table someone is editing from a laptop. The backend reads both, so its developers can build against a mart before it reaches production. Table names are the same in both schemas, so promotion changes the schema and nothing else. Verified against a throwaway Postgres 16, all six directions: dev writes analytics_dev, dev refused on analytics, dev reads analytics, airflow writes analytics, airflow refused writing and reading analytics_dev. SchemaAccess.read_only becomes reads, a tuple, because the dev role needs two.
Two data trainees share one analytics_dev schema, and the publish drops and renames, so the last one to run wins. That is the right behaviour for a place the two tracks meet: the backend wants one table called fct_postings, not three named after people. What it lacked was any way to answer 'why did the columns change this morning'. So the swap now ends with a comment naming the warehouse schema the rows came from: 'from team_a.dev_alex at 2026-08-13T11:21Z'. Visible in psql and every GUI, needs no new setting because the DAG already knows DBT_SCHEMA, and it describes the table rather than widening what the backend selects. Useful in production too, where it reads team_a.analytics and dates the last successful publish. Verified against Postgres 16: published as dev_alex, republished as dev_maria, comment followed the second writer and the rows were replaced, which is the collision the stamp exists to explain.
Settings and README followed the old model, where a trainee published to a Postgres in Docker. They now name the real host, analytics_dev_user, and analytics_dev, and say why each is not the production equivalent: analytics_user writes production and only the team's Airflow VM can read its password. sslmode goes back to require, since the target is Azure rather than a local container with no certificate. Also documents the one consequence of sharing a dev schema: the last publish wins, and the table comment is how you tell whose run you are looking at.
…atabase Verifying the documented dev workflow end to end found this. The settings now send local runs to analytics_dev on the team's real Postgres, but this file still pinned BACKEND_PG_HOST to the local `db` container and sslmode to prefer. So `uv run` published to Azure and the same task under `astro dev start` published somewhere else entirely, silently, which is the exact failure this file exists to prevent. The overrides go, and with them the external `finalproject` network, which was only there to reach that container. That also removes a trap: `astro dev start` used to fail unless you had first run `docker compose up -d db`, a database the data pipeline no longer touches. Verified after the change, against real infrastructure: astro dev start came up with no local db and no network airflow tasks test dbt_build PASS=37 WARN=0 ERROR=0 airflow tasks test publish_to_backend published 176 rows to analytics_dev
CI runs `black --check` alongside ruff; the stamp test was added with only ruff run locally, so the whole branch failed on formatting.
lassebenni
force-pushed
the
feat/analytics-dev-schema
branch
from
August 13, 2026 20:32
ff3e667 to
80ae425
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What I built
A third database role,
analytics_dev_user, that owns theanalytics_devschema and can only read everything else. Trainees get that credential and publish their in-progress mart toanalytics_devfrom a local Airflow run; the scheduled pipeline keepsanalytics_userand stays the only writer of productionanalytics. The publish step also stamps the table it writes with the schema and time the rows came from, so a reader can tell a hand-run mart from a scheduled one.Why this approach
analytics_devalready existed after #19, butanalytics_userowned both analytics schemas. That means handing a trainee the credential to writeanalytics_devalso hands them ownership of productionanalytics, so the dev/prod split would have rested on people not typing the wrong schema name. Making it a separate role moves the boundary into Postgres, where it is enforced rather than agreed.This keeps #19's
SCHEMA_OWNERSstructure exactly as it is, which is better than the two-list version I originally wrote. The whole change is three lines: a new role constant, adding it toROLES, and pointinganalytics_devat it. The existing loop grants full access on owned schemas and read-only on the rest, so nothing else needed touching.Rejected: one role with
CREATEonanalytics_devand onlyUSAGEonanalytics. It works, but the same login then appears in both the trainee's.envand the scheduled pipeline's Key Vault secret, so rotating it for one breaks the other.docker-compose.override.ymlalso loses itsBACKEND_PG_HOST: dboverride. It pointed Airflow tasks at a Postgres container whileuv runon the same machine wrote the real database, so the two documented ways of running the pipeline silently disagreed about the destination.Contract impact
None for the backend.
analytics.fct_postingskeeps its shape andanalytics_userkeeps writing it. The newanalytics_devschema is trainee-facing only;app_usergains read access to it but nothing reads it yet.How to run
Self-check
Verified beyond the tests: the same seven-check boundary probe passes both against a database
db-setup.pybuilt from scratch and against the live project database, where the role now exists.