Mercurial > wiki
comparison atulweb.py @ 28:602baadb535a default tip
Added a requesthandler infrastructure to atulweb.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Wed, 11 Mar 2009 23:19:18 -0500 |
parents | 1850638c1da5 |
children |
comparison
equal
deleted
inserted
replaced
27:a42400d52a1e | 28:602baadb535a |
---|---|
3 import cgi | 3 import cgi |
4 import traceback | 4 import traceback |
5 import sys | 5 import sys |
6 import re | 6 import re |
7 import os | 7 import os |
8 import types | |
8 import mimetypes | 9 import mimetypes |
9 | 10 |
10 try: | 11 try: |
11 import json | 12 import json |
12 except ImportError: | 13 except ImportError: |
13 import simplejson as json | 14 import simplejson as json |
14 | 15 |
15 DEFAULT_LISTEN_PORT = 8000 | 16 DEFAULT_LISTEN_PORT = 8000 |
17 | |
18 OPEN_STR = "{" | |
19 CLOSE_STR = "}" | |
20 | |
21 def _parse_request_path_pattern(pattern, current=None): | |
22 """ | |
23 >>> _parse_request_path_pattern('/blarg/{foob}/narb') | |
24 [('s', '/blarg/'), ('n', 'foob'), ('s', '/narb')] | |
25 | |
26 >>> _parse_request_path_pattern('{foob}/gump') | |
27 [('n', 'foob'), ('s', '/gump')] | |
28 | |
29 >>> _parse_request_path_pattern('{foob}') | |
30 [('n', 'foob')] | |
31 | |
32 >>> _parse_request_path_pattern('gump') | |
33 [('s', 'gump')] | |
34 | |
35 >>> _parse_request_path_pattern('{}') | |
36 Traceback (most recent call last): | |
37 ... | |
38 ValueError: empty noun | |
39 | |
40 >>> _parse_request_path_pattern('/goop/{regr') | |
41 Traceback (most recent call last): | |
42 ... | |
43 ValueError: unclosed noun | |
44 """ | |
45 | |
46 if current is None: | |
47 current = [] | |
48 | |
49 open_index = pattern.find(OPEN_STR) | |
50 if open_index != -1: | |
51 string_part = pattern[:open_index] | |
52 rest = pattern[open_index+len(OPEN_STR):] | |
53 close_index = rest.find(CLOSE_STR) | |
54 if close_index == -1: | |
55 raise ValueError("unclosed noun") | |
56 noun_part = rest[:close_index] | |
57 rest = rest[close_index+len(CLOSE_STR):] | |
58 if string_part: | |
59 current.append(('s', string_part)) | |
60 if noun_part: | |
61 current.append(('n', noun_part)) | |
62 _parse_request_path_pattern(rest, current) | |
63 else: | |
64 raise ValueError("empty noun") | |
65 elif pattern: | |
66 current.append(('s', pattern)) | |
67 return current | |
68 | |
69 def make_request_map(requests): | |
70 """ | |
71 >>> requests = {'blarg': 'GET /blar/{goop}', | |
72 ... 'fnarg': 'POST /blarg/goopy'} | |
73 >>> req_map = make_request_map(requests) | |
74 | |
75 >>> req_map['GET']['blarg'] | |
76 [('s', '/blar/'), ('n', 'goop')] | |
77 | |
78 >>> req_map['POST']['fnarg'] | |
79 [('s', '/blarg/goopy')] | |
80 """ | |
81 | |
82 request_map = {} | |
83 for key in requests: | |
84 method, pattern = requests[key].split(' ') | |
85 if method not in request_map: | |
86 request_map[method] = {} | |
87 request_map[method][key] = _parse_request_path_pattern(pattern) | |
88 return request_map | |
89 | |
90 def _match_request_path(path_pattern, path, nouns): | |
91 """ | |
92 >>> import re | |
93 >>> pattern = [('s', '/bla/'), ('n', 'doc')] | |
94 >>> nouns = {'doc': re.compile(r'[0-9]+')} | |
95 >>> _match_request_path(pattern, '/bla/4532', nouns) | |
96 {'doc': '4532'} | |
97 | |
98 >>> _match_request_path(pattern, '/bla/garg/4532/', nouns) | |
99 | |
100 >>> _match_request_path(pattern, '/bla/garg/4532', nouns) | |
101 | |
102 >>> _match_request_path(pattern, '/4532', {'doc': r'.*'}) | |
103 | |
104 >>> _match_request_path([('s', '/status')], '/status', {}) | |
105 {} | |
106 | |
107 >>> _match_request_path([('n', 'doc')], '345', nouns) | |
108 {'doc': '345'} | |
109 | |
110 >>> _match_request_path([('n', 'doc'), ('s', '/go')], '345/go', nouns) | |
111 {'doc': '345'} | |
112 | |
113 >>> _match_request_path([('n', 'doc'), ('s', '/go')], '345', nouns) | |
114 """ | |
115 | |
116 matches = {} | |
117 curr_noun = None | |
118 path_pattern = list(path_pattern) | |
119 path_pattern.append(('end', None)) | |
120 for seg_type, seg_value in path_pattern: | |
121 noun_to_check = None | |
122 if seg_type == 'n': | |
123 curr_noun = seg_value | |
124 elif seg_type == 's': | |
125 index = path.find(seg_value) | |
126 if (index == -1): | |
127 return None | |
128 elif index == 0: | |
129 if curr_noun: | |
130 return None | |
131 else: | |
132 path = path[len(seg_value):] | |
133 else: | |
134 if curr_noun: | |
135 noun_to_check = path[:index] | |
136 path = path[index:] | |
137 else: | |
138 return None | |
139 elif seg_type == 'end': | |
140 if curr_noun is not None: | |
141 noun_to_check = path | |
142 else: | |
143 raise ValueError('unknown segment type in path pattern') | |
144 if noun_to_check is not None: | |
145 if nouns[curr_noun].match(noun_to_check): | |
146 matches[curr_noun] = noun_to_check | |
147 curr_noun = None | |
148 else: | |
149 return None | |
150 return matches | |
151 | |
152 def map_request(environ, request_map, nouns, mapping_obj): | |
153 method = environ['REQUEST_METHOD'] | |
154 if method in request_map: | |
155 for name in request_map[method]: | |
156 match = _match_request_path(request_map[method][name], | |
157 environ['PATH_INFO'], | |
158 nouns) | |
159 if match is not None: | |
160 return getattr(mapping_obj, name)(**match) | |
161 raise HttpError('not found') | |
162 else: | |
163 raise HttpError('method not allowed') | |
164 | |
165 def make_wsgiapp(request_handler): | |
166 requests = {} | |
167 for name in dir(request_handler): | |
168 method = getattr(request_handler, name) | |
169 if type(method) == types.MethodType: | |
170 requests[name] = method.__doc__.strip() | |
171 request_map = make_request_map(requests) | |
172 | |
173 @wsgiapp | |
174 def application(environ, start_response): | |
175 request_handler.environ = environ | |
176 return map_request(environ, | |
177 request_map, | |
178 request_handler.NOUNS, | |
179 request_handler) | |
180 | |
181 return application | |
16 | 182 |
17 class HttpError(Exception): | 183 class HttpError(Exception): |
18 def __init__(self, status, body=None, headers=None): | 184 def __init__(self, status, body=None, headers=None): |
19 if isinstance(status, basestring): | 185 if isinstance(status, basestring): |
20 if not status[0].isdigit(): | 186 if not status[0].isdigit(): |
124 return Response(open(filename, 'r').read(), | 290 return Response(open(filename, 'r').read(), |
125 mimetype = mimetypes.guess_type(filename)[0]) | 291 mimetype = mimetypes.guess_type(filename)[0]) |
126 else: | 292 else: |
127 raise HttpError('not found') | 293 raise HttpError('not found') |
128 | 294 |
295 | |
296 | |
129 def test_request(application, requestline='', input='', **kwargs): | 297 def test_request(application, requestline='', input='', **kwargs): |
130 environ = {} | 298 environ = {} |
131 words = requestline.split() | 299 words = requestline.split() |
132 if words: | 300 if words: |
133 environ['REQUEST_METHOD'] = words[0] | 301 environ['REQUEST_METHOD'] = words[0] |
199 def __init(): | 367 def __init(): |
200 for key, value in responses.items(): | 368 for key, value in responses.items(): |
201 status_codes[value.lower()] = key | 369 status_codes[value.lower()] = key |
202 | 370 |
203 __init() | 371 __init() |
372 | |
373 if __name__ == '__main__': | |
374 import doctest | |
375 doctest.testmod(verbose=True) |