pyramid-debugtoolbar-api-performance 1.0.0__tar.gz → 1.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.
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/CHANGES.txt +3 -0
- {pyramid_debugtoolbar_api_performance-1.0.0/src/pyramid_debugtoolbar_api_performance.egg-info → pyramid_debugtoolbar_api_performance-1.1.0}/PKG-INFO +1 -1
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/src/pyramid_debugtoolbar_api_performance/_version.py +1 -1
- pyramid_debugtoolbar_api_performance-1.1.0/src/pyramid_debugtoolbar_api_performance/spreadsheet_utils.py +14 -0
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/src/pyramid_debugtoolbar_api_performance/views.py +27 -17
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0/src/pyramid_debugtoolbar_api_performance.egg-info}/PKG-INFO +1 -1
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/src/pyramid_debugtoolbar_api_performance.egg-info/SOURCES.txt +1 -0
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/tests/test_config.py +46 -0
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/LICENSE +0 -0
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/MANIFEST.in +0 -0
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/README.rst +0 -0
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/TODO.txt +0 -0
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/pyproject.toml +0 -0
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/setup.cfg +0 -0
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/src/pyramid_debugtoolbar_api_performance/__init__.py +0 -0
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/src/pyramid_debugtoolbar_api_performance/panels.py +0 -0
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/src/pyramid_debugtoolbar_api_performance/templates/performance_csv.dbtmako +0 -0
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/src/pyramid_debugtoolbar_api_performance/utils.py +0 -0
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/src/pyramid_debugtoolbar_api_performance.egg-info/dependency_links.txt +0 -0
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/src/pyramid_debugtoolbar_api_performance.egg-info/not-zip-safe +0 -0
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/src/pyramid_debugtoolbar_api_performance.egg-info/requires.txt +0 -0
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/src/pyramid_debugtoolbar_api_performance.egg-info/top_level.txt +0 -0
- {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/tests/__init__.py +0 -0
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
v1.1.0
|
|
2
|
+
* Add spreadsheet=1 to both performance CSV routes to prefix formula-like text.
|
|
3
|
+
|
|
1
4
|
v1.0.0
|
|
2
5
|
* Explicitly package the CSV template as data to avoid setuptools package discovery warnings.
|
|
3
6
|
* Keep runtime dependencies out of version discovery on legacy setuptools.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Helpers for exporting text fields to spreadsheet CSV files."""
|
|
2
|
+
|
|
3
|
+
from six import text_type
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def sanitize_spreadsheet_cell(value):
|
|
7
|
+
# type: (text_type) -> text_type
|
|
8
|
+
"""Prefix formula-like text with a spreadsheet text marker."""
|
|
9
|
+
stripped = value.lstrip()
|
|
10
|
+
if value.startswith((u"\t", u"\r", u"\n")) or stripped.startswith(
|
|
11
|
+
(u"=", u"+", u"-", u"@", u"\uff1d", u"\uff0b", u"\uff0d", u"\uff20")
|
|
12
|
+
):
|
|
13
|
+
return u"'" + value
|
|
14
|
+
return value
|
|
@@ -12,9 +12,10 @@ from pyramid.view import view_config
|
|
|
12
12
|
from pyramid.exceptions import NotFound
|
|
13
13
|
|
|
14
14
|
# Python 2's csv module requires a byte buffer.
|
|
15
|
-
from six import StringIO
|
|
15
|
+
from six import PY2, StringIO, binary_type
|
|
16
16
|
|
|
17
17
|
# local
|
|
18
|
+
from .spreadsheet_utils import sanitize_spreadsheet_cell
|
|
18
19
|
from .utils import get_performance_panel
|
|
19
20
|
|
|
20
21
|
|
|
@@ -40,11 +41,21 @@ def _standardized_setup(request):
|
|
|
40
41
|
return request_id, panel
|
|
41
42
|
|
|
42
43
|
|
|
43
|
-
def
|
|
44
|
-
# type: (
|
|
44
|
+
def _spreadsheet_text(value):
|
|
45
|
+
# type: (str) -> str
|
|
46
|
+
"""Sanitize a CSV text field using the export's encoding."""
|
|
47
|
+
if isinstance(value, binary_type):
|
|
48
|
+
value = value.decode(ENCODING)
|
|
49
|
+
value = sanitize_spreadsheet_cell(value)
|
|
50
|
+
return value.encode(ENCODING) if PY2 else value
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _csv_response(csvfile, report, request_id, spreadsheet):
|
|
54
|
+
# type: (StringIO, str, str, bool) -> Response
|
|
45
55
|
"""Return a CSV attachment with a header-safe filename."""
|
|
46
56
|
safe_id = re.sub(r"[^a-zA-Z0-9_-]", "_", request_id)
|
|
47
|
-
|
|
57
|
+
suffix = "-spreadsheet" if spreadsheet else ""
|
|
58
|
+
filename = "performance-%s-%s%s.csv" % (report, safe_id, suffix)
|
|
48
59
|
response = Response(
|
|
49
60
|
content_type="text/csv", body=csvfile.getvalue(), charset=ENCODING
|
|
50
61
|
)
|
|
@@ -59,19 +70,14 @@ def csv_timing(request):
|
|
|
59
70
|
"""Export timing rows for a recorded request."""
|
|
60
71
|
(request_id, panel) = _standardized_setup(request)
|
|
61
72
|
|
|
73
|
+
spreadsheet = request.GET.get("spreadsheet") == "1"
|
|
62
74
|
csvfile = StringIO()
|
|
63
75
|
csvwriter = csv.writer(csvfile)
|
|
64
|
-
for
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# ('Total CPU time', '184.373 msec'),
|
|
70
|
-
# ('Elapsed time', '205.099 msec'),
|
|
71
|
-
# ('Context switches', '19 voluntary, 1233 involuntary')
|
|
72
|
-
# )
|
|
73
|
-
csvwriter.writerow(row)
|
|
74
|
-
return _csv_response(csvfile, "timing", request_id)
|
|
76
|
+
for label, timing in panel.data["timing_rows"]:
|
|
77
|
+
if spreadsheet:
|
|
78
|
+
label = _spreadsheet_text(label)
|
|
79
|
+
csvwriter.writerow((label, timing))
|
|
80
|
+
return _csv_response(csvfile, "timing", request_id, spreadsheet)
|
|
75
81
|
|
|
76
82
|
|
|
77
83
|
@view_config(route_name="debugtoolbar.api_performance_csv.function_calls")
|
|
@@ -80,11 +86,15 @@ def csv_function_calls(request):
|
|
|
80
86
|
"""Export profiler rows for a recorded request."""
|
|
81
87
|
(request_id, panel) = _standardized_setup(request)
|
|
82
88
|
|
|
89
|
+
spreadsheet = request.GET.get("spreadsheet") == "1"
|
|
83
90
|
csvfile = StringIO()
|
|
84
91
|
csvwriter = csv.writer(csvfile)
|
|
85
92
|
if panel.data["stats"]:
|
|
86
93
|
csvwriter.writerow(("Calls", "Total", "Percall", "Cumu", "CumuPer", "Func"))
|
|
87
94
|
for row in panel.data["function_calls"]:
|
|
95
|
+
filename = row["filename_long"]
|
|
96
|
+
if spreadsheet:
|
|
97
|
+
filename = _spreadsheet_text(filename)
|
|
88
98
|
csvwriter.writerow(
|
|
89
99
|
(
|
|
90
100
|
row["ncalls"],
|
|
@@ -92,7 +102,7 @@ def csv_function_calls(request):
|
|
|
92
102
|
row["percall"],
|
|
93
103
|
row["cumtime"],
|
|
94
104
|
row["percall_cum"],
|
|
95
|
-
|
|
105
|
+
filename,
|
|
96
106
|
)
|
|
97
107
|
)
|
|
98
|
-
return _csv_response(csvfile, "function_calls", request_id)
|
|
108
|
+
return _csv_response(csvfile, "function_calls", request_id, spreadsheet)
|
|
@@ -8,6 +8,7 @@ setup.cfg
|
|
|
8
8
|
src/pyramid_debugtoolbar_api_performance/__init__.py
|
|
9
9
|
src/pyramid_debugtoolbar_api_performance/_version.py
|
|
10
10
|
src/pyramid_debugtoolbar_api_performance/panels.py
|
|
11
|
+
src/pyramid_debugtoolbar_api_performance/spreadsheet_utils.py
|
|
11
12
|
src/pyramid_debugtoolbar_api_performance/utils.py
|
|
12
13
|
src/pyramid_debugtoolbar_api_performance/views.py
|
|
13
14
|
src/pyramid_debugtoolbar_api_performance.egg-info/PKG-INFO
|
|
@@ -172,3 +172,49 @@ class TestDebugtoolbarPanel(unittest.TestCase):
|
|
|
172
172
|
)
|
|
173
173
|
request.remote_addr = "127.0.0.1"
|
|
174
174
|
self.assertEqual(request.get_response(app).status_code, 404)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_spreadsheet_exports(self):
|
|
178
|
+
# type: () -> None
|
|
179
|
+
"""Protect both CSV text fields while preserving default output."""
|
|
180
|
+
from pyramid_debugtoolbar.panels.performance import PerformanceDebugPanel
|
|
181
|
+
|
|
182
|
+
app = self.config.make_wsgi_app()
|
|
183
|
+
initial = Request.blank("/")
|
|
184
|
+
initial.remote_addr = "127.0.0.1"
|
|
185
|
+
initial.get_response(app)
|
|
186
|
+
request_id, toolbar = self.config.registry.pdtb_history.last(1)[0]
|
|
187
|
+
panel = next(p for p in toolbar.panels if isinstance(p, PerformanceDebugPanel))
|
|
188
|
+
panel.data.update(
|
|
189
|
+
timing_rows=[(str("=label"), str("-2 msec"))],
|
|
190
|
+
stats=True,
|
|
191
|
+
function_calls=[
|
|
192
|
+
dict(
|
|
193
|
+
ncalls="1",
|
|
194
|
+
tottime="-2",
|
|
195
|
+
percall="3",
|
|
196
|
+
cumtime="4",
|
|
197
|
+
percall_cum="5",
|
|
198
|
+
filename_long=" @module",
|
|
199
|
+
)
|
|
200
|
+
],
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
for report, raw, safe in (
|
|
204
|
+
("timing", "=label,-2 msec", "'=label,-2 msec"),
|
|
205
|
+
("function_calls", "1,-2,3,4,5, @module", "1,-2,3,4,5,' @module"),
|
|
206
|
+
):
|
|
207
|
+
path = "/_debug_toolbar/api-performance/%s-%s.csv" % (report, request_id)
|
|
208
|
+
for query, row in (("", raw), ("?spreadsheet=1", safe)):
|
|
209
|
+
request = Request.blank(path + query)
|
|
210
|
+
request.remote_addr = "127.0.0.1"
|
|
211
|
+
response = request.get_response(app)
|
|
212
|
+
self.assertEqual(response.status_code, 200)
|
|
213
|
+
self.assertIn(row + "\r\n", response.text)
|
|
214
|
+
self.assertTrue(response.cache_control.no_store)
|
|
215
|
+
if query:
|
|
216
|
+
self.assertTrue(
|
|
217
|
+
response.headers["Content-Disposition"].endswith(
|
|
218
|
+
'-spreadsheet.csv"'
|
|
219
|
+
)
|
|
220
|
+
)
|
{pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
{pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/README.rst
RENAMED
|
File without changes
|
{pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/TODO.txt
RENAMED
|
File without changes
|
|
File without changes
|
{pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/setup.cfg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|