deployangel 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 (49) hide show
  1. deployangel-0.1.0/.github/workflows/ci.yml +80 -0
  2. deployangel-0.1.0/.github/workflows/release.yml +110 -0
  3. deployangel-0.1.0/.gitignore +7 -0
  4. deployangel-0.1.0/AGENTS.md +60 -0
  5. deployangel-0.1.0/CHANGELOG.md +18 -0
  6. deployangel-0.1.0/LICENSE.txt +21 -0
  7. deployangel-0.1.0/PKG-INFO +343 -0
  8. deployangel-0.1.0/README.md +309 -0
  9. deployangel-0.1.0/SECURITY.md +23 -0
  10. deployangel-0.1.0/bench/overhead.py +62 -0
  11. deployangel-0.1.0/pyproject.toml +52 -0
  12. deployangel-0.1.0/src/deployangel/__init__.py +176 -0
  13. deployangel-0.1.0/src/deployangel/agent.py +270 -0
  14. deployangel-0.1.0/src/deployangel/asgi.py +216 -0
  15. deployangel-0.1.0/src/deployangel/celery.py +278 -0
  16. deployangel-0.1.0/src/deployangel/config.py +71 -0
  17. deployangel-0.1.0/src/deployangel/core/__init__.py +0 -0
  18. deployangel-0.1.0/src/deployangel/core/aggregator.py +193 -0
  19. deployangel-0.1.0/src/deployangel/core/buffer.py +36 -0
  20. deployangel-0.1.0/src/deployangel/core/fingerprint.py +162 -0
  21. deployangel-0.1.0/src/deployangel/core/histogram.py +34 -0
  22. deployangel-0.1.0/src/deployangel/core/instance.py +34 -0
  23. deployangel-0.1.0/src/deployangel/core/protocol.py +69 -0
  24. deployangel-0.1.0/src/deployangel/core/redaction.py +23 -0
  25. deployangel-0.1.0/src/deployangel/core/release.py +155 -0
  26. deployangel-0.1.0/src/deployangel/core/transport.py +87 -0
  27. deployangel-0.1.0/src/deployangel/django/__init__.py +3 -0
  28. deployangel-0.1.0/src/deployangel/django/apps.py +51 -0
  29. deployangel-0.1.0/src/deployangel/django/metadata.py +75 -0
  30. deployangel-0.1.0/src/deployangel/django/middleware.py +145 -0
  31. deployangel-0.1.0/src/deployangel/fastapi.py +9 -0
  32. deployangel-0.1.0/src/deployangel/flask.py +130 -0
  33. deployangel-0.1.0/src/deployangel/http.py +69 -0
  34. deployangel-0.1.0/src/deployangel/metadata.py +154 -0
  35. deployangel-0.1.0/src/deployangel/rq.py +103 -0
  36. deployangel-0.1.0/src/deployangel/starlette.py +9 -0
  37. deployangel-0.1.0/src/deployangel/version.py +1 -0
  38. deployangel-0.1.0/tests/conftest.py +81 -0
  39. deployangel-0.1.0/tests/django_app/__init__.py +0 -0
  40. deployangel-0.1.0/tests/django_app/urls.py +20 -0
  41. deployangel-0.1.0/tests/django_app/views.py +42 -0
  42. deployangel-0.1.0/tests/test_agent.py +268 -0
  43. deployangel-0.1.0/tests/test_core.py +293 -0
  44. deployangel-0.1.0/tests/test_django.py +116 -0
  45. deployangel-0.1.0/tests/test_jobs.py +207 -0
  46. deployangel-0.1.0/tests/test_web.py +154 -0
  47. deployangel-0.1.0/tests/web_apps/__init__.py +0 -0
  48. deployangel-0.1.0/tests/web_apps/fastapi_app.py +51 -0
  49. deployangel-0.1.0/tests/web_apps/flask_app.py +25 -0
