fetchnode 0.4.0__tar.gz → 0.4.2__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 (41) hide show
  1. fetchnode-0.4.2/PKG-INFO +284 -0
  2. fetchnode-0.4.2/README.md +247 -0
  3. {fetchnode-0.4.0 → fetchnode-0.4.2}/pyproject.toml +4 -1
  4. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode/apps.py +4 -2
  5. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode/conf.py +4 -1
  6. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode/handlers.py +2 -0
  7. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode/middleware.py +6 -2
  8. fetchnode-0.4.2/src/fetchnode/py.typed +0 -0
  9. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode/query.py +1 -1
  10. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode/scrub.py +7 -1
  11. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode/sender.py +1 -0
  12. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode/version.py +1 -1
  13. fetchnode-0.4.2/src/fetchnode_client/py.typed +0 -0
  14. fetchnode-0.4.0/PKG-INFO +0 -166
  15. fetchnode-0.4.0/README.md +0 -132
  16. {fetchnode-0.4.0 → fetchnode-0.4.2}/.gitignore +0 -0
  17. {fetchnode-0.4.0 → fetchnode-0.4.2}/LICENSE +0 -0
  18. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode/__init__.py +0 -0
  19. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode/api.py +0 -0
  20. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode/capture.py +0 -0
  21. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode/celery.py +0 -0
  22. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode/demo.py +0 -0
  23. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode/excepthook.py +0 -0
  24. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode/scope.py +0 -0
  25. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode/utils.py +0 -0
  26. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode_client/__init__.py +0 -0
  27. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode_client/api.py +0 -0
  28. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode_client/apps.py +0 -0
  29. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode_client/capture.py +0 -0
  30. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode_client/celery.py +0 -0
  31. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode_client/conf.py +0 -0
  32. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode_client/demo.py +0 -0
  33. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode_client/excepthook.py +0 -0
  34. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode_client/handlers.py +0 -0
  35. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode_client/middleware.py +0 -0
  36. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode_client/query.py +0 -0
  37. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode_client/scope.py +0 -0
  38. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode_client/scrub.py +0 -0
  39. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode_client/sender.py +0 -0
  40. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode_client/utils.py +0 -0
  41. {fetchnode-0.4.0 → fetchnode-0.4.2}/src/fetchnode_client/version.py +0 -0
