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)