limits 4.7.3__tar.gz → 4.8.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 (101) hide show
  1. {limits-4.7.3 → limits-4.8.0}/HISTORY.rst +11 -0
  2. {limits-4.7.3 → limits-4.8.0}/PKG-INFO +1 -1
  3. {limits-4.7.3 → limits-4.8.0}/doc/source/ext/_static/benchmark-chart.css +5 -8
  4. limits-4.8.0/doc/source/ext/_static/js/benchmark-chart.js +453 -0
  5. limits-4.8.0/doc/source/ext/_static/js/benchmark-details.js +117 -0
  6. limits-4.8.0/doc/source/ext/_static/js/benchmark-loader.js +43 -0
  7. limits-4.8.0/doc/source/ext/_templates/git_info.js +16 -0
  8. {limits-4.7.3 → limits-4.8.0}/limits/_version.py +3 -3
  9. {limits-4.7.3 → limits-4.8.0}/limits/aio/storage/redis/__init__.py +22 -6
  10. {limits-4.7.3 → limits-4.8.0}/limits/aio/storage/redis/bridge.py +3 -2
  11. {limits-4.7.3 → limits-4.8.0}/limits/storage/redis.py +5 -2
  12. {limits-4.7.3 → limits-4.8.0}/limits/storage/redis_cluster.py +5 -2
  13. {limits-4.7.3 → limits-4.8.0}/limits/storage/redis_sentinel.py +3 -0
  14. {limits-4.7.3 → limits-4.8.0}/limits.egg-info/PKG-INFO +1 -1
  15. limits-4.7.3/doc/source/ext/_static/js/benchmark-chart.js +0 -343
  16. limits-4.7.3/doc/source/ext/_static/js/benchmark-details.js +0 -103
  17. limits-4.7.3/doc/source/ext/_static/js/benchmark-loader.js +0 -31
  18. limits-4.7.3/doc/source/ext/_templates/git_info.js +0 -2
  19. {limits-4.7.3 → limits-4.8.0}/CLASSIFIERS +0 -0
  20. {limits-4.7.3 → limits-4.8.0}/CONTRIBUTIONS.rst +0 -0
  21. {limits-4.7.3 → limits-4.8.0}/LICENSE.txt +0 -0
  22. {limits-4.7.3 → limits-4.8.0}/MANIFEST.in +0 -0
  23. {limits-4.7.3 → limits-4.8.0}/README.rst +0 -0
  24. {limits-4.7.3 → limits-4.8.0}/doc/Makefile +0 -0
  25. {limits-4.7.3 → limits-4.8.0}/doc/source/_static/custom.css +0 -0
  26. {limits-4.7.3 → limits-4.8.0}/doc/source/api.rst +0 -0
  27. {limits-4.7.3 → limits-4.8.0}/doc/source/async.rst +0 -0
  28. {limits-4.7.3 → limits-4.8.0}/doc/source/changelog.rst +0 -0
  29. {limits-4.7.3 → limits-4.8.0}/doc/source/conf.py +0 -0
  30. {limits-4.7.3 → limits-4.8.0}/doc/source/custom-storage.rst +0 -0
  31. {limits-4.7.3 → limits-4.8.0}/doc/source/ext/bench_chart.py +0 -0
  32. {limits-4.7.3 → limits-4.8.0}/doc/source/index.rst +0 -0
  33. {limits-4.7.3 → limits-4.8.0}/doc/source/installation.rst +0 -0
  34. {limits-4.7.3 → limits-4.8.0}/doc/source/performance.rst +0 -0
  35. {limits-4.7.3 → limits-4.8.0}/doc/source/quickstart.rst +0 -0
  36. {limits-4.7.3 → limits-4.8.0}/doc/source/storage.rst +0 -0
  37. {limits-4.7.3 → limits-4.8.0}/doc/source/strategies.rst +0 -0
  38. {limits-4.7.3 → limits-4.8.0}/doc/source/theme_config.py +0 -0
  39. {limits-4.7.3 → limits-4.8.0}/limits/__init__.py +0 -0
  40. {limits-4.7.3 → limits-4.8.0}/limits/aio/__init__.py +0 -0
  41. {limits-4.7.3 → limits-4.8.0}/limits/aio/storage/__init__.py +0 -0
  42. {limits-4.7.3 → limits-4.8.0}/limits/aio/storage/base.py +0 -0
  43. {limits-4.7.3 → limits-4.8.0}/limits/aio/storage/etcd.py +0 -0
  44. {limits-4.7.3 → limits-4.8.0}/limits/aio/storage/memcached.py +0 -0
  45. {limits-4.7.3 → limits-4.8.0}/limits/aio/storage/memory.py +0 -0
  46. {limits-4.7.3 → limits-4.8.0}/limits/aio/storage/mongodb.py +0 -0
  47. {limits-4.7.3 → limits-4.8.0}/limits/aio/storage/redis/coredis.py +0 -0
  48. {limits-4.7.3 → limits-4.8.0}/limits/aio/storage/redis/redispy.py +0 -0
  49. {limits-4.7.3 → limits-4.8.0}/limits/aio/storage/redis/valkey.py +0 -0
  50. {limits-4.7.3 → limits-4.8.0}/limits/aio/strategies.py +0 -0
  51. {limits-4.7.3 → limits-4.8.0}/limits/errors.py +0 -0
  52. {limits-4.7.3 → limits-4.8.0}/limits/limits.py +0 -0
  53. {limits-4.7.3 → limits-4.8.0}/limits/py.typed +0 -0
  54. {limits-4.7.3 → limits-4.8.0}/limits/resources/redis/lua_scripts/acquire_moving_window.lua +0 -0
  55. {limits-4.7.3 → limits-4.8.0}/limits/resources/redis/lua_scripts/acquire_sliding_window.lua +0 -0
  56. {limits-4.7.3 → limits-4.8.0}/limits/resources/redis/lua_scripts/clear_keys.lua +0 -0
  57. {limits-4.7.3 → limits-4.8.0}/limits/resources/redis/lua_scripts/incr_expire.lua +0 -0
  58. {limits-4.7.3 → limits-4.8.0}/limits/resources/redis/lua_scripts/moving_window.lua +0 -0
  59. {limits-4.7.3 → limits-4.8.0}/limits/resources/redis/lua_scripts/sliding_window.lua +0 -0
  60. {limits-4.7.3 → limits-4.8.0}/limits/storage/__init__.py +0 -0
  61. {limits-4.7.3 → limits-4.8.0}/limits/storage/base.py +0 -0
  62. {limits-4.7.3 → limits-4.8.0}/limits/storage/etcd.py +0 -0
  63. {limits-4.7.3 → limits-4.8.0}/limits/storage/memcached.py +0 -0
  64. {limits-4.7.3 → limits-4.8.0}/limits/storage/memory.py +0 -0
  65. {limits-4.7.3 → limits-4.8.0}/limits/storage/mongodb.py +0 -0
  66. {limits-4.7.3 → limits-4.8.0}/limits/storage/registry.py +0 -0
  67. {limits-4.7.3 → limits-4.8.0}/limits/strategies.py +0 -0
  68. {limits-4.7.3 → limits-4.8.0}/limits/typing.py +0 -0
  69. {limits-4.7.3 → limits-4.8.0}/limits/util.py +0 -0
  70. {limits-4.7.3 → limits-4.8.0}/limits/version.py +0 -0
  71. {limits-4.7.3 → limits-4.8.0}/limits.egg-info/SOURCES.txt +0 -0
  72. {limits-4.7.3 → limits-4.8.0}/limits.egg-info/dependency_links.txt +0 -0
  73. {limits-4.7.3 → limits-4.8.0}/limits.egg-info/not-zip-safe +0 -0
  74. {limits-4.7.3 → limits-4.8.0}/limits.egg-info/requires.txt +0 -0
  75. {limits-4.7.3 → limits-4.8.0}/limits.egg-info/top_level.txt +0 -0
  76. {limits-4.7.3 → limits-4.8.0}/pyproject.toml +0 -0
  77. {limits-4.7.3 → limits-4.8.0}/requirements/ci.txt +0 -0
  78. {limits-4.7.3 → limits-4.8.0}/requirements/dev.txt +0 -0
  79. {limits-4.7.3 → limits-4.8.0}/requirements/docs.txt +0 -0
  80. {limits-4.7.3 → limits-4.8.0}/requirements/main.txt +0 -0
  81. {limits-4.7.3 → limits-4.8.0}/requirements/storage/async-etcd.txt +0 -0
  82. {limits-4.7.3 → limits-4.8.0}/requirements/storage/async-memcached.txt +0 -0
  83. {limits-4.7.3 → limits-4.8.0}/requirements/storage/async-mongodb.txt +0 -0
  84. {limits-4.7.3 → limits-4.8.0}/requirements/storage/async-redis.txt +0 -0
  85. {limits-4.7.3 → limits-4.8.0}/requirements/storage/async-valkey.txt +0 -0
  86. {limits-4.7.3 → limits-4.8.0}/requirements/storage/etcd.txt +0 -0
  87. {limits-4.7.3 → limits-4.8.0}/requirements/storage/memcached.txt +0 -0
  88. {limits-4.7.3 → limits-4.8.0}/requirements/storage/mongodb.txt +0 -0
  89. {limits-4.7.3 → limits-4.8.0}/requirements/storage/redis.txt +0 -0
  90. {limits-4.7.3 → limits-4.8.0}/requirements/storage/rediscluster.txt +0 -0
  91. {limits-4.7.3 → limits-4.8.0}/requirements/storage/valkey.txt +0 -0
  92. {limits-4.7.3 → limits-4.8.0}/requirements/test.txt +0 -0
  93. {limits-4.7.3 → limits-4.8.0}/setup.cfg +0 -0
  94. {limits-4.7.3 → limits-4.8.0}/setup.py +0 -0
  95. {limits-4.7.3 → limits-4.8.0}/tests/test_limit_granularities.py +0 -0
  96. {limits-4.7.3 → limits-4.8.0}/tests/test_limits.py +0 -0
  97. {limits-4.7.3 → limits-4.8.0}/tests/test_ratelimit_parser.py +0 -0
  98. {limits-4.7.3 → limits-4.8.0}/tests/test_storage.py +0 -0
  99. {limits-4.7.3 → limits-4.8.0}/tests/test_strategy.py +0 -0
  100. {limits-4.7.3 → limits-4.8.0}/tests/test_utils.py +0 -0
  101. {limits-4.7.3 → limits-4.8.0}/versioneer.py +0 -0