@@ -0,0 +1,80 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [ main ]
7
+
8
+ jobs:
9
+ # The agent supports Python 3.10 and each framework's oldest listed version
10
+ # or later, so it runs on the oldest and newest of each.
11
+ test:
12
+ name: Python ${{ matrix.python }}, ${{ matrix.name }}
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ include:
18
+ - python: "3.10"
19
+ name: oldest frameworks
20
+ packages: >-
21
+ django==4.2.* djangorestframework==3.14.* django-celery-beat==2.5.*
22
+ fastapi==0.100.* flask==2.3.* celery==5.3.* rq==1.16.* "httpx<0.28" "pytest-asyncio<1"
23
+ - python: "3.12"
24
+ name: current LTS releases
25
+ packages: >-
26
+ django==5.2.* djangorestframework django-celery-beat
27
+ fastapi flask celery rq httpx pytest-asyncio
28
+ - python: "3.14"
29
+ name: newest frameworks
30
+ packages: >-
31
+ django djangorestframework django-celery-beat
32
+ fastapi flask celery rq httpx pytest-asyncio
33
+ services:
34
+ redis:
35
+ image: redis:7
36
+ ports: [ "6379:6379" ]
37
+ env:
38
+ RQ_REDIS_URL: redis://localhost:6379/15
39
+
40
+ steps:
41
+ - name: Checkout code
42
+ uses: actions/checkout@v7
43
+
44
+ - name: Set up Python
45
+ uses: actions/setup-python@v6
46
+ with:
47
+ python-version: ${{ matrix.python }}
48
+
49
+ - name: Install
50
+ run: pip install -e . ${{ matrix.packages }} fakeredis redis pytest
51
+
52
+ - name: Run tests
53
+ run: pytest
54
+
55
+ - name: Check overhead
56
+ run: python bench/overhead.py
57
+
58
+ # The agent itself imports nothing outside the standard library.
59
+ no-frameworks:
60
+ name: Without any framework installed
61
+ runs-on: ubuntu-latest
62
+ steps:
63
+ - uses: actions/checkout@v7
64
+ - uses: actions/setup-python@v6
65
+ with:
66
+ python-version: "3.10"
67
+ - run: pip install -e . pytest
68
+ - run: pytest tests/test_core.py tests/test_agent.py
69
+
70
+ package:
71
+ name: Build the package
72
+ runs-on: ubuntu-latest
73
+ steps:
74
+ - uses: actions/checkout@v7
75
+ - uses: actions/setup-python@v6
76
+ with:
77
+ python-version: "3.14"
78
+ - run: pip install build twine
79
+ - run: python -m build
80
+ - run: twine check --strict dist/*
@@ -0,0 +1,110 @@
1
+ name: Release
2
+
3
+ # Pushing a version tag (v0.1.0) builds the package, publishes it to PyPI
4
+ # once a maintainer approves the pypi environment, and then creates a GitHub
5
+ # release whose notes are that version's section of CHANGELOG.md.
6
+ #
7
+ # PyPI trusts this workflow directly (trusted publishing), so there's no API
8
+ # token anywhere: PyPI's publisher for the deployangel project names this
9
+ # repository, release.yml, and the pypi environment.
10
+ on:
11
+ push:
12
+ tags: [ "v*" ]
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ jobs:
18
+ build:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - name: Checkout code
22
+ uses: actions/checkout@v7
23
+
24
+ - name: Set up Python
25
+ uses: actions/setup-python@v6
26
+ with:
27
+ python-version: "3.14"
28
+
29
+ - name: Check the tag, version, and CHANGELOG agree
30
+ env:
31
+ TAG: ${{ github.ref_name }}
32
+ run: |
33
+ version=$(sed -n 's/^VERSION = "\(.*\)"$/\1/p' src/deployangel/version.py)
34
+ if [ "v$version" != "$TAG" ]; then
35
+ echo "::error::Tag $TAG doesn't match version $version in src/deployangel/version.py."
36
+ exit 1
37
+ fi
38
+ if ! grep -q "^## $version " CHANGELOG.md; then
39
+ echo "::error::CHANGELOG.md has no section for $version."
40
+ exit 1
41
+ fi
42
+
43
+ - name: Build
44
+ run: |
45
+ pip install build twine
46
+ python -m build
47
+ twine check --strict dist/*
48
+
49
+ - uses: actions/upload-artifact@v7
50
+ with:
51
+ name: dist
52
+ path: dist/
53
+
54
+ publish:
55
+ needs: build
56
+ runs-on: ubuntu-latest
57
+ environment:
58
+ name: pypi
59
+ url: https://pypi.org/project/deployangel/
60
+ permissions:
61
+ id-token: write
62
+ steps:
63
+ - uses: actions/download-artifact@v8
64
+ with:
65
+ name: dist
66
+ path: dist/
67
+
68
+ - name: Publish to PyPI
69
+ uses: pypa/gh-action-pypi-publish@release/v1
70
+
71
+ release:
72
+ needs: publish
73
+ runs-on: ubuntu-latest
74
+ permissions:
75
+ contents: write
76
+ steps:
77
+ - name: Checkout code
78
+ uses: actions/checkout@v7
79
+
80
+ - uses: actions/download-artifact@v8
81
+ with:
82
+ name: dist
83
+ path: dist/
84
+
85
+ - name: Create release from CHANGELOG.md
86
+ env:
87
+ GH_TOKEN: ${{ github.token }}
88
+ TAG: ${{ github.ref_name }}
89
+ run: |
90
+ version="${TAG#v}"
91
+
92
+ if gh release view "$TAG" > /dev/null 2>&1; then
93
+ echo "A release for $TAG already exists."
94
+ exit 0
95
+ fi
96
+
97
+ # The lines under "## <version> (date)", up to the next heading,
98
+ # without leading or trailing blank lines.
99
+ awk -v v="$version" '
100
+ index($0, "## " v " ") == 1 { found = 1; next }
101
+ found && /^## / { exit }
102
+ found { lines[++n] = $0 }
103
+ END {
104
+ first = 1; while (first <= n && lines[first] == "") first++
105
+ last = n; while (last >= first && lines[last] == "") last--
106
+ for (i = first; i <= last; i++) print lines[i]
107
+ }
108
+ ' CHANGELOG.md > notes.md
109
+
110
+ gh release create "$TAG" dist/* --title "deployangel (Python) $version" --notes-file notes.md
@@ -0,0 +1,7 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .pytest_cache/
@@ -0,0 +1,60 @@
1
+ # DeployAngel agent for Python: working rules
2
+
3
+ The `deployangel` package runs inside customers' production Python apps.
4
+ These rules come before any feature.
5
+
6
+ ## Never harm the customer's app
7
+
8
+ - No DeployAngel network calls during a request or job. Payloads go out from a
9
+ background thread, once a minute, with short timeouts.
10
+ - Fail open: every public function catches its own errors. Nothing may raise
11
+ into, or block, the app.
12
+ - Keep buffers bounded, and drop telemetry rather than block or fail.
13
+ - Keep overhead very low: around 1% CPU or less, where practical
14
+ (`python bench/overhead.py`).
15
+ - If DeployAngel is down, the customer's app must not notice.
16
+ - No runtime dependencies: the standard library only. Framework imports stay
17
+ inside their integration module.
18
+
19
+ ## Send aggregates, never events
20
+
21
+ - One payload per process per minute, whatever the traffic.
22
+ - Only mergeable values: counts and histograms. Never percentiles or rates;
23
+ the cloud computes those after merging processes.
24
+ - Bound every list in a payload (routes, exceptions, jobs, checkpoints).
25
+
26
+ ## Send as little as possible
27
+
28
+ Never collect request bodies, parameters, cookies, authorization headers,
29
+ session contents, SQL parameters, email addresses, other personal data, or raw
30
+ logs. Record route patterns (`/users/<int:pk>/`), never raw paths, and
31
+ sanitize exception messages. If a change would add a line to the README's
32
+ "What it sends" section, that's a decision for the maintainer, not an
33
+ implementation detail.
34
+
35
+ ## Keep the protocol framework-neutral
36
+
37
+ The Agent Protocol speaks in HTTP, routes, exceptions, jobs, queues, and
38
+ scheduled tasks, never in Django views, Celery tasks, or Flask blueprints. The
39
+ integrations (`deployangel/django/`, `asgi.py`, `flask.py`, `celery.py`,
40
+ `rq.py`) translate their framework into those concepts, and the Ruby agent
41
+ speaks the same protocol. Protocol changes must work for both, and the
42
+ DeployAngel cloud has to accept them first.
43
+
44
+ ## Compatibility and tests
45
+
46
+ - Supports Python 3.10, Django 4.2, FastAPI 0.100, Starlette 0.27, Flask 2.3,
47
+ Celery 5.3, and RQ 1.16, or later. CI runs the oldest and newest of each
48
+ (`.github/workflows/ci.yml`), so don't use newer APIs without a fallback.
49
+ - `pytest` must pass. Test the failure paths too: network errors, timeouts,
50
+ full buffers, and forks.
51
+ - Update the README and CHANGELOG when what the agent sends, or how it's
52
+ configured, changes.
53
+
54
+ ## Releasing
55
+
56
+ Bump `src/deployangel/version.py`, rename the CHANGELOG's "Unreleased"
57
+ heading to `## X.Y.Z (date)`, commit "Release X.Y.Z", and push a `vX.Y.Z`
58
+ tag. `.github/workflows/release.yml` builds the package, waits for a
59
+ maintainer to approve the `pypi` environment, publishes to PyPI through
60
+ trusted publishing (no API token), and then creates the GitHub release.
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-10-06)
4
+
5
+ - First release: the DeployAngel agent for Python, speaking Agent Protocol v1.
6
+ - Django 4.2+ (WSGI and ASGI), FastAPI 0.100+, Starlette 0.27+, and Flask 2.3+:
7
+ requests by matched route pattern, status codes, latency histograms, and
8
+ unhandled exceptions; the route table with each view's file.
9
+ - Celery 5.3+: every task attempt, retries and failures, queue latency, task
10
+ files, and Celery Beat schedules, from settings and from django-celery-beat.
11
+ - RQ 1.16+: `deployangel.rq.Worker` and `SimpleWorker` record every job,
12
+ including failures from RQ's forked work horses.
13
+ - Checkpoints (`deployangel.checkpoint`), handled exceptions
14
+ (`deployangel.notify`), critical flows, ignored routes, and turning
15
+ exception messages off.
16
+ - Release identity from the same sources as the Ruby agent: configuration,
17
+ Heroku dyno metadata, Kamal, Render, Fly.io, Railway, Coolify, Dokku, a
18
+ `REVISION` file, and ECS.
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Jordan Owens
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
13
+ all 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
21
+ THE SOFTWARE.
@@ -0,0 +1,343 @@
1
+ Metadata-Version: 2.5
2
+ Name: deployangel
3
+ Version: 0.1.0
4
+ Summary: DeployAngel agent for Django, FastAPI, and Flask: verifies every deploy from aggregated HTTP, job, and exception telemetry, including Celery and RQ jobs and Celery Beat schedules.
5
+ Project-URL: Homepage, https://deployangel.com
6
+ Project-URL: Source, https://github.com/DeployAngel/deployangel-python
7
+ Project-URL: Changelog, https://github.com/DeployAngel/deployangel-python/blob/main/CHANGELOG.md
8
+ Author-email: Jordan Owens <jordan@deployangel.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE.txt
11
+ Keywords: celery,deployment,django,fastapi,flask,monitoring,verification
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Framework :: Celery
14
+ Classifier: Framework :: Django
15
+ Classifier: Framework :: FastAPI
16
+ Classifier: Framework :: Flask
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Topic :: System :: Monitoring
20
+ Requires-Python: >=3.10
21
+ Provides-Extra: celery
22
+ Requires-Dist: celery>=5.3; extra == 'celery'
23
+ Provides-Extra: django
24
+ Requires-Dist: django>=4.2; extra == 'django'
25
+ Provides-Extra: fastapi
26
+ Requires-Dist: fastapi>=0.100; extra == 'fastapi'
27
+ Provides-Extra: flask
28
+ Requires-Dist: flask>=2.3; extra == 'flask'
29
+ Provides-Extra: rq
30
+ Requires-Dist: rq>=1.16; extra == 'rq'
31
+ Provides-Extra: starlette
32
+ Requires-Dist: starlette>=0.27; extra == 'starlette'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # DeployAngel for Python
36
+
37
+ The DeployAngel agent for Django, FastAPI, Starlette, and Flask, with Celery
38
+ and RQ jobs. It watches what your app does in production and reports one
39
+ small aggregated payload per process per minute. DeployAngel uses that to
40
+ verify every deployment, and to tell you (or your coding agent) when a release
41
+ is **cleared** and you can stop watching it.
42
+
43
+ ## Install
44
+
45
+ Requires Python 3.10 or later. The package has no dependencies of its own.
46
+
47
+ ```bash
48
+ pip install deployangel
49
+ ```
50
+
51
+ Set this in production, for every process (web servers and job workers alike):
52
+
53
+ ```bash
54
+ DEPLOYANGEL_TOKEN=da_live_... # an ingestion token with the telemetry scope
55
+ ```
56
+
57
+ On Heroku, the DeployAngel add-on sets it for you.
58
+
59
+ ### Django
60
+
61
+ Supports Django 4.2 or later.
62
+
63
+ ```python
64
+ # settings.py
65
+ INSTALLED_APPS = [
66
+ # ...
67
+ "deployangel.django",
68
+ ]
69
+ ```
70
+
71
+ That's all. The app puts DeployAngel's middleware at the top of `MIDDLEWARE`
72
+ (list it yourself to place it), records requests under WSGI and ASGI, and
73
+ instruments Celery when it's installed. Reporting is on when `DEBUG` is off.
74
+
75
+ ### FastAPI and Starlette
76
+
77
+ Supports FastAPI 0.100 and Starlette 0.27 or later.
78
+
79
+ ```python
80
+ import deployangel.fastapi
81
+
82
+ app = FastAPI()
83
+ deployangel.fastapi.init(app) # deployangel.starlette.init(app) for Starlette
84
+ ```
85
+
86
+ Call it where the app is created, before it serves requests.
87
+
88
+ ### Flask
89
+
90
+ Supports Flask 2.3 or later.
91
+
92
+ ```python
93
+ import deployangel.flask
94
+
95
+ app = Flask(__name__)
96
+ deployangel.flask.init_app(app)
97
+ ```
98
+
99
+ ### Celery
100
+
101
+ Supports Celery 5.3 or later. In Django it's automatic. Elsewhere, call
102
+ `init` where the Celery app is created, so worker processes start the agent:
103
+
104
+ ```python
105
+ import deployangel.celery
106
+
107
+ app = Celery("shop")
108
+ deployangel.celery.init(app)
109
+ ```
110
+
111
+ ### RQ
112
+
113
+ Supports RQ 1.16 or later. Run workers with DeployAngel's worker class:
114
+
115
+ ```bash
116
+ rq worker -w deployangel.rq.Worker # or deployangel.rq.SimpleWorker
117
+ python manage.py rqworker --worker-class deployangel.rq.Worker # django-rq
118
+ ```
119
+
120
+ RQ runs each job in a forked process that exits as soon as the job ends, so
121
+ the worker records each job after it finishes, from what RQ saved in Redis.
122
+ That includes a failed job's traceback, but not the exception of an attempt RQ
123
+ retries.
124
+
125
+ ### Which release is running
126
+
127
+ The agent must know which release it's running. It finds it in this order:
128
+
129
+ 1. `DEPLOYANGEL_REVISION` (the commit SHA) and `DEPLOYANGEL_RELEASE_VERSION`, if you set them
130
+ 2. Heroku dyno metadata. Enable it with `heroku labs:enable runtime-dyno-metadata`
131
+ and `heroku labs:enable runtime-dyno-build-metadata`; both take effect on the
132
+ next deploy
133
+ 3. Kamal: `KAMAL_VERSION`
134
+ 4. Render: `RENDER_GIT_COMMIT`
135
+ 5. Fly.io: the deploy's image tag, from `FLY_IMAGE_REF`. Fly.io sets no commit, so
136
+ pass one in for commit-level change tracking (`ENV DEPLOYANGEL_REVISION=$GIT_SHA`
137
+ in the Dockerfile, with `--build-arg GIT_SHA=$(git rev-parse HEAD)`)
138
+ 6. Railway: `RAILWAY_GIT_COMMIT_SHA`, or `RAILWAY_DEPLOYMENT_ID`
139
+ 7. Coolify: `SOURCE_COMMIT`. Dokku: `GIT_REV`
140
+ 8. a `REVISION` file in the app's root
141
+ 9. ECS, including Fargate: the container's image, from the metadata endpoint ECS
142
+ provides (one local request at boot)
143
+
144
+ For other Docker deploys, bake the commit into the image with
145
+ `ARG GIT_SHA` and `ENV DEPLOYANGEL_REVISION=$GIT_SHA`. On DigitalOcean App
146
+ Platform, set `DEPLOYANGEL_REVISION: ${_self.COMMIT_HASH}` in the app spec.
147
+
148
+ ## What it sends
149
+
150
+ - HTTP request counts, 4xx/5xx counts by status code, unhandled exceptions,
151
+ and a latency histogram, both per app and per route.
152
+ - Routes are recorded as the pattern the framework matched
153
+ (`GET /users/<int:pk>/`, `GET /items/{item_id}`), never the raw path. At
154
+ most 100 routes are sent per payload; the rest are folded into `__other__`.
155
+ - Background jobs: attempts, failed attempts, discarded jobs, duration, and
156
+ queue latency, per task. For queue latency, Celery task messages carry one
157
+ extra header, `deployangel_published_at`, the time they were sent.
158
+ - Release identity, runtime versions, and a per-process instance ID.
159
+ - Exceptions: a stable fingerprint, the exception class, the first line of the
160
+ message, and application frames only (file paths and function names). In
161
+ the message, numbers, IDs, emails, UUIDs, long hex strings, quoted values,
162
+ and the request's host are replaced with placeholders. Other words are kept:
163
+ `Payment failed for Jane Doe` is sent as it is. If your app's messages might
164
+ hold personal or health data, [turn messages off](#exception-messages).
165
+ - Once per process: the route table (with each view's module and file), task
166
+ names and files, recurring schedules declared for Celery Beat (in settings
167
+ or in django-celery-beat's tables), critical flows, and file digests
168
+ (relative paths and hashes, never file contents) so DeployAngel can tell
169
+ which routes changed in a release. Disable digests with
170
+ `DEPLOYANGEL_FILE_DIGESTS=false`.
171
+
172
+ It does not send request bodies, parameters, headers, cookies, SQL, logs, or
173
+ user data.
174
+
175
+ ## Data and pricing
176
+
177
+ - Everything goes to DeployAngel's hosted service, which runs on DigitalOcean
178
+ in the United States. There's no self-hosted version, and no choice of
179
+ region.
180
+ - Telemetry is kept for 21 days, and release history for 7, 30, or 90 days
181
+ depending on the plan. The [privacy policy](https://deployangel.com/privacy)
182
+ has the details and the services DeployAngel uses.
183
+ - DeployAngel is free during the beta, with notice before paid plans start.
184
+ See [pricing](https://deployangel.com/pricing).
185
+
186
+ ## Safety
187
+
188
+ - Nothing runs on the network during a request or job. Recording only updates
189
+ in-memory counters.
190
+ - Payloads are sent from a background thread, once a minute, with short
191
+ timeouts.
192
+ - When DeployAngel is unreachable, the buffer is bounded (10 payloads, kept as
193
+ gzipped JSON) and the oldest are dropped. Your app is never blocked or failed.
194
+ - Safe across forks (Gunicorn, Celery's prefork pool, uWSGI), and the minute
195
+ in progress is flushed at shutdown. Under uWSGI, enable threads
196
+ (`enable-threads = true`).
197
+
198
+ `python bench/overhead.py` measures this. On an Apple M-series laptop with
199
+ Python 3.14, recording a request adds about 1.7 µs, and with every route, job,
200
+ checkpoint, and exception list at its cap the agent holds about 0.5 MB,
201
+ including 10 unsent minutes.
202
+
203
+ ## Configuration
204
+
205
+ Environment variables are enough for most apps. In Django, settings go in a
206
+ `DEPLOYANGEL` dict; elsewhere, call `deployangel.configure` before `init`:
207
+
208
+ ```python
209
+ # settings.py
210
+ DEPLOYANGEL = {
211
+ "environments": ["production", "staging"], # default: production only
212
+ }
213
+
214
+ # or anywhere else
215
+ deployangel.configure(environments=["production", "staging"])
216
+ ```
217
+
218
+ The agent reports in the environments listed. Python frameworks don't name an
219
+ environment, so it's `DEPLOYANGEL_ENVIRONMENT` if set, otherwise
220
+ `development` when the framework's debug mode is on and `production` when
221
+ it's off. `DEPLOYANGEL_ENABLED=true|false` forces reporting on or off
222
+ anywhere. `DEPLOYANGEL_URL` overrides the API endpoint (default
223
+ `https://api.deployangel.com`).
224
+
225
+ File paths are relative to the app's root, the working directory by default
226
+ (on Heroku and in most containers, the repository's root). Set
227
+ `DEPLOYANGEL_ROOT` if your processes start somewhere else.
228
+
229
+ Critical flows (for example sign-up or password reset) are always listed in
230
+ clearance reports:
231
+
232
+ ```python
233
+ DEPLOYANGEL = {
234
+ "critical_flows": {"password_reset": ["POST /password-reset/", "job:accounts.tasks.send_reset_email"]},
235
+ }
236
+ ```
237
+
238
+ Health checks aren't recorded: load balancers and uptime monitors call them
239
+ all the time and they always answer fast, so they would make your app look
240
+ busier and healthier than its real pages. The agent recognizes
241
+ django-health-check, django-alive, and django-watchman wherever they're
242
+ mounted, and any route at a conventional path (`/up`, `/health`, `/healthz`,
243
+ `/healthcheck`, `/health_check`, `/livez`, `/readyz`, `/statusz`, `/ping`,
244
+ `/ht`, `/alive`). If yours is somewhere else, list it the way the dashboard
245
+ shows it. HEAD requests to it are left out too:
246
+
247
+ ```python
248
+ DEPLOYANGEL = {"ignored_routes": ["GET /status/"]}
249
+ ```
250
+
251
+ ### Exception messages
252
+
253
+ To send exceptions without any message, only their class, fingerprint, and
254
+ application frames:
255
+
256
+ ```python
257
+ DEPLOYANGEL = {"exception_messages": False} # or DEPLOYANGEL_EXCEPTION_MESSAGES=false
258
+ ```
259
+
260
+ Grouping, new-exception detection, and verdicts work the same, since the
261
+ fingerprint never uses the message. You lose the message text in the
262
+ dashboard, notifications, and AI investigation.
263
+
264
+ ### Handled exceptions
265
+
266
+ Exceptions your code catches aren't seen. To report one for context (handled
267
+ exceptions never fail a release):
268
+
269
+ ```python
270
+ try:
271
+ sync_inventory()
272
+ except UpstreamError as error:
273
+ deployangel.notify(error)
274
+ ```
275
+
276
+ ### Recurring jobs
277
+
278
+ DeployAngel expects declared recurring tasks on schedule. It reads Celery
279
+ Beat's `beat_schedule` (crontabs, and intervals, which repeat from when Beat
280
+ starts) in the zone Celery uses, and, when `django_celery_beat` is installed,
281
+ its enabled periodic tasks. Solar schedules aren't read, and neither are
282
+ RQ's schedulers, whose jobs live only in Redis. DeployAngel also learns
283
+ recurring jobs from their history.
284
+
285
+ ## Checkpoints
286
+
287
+ Errors and latency don't catch work that silently stops happening. Count the
288
+ business events that matter with one line:
289
+
290
+ ```python
291
+ deployangel.checkpoint("order.created")
292
+ deployangel.checkpoint("webhook.stripe.processed", count=len(events))
293
+ ```
294
+
295
+ DeployAngel learns each checkpoint's normal rate relative to your traffic and
296
+ fails a release after which it drops sharply or stops, even when every request
297
+ and job still succeeds. Only drops are flagged, and a checkpoint without enough
298
+ traffic never blocks a release from being cleared. Checkpoints can also be part
299
+ of a critical flow (`checkpoint:order.created`).
300
+
301
+ It's safe to call anywhere: it never raises, never touches the network, and is
302
+ ignored outside reporting environments. Names use letters, numbers, and
303
+ `. _ : -` (up to 100 characters); keep them to a fixed set rather than
304
+ including IDs, since only 100 distinct names are counted per minute.
305
+
306
+ ## Registering deploys
307
+
308
+ DeployAngel notices a new release when the agent first reports it, and
309
+ verifies it from there, with nothing to set up. On Heroku, the add-on also
310
+ registers every release for you.
311
+
312
+ To register deploys from CI, which also catches a release that never boots,
313
+ post the commit with an API token created for **CI deploys** in the dashboard:
314
+
315
+ ```bash
316
+ curl -fsS https://api.deployangel.com/api/v1/deployments \
317
+ -H "Authorization: Bearer $DEPLOYANGEL_API_TOKEN" \
318
+ -H "Content-Type: application/json" \
319
+ -d "{\"commit\": \"$GIT_SHA\"}"
320
+ ```
321
+
322
+ The `deployangel` command line tool and MCP server for coding agents
323
+ (`deployangel verify --wait`) ship with the
324
+ [Ruby gem](https://github.com/DeployAngel/deployangel-ruby) for now.
325
+ `gem install deployangel` installs them; they need Ruby, but not Rails or
326
+ your app.
327
+
328
+ ## Development
329
+
330
+ ```bash
331
+ python -m venv .venv
332
+ .venv/bin/pip install -e . django djangorestframework django-celery-beat fastapi httpx flask celery rq fakeredis pytest pytest-asyncio
333
+ .venv/bin/pytest
334
+ ```
335
+
336
+ `RQ_REDIS_URL=redis://localhost:6379/15` also runs RQ's forking worker
337
+ against a real Redis.
338
+
339
+ The agent speaks DeployAngel Agent Protocol v1: one gzipped JSON payload per
340
+ process per minute to `POST /api/v1/telemetry`, and the application's metadata
341
+ once per process to `POST /api/v1/application_metadata`.
342
+ `src/deployangel/core/protocol.py` and `src/deployangel/metadata.py` build
343
+ them.