django-devbar 0.6.0__tar.gz → 0.7.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.
- {django_devbar-0.6.0 → django_devbar-0.7.0}/PKG-INFO +15 -4
- {django_devbar-0.6.0 → django_devbar-0.7.0}/README.md +12 -2
- {django_devbar-0.6.0 → django_devbar-0.7.0}/pyproject.toml +2 -1
- {django_devbar-0.6.0 → django_devbar-0.7.0}/src/django_devbar/conf.py +12 -0
- {django_devbar-0.6.0 → django_devbar-0.7.0}/src/django_devbar/middleware.py +91 -19
- {django_devbar-0.6.0 → django_devbar-0.7.0}/src/django_devbar/tracker.py +1 -1
- {django_devbar-0.6.0 → django_devbar-0.7.0}/src/django_devbar/__init__.py +0 -0
- {django_devbar-0.6.0 → django_devbar-0.7.0}/src/django_devbar/templates/django_devbar/devbar.html +0 -0
- {django_devbar-0.6.0 → django_devbar-0.7.0}/src/django_devbar/templates/django_devbar/duplicates.html +0 -0
- {django_devbar-0.6.0 → django_devbar-0.7.0}/src/django_devbar/templates/django_devbar/similar.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-devbar
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Lightweight performance devbar for Django
|
|
5
5
|
Keywords: django,debug,performance,monitoring,development
|
|
6
6
|
Author: amureki
|
|
@@ -22,11 +22,12 @@ Classifier: Framework :: Django :: 5.2
|
|
|
22
22
|
Classifier: Framework :: Django :: 6.0
|
|
23
23
|
Classifier: Topic :: Software Development
|
|
24
24
|
Classifier: Intended Audience :: Developers
|
|
25
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
25
26
|
Requires-Dist: django>=4.2
|
|
26
27
|
Requires-Python: >=3.10
|
|
27
28
|
Project-URL: Homepage, https://github.com/amureki/django-devbar
|
|
28
|
-
Project-URL: Issues, https://github.com/amureki/django-devbar/issues
|
|
29
29
|
Project-URL: Repository, https://github.com/amureki/django-devbar
|
|
30
|
+
Project-URL: Issues, https://github.com/amureki/django-devbar/issues
|
|
30
31
|
Description-Content-Type: text/markdown
|
|
31
32
|
|
|
32
33
|
# django-devbar
|
|
@@ -71,16 +72,21 @@ uv add --dev django-devbar
|
|
|
71
72
|
pip install django-devbar
|
|
72
73
|
```
|
|
73
74
|
|
|
74
|
-
Add to your middleware
|
|
75
|
+
Add to your middleware early. For example:
|
|
75
76
|
|
|
76
77
|
```python
|
|
77
78
|
MIDDLEWARE = [
|
|
78
79
|
"django.middleware.security.SecurityMiddleware",
|
|
79
|
-
"django_devbar.DevBarMiddleware",
|
|
80
80
|
# ...
|
|
81
81
|
]
|
|
82
|
+
|
|
83
|
+
if DEBUG:
|
|
84
|
+
idx = MIDDLEWARE.index("django.middleware.security.SecurityMiddleware")
|
|
85
|
+
MIDDLEWARE.insert(idx + 1, "django_devbar.DevBarMiddleware")
|
|
82
86
|
```
|
|
83
87
|
|
|
88
|
+
This keeps the middleware active only in development and avoids import errors if the package isn't installed in production.
|
|
89
|
+
|
|
84
90
|
## Configuration
|
|
85
91
|
|
|
86
92
|
All settings are optional. Configure via a `DEVBAR` dict in your Django settings:
|
|
@@ -90,6 +96,8 @@ DEVBAR = {
|
|
|
90
96
|
"POSITION": "bottom-right", # bottom-right (default), bottom-left, top-right, top-left
|
|
91
97
|
"SHOW_BAR": None, # follows DEBUG; set True/False to override
|
|
92
98
|
"ENABLE_DEVTOOLS_DATA": None, # follows DEBUG; set True/False to override
|
|
99
|
+
"DEVTOOLS_HEADER_MAX_BYTES": 6144, # max bytes for DevBar-Data header payload
|
|
100
|
+
"DEVTOOLS_MAX_QUERIES": None, # optional hard cap for q/dup entries sent to DevTools
|
|
93
101
|
}
|
|
94
102
|
```
|
|
95
103
|
|
|
@@ -102,6 +110,9 @@ Django DevBar adds HTTP response headers with performance metrics:
|
|
|
102
110
|
An additional header is included by default in DEBUG mode:
|
|
103
111
|
|
|
104
112
|
- **DevBar-Data** - JSON header with comprehensive metrics including similar and duplicate query details
|
|
113
|
+
- Query-level flags are embedded in `q` entries (`dup` and `sim`)
|
|
114
|
+
- Payload is automatically truncated to fit `DEVTOOLS_HEADER_MAX_BYTES`
|
|
115
|
+
- When truncated, response includes metadata keys: `tr`, `q_total`, `q_sent`
|
|
105
116
|
|
|
106
117
|
This is useful for:
|
|
107
118
|
|
|
@@ -40,16 +40,21 @@ uv add --dev django-devbar
|
|
|
40
40
|
pip install django-devbar
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
Add to your middleware
|
|
43
|
+
Add to your middleware early. For example:
|
|
44
44
|
|
|
45
45
|
```python
|
|
46
46
|
MIDDLEWARE = [
|
|
47
47
|
"django.middleware.security.SecurityMiddleware",
|
|
48
|
-
"django_devbar.DevBarMiddleware",
|
|
49
48
|
# ...
|
|
50
49
|
]
|
|
50
|
+
|
|
51
|
+
if DEBUG:
|
|
52
|
+
idx = MIDDLEWARE.index("django.middleware.security.SecurityMiddleware")
|
|
53
|
+
MIDDLEWARE.insert(idx + 1, "django_devbar.DevBarMiddleware")
|
|
51
54
|
```
|
|
52
55
|
|
|
56
|
+
This keeps the middleware active only in development and avoids import errors if the package isn't installed in production.
|
|
57
|
+
|
|
53
58
|
## Configuration
|
|
54
59
|
|
|
55
60
|
All settings are optional. Configure via a `DEVBAR` dict in your Django settings:
|
|
@@ -59,6 +64,8 @@ DEVBAR = {
|
|
|
59
64
|
"POSITION": "bottom-right", # bottom-right (default), bottom-left, top-right, top-left
|
|
60
65
|
"SHOW_BAR": None, # follows DEBUG; set True/False to override
|
|
61
66
|
"ENABLE_DEVTOOLS_DATA": None, # follows DEBUG; set True/False to override
|
|
67
|
+
"DEVTOOLS_HEADER_MAX_BYTES": 6144, # max bytes for DevBar-Data header payload
|
|
68
|
+
"DEVTOOLS_MAX_QUERIES": None, # optional hard cap for q/dup entries sent to DevTools
|
|
62
69
|
}
|
|
63
70
|
```
|
|
64
71
|
|
|
@@ -71,6 +78,9 @@ Django DevBar adds HTTP response headers with performance metrics:
|
|
|
71
78
|
An additional header is included by default in DEBUG mode:
|
|
72
79
|
|
|
73
80
|
- **DevBar-Data** - JSON header with comprehensive metrics including similar and duplicate query details
|
|
81
|
+
- Query-level flags are embedded in `q` entries (`dup` and `sim`)
|
|
82
|
+
- Payload is automatically truncated to fit `DEVTOOLS_HEADER_MAX_BYTES`
|
|
83
|
+
- When truncated, response includes metadata keys: `tr`, `q_total`, `q_sent`
|
|
74
84
|
|
|
75
85
|
This is useful for:
|
|
76
86
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "django-devbar"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.7.0"
|
|
4
4
|
description = "Lightweight performance devbar for Django"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -27,6 +27,7 @@ classifiers = [
|
|
|
27
27
|
"Framework :: Django :: 6.0",
|
|
28
28
|
"Topic :: Software Development",
|
|
29
29
|
"Intended Audience :: Developers",
|
|
30
|
+
"License :: OSI Approved :: MIT License",
|
|
30
31
|
]
|
|
31
32
|
|
|
32
33
|
[project.urls]
|
|
@@ -13,6 +13,8 @@ def get_config():
|
|
|
13
13
|
"POSITION": "bottom-right",
|
|
14
14
|
"SHOW_BAR": None, # None = use settings.DEBUG
|
|
15
15
|
"ENABLE_DEVTOOLS_DATA": None, # None = use settings.DEBUG
|
|
16
|
+
"DEVTOOLS_HEADER_MAX_BYTES": 6144,
|
|
17
|
+
"DEVTOOLS_MAX_QUERIES": None,
|
|
16
18
|
**getattr(settings, "DEVBAR", {}),
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -33,3 +35,13 @@ def get_enable_devtools_data():
|
|
|
33
35
|
config = get_config()
|
|
34
36
|
enable = config["ENABLE_DEVTOOLS_DATA"]
|
|
35
37
|
return settings.DEBUG if enable is None else enable
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_devtools_header_max_bytes():
|
|
41
|
+
config = get_config()
|
|
42
|
+
return config["DEVTOOLS_HEADER_MAX_BYTES"]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_devtools_max_queries():
|
|
46
|
+
config = get_config()
|
|
47
|
+
return config["DEVTOOLS_MAX_QUERIES"]
|
|
@@ -9,6 +9,8 @@ from django.template import Context, Engine
|
|
|
9
9
|
|
|
10
10
|
from . import tracker
|
|
11
11
|
from .conf import (
|
|
12
|
+
get_devtools_header_max_bytes,
|
|
13
|
+
get_devtools_max_queries,
|
|
12
14
|
get_enable_devtools_data,
|
|
13
15
|
get_position,
|
|
14
16
|
get_show_bar,
|
|
@@ -58,34 +60,104 @@ class DevBarMiddleware:
|
|
|
58
60
|
return response
|
|
59
61
|
|
|
60
62
|
def _add_devtools_data_header(self, response, stats):
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
max_bytes = get_devtools_header_max_bytes()
|
|
64
|
+
if max_bytes <= 0:
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
summary_data = {
|
|
63
68
|
"c": stats["count"],
|
|
64
69
|
"db": round(stats["duration"], 2),
|
|
65
70
|
"app": stats["python_time"],
|
|
66
71
|
"full": stats["total_time"],
|
|
67
72
|
}
|
|
73
|
+
if self._json_size(summary_data) > max_bytes:
|
|
74
|
+
return
|
|
68
75
|
|
|
69
|
-
|
|
70
|
-
|
|
76
|
+
raw_queries = stats.get("queries", [])
|
|
77
|
+
total_query_count = len(raw_queries)
|
|
78
|
+
max_queries = get_devtools_max_queries()
|
|
79
|
+
all_queries = (
|
|
80
|
+
raw_queries[:max_queries] if max_queries is not None else raw_queries
|
|
81
|
+
)
|
|
82
|
+
processed_queries = [
|
|
83
|
+
{
|
|
84
|
+
"s": truncate_sql(q["sql"]),
|
|
85
|
+
"dur": q["duration"],
|
|
86
|
+
"dup": 1 if q["is_duplicate"] else 0,
|
|
87
|
+
"sim": 1 if q.get("is_similar") else 0,
|
|
88
|
+
}
|
|
89
|
+
for q in all_queries
|
|
90
|
+
]
|
|
91
|
+
is_truncated = len(processed_queries) < total_query_count
|
|
71
92
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"sim": 1 if q.get("is_similar") else 0,
|
|
79
|
-
}
|
|
80
|
-
for q in all_queries
|
|
81
|
-
]
|
|
82
|
-
extension_data["q"] = processed_queries
|
|
93
|
+
full_payload = summary_data.copy()
|
|
94
|
+
if processed_queries:
|
|
95
|
+
full_payload["q"] = processed_queries
|
|
96
|
+
if self._json_size(full_payload) <= max_bytes and not is_truncated:
|
|
97
|
+
response["DevBar-Data"] = self._serialize_payload(full_payload)
|
|
98
|
+
return
|
|
83
99
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
100
|
+
best_count = self._max_queries_that_fit(
|
|
101
|
+
summary_data,
|
|
102
|
+
processed_queries,
|
|
103
|
+
max_bytes,
|
|
104
|
+
total_query_count,
|
|
105
|
+
)
|
|
106
|
+
payload = self._build_truncated_payload(
|
|
107
|
+
summary_data,
|
|
108
|
+
processed_queries,
|
|
109
|
+
best_count,
|
|
110
|
+
total_query_count,
|
|
111
|
+
)
|
|
87
112
|
|
|
88
|
-
|
|
113
|
+
if self._json_size(payload) <= max_bytes:
|
|
114
|
+
response["DevBar-Data"] = self._serialize_payload(payload)
|
|
115
|
+
else:
|
|
116
|
+
response["DevBar-Data"] = self._serialize_payload(summary_data)
|
|
117
|
+
|
|
118
|
+
def _json_size(self, data):
|
|
119
|
+
return len(self._serialize_payload(data).encode("utf-8"))
|
|
120
|
+
|
|
121
|
+
def _serialize_payload(self, payload):
|
|
122
|
+
return json.dumps(payload, separators=(",", ":"))
|
|
123
|
+
|
|
124
|
+
def _build_truncated_payload(
|
|
125
|
+
self,
|
|
126
|
+
summary_data,
|
|
127
|
+
processed_queries,
|
|
128
|
+
query_count,
|
|
129
|
+
queries_total,
|
|
130
|
+
):
|
|
131
|
+
payload = summary_data.copy()
|
|
132
|
+
if query_count:
|
|
133
|
+
payload["q"] = processed_queries[:query_count]
|
|
134
|
+
payload["tr"] = 1
|
|
135
|
+
payload["q_total"] = queries_total
|
|
136
|
+
payload["q_sent"] = query_count
|
|
137
|
+
return payload
|
|
138
|
+
|
|
139
|
+
def _max_queries_that_fit(
|
|
140
|
+
self,
|
|
141
|
+
summary_data,
|
|
142
|
+
processed_queries,
|
|
143
|
+
max_bytes,
|
|
144
|
+
queries_total,
|
|
145
|
+
):
|
|
146
|
+
low = 0
|
|
147
|
+
high = len(processed_queries)
|
|
148
|
+
while low < high:
|
|
149
|
+
mid = (low + high + 1) // 2
|
|
150
|
+
payload = self._build_truncated_payload(
|
|
151
|
+
summary_data,
|
|
152
|
+
processed_queries,
|
|
153
|
+
mid,
|
|
154
|
+
queries_total,
|
|
155
|
+
)
|
|
156
|
+
if self._json_size(payload) <= max_bytes:
|
|
157
|
+
low = mid
|
|
158
|
+
else:
|
|
159
|
+
high = mid - 1
|
|
160
|
+
return low
|
|
89
161
|
|
|
90
162
|
def _add_server_timing_header(self, response, stats):
|
|
91
163
|
parts = [
|
|
File without changes
|
{django_devbar-0.6.0 → django_devbar-0.7.0}/src/django_devbar/templates/django_devbar/devbar.html
RENAMED
|
File without changes
|
|
File without changes
|
{django_devbar-0.6.0 → django_devbar-0.7.0}/src/django_devbar/templates/django_devbar/similar.html
RENAMED
|
File without changes
|