django-dbos 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. django_dbos-0.1.0/.github/workflows/ci.yml +60 -0
  2. django_dbos-0.1.0/.github/workflows/docs.yml +32 -0
  3. django_dbos-0.1.0/.github/workflows/release.yml +49 -0
  4. django_dbos-0.1.0/.gitignore +34 -0
  5. django_dbos-0.1.0/.pre-commit-config.yaml +31 -0
  6. django_dbos-0.1.0/AGENTS.md +137 -0
  7. django_dbos-0.1.0/CHANGELOG.md +11 -0
  8. django_dbos-0.1.0/CLAUDE.md +1 -0
  9. django_dbos-0.1.0/LICENSE +21 -0
  10. django_dbos-0.1.0/PKG-INFO +247 -0
  11. django_dbos-0.1.0/README.md +202 -0
  12. django_dbos-0.1.0/benchmarks/README.md +34 -0
  13. django_dbos-0.1.0/benchmarks/serializers.py +117 -0
  14. django_dbos-0.1.0/changelog_template.md.j2 +20 -0
  15. django_dbos-0.1.0/django_dbos/__init__.py +13 -0
  16. django_dbos-0.1.0/django_dbos/admin.py +257 -0
  17. django_dbos-0.1.0/django_dbos/admin_views.py +644 -0
  18. django_dbos-0.1.0/django_dbos/apps.py +28 -0
  19. django_dbos-0.1.0/django_dbos/backends.py +197 -0
  20. django_dbos-0.1.0/django_dbos/capacity.py +363 -0
  21. django_dbos-0.1.0/django_dbos/checks.py +260 -0
  22. django_dbos-0.1.0/django_dbos/client.py +91 -0
  23. django_dbos-0.1.0/django_dbos/context.py +357 -0
  24. django_dbos-0.1.0/django_dbos/dbos_setup.py +162 -0
  25. django_dbos-0.1.0/django_dbos/discovery.py +41 -0
  26. django_dbos-0.1.0/django_dbos/exceptions.py +83 -0
  27. django_dbos-0.1.0/django_dbos/heartbeat.py +217 -0
  28. django_dbos-0.1.0/django_dbos/logging.py +13 -0
  29. django_dbos-0.1.0/django_dbos/management/__init__.py +0 -0
  30. django_dbos-0.1.0/django_dbos/management/commands/__init__.py +0 -0
  31. django_dbos-0.1.0/django_dbos/management/commands/dbos.py +213 -0
  32. django_dbos-0.1.0/django_dbos/migrations/0001_initial.py +40 -0
  33. django_dbos-0.1.0/django_dbos/migrations/__init__.py +0 -0
  34. django_dbos-0.1.0/django_dbos/models.py +50 -0
  35. django_dbos-0.1.0/django_dbos/params.py +181 -0
  36. django_dbos-0.1.0/django_dbos/params_dataclass.py +55 -0
  37. django_dbos-0.1.0/django_dbos/pytest_plugin.py +288 -0
  38. django_dbos-0.1.0/django_dbos/results.py +199 -0
  39. django_dbos-0.1.0/django_dbos/retention.py +71 -0
  40. django_dbos-0.1.0/django_dbos/scheduling.py +219 -0
  41. django_dbos-0.1.0/django_dbos/sentry.py +163 -0
  42. django_dbos-0.1.0/django_dbos/serializers.py +134 -0
  43. django_dbos-0.1.0/django_dbos/settings.py +536 -0
  44. django_dbos-0.1.0/django_dbos/static/django_dbos/admin.css +134 -0
  45. django_dbos-0.1.0/django_dbos/system_db.py +161 -0
  46. django_dbos-0.1.0/django_dbos/system_engine.py +47 -0
  47. django_dbos-0.1.0/django_dbos/tasks.py +38 -0
  48. django_dbos-0.1.0/django_dbos/tasks_registry.py +246 -0
  49. django_dbos-0.1.0/django_dbos/templates/admin/django_dbos/base.html +10 -0
  50. django_dbos-0.1.0/django_dbos/templates/admin/django_dbos/delete_confirmation.html +15 -0
  51. django_dbos-0.1.0/django_dbos/templates/admin/django_dbos/error.html +10 -0
  52. django_dbos-0.1.0/django_dbos/templates/admin/django_dbos/schedule_list.html +50 -0
  53. django_dbos-0.1.0/django_dbos/templates/admin/django_dbos/workflow_detail.html +118 -0
  54. django_dbos-0.1.0/django_dbos/templates/admin/django_dbos/workflow_list.html +63 -0
  55. django_dbos-0.1.0/django_dbos/templatetags/__init__.py +0 -0
  56. django_dbos-0.1.0/django_dbos/templatetags/dbos_admin.py +41 -0
  57. django_dbos-0.1.0/django_dbos/test.py +35 -0
  58. django_dbos-0.1.0/django_dbos/workflow_names.py +40 -0
  59. django_dbos-0.1.0/docs/admin.md +101 -0
  60. django_dbos-0.1.0/docs/ai-agents.md +87 -0
  61. django_dbos-0.1.0/docs/assets/admin/schedules-dark.png +0 -0
  62. django_dbos-0.1.0/docs/assets/admin/schedules-light.png +0 -0
  63. django_dbos-0.1.0/docs/assets/admin/workflow-detail-dark.png +0 -0
  64. django_dbos-0.1.0/docs/assets/admin/workflow-detail-light.png +0 -0
  65. django_dbos-0.1.0/docs/assets/admin/workflows-dark.png +0 -0
  66. django_dbos-0.1.0/docs/assets/admin/workflows-light.png +0 -0
  67. django_dbos-0.1.0/docs/configuration.md +714 -0
  68. django_dbos-0.1.0/docs/cron.md +133 -0
  69. django_dbos-0.1.0/docs/deployment.md +299 -0
  70. django_dbos-0.1.0/docs/first-task.md +189 -0
  71. django_dbos-0.1.0/docs/index.md +146 -0
  72. django_dbos-0.1.0/docs/tasks.md +387 -0
  73. django_dbos-0.1.0/docs/testing.md +153 -0
  74. django_dbos-0.1.0/docs/troubleshooting.md +279 -0
  75. django_dbos-0.1.0/docs/worker-capacity.md +258 -0
  76. django_dbos-0.1.0/docs/workflows.md +365 -0
  77. django_dbos-0.1.0/examples/cron_report/.gitignore +3 -0
  78. django_dbos-0.1.0/examples/cron_report/README.md +67 -0
  79. django_dbos-0.1.0/examples/cron_report/config/__init__.py +0 -0
  80. django_dbos-0.1.0/examples/cron_report/config/settings.py +84 -0
  81. django_dbos-0.1.0/examples/cron_report/config/urls.py +6 -0
  82. django_dbos-0.1.0/examples/cron_report/manage.py +22 -0
  83. django_dbos-0.1.0/examples/cron_report/pyproject.toml +16 -0
  84. django_dbos-0.1.0/examples/cron_report/reports_app/__init__.py +0 -0
  85. django_dbos-0.1.0/examples/cron_report/reports_app/admin.py +5 -0
  86. django_dbos-0.1.0/examples/cron_report/reports_app/management/__init__.py +0 -0
  87. django_dbos-0.1.0/examples/cron_report/reports_app/management/commands/__init__.py +0 -0
  88. django_dbos-0.1.0/examples/cron_report/reports_app/management/commands/seed_signups.py +18 -0
  89. django_dbos-0.1.0/examples/cron_report/reports_app/migrations/0001_initial.py +20 -0
  90. django_dbos-0.1.0/examples/cron_report/reports_app/migrations/__init__.py +0 -0
  91. django_dbos-0.1.0/examples/cron_report/reports_app/models.py +9 -0
  92. django_dbos-0.1.0/examples/cron_report/reports_app/tasks.py +20 -0
  93. django_dbos-0.1.0/examples/cron_report/uv.lock +555 -0
  94. django_dbos-0.1.0/examples/ninja_api/.gitignore +3 -0
  95. django_dbos-0.1.0/examples/ninja_api/README.md +72 -0
  96. django_dbos-0.1.0/examples/ninja_api/config/__init__.py +0 -0
  97. django_dbos-0.1.0/examples/ninja_api/config/settings.py +86 -0
  98. django_dbos-0.1.0/examples/ninja_api/config/urls.py +8 -0
  99. django_dbos-0.1.0/examples/ninja_api/manage.py +22 -0
  100. django_dbos-0.1.0/examples/ninja_api/pyproject.toml +16 -0
  101. django_dbos-0.1.0/examples/ninja_api/tasks_app/__init__.py +0 -0
  102. django_dbos-0.1.0/examples/ninja_api/tasks_app/api.py +43 -0
  103. django_dbos-0.1.0/examples/ninja_api/tasks_app/tasks.py +15 -0
  104. django_dbos-0.1.0/examples/ninja_api/uv.lock +679 -0
  105. django_dbos-0.1.0/noxfile.py +43 -0
  106. django_dbos-0.1.0/pyproject.toml +168 -0
  107. django_dbos-0.1.0/scripts/admin_screenshots.py +236 -0
  108. django_dbos-0.1.0/skills/django-dbos/SKILL.md +463 -0
  109. django_dbos-0.1.0/tests/__init__.py +0 -0
  110. django_dbos-0.1.0/tests/conftest.py +32 -0
  111. django_dbos-0.1.0/tests/helpers.py +146 -0
  112. django_dbos-0.1.0/tests/settings.py +77 -0
  113. django_dbos-0.1.0/tests/tasks.py +204 -0
  114. django_dbos-0.1.0/tests/tasks_invalid_cron.py +11 -0
  115. django_dbos-0.1.0/tests/test_admin.py +699 -0
  116. django_dbos-0.1.0/tests/test_admin_format.py +40 -0
  117. django_dbos-0.1.0/tests/test_apps.py +26 -0
  118. django_dbos-0.1.0/tests/test_backends.py +170 -0
  119. django_dbos-0.1.0/tests/test_backends_client.py +173 -0
  120. django_dbos-0.1.0/tests/test_capacity.py +514 -0
  121. django_dbos-0.1.0/tests/test_checks.py +359 -0
  122. django_dbos-0.1.0/tests/test_client.py +154 -0
  123. django_dbos-0.1.0/tests/test_cockroachdb.py +83 -0
  124. django_dbos-0.1.0/tests/test_context.py +239 -0
  125. django_dbos-0.1.0/tests/test_cron.py +177 -0
  126. django_dbos-0.1.0/tests/test_dbos_setup.py +281 -0
  127. django_dbos-0.1.0/tests/test_discovery.py +62 -0
  128. django_dbos-0.1.0/tests/test_heartbeat.py +211 -0
  129. django_dbos-0.1.0/tests/test_integration.py +214 -0
  130. django_dbos-0.1.0/tests/test_integration_serializers.py +64 -0
  131. django_dbos-0.1.0/tests/test_migrate_command.py +103 -0
  132. django_dbos-0.1.0/tests/test_params.py +140 -0
  133. django_dbos-0.1.0/tests/test_public_helpers.py +15 -0
  134. django_dbos-0.1.0/tests/test_pytest_plugin.py +476 -0
  135. django_dbos-0.1.0/tests/test_registry.py +65 -0
  136. django_dbos-0.1.0/tests/test_results.py +201 -0
  137. django_dbos-0.1.0/tests/test_retention.py +82 -0
  138. django_dbos-0.1.0/tests/test_sentry.py +157 -0
  139. django_dbos-0.1.0/tests/test_serializers.py +186 -0
  140. django_dbos-0.1.0/tests/test_settings.py +514 -0
  141. django_dbos-0.1.0/tests/test_smoke.py +11 -0
  142. django_dbos-0.1.0/tests/test_system_db.py +262 -0
  143. django_dbos-0.1.0/tests/test_system_engine.py +40 -0
  144. django_dbos-0.1.0/tests/test_task_execution.py +175 -0
  145. django_dbos-0.1.0/tests/test_two_processes.py +54 -0
  146. django_dbos-0.1.0/tests/test_worker_command.py +102 -0
  147. django_dbos-0.1.0/tests/urls.py +8 -0
  148. django_dbos-0.1.0/tests/urls_two_sites.py +15 -0
  149. django_dbos-0.1.0/uv.lock +1916 -0
  150. django_dbos-0.1.0/zensical.toml +156 -0
