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.
- deployangel-0.1.0/.github/workflows/ci.yml +80 -0
- deployangel-0.1.0/.github/workflows/release.yml +110 -0
- deployangel-0.1.0/.gitignore +7 -0
- deployangel-0.1.0/AGENTS.md +60 -0
- deployangel-0.1.0/CHANGELOG.md +18 -0
- deployangel-0.1.0/LICENSE.txt +21 -0
- deployangel-0.1.0/PKG-INFO +343 -0
- deployangel-0.1.0/README.md +309 -0
- deployangel-0.1.0/SECURITY.md +23 -0
- deployangel-0.1.0/bench/overhead.py +62 -0
- deployangel-0.1.0/pyproject.toml +52 -0
- deployangel-0.1.0/src/deployangel/__init__.py +176 -0
- deployangel-0.1.0/src/deployangel/agent.py +270 -0
- deployangel-0.1.0/src/deployangel/asgi.py +216 -0
- deployangel-0.1.0/src/deployangel/celery.py +278 -0
- deployangel-0.1.0/src/deployangel/config.py +71 -0
- deployangel-0.1.0/src/deployangel/core/__init__.py +0 -0
- deployangel-0.1.0/src/deployangel/core/aggregator.py +193 -0
- deployangel-0.1.0/src/deployangel/core/buffer.py +36 -0
- deployangel-0.1.0/src/deployangel/core/fingerprint.py +162 -0
- deployangel-0.1.0/src/deployangel/core/histogram.py +34 -0
- deployangel-0.1.0/src/deployangel/core/instance.py +34 -0
- deployangel-0.1.0/src/deployangel/core/protocol.py +69 -0
- deployangel-0.1.0/src/deployangel/core/redaction.py +23 -0
- deployangel-0.1.0/src/deployangel/core/release.py +155 -0
- deployangel-0.1.0/src/deployangel/core/transport.py +87 -0
- deployangel-0.1.0/src/deployangel/django/__init__.py +3 -0
- deployangel-0.1.0/src/deployangel/django/apps.py +51 -0
- deployangel-0.1.0/src/deployangel/django/metadata.py +75 -0
- deployangel-0.1.0/src/deployangel/django/middleware.py +145 -0
- deployangel-0.1.0/src/deployangel/fastapi.py +9 -0
- deployangel-0.1.0/src/deployangel/flask.py +130 -0
- deployangel-0.1.0/src/deployangel/http.py +69 -0
- deployangel-0.1.0/src/deployangel/metadata.py +154 -0
- deployangel-0.1.0/src/deployangel/rq.py +103 -0
- deployangel-0.1.0/src/deployangel/starlette.py +9 -0
- deployangel-0.1.0/src/deployangel/version.py +1 -0
- deployangel-0.1.0/tests/conftest.py +81 -0
- deployangel-0.1.0/tests/django_app/__init__.py +0 -0
- deployangel-0.1.0/tests/django_app/urls.py +20 -0
- deployangel-0.1.0/tests/django_app/views.py +42 -0
- deployangel-0.1.0/tests/test_agent.py +268 -0
- deployangel-0.1.0/tests/test_core.py +293 -0
- deployangel-0.1.0/tests/test_django.py +116 -0
- deployangel-0.1.0/tests/test_jobs.py +207 -0
- deployangel-0.1.0/tests/test_web.py +154 -0
- deployangel-0.1.0/tests/web_apps/__init__.py +0 -0
- deployangel-0.1.0/tests/web_apps/fastapi_app.py +51 -0
- 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,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.
|