Skip to content

Cross-database pg_cron scheduling (restore xdist + test isolation) - #107

Merged
marcgibbons merged 44 commits into
mainfrom
pg-cron-test-isolation
Jul 27, 2026
Merged

Cross-database pg_cron scheduling (restore xdist + test isolation)#107
marcgibbons merged 44 commits into
mainfrom
pg-cron-test-isolation

Conversation

@marcgibbons

Copy link
Copy Markdown
Contributor

The app/test DB no longer holds the pg_cron extension — it's operator-managed once on a central, auto-discovered cron.database_name DB, and jobs schedule cross-database via schedule_in_database. This restores pytest-xdist + standard test_ isolation and drops the --create-db eviction hack.

All cron.* route through one seam (pg_cron/catalog.py); jobnames are db-namespaced (_dj:<db>:<source>:<name>) to keep the shared catalog safe across environments; the seam is inert under tests by default (opt-in OPTIONS["PG_CRON_ON_TEST_DB"]). Migration CreateExtension is dropped; fail-fast moves to E011/E012 checks.

Example apps (web/beat/pg_cron) now run their own pytest suites in isolated Docker stacks.

Make django_absurd.pg_cron inert in tests by default so downstream projects can
run pytest-xdist + standard test_ isolation without CREATE EXTENSION / cron.*
breaking. State-based test-DB detection + conditional CreateExtension + gating of
all 5 cron.* sites + PG_CRON_ON_TEST_DB opt-in + real-DB fail-safe check.
…aints)

Captures the single-extension-per-cluster constraint, cron.database_name, cron.schedule
vs schedule_in_database (target DB needs no extension — verified it schedules AND fires
into an extension-less DB), the cron.job schema, and why installing django_absurd.pg_cron
breaks pytest-xdist / test_ isolation. Facts verified live against pg_cron 1.6.
Reframe from the test-DB problem to a reusable understanding of pg_cron: architecture,
the full scheduling API + signatures, cron.job / cron.job_run_details schemas, schedule
syntax, cross-database jobs, and how to inspect a live instance. The django-absurd
specifics are now a short closing note, not the focus.
…grants, reconcile sketch, sweep/fixture details, change-list additions, task order)
…dated) + transition sweep (alpha, from-scratch)
…, lock, CRON_DATABASE_NAME, cleanup verbs, dup+deselected tests; add post-impl validation scenarios
…le db-namespaced build_jobname; keep alter_job (active applies on insert only)
…X000 tolerance was dead code on the unwrapped central cursor); dedup live_database into utils.fetch_live_database
…entral DB GUC drift must not 500 a saved row)
…ppend, fix hint wording, assert full check message
… markers, unused param, verb-named test helpers)
…_init.sql; trim to CREATE EXTENSION (grants are owner no-ops, ablation-proven); fix stale example docstrings
…it.sql removed); doc updates; example mounts ./:/app
…e apps (beat/pg_cron/web) + Codecov CI matrix

Runs on the cross-database pg_cron topology: the pg_cron example needs no
CRON_DATABASE_NAME override (central cron.database_name=postgres; the test DB is
ordinary + extension-less, scheduled cross-database). Validated: all 3 example
suites pass in their own Docker stacks.
…ving Django DB fixtures) + resolve the sweep's DB alias
@socket-security

socket-security Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedpypi/​pytest-xdist@​3.8.0100100100100100

View full report

@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.83%. Comparing base (9341ac3) to head (bf5bc01).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #107      +/-   ##
==========================================
+ Coverage   99.79%   99.83%   +0.04%     
==========================================
  Files         122      137      +15     
  Lines        6211     6869     +658     
  Branches      216      244      +28     
==========================================
+ Hits         6198     6858     +660     
+ Misses          9        8       -1     
+ Partials        4        3       -1     
Flag Coverage Δ
dev 99.83% <100.00%> (+0.04%) ⬆️
examples-beat 100.00% <100.00%> (?)
examples-pg_cron 100.00% <100.00%> (?)
examples-web 100.00% <100.00%> (ø)
py312-django60 99.54% <100.00%> (+0.06%) ⬆️
py313-django60 99.54% <100.00%> (+0.06%) ⬆️
py314-django60 99.54% <100.00%> (+0.06%) ⬆️

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.

@marcgibbons
marcgibbons marked this pull request as ready for review July 27, 2026 01:15
@marcgibbons
marcgibbons merged commit 3d4ec65 into main Jul 27, 2026
17 checks passed
@marcgibbons
marcgibbons deleted the pg-cron-test-isolation branch July 27, 2026 01:16
marcgibbons added a commit that referenced this pull request Jul 27, 2026
* docs: present manage.py test cleanup as intentional, drop closed-issue ref

