Total Complexity | 24 |
Total Lines | 170 |
Duplicated Lines | 0 % |
Changes | 0 |
1 | import functools |
||
|
|||
2 | import hashlib |
||
3 | from flask import jsonify, request, url_for, current_app, make_response, g |
||
4 | from .rate_limit import RateLimit |
||
5 | from .errors import too_many_requests, precondition_failed, not_modified |
||
6 | |||
7 | |||
8 | def json(f): |
||
9 | @functools.wraps(f) |
||
1 ignored issue
–
show
|
|||
10 | def wrapped(*args, **kwargs): |
||
1 ignored issue
–
show
|
|||
11 | rv = f(*args, **kwargs) |
||
1 ignored issue
–
show
|
|||
12 | status_or_headers = None |
||
1 ignored issue
–
show
|
|||
13 | headers = None |
||
1 ignored issue
–
show
|
|||
14 | if isinstance(rv, tuple): |
||
1 ignored issue
–
show
|
|||
15 | rv, status_or_headers, headers = rv + (None, ) * (3 - len(rv)) |
||
1 ignored issue
–
show
|
|||
16 | if isinstance(status_or_headers, (dict, list)): |
||
1 ignored issue
–
show
|
|||
17 | headers, status_or_headers = status_or_headers, None |
||
1 ignored issue
–
show
|
|||
18 | if not isinstance(rv, dict): |
||
1 ignored issue
–
show
|
|||
19 | rv = rv.to_json() |
||
1 ignored issue
–
show
|
|||
20 | rv = jsonify(rv) |
||
1 ignored issue
–
show
|
|||
21 | if status_or_headers is not None: |
||
1 ignored issue
–
show
|
|||
22 | rv.status_code = status_or_headers |
||
1 ignored issue
–
show
|
|||
23 | if headers is not None: |
||
1 ignored issue
–
show
|
|||
24 | rv.headers.extend(headers) |
||
1 ignored issue
–
show
|
|||
25 | return rv |
||
1 ignored issue
–
show
|
|||
26 | |||
27 | return wrapped |
||
1 ignored issue
–
show
|
|||
28 | |||
29 | |||
30 | def hit_count(f): |
||
31 | @functools.wraps(f) |
||
1 ignored issue
–
show
|
|||
32 | def wrapped(*args, **kwargs): |
||
1 ignored issue
–
show
|
|||
33 | rv = f(*args, **kwargs) |
||
1 ignored issue
–
show
|
|||
34 | |||
35 | if current_app.config['TESTING']: |
||
1 ignored issue
–
show
|
|||
36 | from .rate_limit import FakeRedis |
||
1 ignored issue
–
show
|
|||
37 | redis = FakeRedis() |
||
1 ignored issue
–
show
|
|||
38 | else: # pragma: no cover |
||
1 ignored issue
–
show
|
|||
39 | from redis import Redis |
||
1 ignored issue
–
show
|
|||
40 | redis = Redis() |
||
1 ignored issue
–
show
|
|||
41 | |||
42 | key = 'hit-count%s' % (request.path) |
||
1 ignored issue
–
show
|
|||
43 | redis.incr(key) |
||
1 ignored issue
–
show
|
|||
44 | return rv |
||
1 ignored issue
–
show
|
|||
45 | return wrapped |
||
1 ignored issue
–
show
|
|||
46 | |||
47 | |||
48 | def rate_limit(limit, per, scope_func=lambda: request.remote_addr): |
||
49 | def decorator(f): |
||
1 ignored issue
–
show
|
|||
50 | @functools.wraps(f) |
||
1 ignored issue
–
show
|
|||
51 | def wrapped(*args, **kwargs): |
||
1 ignored issue
–
show
|
|||
52 | if current_app.config['USE_RATE_LIMITS']: |
||
1 ignored issue
–
show
|
|||
53 | key = 'rate-limit/%s/%s/' % (f.__name__, scope_func()) |
||
1 ignored issue
–
show
|
|||
54 | limiter = RateLimit(key, limit, per) |
||
1 ignored issue
–
show
|
|||
55 | if not limiter.over_limit: |
||
1 ignored issue
–
show
|
|||
56 | rv = f(*args, **kwargs) |
||
1 ignored issue
–
show
|
|||
57 | else: |
||
1 ignored issue
–
show
|
|||
58 | rv = too_many_requests('You have exceeded your request rate') |
||
1 ignored issue
–
show
|
|||
59 | # rv = make_response(rv) |
||
60 | g.headers = { |
||
1 ignored issue
–
show
|
|||
61 | 'X-RateLimit-Remaining': str(limiter.remaining), |
||
62 | 'X-RateLimit-Limit': str(limiter.limit), |
||
63 | 'X-RateLimit-Reset': str(limiter.reset) |
||
64 | } |
||
65 | return rv |
||
1 ignored issue
–
show
|
|||
66 | else: |
||
1 ignored issue
–
show
|
|||
67 | return f(*args, **kwargs) |
||
1 ignored issue
–
show
|
|||
68 | |||
69 | return wrapped |
||
1 ignored issue
–
show
|
|||
70 | |||
71 | return decorator |
||
1 ignored issue
–
show
|
|||
72 | |||
73 | |||
74 | def paginate(max_per_page=10): |
||
75 | def decorator(f): |
||
1 ignored issue
–
show
|
|||
76 | @functools.wraps(f) |
||
1 ignored issue
–
show
|
|||
77 | def wrapped(*args, **kwargs): |
||
1 ignored issue
–
show
|
|||
78 | page = request.args.get('page', 1, type=int) |
||
1 ignored issue
–
show
|
|||
79 | per_page = min( |
||
1 ignored issue
–
show
|
|||
80 | request.args.get('per_page', max_per_page, type=int), max_per_page) |
||
81 | query = f(*args, **kwargs) |
||
1 ignored issue
–
show
|
|||
82 | p = query.paginate(page, per_page) |
||
1 ignored issue
–
show
|
|||
83 | pages = { |
||
1 ignored issue
–
show
|
|||
84 | 'page': page, |
||
85 | 'per_page': per_page, |
||
86 | 'total': p.total, |
||
87 | 'pages': p.pages |
||
88 | } |
||
89 | if p.has_prev: |
||
1 ignored issue
–
show
|
|||
90 | pages['prev'] = url_for( |
||
1 ignored issue
–
show
|
|||
91 | request.endpoint, |
||
92 | page=p.prev_num, |
||
93 | per_page=per_page, |
||
94 | _external=True, |
||
95 | **kwargs) |
||
96 | else: |
||
1 ignored issue
–
show
|
|||
97 | pages['prev'] = None |
||
1 ignored issue
–
show
|
|||
98 | if p.has_next: |
||
1 ignored issue
–
show
|
|||
99 | pages['next'] = url_for( |
||
1 ignored issue
–
show
|
|||
100 | request.endpoint, |
||
101 | page=p.next_num, |
||
102 | per_page=per_page, |
||
103 | _external=True, |
||
104 | **kwargs) |
||
105 | else: |
||
1 ignored issue
–
show
|
|||
106 | pages['next'] = None |
||
1 ignored issue
–
show
|
|||
107 | pages['first'] = url_for( |
||
1 ignored issue
–
show
|
|||
108 | request.endpoint, |
||
109 | page=1, |
||
110 | per_page=per_page, |
||
111 | _external=True, |
||
112 | **kwargs) |
||
113 | pages['last'] = url_for( |
||
1 ignored issue
–
show
|
|||
114 | request.endpoint, |
||
115 | page=p.pages, |
||
116 | per_page=per_page, |
||
117 | _external=True, |
||
118 | **kwargs) |
||
119 | return jsonify({ |
||
1 ignored issue
–
show
|
|||
120 | 'urls': [item.get_url() for item in p.items], |
||
121 | 'meta': pages |
||
122 | }) |
||
123 | |||
124 | return wrapped |
||
1 ignored issue
–
show
|
|||
125 | |||
126 | return decorator |
||
1 ignored issue
–
show
|
|||
127 | |||
128 | |||
129 | def cache_control(*directives): |
||
130 | def decorator(f): |
||
1 ignored issue
–
show
|
|||
131 | @functools.wraps(f) |
||
1 ignored issue
–
show
|
|||
132 | def wrapped(*args, **kwargs): |
||
1 ignored issue
–
show
|
|||
133 | rv = f(*args, **kwargs) |
||
1 ignored issue
–
show
|
|||
134 | rv = make_response(rv) |
||
1 ignored issue
–
show
|
|||
135 | rv.headers['Cache-Control'] = ', '.join(directives) |
||
1 ignored issue
–
show
|
|||
136 | return rv |
||
1 ignored issue
–
show
|
|||
137 | |||
138 | return wrapped |
||
1 ignored issue
–
show
|
|||
139 | |||
140 | return decorator |
||
1 ignored issue
–
show
|
|||
141 | |||
142 | |||
143 | def no_cache(f): |
||
144 | return cache_control('no-cache', 'no-store', 'max-age=0')(f) |
||
1 ignored issue
–
show
|
|||
145 | |||
146 | |||
147 | def etag(f): |
||
148 | @functools.wraps(f) |
||
1 ignored issue
–
show
|
|||
149 | def wrapped(*args, **kwargs): |
||
1 ignored issue
–
show
|
|||
150 | # only for HEAD and GET requests |
||
151 | assert request.method in ['HEAD', 'GET'],\ |
||
1 ignored issue
–
show
|
|||
152 | '@etag is only supported for GET requests' |
||
153 | rv = f(*args, **kwargs) |
||
1 ignored issue
–
show
|
|||
154 | rv = make_response(rv) |
||
1 ignored issue
–
show
|
|||
155 | etag = '"' + hashlib.md5(rv.get_data()).hexdigest() + '"' |
||
1 ignored issue
–
show
|
|||
156 | rv.headers['ETag'] = etag |
||
1 ignored issue
–
show
|
|||
157 | if_match = request.headers.get('If-Match') |
||
1 ignored issue
–
show
|
|||
158 | if_none_match = request.headers.get('If-None-Match') |
||
1 ignored issue
–
show
|
|||
159 | if if_match: |
||
1 ignored issue
–
show
|
|||
160 | etag_list = [tag.strip() for tag in if_match.split(',')] |
||
1 ignored issue
–
show
|
|||
161 | if etag not in etag_list and '*' not in etag_list: |
||
1 ignored issue
–
show
|
|||
162 | rv = precondition_failed() |
||
1 ignored issue
–
show
|
|||
163 | elif if_none_match: |
||
1 ignored issue
–
show
|
|||
164 | etag_list = [tag.strip() for tag in if_none_match.split(',')] |
||
1 ignored issue
–
show
|
|||
165 | if etag in etag_list or '*' in etag_list: |
||
1 ignored issue
–
show
|
|||
166 | rv = not_modified() |
||
1 ignored issue
–
show
|
|||
167 | return rv |
||
1 ignored issue
–
show
|
|||
168 | |||
169 | return wrapped |
||
1 ignored issue
–
show
|
|||
170 |
The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:
If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.