@@ -0,0 +1,284 @@
1
+ Metadata-Version: 2.5
2
+ Name: fetchnode
3
+ Version: 0.4.2
4
+ Summary: Official Django and Python client SDK for FetchNode monitoring
5
+ Project-URL: Homepage, https://fetchnode.com
6
+ Project-URL: Documentation, https://fetchnode.com/guide/
7
+ Project-URL: Repository, https://github.com/remcovanwijk040/fetchnode
8
+ Project-URL: Issues, https://github.com/remcovanwijk040/fetchnode/issues
9
+ Project-URL: Changelog, https://fetchnode.com/changelog/
10
+ Author-email: Remco van Wijk <remcovanwijk040@users.noreply.github.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Web Environment
15
+ Classifier: Framework :: Django
16
+ Classifier: Framework :: Django :: 4.2
17
+ Classifier: Framework :: Django :: 5.0
18
+ Classifier: Framework :: Django :: 5.1
19
+ Classifier: Framework :: Django :: 5.2
20
+ Classifier: Intended Audience :: Developers
21
+ Classifier: License :: OSI Approved :: MIT License
22
+ Classifier: Operating System :: OS Independent
23
+ Classifier: Programming Language :: Python :: 3
24
+ Classifier: Programming Language :: Python :: 3.10
25
+ Classifier: Programming Language :: Python :: 3.11
26
+ Classifier: Programming Language :: Python :: 3.12
27
+ Classifier: Programming Language :: Python :: 3.13
28
+ Classifier: Programming Language :: Python :: 3.14
29
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
30
+ Classifier: Topic :: System :: Monitoring
31
+ Classifier: Typing :: Typed
32
+ Requires-Python: >=3.10
33
+ Requires-Dist: django>=4.2
34
+ Provides-Extra: celery
35
+ Requires-Dist: celery>=5.0; extra == 'celery'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # FetchNode Python & Django SDK
39
+
40
+ [![PyPI version](https://img.shields.io/pypi/v/fetchnode.svg)](https://pypi.org/project/fetchnode/)
41
+ [![Python Versions](https://img.shields.io/pypi/pyversions/fetchnode.svg)](https://pypi.org/project/fetchnode/)
42
+ [![Django Versions](https://img.shields.io/badge/django-4.2%20%7C%205.0%20%7C%205.1%20%7C%205.2-blue.svg)](https://www.djangoproject.com/)
43
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
44
+ [![Typing: Typed](https://img.shields.io/badge/typing-typed-green.svg)](https://peps.python.org/pep-0561/)
45
+
46
+ Official Python and Django client SDK for [FetchNode](https://fetchnode.com) — the focused operations workspace for developers and teams managing multiple production Django websites.
47
+
48
+ FetchNode captures unhandled exceptions, error logs, slow database queries, N+1 query patterns, Celery background tasks, and authoritative business conversions with zero bloat and privacy-safe defaults.
49
+
50
+ ---
51
+
52
+ ## Table of Contents
53
+
54
+ - [Installation](#installation)
55
+ - [Quickstart (Django)](#quickstart-django)
56
+ - [Configuration Options](#configuration-options)
57
+ - [Telemetry & Usage](#telemetry--usage)
58
+ - [Conversion & Lead Tracking](#conversion--lead-tracking)
59
+ - [Automatic Error Monitoring](#automatic-error-monitoring)
60
+ - [Manual Exception Capture](#manual-exception-capture)
61
+ - [Celery Job Monitoring](#celery-job-monitoring)
62
+ - [Logging Handler](#logging-handler)
63
+ - [Privacy & Security Defaults](#privacy--security-defaults)
64
+ - [Testing in Development](#testing-in-development)
65
+ - [Compatibility](#compatibility)
66
+ - [Links & Resources](#links--resources)
67
+ - [License](#license)
68
+
69
+ ---
70
+
71
+ ## Installation
72
+
73
+ Install via `pip`:
74
+
75
+ ```bash
76
+ pip install fetchnode
77
+ ```
78
+
79
+ Or using `uv`:
80
+
81
+ ```bash
82
+ uv add fetchnode
83
+ ```
84
+
85
+ *(For Celery support, you can optionally install `fetchnode[celery]`)*
86
+
87
+ ---
88
+
89
+ ## Quickstart (Django)
90
+
91
+ ### 1. Add to `INSTALLED_APPS`
92
+
93
+ In your Django `settings.py`:
94
+
95
+ ```python
96
+ INSTALLED_APPS = [
97
+ # ...
98
+ "fetchnode",
99
+ ]
100
+ ```
101
+
102
+ ### 2. Add the Middleware
103
+
104
+ Add `FetchNodeMiddleware` near the top of your `MIDDLEWARE` list for comprehensive request, query, and exception coverage:
105
+
106
+ ```python
107
+ MIDDLEWARE = [
108
+ "fetchnode.middleware.FetchNodeMiddleware",
109
+ "django.middleware.security.SecurityMiddleware",
110
+ # ...
111
+ ]
112
+ ```
113
+
114
+ ### 3. Configure `settings.FETCHNODE`
115
+
116
+ ```python
117
+ FETCHNODE = {
118
+ "API_KEY": "fn_live_your_project_key_here",
119
+ "ENVIRONMENT": "production", # e.g. "production", "staging"
120
+ }
121
+ ```
122
+
123
+ That's it! Django will automatically initialize FetchNode upon startup and capture unhandled exceptions, slow queries, and request telemetry.
124
+
125
+ ---
126
+
127
+ ## Configuration Options
128
+
129
+ Configure via `settings.FETCHNODE` or environment variables:
130
+
131
+ | Setting Key | Environment Variable | Default | Description |
132
+ | :--- | :--- | :--- | :--- |
133
+ | `API_KEY` | `FETCHNODE_API_KEY` | `""` | **Required.** Your FetchNode project key (starts with `fn_live_` or `fn_test_`). |
134
+ | `ENDPOINT` / `API_URL` | `FETCHNODE_ENDPOINT` | `https://monitor.fetchnode.com/api/events/ingest/` | Processing server URL. Point to your self-hosted instance if applicable. |
135
+ | `ENVIRONMENT` | `FETCHNODE_ENVIRONMENT` | `"development"` | Active environment name (`"production"`, `"staging"`). |
136
+ | `RELEASE` | `FETCHNODE_RELEASE` | `""` | Release tag or git commit hash (e.g. `git rev-parse HEAD`). |
137
+ | `ENABLED` | `FETCHNODE_ENABLED` | `True` if API_KEY present | Master toggle for event sending. |
138
+ | `CAPTURE_TRANSACTIONS` | — | `True` | Track request duration and database query counts. |
139
+ | `CAPTURE_BODY` | — | `False` | Privacy safeguard: request bodies are excluded by default. |
140
+ | `CAPTURE_HEADERS` | — | `False` | Privacy safeguard: request headers are excluded by default. |
141
+ | `QUERY_SLOW_THRESHOLD_MS` | — | `100` | Minimum execution time (ms) to flag an ORM query as slow. |
142
+ | `N_PLUS_ONE_THRESHOLD` | — | `5` | Identical SELECT query repetition threshold to flag N+1 issues. |
143
+ | `SENSITIVE_KEYS` | — | *(built-in list)* | Extra field names to scrub from payloads (additive). |
144
+
145
+ ---
146
+
147
+ ## Telemetry & Usage
148
+
149
+ ### Conversion & Lead Tracking
150
+
151
+ Track authoritative business events server-side (form submissions, payments, lead captures) with automatic session and visitor cookie attribution:
152
+
153
+ ```python
154
+ import fetchnode
155
+
156
+ def contact_view(request):
157
+ form = ContactForm(request.POST)
158
+ if form.is_valid():
159
+ lead = form.save()
160
+
161
+ # Track authoritative conversion
162
+ fetchnode.track(
163
+ "lead_submitted",
164
+ request=request,
165
+ idempotency_key=f"lead:{lead.id}",
166
+ properties={"email": lead.email},
167
+ conversion=True,
168
+ )
169
+ return redirect("thank_you")
170
+ ```
171
+
172
+ **Why `idempotency_key` matters:** Providing an `idempotency_key` (such as the database record ID or payment provider ID) ensures that retried requests, webhook replays, or browser reloads do not inflate your conversion metrics. FetchNode securely hashes the key client-side to produce a deterministic event ID.
173
+
174
+ ### Automatic Error Monitoring
175
+
176
+ Unhandled exceptions in Django views are caught by `FetchNodeMiddleware`, decorated with:
177
+ * The exact failing line of code and stack trace.
178
+ * In-app vs. framework/library frame classification.
179
+ * Database query count and execution time during the failing request.
180
+ * Correlated request ID and trace ID.
181
+
182
+ ### Manual Exception Capture
183
+
184
+ Capture handled exceptions or background exceptions:
185
+
186
+ ```python
187
+ import fetchnode
188
+
189
+ try:
190
+ external_service_call()
191
+ except ThirdPartyAPIError as exc:
192
+ fetchnode.capture_exception(exc, tags={"provider": "stripe"})
193
+ ```
194
+
195
+ ### Celery Job Monitoring
196
+
197
+ FetchNode automatically instruments Celery when installed. You can also explicitly decorate tasks:
198
+
199
+ ```python
200
+ from fetchnode import capture_job
201
+
202
+ @capture_job(name="sync_external_inventory")
203
+ def sync_inventory():
204
+ ...
205
+ ```
206
+
207
+ ### Logging Handler
208
+
209
+ Send standard `logging.ERROR` and `logging.CRITICAL` records to FetchNode:
210
+
211
+ ```python
212
+ LOGGING = {
213
+ "version": 1,
214
+ "disable_existing_loggers": False,
215
+ "handlers": {
216
+ "fetchnode": {
217
+ "level": "ERROR",
218
+ "class": "fetchnode.handlers.FetchNodeHandler",
219
+ },
220
+ },
221
+ "loggers": {
222
+ "django": {
223
+ "handlers": ["fetchnode"],
224
+ "level": "ERROR",
225
+ "propagate": True,
226
+ },
227
+ },
228
+ }
229
+ ```
230
+
231
+ ---
232
+
233
+ ## Privacy & Security Defaults
234
+
235
+ FetchNode is engineered with a strict **privacy-first architecture**:
236
+ 1. **Client-side Scrubbing:** Sensitive keys (`password`, `token`, `secret`, `api_key`, `authorization`, `cookie`, `csrfmiddlewaretoken`) and any compound keys like `user_password` or `client_secret` are scrubbed before payload transmission.
237
+ 2. **Safe SQL Normalization:** Raw SQL text is stripped of literals and parameters (`SELECT * FROM user WHERE id = 123` becomes `SELECT * FROM user WHERE id = ?`).
238
+ 3. **No Unintentional PII:** Request bodies, query strings, and raw IP addresses are excluded by default.
239
+ 4. **Inspectable & Lightweight:** The entire client is open-source Python with zero compiled binaries and zero unnecessary dependencies.
240
+
241
+ ---
242
+
243
+ ## Testing in Development
244
+
245
+ In your local test suite (e.g. `pytest` or `python manage.py test`), FetchNode can be disabled by omitting the API key or explicitly setting:
246
+
247
+ ```python
248
+ # settings_test.py
249
+ FETCHNODE = {
250
+ "ENABLED": False,
251
+ }
252
+ ```
253
+
254
+ To test that event transmission works in your staging environment, run:
255
+
256
+ ```python
257
+ import fetchnode
258
+ fetchnode.capture_message("FetchNode verification ping", level="info")
259
+ fetchnode.flush()
260
+ ```
261
+
262
+ ---
263
+
264
+ ## Compatibility
265
+
266
+ - **Python:** 3.10, 3.11, 3.12, 3.13, 3.14
267
+ - **Django:** 4.2 LTS, 5.0, 5.1, 5.2
268
+ - **Operating Systems:** Linux, macOS, Windows
269
+
270
+ ---
271
+
272
+ ## Links & Resources
273
+
274
+ - **Website & Cloud App:** [fetchnode.com](https://fetchnode.com)
275
+ - **Documentation & Setup Guide:** [fetchnode.com/guide/](https://fetchnode.com/guide/)
276
+ - **Changelog:** [fetchnode.com/changelog/](https://fetchnode.com/changelog/)
277
+ - **GitHub Repository:** [github.com/remcovanwijk040/fetchnode](https://github.com/remcovanwijk040/fetchnode)
278
+ - **Issue Tracker:** [github.com/remcovanwijk040/fetchnode/issues](https://github.com/remcovanwijk040/fetchnode/issues)
279
+
280
+ ---
281
+
282
+ ## License
283
+
284
+ MIT License. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,247 @@
1
+ # FetchNode Python & Django SDK
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/fetchnode.svg)](https://pypi.org/project/fetchnode/)
4
+ [![Python Versions](https://img.shields.io/pypi/pyversions/fetchnode.svg)](https://pypi.org/project/fetchnode/)
5
+ [![Django Versions](https://img.shields.io/badge/django-4.2%20%7C%205.0%20%7C%205.1%20%7C%205.2-blue.svg)](https://www.djangoproject.com/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![Typing: Typed](https://img.shields.io/badge/typing-typed-green.svg)](https://peps.python.org/pep-0561/)
8
+
9
+ Official Python and Django client SDK for [FetchNode](https://fetchnode.com) — the focused operations workspace for developers and teams managing multiple production Django websites.
10
+
11
+ FetchNode captures unhandled exceptions, error logs, slow database queries, N+1 query patterns, Celery background tasks, and authoritative business conversions with zero bloat and privacy-safe defaults.
12
+
13
+ ---
14
+
15
+ ## Table of Contents
16
+
17
+ - [Installation](#installation)
18
+ - [Quickstart (Django)](#quickstart-django)
19
+ - [Configuration Options](#configuration-options)
20
+ - [Telemetry & Usage](#telemetry--usage)
21
+ - [Conversion & Lead Tracking](#conversion--lead-tracking)
22
+ - [Automatic Error Monitoring](#automatic-error-monitoring)
23
+ - [Manual Exception Capture](#manual-exception-capture)
24
+ - [Celery Job Monitoring](#celery-job-monitoring)
25
+ - [Logging Handler](#logging-handler)
26
+ - [Privacy & Security Defaults](#privacy--security-defaults)
27
+ - [Testing in Development](#testing-in-development)
28
+ - [Compatibility](#compatibility)
29
+ - [Links & Resources](#links--resources)
30
+ - [License](#license)
31
+
32
+ ---
33
+
34
+ ## Installation
35
+
36
+ Install via `pip`:
37
+
38
+ ```bash
39
+ pip install fetchnode
40
+ ```
41
+
42
+ Or using `uv`:
43
+
44
+ ```bash
45
+ uv add fetchnode
46
+ ```
47
+
48
+ *(For Celery support, you can optionally install `fetchnode[celery]`)*
49
+
50
+ ---
51
+
52
+ ## Quickstart (Django)
53
+
54
+ ### 1. Add to `INSTALLED_APPS`
55
+
56
+ In your Django `settings.py`:
57
+
58
+ ```python
59
+ INSTALLED_APPS = [
60
+ # ...
61
+ "fetchnode",
62
+ ]
63
+ ```
64
+
65
+ ### 2. Add the Middleware
66
+
67
+ Add `FetchNodeMiddleware` near the top of your `MIDDLEWARE` list for comprehensive request, query, and exception coverage:
68
+
69
+ ```python
70
+ MIDDLEWARE = [
71
+ "fetchnode.middleware.FetchNodeMiddleware",
72
+ "django.middleware.security.SecurityMiddleware",
73
+ # ...
74
+ ]
75
+ ```
76
+
77
+ ### 3. Configure `settings.FETCHNODE`
78
+
79
+ ```python
80
+ FETCHNODE = {
81
+ "API_KEY": "fn_live_your_project_key_here",
82
+ "ENVIRONMENT": "production", # e.g. "production", "staging"
83
+ }
84
+ ```
85
+
86
+ That's it! Django will automatically initialize FetchNode upon startup and capture unhandled exceptions, slow queries, and request telemetry.
87
+
88
+ ---
89
+
90
+ ## Configuration Options
91
+
92
+ Configure via `settings.FETCHNODE` or environment variables:
93
+
94
+ | Setting Key | Environment Variable | Default | Description |
95
+ | :--- | :--- | :--- | :--- |
96
+ | `API_KEY` | `FETCHNODE_API_KEY` | `""` | **Required.** Your FetchNode project key (starts with `fn_live_` or `fn_test_`). |
97
+ | `ENDPOINT` / `API_URL` | `FETCHNODE_ENDPOINT` | `https://monitor.fetchnode.com/api/events/ingest/` | Processing server URL. Point to your self-hosted instance if applicable. |
98
+ | `ENVIRONMENT` | `FETCHNODE_ENVIRONMENT` | `"development"` | Active environment name (`"production"`, `"staging"`). |
99
+ | `RELEASE` | `FETCHNODE_RELEASE` | `""` | Release tag or git commit hash (e.g. `git rev-parse HEAD`). |
100
+ | `ENABLED` | `FETCHNODE_ENABLED` | `True` if API_KEY present | Master toggle for event sending. |
101
+ | `CAPTURE_TRANSACTIONS` | — | `True` | Track request duration and database query counts. |
102
+ | `CAPTURE_BODY` | — | `False` | Privacy safeguard: request bodies are excluded by default. |
103
+ | `CAPTURE_HEADERS` | — | `False` | Privacy safeguard: request headers are excluded by default. |
104
+ | `QUERY_SLOW_THRESHOLD_MS` | — | `100` | Minimum execution time (ms) to flag an ORM query as slow. |
105
+ | `N_PLUS_ONE_THRESHOLD` | — | `5` | Identical SELECT query repetition threshold to flag N+1 issues. |
106
+ | `SENSITIVE_KEYS` | — | *(built-in list)* | Extra field names to scrub from payloads (additive). |
107
+
108
+ ---
109
+
110
+ ## Telemetry & Usage
111
+
112
+ ### Conversion & Lead Tracking
113
+
114
+ Track authoritative business events server-side (form submissions, payments, lead captures) with automatic session and visitor cookie attribution:
115
+
116
+ ```python
117
+ import fetchnode
118
+
119
+ def contact_view(request):
120
+ form = ContactForm(request.POST)
121
+ if form.is_valid():
122
+ lead = form.save()
123
+
124
+ # Track authoritative conversion
125
+ fetchnode.track(
126
+ "lead_submitted",
127
+ request=request,
128
+ idempotency_key=f"lead:{lead.id}",
129
+ properties={"email": lead.email},
130
+ conversion=True,
131
+ )
132
+ return redirect("thank_you")
133
+ ```
134
+
135
+ **Why `idempotency_key` matters:** Providing an `idempotency_key` (such as the database record ID or payment provider ID) ensures that retried requests, webhook replays, or browser reloads do not inflate your conversion metrics. FetchNode securely hashes the key client-side to produce a deterministic event ID.
136
+
137
+ ### Automatic Error Monitoring
138
+
139
+ Unhandled exceptions in Django views are caught by `FetchNodeMiddleware`, decorated with:
140
+ * The exact failing line of code and stack trace.
141
+ * In-app vs. framework/library frame classification.
142
+ * Database query count and execution time during the failing request.
143
+ * Correlated request ID and trace ID.
144
+
145
+ ### Manual Exception Capture
146
+
147
+ Capture handled exceptions or background exceptions:
148
+
149
+ ```python
150
+ import fetchnode
151
+
152
+ try:
153
+ external_service_call()
154
+ except ThirdPartyAPIError as exc:
155
+ fetchnode.capture_exception(exc, tags={"provider": "stripe"})
156
+ ```
157
+
158
+ ### Celery Job Monitoring
159
+
160
+ FetchNode automatically instruments Celery when installed. You can also explicitly decorate tasks:
161
+
162
+ ```python
163
+ from fetchnode import capture_job
164
+
165
+ @capture_job(name="sync_external_inventory")
166
+ def sync_inventory():
167
+ ...
168
+ ```
169
+
170
+ ### Logging Handler
171
+
172
+ Send standard `logging.ERROR` and `logging.CRITICAL` records to FetchNode:
173
+
174
+ ```python
175
+ LOGGING = {
176
+ "version": 1,
177
+ "disable_existing_loggers": False,
178
+ "handlers": {
179
+ "fetchnode": {
180
+ "level": "ERROR",
181
+ "class": "fetchnode.handlers.FetchNodeHandler",
182
+ },
183
+ },
184
+ "loggers": {
185
+ "django": {
186
+ "handlers": ["fetchnode"],
187
+ "level": "ERROR",
188
+ "propagate": True,
189
+ },
190
+ },
191
+ }
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Privacy & Security Defaults
197
+
198
+ FetchNode is engineered with a strict **privacy-first architecture**:
199
+ 1. **Client-side Scrubbing:** Sensitive keys (`password`, `token`, `secret`, `api_key`, `authorization`, `cookie`, `csrfmiddlewaretoken`) and any compound keys like `user_password` or `client_secret` are scrubbed before payload transmission.
200
+ 2. **Safe SQL Normalization:** Raw SQL text is stripped of literals and parameters (`SELECT * FROM user WHERE id = 123` becomes `SELECT * FROM user WHERE id = ?`).
201
+ 3. **No Unintentional PII:** Request bodies, query strings, and raw IP addresses are excluded by default.
202
+ 4. **Inspectable & Lightweight:** The entire client is open-source Python with zero compiled binaries and zero unnecessary dependencies.
203
+
204
+ ---
205
+
206
+ ## Testing in Development
207
+
208
+ In your local test suite (e.g. `pytest` or `python manage.py test`), FetchNode can be disabled by omitting the API key or explicitly setting:
209
+
210
+ ```python
211
+ # settings_test.py
212
+ FETCHNODE = {
213
+ "ENABLED": False,
214
+ }
215
+ ```
216
+
217
+ To test that event transmission works in your staging environment, run:
218
+
219
+ ```python
220
+ import fetchnode
221
+ fetchnode.capture_message("FetchNode verification ping", level="info")
222
+ fetchnode.flush()
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Compatibility
228
+
229
+ - **Python:** 3.10, 3.11, 3.12, 3.13, 3.14
230
+ - **Django:** 4.2 LTS, 5.0, 5.1, 5.2
231
+ - **Operating Systems:** Linux, macOS, Windows
232
+
233
+ ---
234
+
235
+ ## Links & Resources
236
+
237
+ - **Website & Cloud App:** [fetchnode.com](https://fetchnode.com)
238
+ - **Documentation & Setup Guide:** [fetchnode.com/guide/](https://fetchnode.com/guide/)
239
+ - **Changelog:** [fetchnode.com/changelog/](https://fetchnode.com/changelog/)
240
+ - **GitHub Repository:** [github.com/remcovanwijk040/fetchnode](https://github.com/remcovanwijk040/fetchnode)
241
+ - **Issue Tracker:** [github.com/remcovanwijk040/fetchnode/issues](https://github.com/remcovanwijk040/fetchnode/issues)
242
+
243
+ ---
244
+
245
+ ## License
246
+
247
+ MIT License. See [LICENSE](LICENSE) for details.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "fetchnode"
7
- version = "0.4.0"
7
+ version = "0.4.2"
8
8
  description = "Official Django and Python client SDK for FetchNode monitoring"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -31,6 +31,7 @@ classifiers = [
31
31
  "Programming Language :: Python :: 3.14",
32
32
  "Topic :: System :: Monitoring",
33
33
  "Topic :: Software Development :: Libraries :: Python Modules",
34
+ "Typing :: Typed",
34
35
  ]
35
36
  dependencies = [
36
37
  "django>=4.2",
@@ -43,8 +44,10 @@ celery = [
43
44
 
44
45
  [project.urls]
45
46
  Homepage = "https://fetchnode.com"
47
+ Documentation = "https://fetchnode.com/guide/"
46
48
  Repository = "https://github.com/remcovanwijk040/fetchnode"
47
49
  Issues = "https://github.com/remcovanwijk040/fetchnode/issues"
50
+ Changelog = "https://fetchnode.com/changelog/"
48
51
 
49
52
  [tool.hatch.build.targets.wheel]
50
53
  packages = ["src/fetchnode", "src/fetchnode_client"]
@@ -13,8 +13,10 @@ class FetchNodeConfig(AppConfig):
13
13
  # Ensure settings.fetchnode_track is available if needed by legacy code
14
14
  try:
15
15
  from django.conf import settings
16
- setattr(settings, "fetchnode_track", track_event)
17
- setattr(settings, "FETCHNODE_TRACK", track_event)
16
+ if not hasattr(settings, "fetchnode_track"):
17
+ setattr(settings, "fetchnode_track", track_event)
18
+ if not hasattr(settings, "FETCHNODE_TRACK"):
19
+ setattr(settings, "FETCHNODE_TRACK", track_event)
18
20
  except Exception:
19
21
  pass
20
22
 
@@ -22,6 +22,7 @@ DEFAULTS = {
22
22
  "KEEP_TRANSACTION_OUTLIER_MS": 1000,
23
23
  "CAPTURE_TRANSACTIONS": True,
24
24
  "CAPTURE_JOBS": True,
25
+ "CAPTURE_404": True,
25
26
  "INSTRUMENT_CELERY": True,
26
27
  "INSTRUMENT_LOGGING": True,
27
28
  "INSTRUMENT_EXCEPTHOOK": True,
@@ -118,7 +119,9 @@ def get_client_settings() -> dict:
118
119
  elif "FETCHNODE_ENABLED" in os.environ:
119
120
  merged["ENABLED"] = os.environ["FETCHNODE_ENABLED"].lower() in {"1", "true", "yes"}
120
121
  else:
121
- merged["ENABLED"] = bool(merged["API_KEY"])
122
+ import sys
123
+ is_testing = any(arg == "test" or "pytest" in arg for arg in sys.argv)
124
+ merged["ENABLED"] = bool(merged["API_KEY"]) and not is_testing
122
125
 
123
126
  # Security filters are additive: a project can protect extra business
124
127
  # fields without accidentally unprotecting cookies or authorization data.
@@ -26,6 +26,8 @@ class FetchNodeHandler(logging.Handler):
26
26
  record.levelno < logging.ERROR
27
27
  or record.name == "fetchnode"
28
28
  or record.name.startswith("fetchnode.")
29
+ or record.name == "fetchnode_client"
30
+ or record.name.startswith("fetchnode_client.")
29
31
  or getattr(record, _CAPTURED_RECORD_ATTRIBUTE, False)
30
32
  ):
31
33
  return
@@ -41,8 +41,10 @@ class ErrorCaptureMiddleware:
41
41
  try:
42
42
  from django.conf import settings
43
43
  from fetchnode.api import track_event
44
- setattr(settings, "fetchnode_track", track_event)
45
- setattr(settings, "FETCHNODE_TRACK", track_event)
44
+ if not hasattr(settings, "fetchnode_track"):
45
+ setattr(settings, "fetchnode_track", track_event)
46
+ if not hasattr(settings, "FETCHNODE_TRACK"):
47
+ setattr(settings, "FETCHNODE_TRACK", track_event)
46
48
  except Exception:
47
49
  pass
48
50
 
@@ -120,6 +122,8 @@ class ErrorCaptureMiddleware:
120
122
  request.path.startswith(prefix) for prefix in config["IGNORE_PATH_PREFIXES"]
121
123
  ):
122
124
  return
125
+ if not config.get("CAPTURE_404", True) and status_code == 404:
126
+ return
123
127
  resolver_match = getattr(request, "resolver_match", None)
124
128
  auth_user = getattr(request, "user", None)
125
129
  user_identifier = ""
File without changes
@@ -39,7 +39,7 @@ def application_stack_location() -> str:
39
39
  """Return the nearest application callsite without local variables or values."""
40
40
  for frame in reversed(traceback.extract_stack(limit=30)[:-1]):
41
41
  filename = frame.filename.replace("\\", "/")
42
- if any(marker in filename for marker in ("/django/", "/site-packages/", "/fetchnode/")):
42
+ if any(marker in filename for marker in ("/django/", "/site-packages/", "/fetchnode/", "/fetchnode_client/")):
43
43
  continue
44
44
  return f"{frame.filename}:{frame.lineno} in {frame.name}"[:1024]
45
45
  return ""
@@ -8,7 +8,13 @@ MAX_VALUE_LENGTH = 4000
8
8
 
9
9
  def is_sensitive_key(key: str, sensitive_keys: list[str]) -> bool:
10
10
  normalized = (key or "").replace("-", "_").lower()
11
- return normalized in {(item or "").replace("-", "_").lower() for item in sensitive_keys}
11
+ norm_set = {(item or "").replace("-", "_").lower() for item in (sensitive_keys or []) if item}
12
+ if normalized in norm_set:
13
+ return True
14
+ for item in norm_set:
15
+ if normalized.startswith(f"{item}_") or normalized.endswith(f"_{item}") or f"_{item}_" in normalized:
16
+ return True
17
+ return False
12
18
 
13
19
 
14
20
  def scrub_value(value, *, key: str = "", sensitive_keys: list[str] | None = None, depth: int = 0):
@@ -63,6 +63,7 @@ def _deliver(payload: dict, config: dict) -> bool:
63
63
  headers = {
64
64
  "Content-Type": "application/json",
65
65
  "Authorization": f"Bearer {config['API_KEY']}",
66
+ "User-Agent": f"fetchnode-python/{SDK_VERSION}",
66
67
  }
67
68
  if config.get("COMPRESS_PAYLOADS", True) and len(data) >= int(config.get("COMPRESSION_THRESHOLD_BYTES", 1024)):
68
69
  data = gzip.compress(data, compresslevel=5)
@@ -1,3 +1,3 @@
1
1
  """FetchNode Python SDK version shared by package metadata and telemetry."""
2
2
 
3
- SDK_VERSION = "0.4.0"
3
+ SDK_VERSION = "0.4.2"
File without changes
fetchnode-0.4.0/PKG-INFO DELETED
@@ -1,166 +0,0 @@
1
- Metadata-Version: 2.5
2
- Name: fetchnode
3
- Version: 0.4.0
4
- Summary: Official Django and Python client SDK for FetchNode monitoring
5
- Project-URL: Homepage, https://fetchnode.com
6
- Project-URL: Repository, https://github.com/remcovanwijk040/fetchnode
7
- Project-URL: Issues, https://github.com/remcovanwijk040/fetchnode/issues
8
- Author-email: Remco van Wijk <remcovanwijk040@users.noreply.github.com>
9
- License-Expression: MIT
10
- License-File: LICENSE
11
- Classifier: Development Status :: 4 - Beta
12
- Classifier: Environment :: Web Environment
13
- Classifier: Framework :: Django
14
- Classifier: Framework :: Django :: 4.2
15
- Classifier: Framework :: Django :: 5.0
16
- Classifier: Framework :: Django :: 5.1
17
- Classifier: Framework :: Django :: 5.2
18
- Classifier: Intended Audience :: Developers
19
- Classifier: License :: OSI Approved :: MIT License
20
- Classifier: Operating System :: OS Independent
21
- Classifier: Programming Language :: Python :: 3
22
- Classifier: Programming Language :: Python :: 3.10
23
- Classifier: Programming Language :: Python :: 3.11
24
- Classifier: Programming Language :: Python :: 3.12
25
- Classifier: Programming Language :: Python :: 3.13
26
- Classifier: Programming Language :: Python :: 3.14
27
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
- Classifier: Topic :: System :: Monitoring
29
- Requires-Python: >=3.10
30
- Requires-Dist: django>=4.2
31
- Provides-Extra: celery
32
- Requires-Dist: celery>=5.0; extra == 'celery'
33
- Description-Content-Type: text/markdown
34
-
35
- # FetchNode Python & Django SDK
36
-
37
- [![PyPI](https://img.shields.io/pypi/v/fetchnode.svg)](https://pypi.org/project/fetchnode/)
38
- [![Python Versions](https://img.shields.io/pypi/pyversions/fetchnode.svg)](https://pypi.org/project/fetchnode/)
39
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
40
-
41
- Official Python and Django client SDK for [FetchNode](https://fetchnode.com), the focused operations and monitoring workspace for Django teams and portfolio operators.
42
-
43
- FetchNode captures unhandled exceptions, error logs, slow database queries, Celery background tasks, and business conversions with zero configuration bloat and privacy-safe defaults.
44
-
45
- ---
46
-
47
- ## Installation
48
-
49
- Install the package using `pip` or `uv`:
50
-
51
- ```bash
52
- pip install fetchnode
53
- ```
54
-
55
- *(Or `uv add fetchnode`)*
56
-
57
- ---
58
-
59
- ## Quickstart (Django)
60
-
61
- ### 1. Add to `INSTALLED_APPS`
62
-
63
- In your Django `settings.py`:
64
-
65
- ```python
66
- INSTALLED_APPS = [
67
- # ...
68
- "fetchnode",
69
- ]
70
- ```
71
-
72
- ### 2. Add the Middleware
73
-
74
- Add `FetchNodeMiddleware` near the top of your `MIDDLEWARE` list:
75
-
76
- ```python
77
- MIDDLEWARE = [
78
- "fetchnode.middleware.FetchNodeMiddleware",
79
- "django.middleware.security.SecurityMiddleware",
80
- # ...
81
- ]
82
- ```
83
-
84
- ### 3. Configure `settings.FETCHNODE`
85
-
86
- ```python
87
- FETCHNODE = {
88
- "API_KEY": "fn_live_your_project_key_here",
89
- "ENVIRONMENT": "production", # e.g. "production", "staging"
90
-
91
- # Optional: override endpoint for self-hosted FetchNode instances
92
- # Defaults to FetchNode Cloud: "https://monitor.fetchnode.com"
93
- # "ENDPOINT": "https://monitor.fetchnode.com",
94
- }
95
- ```
96
-
97
- You can also configure via environment variables:
98
- * `FETCHNODE_API_KEY`
99
- * `FETCHNODE_ENVIRONMENT`
100
- * `FETCHNODE_RELEASE`
101
- * `FETCHNODE_ENDPOINT`
102
-
103
- ---
104
-
105
- ## Usage
106
-
107
- ### Milestone & Conversion Tracking
108
-
109
- Track business events (form submits, leads, signups, payments) server-side with automatic visitor attribution:
110
-
111
- ```python
112
- import fetchnode
113
-
114
- def contact_view(request):
115
- form = ContactForm(request.POST)
116
- if form.is_valid():
117
- lead = form.save()
118
-
119
- # Track authoritative conversion
120
- fetchnode.track(
121
- "lead_submitted",
122
- request=request,
123
- idempotency_key=str(lead.id),
124
- properties={"email": lead.email},
125
- conversion=True,
126
- )
127
- return redirect("thank_you")
128
- ```
129
-
130
- ### Manual Exception Capture
131
-
132
- ```python
133
- import fetchnode
134
-
135
- try:
136
- risky_operation()
137
- except Exception as exc:
138
- fetchnode.capture_exception(exc)
139
- ```
140
-
141
- ### Celery Task Monitoring
142
-
143
- FetchNode automatically instruments Celery when installed. To manually monitor tasks or commands:
144
-
145
- ```python
146
- from fetchnode import capture_job
147
-
148
- @capture_job(name="sync_external_inventory")
149
- def sync_inventory():
150
- ...
151
- ```
152
-
153
- ---
154
-
155
- ## Privacy by Default
156
-
157
- FetchNode is engineered with privacy-safe defaults:
158
- * Sensitive keys (`password`, `token`, `secret`, `api_key`, `authorization`, `cookie`, `csrfmiddlewaretoken`) are scrubbed before payload transmission.
159
- * Request bodies and raw IP addresses are excluded by default.
160
- * Query string parameters and raw SQL values are normalized.
161
-
162
- ---
163
-
164
- ## License
165
-
166
- MIT License. See [LICENSE](LICENSE) for details.
fetchnode-0.4.0/README.md DELETED
@@ -1,132 +0,0 @@
1
- # FetchNode Python & Django SDK
2
-
3
- [![PyPI](https://img.shields.io/pypi/v/fetchnode.svg)](https://pypi.org/project/fetchnode/)
4
- [![Python Versions](https://img.shields.io/pypi/pyversions/fetchnode.svg)](https://pypi.org/project/fetchnode/)
5
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
-
7
- Official Python and Django client SDK for [FetchNode](https://fetchnode.com), the focused operations and monitoring workspace for Django teams and portfolio operators.
8
-
9
- FetchNode captures unhandled exceptions, error logs, slow database queries, Celery background tasks, and business conversions with zero configuration bloat and privacy-safe defaults.
10
-
11
- ---
12
-
13
- ## Installation
14
-
15
- Install the package using `pip` or `uv`:
16
-
17
- ```bash
18
- pip install fetchnode
19
- ```
20
-
21
- *(Or `uv add fetchnode`)*
22
-
23
- ---
24
-
25
- ## Quickstart (Django)
26
-
27
- ### 1. Add to `INSTALLED_APPS`
28
-
29
- In your Django `settings.py`:
30
-
31
- ```python
32
- INSTALLED_APPS = [
33
- # ...
34
- "fetchnode",
35
- ]
36
- ```
37
-
38
- ### 2. Add the Middleware
39
-
40
- Add `FetchNodeMiddleware` near the top of your `MIDDLEWARE` list:
41
-
42
- ```python
43
- MIDDLEWARE = [
44
- "fetchnode.middleware.FetchNodeMiddleware",
45
- "django.middleware.security.SecurityMiddleware",
46
- # ...
47
- ]
48
- ```
49
-
50
- ### 3. Configure `settings.FETCHNODE`
51
-
52
- ```python
53
- FETCHNODE = {
54
- "API_KEY": "fn_live_your_project_key_here",
55
- "ENVIRONMENT": "production", # e.g. "production", "staging"
56
-
57
- # Optional: override endpoint for self-hosted FetchNode instances
58
- # Defaults to FetchNode Cloud: "https://monitor.fetchnode.com"
59
- # "ENDPOINT": "https://monitor.fetchnode.com",
60
- }
61
- ```
62
-
63
- You can also configure via environment variables:
64
- * `FETCHNODE_API_KEY`
65
- * `FETCHNODE_ENVIRONMENT`
66
- * `FETCHNODE_RELEASE`
67
- * `FETCHNODE_ENDPOINT`
68
-
69
- ---
70
-
71
- ## Usage
72
-
73
- ### Milestone & Conversion Tracking
74
-
75
- Track business events (form submits, leads, signups, payments) server-side with automatic visitor attribution:
76
-
77
- ```python
78
- import fetchnode
79
-
80
- def contact_view(request):
81
- form = ContactForm(request.POST)
82
- if form.is_valid():
83
- lead = form.save()
84
-
85
- # Track authoritative conversion
86
- fetchnode.track(
87
- "lead_submitted",
88
- request=request,
89
- idempotency_key=str(lead.id),
90
- properties={"email": lead.email},
91
- conversion=True,
92
- )
93
- return redirect("thank_you")
94
- ```
95
-
96
- ### Manual Exception Capture
97
-
98
- ```python
99
- import fetchnode
100
-
101
- try:
102
- risky_operation()
103
- except Exception as exc:
104
- fetchnode.capture_exception(exc)
105
- ```
106
-
107
- ### Celery Task Monitoring
108
-
109
- FetchNode automatically instruments Celery when installed. To manually monitor tasks or commands:
110
-
111
- ```python
112
- from fetchnode import capture_job
113
-
114
- @capture_job(name="sync_external_inventory")
115
- def sync_inventory():
116
- ...
117
- ```
118
-
119
- ---
120
-
121
- ## Privacy by Default
122
-
123
- FetchNode is engineered with privacy-safe defaults:
124
- * Sensitive keys (`password`, `token`, `secret`, `api_key`, `authorization`, `cookie`, `csrfmiddlewaretoken`) are scrubbed before payload transmission.
125
- * Request bodies and raw IP addresses are excluded by default.
126
- * Query string parameters and raw SQL values are normalized.
127
-
128
- ---
129
-
130
- ## License
131
-
132
- MIT License. See [LICENSE](LICENSE) for details.
File without changes
File without changes