@@ -0,0 +1,60 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions: {}
9
+
10
+ jobs:
11
+ lint:
12
+ runs-on: ubuntu-latest
13
+ timeout-minutes: 5
14
+ permissions:
15
+ contents: read
16
+ steps:
17
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
18
+ with:
19
+ persist-credentials: false
20
+ - uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7.1.5
21
+ - run: uv sync
22
+ - run: uv run ruff check .
23
+ - run: uv run ruff format --check .
24
+ - run: uv run pyrefly check --project-excludes 'examples/**'
25
+
26
+ tests:
27
+ runs-on: ${{ matrix.os }}
28
+ timeout-minutes: 10
29
+ permissions:
30
+ contents: read
31
+ strategy:
32
+ fail-fast: false
33
+ matrix:
34
+ os: [ubuntu-latest, windows-latest, macos-latest]
35
+ python-version: ["3.12", "3.13", "3.14", "3.15"]
36
+ steps:
37
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
38
+ with:
39
+ persist-credentials: false
40
+ - uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7.1.5
41
+ - run: uv python install ${{ matrix.python-version }}
42
+ - run: uv run --python ${{ matrix.python-version }} pytest -m "not integration"
43
+
44
+ coverage:
45
+ # Whole suite against a real system database (PostgreSQL via
46
+ # testcontainers on the runner's Docker): unit tests plus admin pages,
47
+ # cron, capacity, dbos migrate and the two-process web/worker scenario.
48
+ # Subprocesses are measured too; fails under the fail_under floor.
49
+ runs-on: ubuntu-latest
50
+ timeout-minutes: 15
51
+ permissions:
52
+ contents: read
53
+ steps:
54
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
55
+ with:
56
+ persist-credentials: false
57
+ - uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7.1.5
58
+ - run: uv run coverage run -m pytest --dbos-system-db=postgres
59
+ - run: uv run coverage combine
60
+ - run: uv run coverage report --skip-covered --format=markdown >> "$GITHUB_STEP_SUMMARY"
@@ -0,0 +1,32 @@
1
+ name: Documentation
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ permissions:
6
+ contents: read
7
+ pages: write
8
+ id-token: write
9
+ jobs:
10
+ deploy:
11
+ # GitHub Pages is not available on private repositories (free plan): skip
12
+ # until the repository is public and Pages is enabled once (see AGENTS.md).
13
+ if: ${{ !github.event.repository.private }}
14
+ environment:
15
+ name: github-pages
16
+ url: ${{ steps.deployment.outputs.page_url }}
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
20
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
21
+ with:
22
+ persist-credentials: false
23
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
24
+ with:
25
+ python-version: 3.x
26
+ - run: pip install zensical
27
+ - run: zensical build --clean
28
+ - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
29
+ with:
30
+ path: site
31
+ - uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
32
+ id: deployment
@@ -0,0 +1,49 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ permissions: {}
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ timeout-minutes: 5
13
+ permissions:
14
+ contents: read
15
+ steps:
16
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
17
+ with:
18
+ persist-credentials: false
19
+ - uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7.1.5
20
+ with:
21
+ enable-cache: false
22
+ # Refuse to publish a tag that does not match the version in pyproject.toml.
23
+ - run: test "v$(uv version --short)" = "$GITHUB_REF_NAME"
24
+ - run: uv build
25
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
26
+ with:
27
+ name: dist
28
+ path: dist/
29
+
30
+ publish:
31
+ # Trusted publishing: PyPI accepts the OIDC token of this workflow
32
+ # (release.yml) running in the `pypi` environment, no API token needed.
33
+ needs: build
34
+ runs-on: ubuntu-latest
35
+ timeout-minutes: 5
36
+ environment:
37
+ name: pypi
38
+ url: https://pypi.org/project/django-dbos/
39
+ permissions:
40
+ id-token: write
41
+ steps:
42
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
43
+ with:
44
+ name: dist
45
+ path: dist/
46
+ - uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7.1.5
47
+ with:
48
+ enable-cache: false
49
+ - run: uv publish --trusted-publishing always
@@ -0,0 +1,34 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ build/
6
+ dist/
7
+
8
+ # Virtual environments
9
+ .venv/
10
+ venv/
11
+
12
+ # Tooling caches
13
+ .pytest_cache/
14
+ .ruff_cache/
15
+ .nox/
16
+ .coverage
17
+ .coverage.*
18
+ htmlcov/
19
+
20
+ # Editors / OS
21
+ .idea/
22
+ .vscode/
23
+ .DS_Store
24
+
25
+ # DBOS
26
+ dbos/
27
+ dbos.sqlite*
28
+ *.sqlite3
29
+
30
+ # ZCode local plans
31
+ .zcode/
32
+
33
+ # Docs build output
34
+ site/
@@ -0,0 +1,31 @@
1
+ repos:
2
+ - repo: https://github.com/commitizen-tools/commitizen
3
+ rev: v4.10.1
4
+ hooks:
5
+ - id: commitizen
6
+ - id: commitizen-branch
7
+ stages: [pre-push]
8
+ - repo: https://github.com/astral-sh/ruff-pre-commit
9
+ rev: v0.16.6
10
+ hooks:
11
+ - id: ruff
12
+ args: [--fix]
13
+ - id: ruff-format
14
+ - repo: https://github.com/osprey-oss/deptry.git
15
+ rev: 0c39226d761685125c5ff71ca81282d6751c9540 # 0.25.1
16
+ hooks:
17
+ - id: deptry
18
+ # The upstream hook hardcodes `deptry .` and would flag the test/dev
19
+ # tooling; scan only the shipped package.
20
+ entry: deptry django_dbos
21
+ pass_filenames: false
22
+ - repo: https://github.com/zizmorcore/zizmor-pre-commit
23
+ rev: v1.32.0
24
+ hooks:
25
+ - id: zizmor
26
+ - repo: https://github.com/pre-commit/pre-commit-hooks
27
+ rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # v6.0.0
28
+ hooks:
29
+ - id: check-yaml
30
+ - id: end-of-file-fixer
31
+ - id: trailing-whitespace
@@ -0,0 +1,137 @@
1
+ # AGENTS.md
2
+
3
+ Reference for AI coding agents (and humans) working on this repository.
4
+
5
+ ## Project
6
+
7
+ `django-dbos` is a DBOS-backed backend for Django's `django.tasks` framework:
8
+ durable background tasks, cron scheduling (`@cron`), optional Sentry cron
9
+ monitors, admin pages and a worker-capacity report. Published on PyPI.
10
+
11
+ ## Layout
12
+
13
+ - Flat package layout: `django_dbos/` at the repo root (no `src/`), managed by **uv** and built with **hatchling**.
14
+ - `docs/` holds the user-facing documentation (Markdown only). The zensical
15
+ config lives at the root: `zensical.toml` (standard layout, do not move it
16
+ into `docs/` — the docs dir resolves relative to the config file).
17
+ - Tests in `tests/` are plain functions (not Django `TestCase` unless a DB is
18
+ needed); integration tests are marked `integration` and run against a real
19
+ system database (PostgreSQL via testcontainers, SQLite file fallback;
20
+ `test_cockroachdb.py` starts its own CockroachDB container).
21
+ - `examples/` holds standalone runnable projects (own `pyproject.toml`/`uv.lock`,
22
+ `django-dbos` pulled in as an editable path dependency) demonstrating the
23
+ package end to end; not part of the root `uv` project or its dependency
24
+ extras, so `uv run pyrefly check` needs `--project-excludes 'examples/**'`
25
+ (its imports are not installed in the root `.venv`) — see Commands below.
26
+
27
+ ## Commands
28
+
29
+ ```sh
30
+ uv run pytest # unit tests
31
+ uv run pytest -m integration # integration tests (Docker recommended)
32
+ uv run coverage run -m pytest && uv run coverage combine && uv run coverage report # coverage, see note
33
+ uv run ruff check . && uv run ruff format --check .
34
+ uv run pyrefly check --project-excludes 'examples/**' # type check (examples/ are separate projects)
35
+ uv run deptry django_dbos # dependency hygiene (package only)
36
+ uv run --group doc zensical build # docs (runs from the repo root)
37
+ uv run --with playwright scripts/admin_screenshots.py # regenerate docs/assets/admin/*.png after admin UI changes
38
+ uv run benchmarks/serializers.py # serializer timings quoted in docs/configuration.md
39
+ uv run cz bump # release (commitizen)
40
+ ```
41
+
42
+ ### Coverage
43
+
44
+ Measured with branch coverage over `django_dbos`, floor **98 %** in
45
+ `[tool.coverage.report]`. Always measure with `coverage run -m pytest`, **not**
46
+ `pytest --cov`: the pytest plugin is imported by pytest before pytest-cov
47
+ starts tracing, so module-level lines would be reported as uncovered.
48
+ Subprocesses (the plugin's pytester runs, the two-process worker) are measured
49
+ too (`parallel = true`, `patch = ["subprocess"]`): each process writes its own
50
+ `.coverage.*` file, so run `coverage combine` before `coverage report`. A
51
+ subprocess killed abruptly writes no data.
52
+
53
+ ### Test environment
54
+
55
+ - `[tool.uv] exclude-newer = "7 days"` pins fresh package resolution.
56
+ - `DJANGO_SETTINGS_MODULE=tests.settings`; `filterwarnings = ["error"]` —
57
+ warnings fail tests.
58
+ - Docker available → Postgres testcontainers for integration tests; otherwise
59
+ SQLite file fallback.
60
+
61
+ ## Publishing the docs
62
+
63
+ The Documentation workflow deploys `zensical build` to GitHub Pages. It is
64
+ skipped while the repository is private (Pages needs a paid plan there).
65
+ When the repository goes public, enable Pages **once** with the "GitHub
66
+ Actions" source — `gh api -X POST repos/lewoudar/django-dbos/pages -f build_type=workflow`
67
+ or Settings › Pages — then every push to `main` deploys.
68
+
69
+ ## Conventions
70
+
71
+ - Commit messages follow **conventional commits** (commitizen,
72
+ `cz_conventional_commits`, tag format `v$version`); CHANGELOG generated by
73
+ commitizen through `changelog_template.md.j2` (config key
74
+ `[tool.commitizen] template = ...`; regenerate with `cz changelog`, use
75
+ `--incremental` to preserve manually edited sections).
76
+ - Logging through structlog under the `django_dbos` logger.
77
+ - CI workflows have their actions pinned by SHA (zizmor-checked); run
78
+ `uv run prek run --all-files` before committing (ruff, commitizen, deptry,
79
+ zizmor).
80
+ - The pytest plugin's runtime imports (pytest, testcontainers) are the
81
+ `django-dbos[test]` extra; deptry checks the package against the declared
82
+ dependencies and extras, so keep the extra in sync with the plugin.
83
+ - Python ≥ 3.12, Django ≥ 6.0.
84
+
85
+ ## Process model (do not regress)
86
+
87
+ - Only `dbos worker` (and the `dbos_system` test fixture) may call
88
+ `dbos_setup.ensure_launched()`: `DBOS.launch()` starts queue/scheduler/
89
+ recovery threads, i.e. it turns the process into a worker. Everything else
90
+ (backend enqueue/get_result, admin views, `capacity.collect`) goes through
91
+ `client.get_client()` (a `DBOSClient`, system database only).
92
+ - The worker registers every known task (`tasks.known_tasks()`, filled by
93
+ `DBOSTask.__post_init__`) as a workflow *before* launch; clients enqueue by
94
+ workflow name and never hold workflow code.
95
+ - `manage.py migrate` migrates the system database through a `post_migrate`
96
+ receiver (`system_db.migrate_after_django_migrate`): a plain
97
+ `SystemDatabase` handle, never `ensure_launched()`. It must stay skipped for
98
+ `flush` (no `plan`), non-default aliases and test databases.
99
+ - Worker heartbeats live in the `WorkerHeartbeat` model (Django database),
100
+ not in the cache; the package ships a migration.
101
+
102
+ ## Pitfalls learned the hard way
103
+
104
+ - `DBOSBackend.__init__` must not raise: Django builds the backend while
105
+ importing task modules and running its checks. A bad queue declaration is
106
+ stored, reported by `django_dbos.E001` and raised on enqueue. In tests,
107
+ override the backend's `OPTIONS` with
108
+ `tests.helpers.set_dbos_options(settings, {...})`.
109
+ - CockroachDB system database: DBOS forces `postgresql+psycopg` when given a
110
+ URL alone, and its SQL fails on CockroachDB under psycopg 3 (typed
111
+ parameters, `float * int`). `system_engine` builds a `cockroachdb+psycopg2`
112
+ engine for the worker and the client (`use_listen_notify=False`), and
113
+ `DBOS.migrate()` takes no engine, so `migrate` only creates the database:
114
+ the worker applies DBOS' migrations on launch (`django_dbos.E005`).
115
+ - **Never patch `django.conf.settings` attributes directly** (lazy object +
116
+ pytest-django `settings` fixture teardown = state leaks between tests).
117
+ Patch the module's own `settings` reference instead (see
118
+ `test_no_usable_django_database_raises` in `tests/test_settings.py`) or
119
+ override via the `settings` fixture for plain settings.
120
+ Overriding `DATABASES` through the fixture also raises a `UserWarning`
121
+ (fatal with `filterwarnings = ["error"]`).
122
+ - No private SDK import (`dbos._*`): workflow functions are kept in
123
+ `tasks_registry._workflows`, errors come from the public `dbos.error`
124
+ alias (`from dbos import error as dbos_error` — `from dbos.error import`
125
+ fails, it is an attribute, not a module), capacity lists active workflows
126
+ through `DBOSClient.list_workflows`. `DBOS.get_schedule` returns a
127
+ **dict**; the synchronous `retrieve_workflow()` refuses to run inside a
128
+ running event loop — use the `*_async` variants in async paths.
129
+ - `django.tasks` validates task construction (module-level function, declared
130
+ queue) at decoration time: build "rogue" tasks inside tests with
131
+ `dataclasses.replace` after overriding `TASKS`, not at module import.
132
+ - Admin URLs must be **prepended** to the admin's URL list (`dbos_urls + urls`)
133
+ or the admin's generic patterns swallow them; URL names are bare (`django_dbos_workflow_changelist`), the `admin:`
134
+ namespace is added by the
135
+ inclusion.
136
+ - DBOS/structlog log on stdout: in tests, capture a command's output with
137
+ `call_command(..., stdout=StringIO())` rather than `capsys`.
@@ -0,0 +1,11 @@
1
+ ## [0.1.0] - 2026-11-08
2
+
3
+ ### Added
4
+
5
+ - `DBOSBackend`, a backend for Django's `django.tasks` framework: every task runs as a durable DBOS workflow that survives crashes and restarts, with configurable queues (concurrency, rate limits) and pluggable serializers.
6
+ - Access to DBOS durable primitives (steps, sleep, events, messages) from inside a task through `get_dbos_context()`.
7
+ - `@cron` scheduling of tasks, with optional Sentry cron monitors.
8
+ - A `dbos` management command: `dbos worker` to run tasks, `dbos migrate` to set up the system database (also applied by `manage.py migrate`), and `dbos status` for a worker-capacity report with pluggable advice.
9
+ - Admin pages, restricted to superusers, to browse, cancel, resume and fork workflows and to manage schedules.
10
+ - PostgreSQL, SQLite and CockroachDB support for the DBOS system database.
11
+ - A pytest plugin (`django-dbos[test]` extra) with `dbos_inline` and `dbos_system` fixtures, to run tasks inline or against a real system database.
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kevin Tewouda
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,247 @@
1
+ Metadata-Version: 2.5
2
+ Name: django-dbos
3
+ Version: 0.1.0
4
+ Summary: DBOS-backed backend for Django's tasks framework: durable background tasks, cron scheduling and optional Sentry monitoring.
5
+ Project-URL: Homepage, https://github.com/lewoudar/django-dbos
6
+ Project-URL: Repository, https://github.com/lewoudar/django-dbos
7
+ Project-URL: Changelog, https://github.com/lewoudar/django-dbos/blob/main/CHANGELOG.md
8
+ Author-email: Kevin Tewouda <rollandkev@yahoo.fr>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Web Environment
13
+ Classifier: Framework :: Django
14
+ Classifier: Framework :: Django :: 6.0
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Programming Language :: Python :: 3.15
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.12
25
+ Requires-Dist: asgiref>=3.8
26
+ Requires-Dist: croniter>=6.0
27
+ Requires-Dist: dbos<4,>=3.0
28
+ Requires-Dist: django-click>=2.5.0
29
+ Requires-Dist: django<7,>=6.0
30
+ Requires-Dist: sqlalchemy<3,>=2.0
31
+ Requires-Dist: structlog>=25.1
32
+ Provides-Extra: cockroachdb
33
+ Requires-Dist: psycopg2-binary>=2.9; extra == 'cockroachdb'
34
+ Requires-Dist: sqlalchemy-cockroachdb>=2.0; extra == 'cockroachdb'
35
+ Provides-Extra: metrics
36
+ Requires-Dist: psutil>=7.0; extra == 'metrics'
37
+ Provides-Extra: msgspec
38
+ Requires-Dist: msgspec>=0.21; extra == 'msgspec'
39
+ Provides-Extra: sentry
40
+ Requires-Dist: sentry-sdk>=2.19; extra == 'sentry'
41
+ Provides-Extra: test
42
+ Requires-Dist: pytest>=8.4; extra == 'test'
43
+ Requires-Dist: testcontainers[postgres]>=4.13; extra == 'test'
44
+ Description-Content-Type: text/markdown
45
+
46
+ # django-dbos
47
+
48
+ **⚠️ Experimental project. Do not use in production.**
49
+
50
+ A [DBOS](https://docs.dbos.dev/)-backed backend for Django's
51
+ [tasks framework](https://docs.djangoproject.com/en/stable/topics/tasks/):
52
+ background tasks that survive a worker crash, cron scheduling, optional
53
+ Sentry monitoring, an admin UI to inspect and operate on tasks, and a worker
54
+ capacity report to drive autoscaling.
55
+
56
+ ## At a glance
57
+
58
+ If you know Django's tasks API, you already know django-dbos:
59
+
60
+ ```python
61
+ from django.tasks import task
62
+
63
+
64
+ @task
65
+ def send_welcome_email(user_id: int) -> None: ...
66
+
67
+
68
+ result = send_welcome_email.enqueue(user_id) # durable by default
69
+ result.status # READY, RUNNING, SUCCESSFUL, FAILED — standard Django TaskResult
70
+ ```
71
+
72
+ Each enqueued task is recorded in a database before it runs. If the worker
73
+ executing it crashes, the task is not lost: it runs again once a worker is
74
+ back.
75
+
76
+ ## Why DBOS
77
+
78
+ Say you send a newsletter to 10,000 users:
79
+
80
+ ```python
81
+ from django.tasks import task
82
+
83
+
84
+ @task
85
+ def send_newsletter(campaign_id: int) -> None:
86
+ for user_id in User.objects.values_list('id', flat=True):
87
+ send_one_email(user_id, campaign_id)
88
+ ```
89
+
90
+ With most task backends, if the worker is killed at email #6,000 — a deploy,
91
+ an OOM, a spot instance reclaimed — the retry restarts the task from #1.
92
+ 6,000 people get the same email twice.
93
+
94
+ With django-dbos, one option and one call per email turn the loop into a
95
+ durable, checkpointed workflow:
96
+
97
+ ```python
98
+ from django.tasks import task
99
+ from django_dbos import dbos_params, get_dbos_context
100
+
101
+
102
+ @dbos_params(durable_operations=True)
103
+ @task
104
+ def send_newsletter(campaign_id: int) -> None:
105
+ ctx = get_dbos_context()
106
+ for user_id in User.objects.values_list('id', flat=True):
107
+ ctx.run_step(send_one_email, user_id, campaign_id, name=f'email-{user_id}')
108
+ ```
109
+
110
+ Each `run_step` call is recorded in the system database as it completes.
111
+ Kill the worker at #6,000, restart it, and the task resumes from there: the
112
+ 5,999 emails already sent are skipped, and only the one in flight at the
113
+ moment of the crash may go out again. No external state to reconcile, no
114
+ idempotency keys to invent yourself.
115
+
116
+ Cron schedules are just `@task`s with a firing time attached — same
117
+ durability, same retries, same admin page:
118
+
119
+ ```python
120
+ from django_dbos import cron
121
+
122
+
123
+ @cron('0 3 * * *') # every day at 3am
124
+ @dbos_params(workflow_timeout=3600, step_retries_allowed=True)
125
+ @task
126
+ def nightly_report() -> None: ...
127
+ ```
128
+
129
+ Schedules are stored in the system database, so they keep firing across
130
+ restarts and deploys — and with the `sentry` extra installed, each firing
131
+ checks in with [Sentry Crons](https://docs.sentry.io/product/crons/)
132
+ automatically.
133
+
134
+ ## What you get
135
+
136
+ - **Durable tasks** — every `.enqueue()` becomes a DBOS workflow: automatic
137
+ recovery after a crash, step retries with backoff, deferred execution
138
+ (`run_after`), deduplication, priorities and workflow timeouts, all through
139
+ Django's own `Task` / `TaskResult` API.
140
+ - **Durable workflows** *(opt-in)* — checkpointed steps, durable sleeps that
141
+ survive restarts, progress events, messages between workflows and child
142
+ workflows, from inside a task.
143
+ - **Cron scheduling** — `@cron('0 3 * * *')` on top of `@task`. Every worker
144
+ fires the schedules, each firing runs once, and no separate scheduler
145
+ process is needed.
146
+ - **Sentry cron monitoring** *(optional)* — scheduled tasks check in with
147
+ [Sentry Crons](https://docs.sentry.io/product/crons/) automatically, no
148
+ monitor to configure by hand.
149
+ - **Admin pages** — inspect every workflow (inputs, outputs, errors, step
150
+ history), and cancel, resume, fork or delete them; manage schedules; see
151
+ which workers are alive. Superuser-only.
152
+ - **Worker capacity report** — `dbos status` (or `capacity.collect()` from
153
+ Python) reports backlog, running count and per-worker load per queue, with
154
+ a scale up/down/OK verdict you can plug into an autoscaler.
155
+ - **Structured logging** — everything goes through
156
+ [structlog](https://www.structlog.org/) under the `django_dbos` logger.
157
+ - **A pytest plugin** — `dbos_inline` runs enqueued tasks synchronously for
158
+ fast unit tests; `dbos_system` launches a real DBOS system database
159
+ (SQLite or a Postgres testcontainer) when you need real semantics.
160
+
161
+ ## Requirements
162
+
163
+ - Python 3.12+
164
+ - Django 6.0+
165
+ - A DBOS system database: PostgreSQL (recommended) or SQLite
166
+
167
+ ## Installation
168
+
169
+ ```bash
170
+ uv add django-dbos
171
+ # or
172
+ pip install django-dbos
173
+ ```
174
+
175
+ Poetry and PDM work too, see
176
+ [Installation](https://github.com/lewoudar/django-dbos/blob/main/docs/index.md#installation).
177
+ Add the `msgspec` extra (`uv add "django-dbos[msgspec]"` or
178
+ `pip install "django-dbos[msgspec]"`) for fast, safe serialization of task data; see
179
+ [Serialization](https://github.com/lewoudar/django-dbos/blob/main/docs/configuration.md#serialization).
180
+
181
+ ```python
182
+ INSTALLED_APPS = [
183
+ ...,
184
+ 'django_dbos',
185
+ ]
186
+
187
+ TASKS = {
188
+ 'default': {
189
+ 'BACKEND': 'django_dbos.backends.DBOSBackend',
190
+ 'QUEUES': ['default', 'emails'],
191
+ # other django-dbos settings go in 'OPTIONS', see the configuration docs
192
+ },
193
+ }
194
+ ```
195
+
196
+ ```bash
197
+ python manage.py migrate # Django's migrations, then the DBOS system database
198
+ python manage.py dbos worker # start executing tasks
199
+ ```
200
+
201
+ Put your tasks in a `tasks.py` module of an installed app: that is where the
202
+ worker looks for them. Your web processes only ever talk to the system
203
+ database — they enqueue tasks, they never execute them.
204
+
205
+ New to background tasks? [Your first task](https://github.com/lewoudar/django-dbos/blob/main/docs/first-task.md) walks through
206
+ writing, running and checking one, step by step.
207
+
208
+ ## Using an AI coding agent
209
+
210
+ django-dbos ships an [agent skill](https://github.com/lewoudar/django-dbos/tree/main/skills/django-dbos)
211
+ that teaches AI coding agents (Claude Code, Codex, Cursor, OpenCode, Pi...)
212
+ how to write tasks, workflows and cron schedules with it. Install it with the
213
+ [`skills`](https://github.com/vercel-labs/skills) CLI:
214
+
215
+ ```bash
216
+ npx skills add lewoudar/django-dbos --skill django-dbos
217
+ ```
218
+
219
+ [AI agents](https://github.com/lewoudar/django-dbos/blob/main/docs/ai-agents.md) has the command for each agent
220
+ and how to install the skill by hand.
221
+
222
+ ## Examples
223
+
224
+ Two runnable projects in [`examples/`](https://github.com/lewoudar/django-dbos/tree/main/examples/), each a standalone `uv`
225
+ project you can `uv sync` and run on its own:
226
+
227
+ - [`ninja_api`](https://github.com/lewoudar/django-dbos/tree/main/examples/ninja_api) — a [Django Ninja](https://django-ninja.dev/)
228
+ API that enqueues a task and polls its status until it's done.
229
+ - [`cron_report`](https://github.com/lewoudar/django-dbos/tree/main/examples/cron_report) — a `@cron` task that fires on its
230
+ own once a worker is running, no external scheduler needed.
231
+
232
+ ## Documentation
233
+
234
+ The full documentation lives in [`docs/`](https://github.com/lewoudar/django-dbos/tree/main/docs/) (a hosted version is coming
235
+ once the repository goes public). Good places to start:
236
+
237
+ - [Your first task](https://github.com/lewoudar/django-dbos/blob/main/docs/first-task.md) — a hands-on walkthrough.
238
+ - [Tasks](https://github.com/lewoudar/django-dbos/blob/main/docs/tasks.md) and [Workflows](https://github.com/lewoudar/django-dbos/blob/main/docs/workflows.md) — options,
239
+ retries, durable primitives.
240
+ - [Cron scheduling](https://github.com/lewoudar/django-dbos/blob/main/docs/cron.md) — schedules and Sentry monitors.
241
+ - [Deployment](https://github.com/lewoudar/django-dbos/blob/main/docs/deployment.md) — what to settle before production.
242
+ - [Troubleshooting](https://github.com/lewoudar/django-dbos/blob/main/docs/troubleshooting.md) — the errors you are most
243
+ likely to meet.
244
+
245
+ ## License
246
+
247
+ MIT