@@ -3,6 +3,16 @@
3
3
  Changelog
4
4
  =========
5
5
 
6
+ v4.8.0
7
+ ------
8
+ Release Date: 2025-04-23
9
+
10
+ * Features
11
+
12
+ * Expose key_prefix constructor argument for all redis storage
13
+ implementations to simplify customizing the prefix used for all keys
14
+ created in redis (Backported from ``5.1.0``).
15
+
6
16
  v4.7.3
7
17
  ------
8
18
  Release Date: 2025-04-12
@@ -886,5 +896,6 @@ Release Date: 2015-01-08
886
896
 
887
897
 
888
898
 
899
+
889
900
 
890
901
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: limits
3
- Version: 4.7.3
3
+ Version: 4.8.0
4
4
  Summary: Rate limiting utilities
5
5
  Home-page: https://limits.readthedocs.org
6
6
  Author: Ali-Akber Saifee
@@ -1,11 +1,3 @@
1
- .plot-container {
2
- filter: invert(100%);
3
- }
4
-
5
- body[data-theme="light"] .plot-container {
6
- filter: invert(0);
7
- }
8
-
9
1
  .benchmark-chart {
10
2
  width: 100%;
11
3
  }
@@ -95,3 +87,8 @@ body[data-theme="light"] .plot-container {
95
87
  }
