Update upstream source from tag 'upstream/3.3.0'
Update to upstream version '3.3.0'
with Debian dir dd6f5364357fd5b42c0f3e62cee07b34c0b18a17
Carsten Schoenert
1 year, 1 month ago
1 | 1 | |
2 | 2 | Changelog |
3 | 3 | ========= |
4 | ||
5 | v3.3.0 | |
6 | ------ | |
7 | Release Date: 2023-02-26 | |
8 | ||
9 | * Bug Fix | |
10 | ||
11 | * Ensure per route limits are preferred (over application limits) | |
12 | when populating rate limiting headers in the case where no rate limit has been | |
13 | breached in the request. | |
4 | 14 | |
5 | 15 | v3.2.0 |
6 | 16 | ------ |
44 | 54 | |
45 | 55 | * Allow scoping regular limit decorators / context managers |
46 | 56 | |
57 | v3.3.0 | |
58 | ------ | |
59 | Release Date: 2023-02-26 | |
60 | ||
47 | 61 | v3.2.0 |
48 | 62 | ------ |
49 | 63 | Release Date: 2023-02-15 |
66 | 80 | |
67 | 81 | * Simplify registration of decorated function & blueprint limits |
68 | 82 | |
83 | ||
84 | v3.3.0 | |
85 | ------ | |
86 | Release Date: 2023-02-26 | |
69 | 87 | |
70 | 88 | v3.2.0 |
71 | 89 | ------ |
925 | 943 | |
926 | 944 | |
927 | 945 | |
946 |
23 | 23 | # setup.py/versioneer.py will grep for the variable names, so they must |
24 | 24 | # each be defined on a line of their own. _version.py will just call |
25 | 25 | # get_keywords(). |
26 | git_refnames = " (HEAD -> master, tag: 3.2.0, stable)" | |
27 | git_full = "b764e15950729baf12820086f9713c47414990d1" | |
28 | git_date = "2023-02-15 16:29:02 -0800" | |
26 | git_refnames = " (tag: 3.3.0, stable)" | |
27 | git_full = "5d4174a6584c369b3a233dd58edcc392f7cbab9b" | |
28 | git_date = "2023-02-26 18:07:51 -0800" | |
29 | 29 | keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} |
30 | 30 | return keywords |
31 | 31 |
971 | 971 | method = self.limiter.hit |
972 | 972 | kwargs["cost"] = lim.cost |
973 | 973 | |
974 | if not limit_for_header or lim.limit < limit_for_header.limit: | |
975 | limit_for_header = RequestLimit(self, lim.limit, args, False) | |
976 | ||
977 | view_limits.append(RequestLimit(self, lim.limit, args, False)) | |
974 | request_limit = RequestLimit(self, lim.limit, args, False, lim.shared) | |
975 | view_limits.append(request_limit) | |
978 | 976 | |
979 | 977 | if not method(lim.limit, *args, **kwargs): |
980 | 978 | self.logger.info( |
989 | 987 | if self._fail_on_first_breach: |
990 | 988 | break |
991 | 989 | |
990 | if not limit_for_header and view_limits: | |
991 | # Pick a non shared limit over a shared one if possible | |
992 | # when no rate limit has been hit. This should be the best hint | |
993 | # for the client. | |
994 | explicit = [limit for limit in view_limits if not limit.shared] | |
995 | limit_for_header = explicit[0] if explicit else view_limits[0] | |
996 | ||
992 | 997 | self.context.view_rate_limit = limit_for_header or None |
993 | 998 | self.context.view_rate_limits = view_limits |
994 | 999 | |
995 | 1000 | on_breach_response = None |
996 | 1001 | for limit in failed_limits: |
997 | request_limit = RequestLimit(self, limit[0].limit, limit[1], True) | |
1002 | request_limit = RequestLimit( | |
1003 | self, limit[0].limit, limit[1], True, limit[0].shared | |
1004 | ) | |
998 | 1005 | for cb in dict.fromkeys([self._on_breach, limit[0].on_breach]): |
999 | 1006 | if cb: |
1000 | 1007 | try: |
27 | 27 | #: Whether the limit was breached within the context of this request |
28 | 28 | breached: bool |
29 | 29 | |
30 | #: Whether the limit is a shared limit | |
31 | shared: bool | |
32 | ||
30 | 33 | def __init__( |
31 | 34 | self, |
32 | 35 | extension: Limiter, |
33 | 36 | limit: RateLimitItem, |
34 | 37 | request_args: List[str], |
35 | 38 | breached: bool, |
39 | shared: bool, | |
36 | 40 | ) -> None: |
37 | 41 | self.extension: weakref.ProxyType[Limiter] = weakref.proxy(extension) |
38 | 42 | self.limit = limit |
39 | 43 | self.request_args = request_args |
40 | 44 | self.key = limit.key_for(*request_args) |
41 | 45 | self.breached = breached |
46 | self.shared = shared | |
42 | 47 | self._window: Optional[Tuple[int, int]] = None |
43 | 48 | |
44 | 49 | @property |
325 | 325 | limiter = Limiter( |
326 | 326 | get_remote_address, |
327 | 327 | app=app, |
328 | application_limits=["60/minute"], | |
328 | 329 | default_limits=["10/minute"], |
329 | 330 | headers_enabled=True, |
330 | 331 | ) |
343 | 344 | resp = cli.get("/t1") |
344 | 345 | assert resp.headers.get("X-RateLimit-Limit") == "10" |
345 | 346 | assert resp.headers.get("X-RateLimit-Remaining") == "9" |
347 | assert resp.headers.get("X-RateLimit-Reset") == str(int(time.time() + 61)) | |
348 | assert resp.headers.get("Retry-After") == str(60) | |
349 | resp = cli.get("/t2") | |
350 | assert resp.headers.get("X-RateLimit-Limit") == "2" | |
351 | assert resp.headers.get("X-RateLimit-Remaining") == "1" | |
352 | assert resp.headers.get("X-RateLimit-Reset") == str(int(time.time() + 2)) | |
353 | ||
354 | assert resp.headers.get("Retry-After") == str(1) | |
355 | ||
356 | ||
357 | def test_headers_application_limits(): | |
358 | app = Flask(__name__) | |
359 | limiter = Limiter( | |
360 | get_remote_address, | |
361 | app=app, | |
362 | application_limits=["60/minute"], | |
363 | headers_enabled=True, | |
364 | ) | |
365 | ||
366 | @app.route("/t1") | |
367 | def t1(): | |
368 | return "test" | |
369 | ||
370 | @app.route("/t2") | |
371 | @limiter.limit("2/second; 5 per minute; 10/hour") | |
372 | def t2(): | |
373 | return "test" | |
374 | ||
375 | with hiro.Timeline().freeze(): | |
376 | with app.test_client() as cli: | |
377 | resp = cli.get("/t1") | |
378 | assert resp.headers.get("X-RateLimit-Limit") == "60" | |
379 | assert resp.headers.get("X-RateLimit-Remaining") == "59" | |
346 | 380 | assert resp.headers.get("X-RateLimit-Reset") == str(int(time.time() + 61)) |
347 | 381 | assert resp.headers.get("Retry-After") == str(60) |
348 | 382 | resp = cli.get("/t2") |