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.
Files changed (23) hide show
  1. {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/CHANGES.txt +3 -0
  2. {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
  3. {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/src/pyramid_debugtoolbar_api_performance/_version.py +1 -1
  4. pyramid_debugtoolbar_api_performance-1.1.0/src/pyramid_debugtoolbar_api_performance/spreadsheet_utils.py +14 -0
  5. {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/src/pyramid_debugtoolbar_api_performance/views.py +27 -17
  6. {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
  7. {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
  8. {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/tests/test_config.py +46 -0
  9. {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/LICENSE +0 -0
  10. {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/MANIFEST.in +0 -0
  11. {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/README.rst +0 -0
  12. {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/TODO.txt +0 -0
  13. {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/pyproject.toml +0 -0
  14. {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/setup.cfg +0 -0
  15. {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/src/pyramid_debugtoolbar_api_performance/__init__.py +0 -0
  16. {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/src/pyramid_debugtoolbar_api_performance/panels.py +0 -0
  17. {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
  18. {pyramid_debugtoolbar_api_performance-1.0.0 → pyramid_debugtoolbar_api_performance-1.1.0}/src/pyramid_debugtoolbar_api_performance/utils.py +0 -0
  19. {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
  20. {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
  21. {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
  22. {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
  23. {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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyramid_debugtoolbar_api_performance
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: performance csv exporting for pyramid_debugtoolbar
5
5
  Home-page: https://github.com/jvanasco/pyramid_debugtoolbar_api_performance
6
6
  Author: Jonathan Vanasco
@@ -1,3 +1,3 @@
1
1
  """Package version shared by runtime and build metadata."""
2
2
 
3
- __VERSION__ = "1.0.0"
3
+ __VERSION__ = "1.1.0"
@@ -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 _csv_response(csvfile, report, request_id):
44
- # type: (StringIO, str, str) -> Response
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
- filename = "performance-%s-%s.csv" % (report, safe_id)
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 row in panel.data["timing_rows"]:
65
- # row is `label, timing`
66
- # example:
67
- # 'timing_rows': (('User CPU time', '119.678 msec'),
68
- # ('System CPU time', '64.695 msec'),
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
- row["filename_long"],
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyramid_debugtoolbar_api_performance
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: performance csv exporting for pyramid_debugtoolbar
5
5
  Home-page: https://github.com/jvanasco/pyramid_debugtoolbar_api_performance
6
6
  Author: Jonathan Vanasco
@@ -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
+ )