watchup 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.
- watchup-0.1.0/PKG-INFO +241 -0
- watchup-0.1.0/README.md +206 -0
- watchup-0.1.0/pyproject.toml +56 -0
- watchup-0.1.0/setup.cfg +4 -0
- watchup-0.1.0/watchup/__init__.py +28 -0
- watchup-0.1.0/watchup/batcher.py +105 -0
- watchup-0.1.0/watchup/client.py +249 -0
- watchup-0.1.0/watchup/middleware.py +255 -0
- watchup-0.1.0/watchup/transport.py +57 -0
- watchup-0.1.0/watchup/types.py +116 -0
- watchup-0.1.0/watchup.egg-info/PKG-INFO +241 -0
- watchup-0.1.0/watchup.egg-info/SOURCES.txt +13 -0
- watchup-0.1.0/watchup.egg-info/dependency_links.txt +1 -0
- watchup-0.1.0/watchup.egg-info/requires.txt +15 -0
- watchup-0.1.0/watchup.egg-info/top_level.txt +1 -0
watchup-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: watchup
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Watchup SDK for Python — error tracking, request tracing, and custom analytics
|
|
5
|
+
Author: Watchup Ltd
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://watchup.site
|
|
8
|
+
Project-URL: Documentation, https://watchup.site/docs/sdks/python
|
|
9
|
+
Project-URL: Repository, https://github.com/watchupltd/watchup-sdk
|
|
10
|
+
Project-URL: Issues, https://github.com/watchupltd/watchup-sdk/issues
|
|
11
|
+
Keywords: watchup,monitoring,observability,apm,tracing,error-tracking,flask,django,fastapi
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Classifier: Topic :: System :: Monitoring
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Provides-Extra: flask
|
|
25
|
+
Requires-Dist: flask>=2.0; extra == "flask"
|
|
26
|
+
Provides-Extra: django
|
|
27
|
+
Requires-Dist: django>=3.2; extra == "django"
|
|
28
|
+
Provides-Extra: fastapi
|
|
29
|
+
Requires-Dist: fastapi>=0.95; extra == "fastapi"
|
|
30
|
+
Requires-Dist: starlette>=0.27; extra == "fastapi"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
33
|
+
Requires-Dist: flask>=2.0; extra == "dev"
|
|
34
|
+
Requires-Dist: responses>=0.25; extra == "dev"
|
|
35
|
+
|
|
36
|
+
# watchup
|
|
37
|
+
|
|
38
|
+
Official Python SDK for [Watchup](https://watchup.site) — error tracking, request tracing, and custom analytics for Python web applications.
|
|
39
|
+
|
|
40
|
+
Works with **Flask**, **Django**, **FastAPI**, and any **WSGI** framework. Zero required dependencies — uses the Python standard library only.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install watchup
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Requires Python 3.9+.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Quick start
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from watchup import Watchup
|
|
58
|
+
|
|
59
|
+
watchup = Watchup(
|
|
60
|
+
api_key="wup_live_xxxxxxxxxxxx", # Dashboard → Project Settings → API Keys
|
|
61
|
+
environment="production",
|
|
62
|
+
release="v1.2.3", # optional: git SHA or version tag
|
|
63
|
+
)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Flask
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from flask import Flask
|
|
72
|
+
from watchup import Watchup
|
|
73
|
+
|
|
74
|
+
watchup = Watchup(api_key="wup_live_xxxxxxxxxxxx")
|
|
75
|
+
app = Flask(__name__)
|
|
76
|
+
|
|
77
|
+
watchup.init_app(app) # registers before_request, after_request, errorhandler hooks
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`init_app` wires up:
|
|
81
|
+
|
|
82
|
+
- **Request tracing** — every request is recorded with method, route, status code, and duration
|
|
83
|
+
- **Error capture** — unhandled exceptions are reported with stack trace and request context
|
|
84
|
+
- **Transparent re-raise** — errors still propagate to your own error handlers
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Django
|
|
89
|
+
|
|
90
|
+
Add `WatchupDjangoMiddleware` to your `MIDDLEWARE` list and set `WATCHUP_API_KEY` in `settings.py`:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
# settings.py
|
|
94
|
+
MIDDLEWARE = [
|
|
95
|
+
"watchup.WatchupDjangoMiddleware",
|
|
96
|
+
# ... rest of your middleware
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
WATCHUP_API_KEY = "wup_live_xxxxxxxxxxxx"
|
|
100
|
+
WATCHUP_ENVIRONMENT = "production" # optional
|
|
101
|
+
WATCHUP_RELEASE = "v1.2.3" # optional
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The middleware initialises the client once on first request and reuses it for the lifetime of the process.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## FastAPI / Starlette
|
|
109
|
+
|
|
110
|
+
Use the WSGI wrapper or instrument manually with `before` / `after` logic via Starlette middleware:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from fastapi import FastAPI, Request
|
|
114
|
+
from watchup import Watchup
|
|
115
|
+
import time
|
|
116
|
+
|
|
117
|
+
watchup = Watchup(api_key="wup_live_xxxxxxxxxxxx")
|
|
118
|
+
app = FastAPI()
|
|
119
|
+
|
|
120
|
+
@app.middleware("http")
|
|
121
|
+
async def watchup_middleware(request: Request, call_next):
|
|
122
|
+
start = time.time()
|
|
123
|
+
response = await call_next(request)
|
|
124
|
+
ms = (time.time() - start) * 1000
|
|
125
|
+
# record trace manually
|
|
126
|
+
end = watchup.start_trace(f"{request.method} {request.url.path}")
|
|
127
|
+
end()
|
|
128
|
+
return response
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## WSGI middleware (framework-agnostic)
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from watchup import Watchup, WatchupWSGI
|
|
137
|
+
|
|
138
|
+
watchup = Watchup(api_key="wup_live_xxxxxxxxxxxx")
|
|
139
|
+
|
|
140
|
+
# Flask example
|
|
141
|
+
from flask import Flask
|
|
142
|
+
flask_app = Flask(__name__)
|
|
143
|
+
flask_app.wsgi_app = WatchupWSGI(flask_app.wsgi_app, watchup)
|
|
144
|
+
|
|
145
|
+
# Any WSGI app
|
|
146
|
+
application = WatchupWSGI(application, watchup)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Manual tracking
|
|
152
|
+
|
|
153
|
+
### Capture an error
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
try:
|
|
157
|
+
process_order(order_id)
|
|
158
|
+
except Exception as exc:
|
|
159
|
+
watchup.capture_error(exc, route="job.process_order", order_id=order_id)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Track a custom event
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
watchup.track("user.signed_up", {"plan": "pro", "source": "invite"})
|
|
166
|
+
watchup.track("order.placed", {"amount": 4999, "currency": "NGN"})
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Time an operation
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
end = watchup.start_trace("db.query_users")
|
|
173
|
+
try:
|
|
174
|
+
rows = db.query("SELECT * FROM users")
|
|
175
|
+
end() # status defaults to "ok"
|
|
176
|
+
except Exception as exc:
|
|
177
|
+
end(status="err", meta={"query": "SELECT * FROM users"})
|
|
178
|
+
raise
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## User identification
|
|
184
|
+
|
|
185
|
+
Attach user identity to errors and traces:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
# After authentication — in a middleware or login view
|
|
189
|
+
watchup.set_user("usr_42", email="alice@example.com", name="Alice", plan="pro")
|
|
190
|
+
|
|
191
|
+
# On logout
|
|
192
|
+
watchup.clear_user()
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Once set, every `capture_error`, `start_trace`, and request trace will include the user context.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Configuration reference
|
|
200
|
+
|
|
201
|
+
| Parameter | Default | Description |
|
|
202
|
+
|---|---|---|
|
|
203
|
+
| `api_key` | *(required)* | Project API key (`wup_live_…`) |
|
|
204
|
+
| `base_url` | `https://api.watchup.site` | Override for self-hosted deployments |
|
|
205
|
+
| `environment` | `WATCHUP_ENV` env var, or `"production"` | Runtime label on every payload |
|
|
206
|
+
| `release` | `None` | App version / git SHA |
|
|
207
|
+
| `flush_interval` | `5.0` | Seconds between automatic flushes |
|
|
208
|
+
| `max_batch_size` | `100` | Item count that triggers an immediate flush |
|
|
209
|
+
| `sample_rate` | `1.0` | Fraction of requests to trace (0–1) |
|
|
210
|
+
| `debug` | `False` | Log SDK warnings to stderr |
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Lifecycle
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
# Force an immediate flush
|
|
218
|
+
watchup.flush()
|
|
219
|
+
|
|
220
|
+
# Stop the background timer and flush remaining items (graceful shutdown)
|
|
221
|
+
watchup.shutdown()
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The batcher runs on a daemon thread and registers an `atexit` handler, so queued items are flushed automatically when the process exits normally.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Links
|
|
229
|
+
|
|
230
|
+
- **Website:** [watchup.site](https://watchup.site)
|
|
231
|
+
- **Documentation:** [watchup.site/docs](https://watchup.site/docs)
|
|
232
|
+
- **Python SDK docs:** [watchup.site/docs/sdks/python](https://watchup.site/docs/sdks/python)
|
|
233
|
+
- **Getting started:** [watchup.site/docs/getting-started](https://watchup.site/docs/getting-started)
|
|
234
|
+
- **Pricing:** [watchup.site/pricing](https://watchup.site/pricing)
|
|
235
|
+
- **Dashboard:** [app.watchup.site](https://watchup.site/login)
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## License
|
|
240
|
+
|
|
241
|
+
MIT © Watchup Ltd
|
watchup-0.1.0/README.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# watchup
|
|
2
|
+
|
|
3
|
+
Official Python SDK for [Watchup](https://watchup.site) — error tracking, request tracing, and custom analytics for Python web applications.
|
|
4
|
+
|
|
5
|
+
Works with **Flask**, **Django**, **FastAPI**, and any **WSGI** framework. Zero required dependencies — uses the Python standard library only.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install watchup
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires Python 3.9+.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from watchup import Watchup
|
|
23
|
+
|
|
24
|
+
watchup = Watchup(
|
|
25
|
+
api_key="wup_live_xxxxxxxxxxxx", # Dashboard → Project Settings → API Keys
|
|
26
|
+
environment="production",
|
|
27
|
+
release="v1.2.3", # optional: git SHA or version tag
|
|
28
|
+
)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Flask
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from flask import Flask
|
|
37
|
+
from watchup import Watchup
|
|
38
|
+
|
|
39
|
+
watchup = Watchup(api_key="wup_live_xxxxxxxxxxxx")
|
|
40
|
+
app = Flask(__name__)
|
|
41
|
+
|
|
42
|
+
watchup.init_app(app) # registers before_request, after_request, errorhandler hooks
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`init_app` wires up:
|
|
46
|
+
|
|
47
|
+
- **Request tracing** — every request is recorded with method, route, status code, and duration
|
|
48
|
+
- **Error capture** — unhandled exceptions are reported with stack trace and request context
|
|
49
|
+
- **Transparent re-raise** — errors still propagate to your own error handlers
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Django
|
|
54
|
+
|
|
55
|
+
Add `WatchupDjangoMiddleware` to your `MIDDLEWARE` list and set `WATCHUP_API_KEY` in `settings.py`:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
# settings.py
|
|
59
|
+
MIDDLEWARE = [
|
|
60
|
+
"watchup.WatchupDjangoMiddleware",
|
|
61
|
+
# ... rest of your middleware
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
WATCHUP_API_KEY = "wup_live_xxxxxxxxxxxx"
|
|
65
|
+
WATCHUP_ENVIRONMENT = "production" # optional
|
|
66
|
+
WATCHUP_RELEASE = "v1.2.3" # optional
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The middleware initialises the client once on first request and reuses it for the lifetime of the process.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## FastAPI / Starlette
|
|
74
|
+
|
|
75
|
+
Use the WSGI wrapper or instrument manually with `before` / `after` logic via Starlette middleware:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from fastapi import FastAPI, Request
|
|
79
|
+
from watchup import Watchup
|
|
80
|
+
import time
|
|
81
|
+
|
|
82
|
+
watchup = Watchup(api_key="wup_live_xxxxxxxxxxxx")
|
|
83
|
+
app = FastAPI()
|
|
84
|
+
|
|
85
|
+
@app.middleware("http")
|
|
86
|
+
async def watchup_middleware(request: Request, call_next):
|
|
87
|
+
start = time.time()
|
|
88
|
+
response = await call_next(request)
|
|
89
|
+
ms = (time.time() - start) * 1000
|
|
90
|
+
# record trace manually
|
|
91
|
+
end = watchup.start_trace(f"{request.method} {request.url.path}")
|
|
92
|
+
end()
|
|
93
|
+
return response
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## WSGI middleware (framework-agnostic)
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from watchup import Watchup, WatchupWSGI
|
|
102
|
+
|
|
103
|
+
watchup = Watchup(api_key="wup_live_xxxxxxxxxxxx")
|
|
104
|
+
|
|
105
|
+
# Flask example
|
|
106
|
+
from flask import Flask
|
|
107
|
+
flask_app = Flask(__name__)
|
|
108
|
+
flask_app.wsgi_app = WatchupWSGI(flask_app.wsgi_app, watchup)
|
|
109
|
+
|
|
110
|
+
# Any WSGI app
|
|
111
|
+
application = WatchupWSGI(application, watchup)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Manual tracking
|
|
117
|
+
|
|
118
|
+
### Capture an error
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
try:
|
|
122
|
+
process_order(order_id)
|
|
123
|
+
except Exception as exc:
|
|
124
|
+
watchup.capture_error(exc, route="job.process_order", order_id=order_id)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Track a custom event
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
watchup.track("user.signed_up", {"plan": "pro", "source": "invite"})
|
|
131
|
+
watchup.track("order.placed", {"amount": 4999, "currency": "NGN"})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Time an operation
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
end = watchup.start_trace("db.query_users")
|
|
138
|
+
try:
|
|
139
|
+
rows = db.query("SELECT * FROM users")
|
|
140
|
+
end() # status defaults to "ok"
|
|
141
|
+
except Exception as exc:
|
|
142
|
+
end(status="err", meta={"query": "SELECT * FROM users"})
|
|
143
|
+
raise
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## User identification
|
|
149
|
+
|
|
150
|
+
Attach user identity to errors and traces:
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
# After authentication — in a middleware or login view
|
|
154
|
+
watchup.set_user("usr_42", email="alice@example.com", name="Alice", plan="pro")
|
|
155
|
+
|
|
156
|
+
# On logout
|
|
157
|
+
watchup.clear_user()
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Once set, every `capture_error`, `start_trace`, and request trace will include the user context.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Configuration reference
|
|
165
|
+
|
|
166
|
+
| Parameter | Default | Description |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| `api_key` | *(required)* | Project API key (`wup_live_…`) |
|
|
169
|
+
| `base_url` | `https://api.watchup.site` | Override for self-hosted deployments |
|
|
170
|
+
| `environment` | `WATCHUP_ENV` env var, or `"production"` | Runtime label on every payload |
|
|
171
|
+
| `release` | `None` | App version / git SHA |
|
|
172
|
+
| `flush_interval` | `5.0` | Seconds between automatic flushes |
|
|
173
|
+
| `max_batch_size` | `100` | Item count that triggers an immediate flush |
|
|
174
|
+
| `sample_rate` | `1.0` | Fraction of requests to trace (0–1) |
|
|
175
|
+
| `debug` | `False` | Log SDK warnings to stderr |
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Lifecycle
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
# Force an immediate flush
|
|
183
|
+
watchup.flush()
|
|
184
|
+
|
|
185
|
+
# Stop the background timer and flush remaining items (graceful shutdown)
|
|
186
|
+
watchup.shutdown()
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The batcher runs on a daemon thread and registers an `atexit` handler, so queued items are flushed automatically when the process exits normally.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Links
|
|
194
|
+
|
|
195
|
+
- **Website:** [watchup.site](https://watchup.site)
|
|
196
|
+
- **Documentation:** [watchup.site/docs](https://watchup.site/docs)
|
|
197
|
+
- **Python SDK docs:** [watchup.site/docs/sdks/python](https://watchup.site/docs/sdks/python)
|
|
198
|
+
- **Getting started:** [watchup.site/docs/getting-started](https://watchup.site/docs/getting-started)
|
|
199
|
+
- **Pricing:** [watchup.site/pricing](https://watchup.site/pricing)
|
|
200
|
+
- **Dashboard:** [app.watchup.site](https://watchup.site/login)
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
MIT © Watchup Ltd
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "watchup"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Watchup SDK for Python — error tracking, request tracing, and custom analytics"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "Watchup Ltd" }]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
keywords = [
|
|
14
|
+
"watchup",
|
|
15
|
+
"monitoring",
|
|
16
|
+
"observability",
|
|
17
|
+
"apm",
|
|
18
|
+
"tracing",
|
|
19
|
+
"error-tracking",
|
|
20
|
+
"flask",
|
|
21
|
+
"django",
|
|
22
|
+
"fastapi",
|
|
23
|
+
]
|
|
24
|
+
classifiers = [
|
|
25
|
+
"Development Status :: 4 - Beta",
|
|
26
|
+
"Intended Audience :: Developers",
|
|
27
|
+
"License :: OSI Approved :: MIT License",
|
|
28
|
+
"Programming Language :: Python :: 3",
|
|
29
|
+
"Programming Language :: Python :: 3.9",
|
|
30
|
+
"Programming Language :: Python :: 3.10",
|
|
31
|
+
"Programming Language :: Python :: 3.11",
|
|
32
|
+
"Programming Language :: Python :: 3.12",
|
|
33
|
+
"Topic :: Software Development :: Libraries",
|
|
34
|
+
"Topic :: System :: Monitoring",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
# No required dependencies — stdlib only.
|
|
38
|
+
# Framework integrations (Flask, Django) import lazily and raise ImportError
|
|
39
|
+
# with a helpful message if the framework is not installed.
|
|
40
|
+
dependencies = []
|
|
41
|
+
|
|
42
|
+
[project.optional-dependencies]
|
|
43
|
+
flask = ["flask>=2.0"]
|
|
44
|
+
django = ["django>=3.2"]
|
|
45
|
+
fastapi = ["fastapi>=0.95", "starlette>=0.27"]
|
|
46
|
+
dev = ["pytest>=8", "flask>=2.0", "responses>=0.25"]
|
|
47
|
+
|
|
48
|
+
[project.urls]
|
|
49
|
+
Homepage = "https://watchup.site"
|
|
50
|
+
Documentation = "https://watchup.site/docs/sdks/python"
|
|
51
|
+
Repository = "https://github.com/watchupltd/watchup-sdk"
|
|
52
|
+
Issues = "https://github.com/watchupltd/watchup-sdk/issues"
|
|
53
|
+
|
|
54
|
+
[tool.setuptools.packages.find]
|
|
55
|
+
where = ["."]
|
|
56
|
+
include = ["watchup*"]
|
watchup-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
watchup — application monitoring SDK for Python
|
|
3
|
+
|
|
4
|
+
Quick start::
|
|
5
|
+
|
|
6
|
+
from watchup import Watchup
|
|
7
|
+
|
|
8
|
+
watchup = Watchup(api_key="wup_live_xxxxxxxxxxxx")
|
|
9
|
+
|
|
10
|
+
# Flask
|
|
11
|
+
watchup.init_app(app)
|
|
12
|
+
|
|
13
|
+
# Manual error capture
|
|
14
|
+
watchup.capture_error(exc, route="job.process_order")
|
|
15
|
+
|
|
16
|
+
# Custom events
|
|
17
|
+
watchup.track("user.signed_up", {"plan": "pro"})
|
|
18
|
+
|
|
19
|
+
# Trace any operation
|
|
20
|
+
end = watchup.start_trace("db.query")
|
|
21
|
+
end()
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from .client import Watchup
|
|
25
|
+
from .middleware import WatchupDjangoMiddleware, WatchupWSGI
|
|
26
|
+
|
|
27
|
+
__all__ = ["Watchup", "WatchupDjangoMiddleware", "WatchupWSGI"]
|
|
28
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
watchup · batcher
|
|
3
|
+
|
|
4
|
+
Accumulates traces/errors/events and flushes them in batches via a
|
|
5
|
+
background daemon thread. The thread is daemonised so it never prevents
|
|
6
|
+
the interpreter from exiting.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import atexit
|
|
12
|
+
import threading
|
|
13
|
+
from typing import List
|
|
14
|
+
|
|
15
|
+
from .transport import Transport
|
|
16
|
+
from .types import ErrorPayload, EventPayload, IngestBatch, TracePayload
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Batcher:
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
transport: Transport,
|
|
23
|
+
flush_interval: float,
|
|
24
|
+
max_batch_size: int,
|
|
25
|
+
) -> None:
|
|
26
|
+
self._transport = transport
|
|
27
|
+
self._flush_interval = flush_interval
|
|
28
|
+
self._max_batch_size = max_batch_size
|
|
29
|
+
|
|
30
|
+
self._lock = threading.Lock()
|
|
31
|
+
self._traces: List[TracePayload] = []
|
|
32
|
+
self._errors: List[ErrorPayload] = []
|
|
33
|
+
self._events: List[EventPayload] = []
|
|
34
|
+
|
|
35
|
+
self._timer: threading.Timer | None = None
|
|
36
|
+
self._started = False
|
|
37
|
+
|
|
38
|
+
# ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
def start(self) -> None:
|
|
41
|
+
if self._started:
|
|
42
|
+
return
|
|
43
|
+
self._started = True
|
|
44
|
+
self._schedule()
|
|
45
|
+
atexit.register(self.flush)
|
|
46
|
+
|
|
47
|
+
def stop(self) -> None:
|
|
48
|
+
self._started = False
|
|
49
|
+
if self._timer is not None:
|
|
50
|
+
self._timer.cancel()
|
|
51
|
+
self._timer = None
|
|
52
|
+
|
|
53
|
+
def _schedule(self) -> None:
|
|
54
|
+
if not self._started:
|
|
55
|
+
return
|
|
56
|
+
self._timer = threading.Timer(self._flush_interval, self._tick)
|
|
57
|
+
self._timer.daemon = True
|
|
58
|
+
self._timer.start()
|
|
59
|
+
|
|
60
|
+
def _tick(self) -> None:
|
|
61
|
+
self.flush()
|
|
62
|
+
self._schedule()
|
|
63
|
+
|
|
64
|
+
# ── Enqueue ───────────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
def add_trace(self, trace: TracePayload) -> None:
|
|
67
|
+
with self._lock:
|
|
68
|
+
self._traces.append(trace)
|
|
69
|
+
should_flush = len(self._traces) >= self._max_batch_size
|
|
70
|
+
if should_flush:
|
|
71
|
+
self.flush()
|
|
72
|
+
|
|
73
|
+
def add_error(self, error: ErrorPayload) -> None:
|
|
74
|
+
with self._lock:
|
|
75
|
+
self._errors.append(error)
|
|
76
|
+
# Errors are higher priority — flush at half capacity
|
|
77
|
+
should_flush = len(self._errors) >= max(1, self._max_batch_size // 2)
|
|
78
|
+
if should_flush:
|
|
79
|
+
self.flush()
|
|
80
|
+
|
|
81
|
+
def add_event(self, event: EventPayload) -> None:
|
|
82
|
+
with self._lock:
|
|
83
|
+
self._events.append(event)
|
|
84
|
+
should_flush = len(self._events) >= self._max_batch_size
|
|
85
|
+
if should_flush:
|
|
86
|
+
self.flush()
|
|
87
|
+
|
|
88
|
+
# ── Flush ─────────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
def flush(self) -> None:
|
|
91
|
+
with self._lock:
|
|
92
|
+
traces = self._traces[:]
|
|
93
|
+
errors = self._errors[:]
|
|
94
|
+
events = self._events[:]
|
|
95
|
+
self._traces.clear()
|
|
96
|
+
self._errors.clear()
|
|
97
|
+
self._events.clear()
|
|
98
|
+
|
|
99
|
+
if not traces and not errors and not events:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
batch = IngestBatch(traces=traces, errors=errors, events=events)
|
|
103
|
+
# Run in a daemon thread so it never blocks the caller
|
|
104
|
+
t = threading.Thread(target=self._transport.send, args=(batch,), daemon=True)
|
|
105
|
+
t.start()
|