#96 closed won't-do (docs are the supported path for the non-pytest
DiscoverRunner minority). Reframe the AGENTS.md + testing.md note from a
'not in this release (issue #96)' deferral into the intentional design it
is.

* docs(why): capture upsert-steal as the reason pg_cron jobnames are db-namespaced

WHY.md framed db-namespacing as multi-tenant tidiness; the load-bearing
reason is safety — pg_cron's job catalog is unique on (jobname, role) alone,
so an un-namespaced schedule silently retargets an existing same-named job to
a new database (a test DB could hijack a prod job). Captured from the pg_cron
spec ahead of archiving it.

* docs: archive shipped pg_cron cross-database spec + plan

Feature shipped (#107); durable why captured in WHY.md. Both recorded in
HISTORY.md with origin/main blob links (@4dde493) before removal.
marcgibbons added a commit that referenced this pull request Aug 12, 2026
* docs: drop Renovate commits from the changelog design

chore(deps) is 100% dev/CI tooling under rangeStrategy update-lockfile, so the deps section and its dedupe were noise-only.

* docs: keep floor changes visible, hand-fix the backfill

Adds a Requirements section from the build type as a safety net Renovate cannot reach, and records that pre-#131 titles are unparsed prose needing a one-time rewrite.

* docs: plan the git-cliff changelog implementation

* docs: use chore for the plan's own tooling commits

build is reserved for the Requirements section, which is for user-visible floor changes only.

* chore(deps): add git-cliff for changelog generation

* chore: configure git-cliff

* docs: correct why three alpha releases render no section

git-cliff omits a release whose filtered commit list is empty; tag_pattern was not the cause.

* docs: fold two config corrections into the backfill task

* chore: keep CI work out of the changelog

* docs: generate the changelog for v0.1.0a1..a5

* fix(cliff): backfill released sections only, no Unreleased

* docs: record that the changelog is released-only

* docs: fill in the pre-conventional releases by hand

* docs: date v0.1.0a2 from the committer timestamp

* docs: attribute the README/AGENTS split to the PR that made it

* docs: cut releases from the changelog

* fix(cliff): stop --prepend duplicating the changelog header

The header is re-rendered on every prepend and only elided when the target
file matches it byte for byte; prettier had re-wrapped CHANGELOG.md's copy, so
each release would have inserted a second header exactly where the release-notes
slice picks up. Wrap cliff.toml's header the way prettier does, and say plainly
in both copies that regeneration destroys the hand-written a2-a4 sections for
good.

* fix: keep the changelog PR out of the next changelog

The suggested title was docs:, which cliff.toml renders — and --fill plus
squash-merge turn it into a commit subject, so each release's changelog PR
would come back as a Documentation bullet in the next one. Title it chore:.

Also warn on the CHANGELOG.md side that its header wording and wrapping are
coupled to cliff.toml's. The note lives inside the header itself: above it, it
is not part of the string --prepend elides, so it would be re-emitted below the
new section, inside the release-notes slice.

* fix: close the gaps that lose changes from the changelog

Four ways a real change could go missing, and two stale claims.

- Commits git-cliff cannot parse are dropped with only a warning; the first
  range under this flow reaches back before the conventional-commit mandate and
  hides #107, runtime pg_cron work. Triage the warning in the changelog PR.
- Anything merged between that PR and the cut falls in neither range, so check
  the gap before slicing.
- GATE 2 sent a correction back through the prepend, which would stack a second
  copy of the same section; hand-edit the merged one instead.
- revert: matched no parser and vanished. It gets its own section, because
  calling a revert a bug fix misreports what happened. A breaking change of a
  dropped type still vanishes, which config cannot fix, so it is a rule.

Also drop an internal spec-retirement entry from a5, and stop claiming the
hand-written sections are unrecoverable rather than unreproducible.

* docs: state the no-second-prepend rule once, in step 4

Step 5 sent a missed commit back to step 4, which opens with the prepend — the
move GATE 2 forbids four paragraphs later, and it stacks a duplicate section.
Put the rule where the merge that triggers it happens and have both remedies
name the same hand edit. Also pin the check's boundary to the most recent
changelog merge, and stop it flagging ci-scoped commits, which never render.

* docs: redo the whole slice step after a missed entry

"Re-run this check" read as the git log line alone, so the corrected section
would never be re-sliced and GATE 2 would approve — and publish — the stale
notes file. Say pull, check and re-slice, matching what GATE 2 already asks
for.

* docs: keep the regeneration warning out of the rendered changelog

The hazard is maintainer-facing; a consumer landing on the file should not read it before any release. Moved into the HTML comment, which both copies must keep byte-identical.

* docs: drop the changelog tagline

* test: allow CHANGELOG.md in the sdist allowlist

* chore(deps): relock the examples after the git-cliff dev dep
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