96
88
  .benchmark-filter input[type="checkbox"] {
97
89
  }
90
+
91
+ .compare-dropdowns {
92
+ display: flex;
93
+ justify-content: flex-end;
94
+ }
@@ -0,0 +1,453 @@
1
+ import { render, html } from "https://unpkg.com/uhtml@3.2.1?module";
2
+ import { BenchmarkLoader } from "./benchmark-loader.js";
3
+ const KNOWN_PARAMS = [
4
+ "storage_type",
5
+ "limit",
6
+ "strategy",
7
+ "async",
8
+ "percentage_full",
9
+ ];
10
+ let dispatched = new Set();
11
+ window.Benchmarks = {};
12
+ let currentLoader = new BenchmarkLoader();
13
+ let compareLoaders = {};
14
+
15
+ class BenchmarkChartUtils {
16
+ static getBenchmarkData(result, query) {
17
+ let benchmarks = result.benchmarks;
18
+ return benchmarks.filter(function (benchmark) {
19
+ let okay = true;
20
+ if (query) {
21
+ Object.entries(query).forEach((entry) => {
22
+ let key = entry[0];
23
+ let value = entry[1];
24
+ if (
25
+ key != "group" &&
26
+ !(value === "") && // i.e. any.
27
+ benchmark.params[key] != null &&
28
+ benchmark.params[key] != value
29
+ ) {
30
+ okay = false;
31
+ } else if (key == "group" && value != benchmark.group) {
32
+ okay = false;
33
+ }
34
+ });
35
+ }
36
+ return okay;
37
+ });
38
+ }
39
+
40
+ static formatParam(key, str) {
41
+ if (key === "limit") {
42
+ var m = str.match(/(\d+(?:\.\d+)?)\s+per\s+1\s+(\w+)/i);
43
+ if (!m) return str;
44
+ var n = parseFloat(m[1]),
45
+ u = m[2].toLowerCase(),
46
+ num =
47
+ n >= 1000
48
+ ? (n / 1000) % 1 === 0
49
+ ? n / 1000 + "K"
50
+ : (n / 1000).toFixed(1) + "K"
51
+ : n.toString(),
52
+ umap = {
53
+ second: "s",
54
+ seconds: "s",
55
+ minute: "min",
56
+ minutes: "mins",
57
+ hour: "hr",
58
+ hours: "hr",
59
+ day: "day",
60
+ days: "day",
61
+ };
62
+ return num + "/" + (umap[u] || u);
63
+ } else if (key === "percentage_full") {
64
+ return `${str}% Seeded`;
65
+ }
66
+ return str;
67
+ }
68
+
69
+ static nameTransform(benchmark, stripParams, query) {
70
+ let name = benchmark.name;
71
+ let params = benchmark.params;
72
+ name = name
73
+ .replace(/\[.*?\]/, "")
74
+ .replace("_async", "")
75
+ .replaceAll("_", "-");
76
+ name = name.replace(benchmark.group, "");
77
+ let queryParam = Object.entries(query).map((entry) => entry[0]);
78
+ let additional = BenchmarkChartUtils.getRemainingGroups(benchmark, query);
79
+ Object.entries(additional).forEach((param) => {
80
+ let value = BenchmarkChartUtils.formatParam(param[0], param[1]);
81
+ if (name) {
82
+ name += ` - ${value}`;
83
+ } else {
84
+ name = `${value}`;
85
+ }
86
+ });
87
+ return name;
88
+ }
89
+
90
+ static getRemainingGroups(benchmark, query) {
91
+ let queryParam = Object.entries(query).map((entry) => entry[0]);
92
+ let additional = {};
93
+ Object.entries(benchmark.params).forEach((param) => {
94
+ const key = param[0];
95
+ const value = param[1];
96
+ if (
97
+ (!queryParam.includes(key) || query?.[key] === "") &&
98
+ KNOWN_PARAMS.includes(key)
99
+ ) {
100
+ additional[key] = value;
101
+ }
102
+ });
103
+ return additional;
104
+ }
105
+
106
+ static getColorForStorage(storageType) {
107
+ const storageColorMap = {
108
+ memory: window
109
+ .getComputedStyle(document.body)
110
+ .getPropertyValue("--color-purple"),
111
+ mongodb: window
112
+ .getComputedStyle(document.body)
113
+ .getPropertyValue("--color-yellow"),
114
+ memcached: window
115
+ .getComputedStyle(document.body)
116
+ .getPropertyValue("--color-blue"),
117
+ redis: window
118
+ .getComputedStyle(document.body)
119
+ .getPropertyValue("--color-red"),
120
+ };
121
+
122
+ // Fallback color if an unknown storageType appears
123
+ return storageColorMap[storageType] || "#7f7f7f"; // gray
124
+ }
125
+
126
+ static sortBenchmarksByParams(benchmarks, sortKeys) {
127
+ return benchmarks.sort(function (a, b) {
128
+ for (const key of sortKeys) {
129
+ let valA = (a.params?.[key] || "").toLowerCase();
130
+ let valB = (b.params?.[key] || "").toLowerCase();
131
+ if (key === "limit") {
132
+ valA = parseInt(valA.split(" ")[0], 10);
133
+ valB = parseInt(valB.split(" ")[0], 10);
134
+ }
135
+ if (valA < valB) return -1;
136
+ if (valA > valB) return 1;
137
+ }
138
+ return a.name.localeCompare(b.name);
139
+ });
140
+ }
141
+
142
+ static fetchComparisonData(source, comparisonSource) {
143
+ if (!compareLoaders[comparisonSource]) {
144
+ compareLoaders[comparisonSource] = new BenchmarkLoader([
145
+ `https://${comparisonSource.replaceAll(".", "-")}--py-limits.netlify.app/`,
146
+ ]);
147
+ }
148
+ return compareLoaders[comparisonSource].fetchBenchmarkData(source);
149
+ }
150
+ }
151
+
152
+ class BenchmarkChart {
153
+ constructor(node) {
154
+ this.chart = node;
155
+ this.source = this.chart.dataset.source;
156
+ this.filters = JSON.parse(this.chart.dataset.filters);
157
+ this.query = JSON.parse(this.chart.dataset.query);
158
+ this.paramMapping = JSON.parse(this.chart.dataset.paramMapping);
159
+ this.chartId = this.chart.dataset.chartId;
160
+ this.currentCompare = null;
161
+ this.sortBy = JSON.parse(
162
+ this.chart.dataset.sortBy || '["storage_type", "limit"]',
163
+ );
164
+ let self = this;
165
+ render(
166
+ this.chart,
167
+ html`
168
+ <div class="benchmark-chart-loading">
169
+ <span>Loading</span>
170
+ </div>
171
+ `,
172
+ );
173
+ if (!dispatched.has(this.source)) {
174
+ currentLoader
175
+ .fetchBenchmarkData(this.source)
176
+ .then((result) => {
177
+ let event = new Event(`${self.source}-loaded`);
178
+ window.dispatchEvent(event);
179
+ })
180
+ .catch((error) => {
181
+ let event = new Event(`${self.source}-failed`);
182
+ window.dispatchEvent(event);
183
+ });
184
+ }
185
+ dispatched.add(this.source);
186
+ window.addEventListener(`${this.source}-failed`, function () {
187
+ self.handleError();
188
+ });
189
+ window.addEventListener(`${this.source}-loaded`, function () {
190
+ self.render();
191
+ });
192
+ }
193
+ handleError() {
194
+ this.chart.querySelector(".benchmark-chart-loading")?.remove();
195
+ render(
196
+ this.chart,
197
+ html`
198
+ <div class="benchmark-chart-error">Benchmark data not available.</div>
199
+ `,
200
+ );
201
+ }
202
+ renderCompare() {
203
+ const compareTargets = ["master", "stable", "4.x"]
204
+ .concat(window.LATEST_RELEASES || [])
205
+ .filter((element) => element != window.GITBRANCH);
206
+ let self = this;
207
+ const compareDropdown = html`
208
+ <div class="compare" title="Compare against another release or branch">
209
+ <label for="compare-select">
210
+ Compare with
211
+ <select
212
+ onchange=${(e) => {
213
+ const value = e.target.value;
214
+ self.currentCompare = value;
215
+ if (value !== "") {
216
+ BenchmarkChartUtils.fetchComparisonData(
217
+ self.source,
218
+ value,
219
+ ).then(function (comparisonData) {
220
+ self.currentComparisonData = comparisonData;
221
+ self.renderChartWithFilters();
222
+ });
223
+ } else {
224
+ self.currentComparisonData = null;
225
+ self.renderChartWithFilters();
226
+ }
227
+ }}
228
+ >
229
+ <option value=""></option>
230
+ ${compareTargets.map(
231
+ (val) => html`
232
+ <option
233
+ value=${val}
234
+ ?selected=${this.currentCompare == val.toString()}
235
+ >
236
+ ${val}
237
+ </option>
238
+ `,
239
+ )}
240
+ </select>
241
+ </label>
242
+ </div>
243
+ `;
244
+ render(
245
+ this.compareTarget,
246
+ html`<div class="compare-dropdowns">${compareDropdown}</div>`,
247
+ );
248
+ }
249
+ renderDropdowns() {
250
+ const dropdowns = Object.entries(this.filters).map(([key]) => {
251
+ const fullName = `${this.chartId}-${key}`;
252
+ const uniqueValues = [
253
+ ...new Set(this.allBenchmarks.map((b) => b.params?.[key])),
254
+ ].sort();
255
+ const isBoolean =
256
+ uniqueValues.length === 2 &&
257
+ uniqueValues.includes(true) &&
258
+ uniqueValues.includes(false);
259
+ let self = this;
260
+ if (isBoolean) {
261
+ return html`
262
+ <div class="benchmark-filter" title=${this.paramMapping[key]?.info}>
263
+ <input
264
+ type="checkbox"
265
+ id=${fullName}
266
+ ?checked=${self.currentFilters[key] === true}
267
+ onchange=${(e) => {
268
+ self.currentFilters[key] = e.target.checked;
269
+ self.renderChartWithFilters();
270
+ }}
271
+ />
272
+ <label for=${fullName}>
273
+ ${self.paramMapping[key]?.display || key}
274
+ </label>
275
+ </div>
276
+ `;
277
+ } else {
278
+ return html`
279
+ <div class="benchmark-filter" title=${self.paramMapping[key]?.info}>
280
+ <label for=${fullName}>
281
+ ${self.paramMapping[key]?.display || key}
282
+ <select
283
+ id=${fullName}
284
+ onchange=${(e) => {
285
+ const value = e.target.value;
286
+ if (value) {
287
+ self.currentFilters[key] =
288
+ value === "false"
289
+ ? false
290
+ : value === "true"
291
+ ? true
292
+ : value;
293
+ } else {
294
+ self.currentFilters[key] = "";
295
+ }
296
+ self.renderChartWithFilters();
297
+ }}
298
+ >
299
+ <option value="" ?selected=${self.currentFilters[key] == ""}>
300
+ All
301
+ </option>
302
+ ${uniqueValues.map(
303
+ (val) => html`
304
+ <option
305
+ value=${val}
306
+ ?selected=${self.currentFilters[key] == val.toString()}
307
+ >
308
+ ${val}
309
+ </option>
310
+ `,
311
+ )}
312
+ </select>
313
+ </label>
314
+ </div>
315
+ `;
316
+ }
317
+ });
318
+ render(
319
+ this.dropdownTarget,
320
+ html`<div class="benchmark-filter-dropdowns">${dropdowns}</div>`,
321
+ );
322
+ }
323
+ renderChartWithFilters() {
324
+ const queryFilter = { ...this.query, ...this.currentFilters };
325
+ let data = BenchmarkChartUtils.sortBenchmarksByParams(
326
+ BenchmarkChartUtils.getBenchmarkData(this.results, queryFilter),
327
+ this.sortBy,
328
+ );
329
+ let comparisonData = [];
330
+ let comparing = false;
331
+ if (this.currentComparisonData?.benchmarks) {
332
+ comparisonData = BenchmarkChartUtils.sortBenchmarksByParams(
333
+ BenchmarkChartUtils.getBenchmarkData(
334
+ this.currentComparisonData,
335
+ queryFilter,
336
+ ),
337
+ this.sortBy,
338
+ );
339
+ comparisonData.forEach((benchmark) => {
340
+ benchmark.forComparison = true;
341
+ });
342
+ comparing = true;
343
+ data = data.concat(comparisonData);
344
+ }
345
+ let legendGroupKey =
346
+ queryFilter?.storage_type == ""
347
+ ? "storage_type"
348
+ : Object.entries(queryFilter).find((entry) => entry[1] === "")?.[0];
349
+ function legendKeyFunc(benchmark, key) {
350
+ return key === "group" ? benchmark.group : benchmark.params[key];
351
+ }
352
+
353
+ Plotly.newPlot(
354
+ this.chartTarget,
355
+ data.map((benchmark) => ({
356
+ type: "box",
357
+ name: BenchmarkChartUtils.nameTransform(benchmark, true, queryFilter),
358
+ opacity: benchmark.forComparison ? 0.75 : comparing ? 0.5 : 1,
359
+ y: benchmark.stats.data || [
360
+ benchmark.stats.min * 1e3,
361
+ benchmark.stats.q1 * 1e3,
362
+ benchmark.stats.median * 1e3,
363
+ benchmark.stats.q3 * 1e3,
364
+ benchmark.stats.max * 1e3,
365
+ ],
366
+ boxmean: true,
367
+ boxpoints: false,
368
+ line: { width: 1 },
369
+ marker: {
370
+ color: benchmark.forComparison
371
+ ? "#39FF14"
372
+ : BenchmarkChartUtils.getColorForStorage(
373
+ benchmark.params.storage_type,
374
+ ),
375
+ },
376
+ showlegend: !benchmark.forComparison,
377
+ legendgroup: legendKeyFunc(benchmark, legendGroupKey),
378
+ legendgrouptitle: {
379
+ text: BenchmarkChartUtils.formatParam(
380
+ legendGroupKey,
381
+ legendKeyFunc(benchmark, legendGroupKey),
382
+ ),
383
+ },
384
+ })),
385
+ {
386
+ yaxis: {
387
+ title: { text: "Time (ms)" },
388
+ exponentformat: "none",
389
+ ticksuffix: " ms",
390
+ tickformat: ",.2f",
391
+ },
392
+ title: comparing
393
+ ? {
394
+ text: `Comparing against: ${this.currentCompare}`,
395
+ font: { color: "39FF14" },
396
+ }
397
+ : {},
398
+ },
399
+ {
400
+ responsive: true,
401
+ displaylogo: false,
402
+ },
403
+ );
404
+ }
405
+ render() {
406
+ this.chart.innerHTML = "";
407
+ this.chart.querySelector(".benchmark-chart-loading")?.remove();
408
+ this.results = currentLoader.sources[this.source];
409
+ this.allBenchmarks = BenchmarkChartUtils.getBenchmarkData(
410
+ this.results,
411
+ this.query,
412
+ );
413
+ this.currentFilters = Object.fromEntries(
414
+ Object.entries(this.filters).map(([key, value]) => {
415
+ return typeof value.default === "boolean"
416
+ ? [key, value.default]
417
+ : [key, value.default != null ? value.default.toString() : ""];
418
+ }),
419
+ );
420
+ this.currentCompare = "";
421
+ this.currentComparisonData = null;
422
+ this.dropdownTarget = document.createElement("div");
423
+ this.compareTarget = document.createElement("div");
424
+ this.dropdownTarget.classList.add("benchmark-filters");
425
+ this.chartTarget = document.createElement("div");
426
+ this.chart.append(this.chartTarget);
427
+ this.chart.append(this.dropdownTarget);
428
+ this.chart.append(this.compareTarget);
429
+ this.renderDropdowns();
430
+ this.renderCompare();
431
+ this.renderChartWithFilters();
432
+ let initial = true;
433
+ this.chartTarget.on("plotly_afterplot", function () {
434
+ const { hash } = window.location;
435
+ if (hash && initial) {
436
+ initial = false;
437
+ const target = document.querySelector(hash);
438
+ if (target) {
439
+ setTimeout(function () {
440
+ target.scrollIntoView({ behavior: "instant" });
441
+ }, 10);
442
+ }
443
+ }
444
+ });
445
+ }
446
+ }
447
+
448
+ document.addEventListener("DOMContentLoaded", function () {
449
+ const charts = document.querySelectorAll(".benchmark-chart");
450
+ charts.forEach((chart) => {
451
+ const chartObject = new BenchmarkChart(chart);
452
+ });
453
+ });
@@ -0,0 +1,117 @@
1
+ import { render, html } from "https://unpkg.com/uhtml@3.2.1?module";
2
+ import { BenchmarkLoader } from "./benchmark-loader.js";
3
+
4
+ let currentLoader = new BenchmarkLoader();
5
+ let dispatched = new Set();
6
+
7
+ class BenchmarkDetails {
8
+ constructor(node) {
9
+ this.detail = node;
10
+ this.source = this.detail.dataset.source;
11
+ let self = this;
12
+ if (!dispatched.has(this.source)) {
13
+ currentLoader
14
+ .fetchBenchmarkData(self.source)
15
+ .then((result) => {
16
+ let event = new Event(`${self.source}-details-loaded`);
17
+ window.dispatchEvent(event);
18
+ })
19
+ .catch((error) => {
20
+ let event = new Event(`${self.source}-details-failed`);
21
+ window.dispatchEvent(event);
22
+ });
23
+ }
24
+ dispatched.add(this.source);
25
+ window.addEventListener(`${this.source}-details-failed`, function () {
26
+ self.handleError();
27
+ });
28
+ window.addEventListener(`${this.source}-details-loaded`, function () {
29
+ self.render();
30
+ });
31
+ }
32
+ handleError() {
33
+ render(
34
+ this.detail,
35
+ html`
36
+ <div class="benchmark-details-error">Benchmark data not available.</div>
37
+ `,
38
+ );
39
+ }
40
+ render() {
41
+ const machine_info = currentLoader.sources[this.source].machine_info;
42
+ const commit_info = currentLoader.sources[this.source].commit_info;
43
+ const cpu = currentLoader.sources[this.source].machine_info.cpu;
44
+ render(
45
+ this.detail,
46
+ html`
47
+ <table class="benchmark-details-section">
48
+ <thead>
49
+ <tr>
50
+ <th>Machine Information</th>
51
+ </tr>
52
+ </thead>
53
+ <tbody>
54
+ <tr>
55
+ <td>Operating System</td>
56
+ <td>${machine_info.system} (${machine_info.release})</td>
57
+ </tr>
58
+ <tr>
59
+ <td>CPU</td>
60
+ <td>
61
+ ${cpu.brand_raw} ${cpu.processor} @ ${cpu.hz_actual_friendly}
62
+ </td>
63
+ </tr>
64
+ <tr>
65
+ <td>Python</td>
66
+ <td>${machine_info.python_version}</td>
67
+ </tr>
68
+ </tbody>
69
+ </table>
70
+ <table class="benchmark-details-section">
71
+ <thead>
72
+ <tr>
73
+ <th>Source Information</th>
74
+ </tr>
75
+ </thead>
76
+ <tbody>
77
+ <tr>
78
+ <td>Branch</td>
79
+ <td>${commit_info.branch}</td>
80
+ </tr>
81
+ <tr>
82
+ <td>Commit Hash</td>
83
+ <td>${commit_info.id}</td>
84
+ </tr>
85
+ </tbody>
86
+ </table>
87
+ <table class="benchmark-details-section">
88
+ <thead>
89
+ <tr>
90
+ <th>Storage Information</th>
91
+ </tr>
92
+ </thead>
93
+ <tbody>
94
+ <tr>
95
+ <td>Redis</td>
96
+ <td>${machine_info.redis.redis_version}</td>
97
+ </tr>
98
+ <tr>
99
+ <td>Memcached</td>
100
+ <td>${machine_info.memcached.version}</td>
101
+ </tr>
102
+ <tr>
103
+ <td>MongoDB</td>
104
+ <td>${machine_info.mongodb.version}</td>
105
+ </tr>
106
+ </tbody>
107
+ </table>
108
+ `,
109
+ );
110
+ }
111
+ }
112
+ document.addEventListener("DOMContentLoaded", function () {
113
+ const details = document.querySelectorAll(".benchmark-details");
114
+ details.forEach((detail) => {
115
+ new BenchmarkDetails(detail);
116
+ });
117
+ });
@@ -0,0 +1,43 @@
1
+ const BENCHMARK_PATHS = [
2
+ `https://${GITSHA}--py-limits.netlify.app`,
3
+ `https://${GITBRANCH}--py-limits.netlify.app`,
4
+ ];
5
+
6
+ class BenchmarkLoader {
7
+ constructor(paths) {
8
+ this.paths = paths || BENCHMARK_PATHS;
9
+ this.sources = {};
10
+ }
11
+ fetchBenchmarkData(source) {
12
+ if (this.sources[source] != null) {
13
+ return Promise.resolve(this.sources[source]);
14
+ }
15
+ let attempts = 0;
16
+ let self = this;
17
+ function tryFetch() {
18
+ if (attempts >= self.paths.length) {
19
+ return Promise.reject(new Error("All fetch attempts failed."));
20
+ }
21
+ const base = self.paths[attempts++];
22
+ const url = `${base}/${source}.json`;
23
+ return fetch(url, { method: "HEAD" })
24
+ .then((headRes) => {
25
+ if (!headRes.ok) throw new Error("HEAD check failed");
26
+ return fetch(url).then((res) => {
27
+ if (!res.ok) throw new Error(`GET failed from ${url}`);
28
+ return res.json().then((json) => {
29
+ self.sources[source] = json;
30
+ return Promise.resolve(self.sources[source]);
31
+ });
32
+ });
33
+ })
34
+ .catch((err) => {
35
+ return tryFetch();
36
+ });
37
+ }
38
+
39
+ return tryFetch();
40
+ }
41
+ }
42
+
43
+ export { BenchmarkLoader };
@@ -0,0 +1,16 @@
1
+ window.GITBRANCH = "{{branch | replace('.', '-')}}";
2
+ window.GITSHA = "{{sha}}";
3
+
4
+ function getReleases() {
5
+ return fetch("https://pypi.org/pypi/limits/json")
6
+ .then((res) => {
7
+ if (!res.ok) throw new Error("Releases not found");
8
+ return res.json();
9
+ })
10
+ .catch((error) => [error]);
11
+ }
12
+
13
+ getReleases().then((response) => {
14
+ const releases = Object.entries(response.releases).map((entry) => entry[0]);
15
+ window.LATEST_RELEASES = releases.slice(-5).reverse();
16
+ });
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-04-12T18:43:47-0700",
11
+ "date": "2025-04-23T13:02:47-0700",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "58af4445a2d0b1de9251cd60e847d25267b4830d",
15
- "version": "4.7.3"
14
+ "full-revisionid": "742f7a2fdf50b5fbd1a263bd50b5dc7f1f64aa48",
15
+ "version": "4.8.0"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18