Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Delta Between Two Patch Sets: cms/bin/test_server.py

Issue 29912588: Issue 7019 - [CMS] Refactor `test_server.py` (Closed)
Left Patch Set: Updated exception tests. Removed duplicates from ignores Created Oct. 18, 2018, 4:09 p.m.
Right Patch Set: Addressed docstring nit Created Oct. 29, 2018, 11 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « .hgignore ('k') | tests/conftest.py » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 # This file is part of the Adblock Plus web scripts, 1 # This file is part of the Adblock Plus web scripts,
2 # Copyright (C) 2006-present eyeo GmbH 2 # Copyright (C) 2006-present eyeo GmbH
3 # 3 #
4 # Adblock Plus is free software: you can redistribute it and/or modify 4 # Adblock Plus is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License version 3 as 5 # it under the terms of the GNU General Public License version 3 as
6 # published by the Free Software Foundation. 6 # published by the Free Software Foundation.
7 # 7 #
8 # Adblock Plus is distributed in the hope that it will be useful, 8 # Adblock Plus is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details. 11 # GNU General Public License for more details.
12 # 12 #
13 # You should have received a copy of the GNU General Public License 13 # You should have received a copy of the GNU General Public License
14 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. 14 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
15 15
16 from __future__ import print_function 16 from __future__ import print_function
17 17
18 import os 18 import os
19 import mimetypes 19 import mimetypes
20 from argparse import ArgumentParser 20 import argparse
Vasily Kuznetsov 2018/10/22 14:36:47 In some cases we get easier to follow code by impo
Tudor Avram 2018/10/22 17:29:38 Well, basically the idea was to make the entire th
Vasily Kuznetsov 2018/10/23 10:16:14 I see. Well, at the moment there's no standard abo
Tudor Avram 2018/10/23 16:44:33 I guess it's more important to make it obvious whe
21 21
22 import jinja2 22 import jinja2
23 23
24 from cms.converters import converters 24 from cms.converters import converters
25 from cms.utils import process_page 25 from cms.utils import process_page
26 from cms.sources import create_source 26 from cms.sources import create_source
27 27
28 UNICODE_ENCODING = 'utf-8' 28 UNICODE_ENCODING = 'utf-8'
29 29
30 ERROR_TEMPLATE = ''' 30 ERROR_TEMPLATE = '''
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
72 def _get_data(self, path): 72 def _get_data(self, path):
73 """Read the data corresponding to a website path. 73 """Read the data corresponding to a website path.
74 74
75 Parameters 75 Parameters
76 ---------- 76 ----------
77 path: str 77 path: str
78 The path to the page to get the data for. 78 The path to the page to get the data for.
79 79
80 Returns 80 Returns
81 ------- 81 -------
82 str/ bytes 82 str or bytes
Vasily Kuznetsov 2018/10/22 14:36:46 Numpy docstring standard [1], that we use here see
Tudor Avram 2018/10/23 16:44:32 Done.
83 The data corresponding to the path we're trying to access. 83 The data corresponding to the path we're trying to access.
84 84
85 """ 85 """
86 if self.source.has_static(path): 86 if self.source.has_static(path):
87 return self.source.read_static(path) 87 return self.source.read_static(path)
88 88
89 page, data = self._get_page(path) 89 page, data = self._get_page(path)
90 90
91 if page and self._has_conflicts(page): 91 if page and self._has_conflicts(page):
92 raise Exception('The requested page conflicts with another page.') 92 raise Exception('The requested page conflicts with another page.')
93 93
94 return data 94 return data
95 95
96 def _get_page(self, path): 96 def _get_page(self, path):
97 """Construct a page and return its contents. 97 """Construct a page and return its contents.
98 98
99 Parameters 99 Parameters
100 ---------- 100 ----------
101 path: str 101 path: str
102 The path of the page we want to construct. 102 The path of the page we want to construct.
103 103
104 Returns 104 Returns
105 ------- 105 -------
106 (str, str) 106 (page_name, page_contents): (str, str)
Vasily Kuznetsov 2018/10/22 14:36:46 Numpy docstring standard (link in the previous com
Tudor Avram 2018/10/23 16:44:32 Done.
107 With the following format:
108 <page_name, page_contents>
109 107
110 """ 108 """
111 path = path.strip('/') 109 path = path.strip('/')
112 if path == '': 110 if path == '':
113 locale, page = self.source.read_config().get('general', 111 locale, page = self.source.read_config().get('general',
114 'defaultlocale'), '' 112 'defaultlocale'), ''
115 elif '/' in path: 113 elif '/' in path:
116 locale, page = path.split('/', 1) 114 locale, page = path.split('/', 1)
117 else: 115 else:
118 locale, page = path, '' 116 locale, page = path, ''
119 117
120 default_page = self.source.read_config().get('general', 'defaultpage') 118 default_page = self.source.read_config().get('general', 'defaultpage')
121 possible_pages = [page, '/'.join([page, default_page]).lstrip('/')] 119 possible_pages = [page, '/'.join([page, default_page]).lstrip('/')]
122 120
123 for page_format in converters.iterkeys(): 121 for page_format in converters.iterkeys():
124 for p in possible_pages: 122 for p in possible_pages:
125 if self.source.has_page(p, page_format): 123 if self.source.has_page(p, page_format):
126 return p, process_page(self.source, locale, p, page_format, 124 return p, process_page(self.source, locale, p, page_format,
127 self.full_url) 125 self.full_url)
128 126
129 if self.source.has_localizable_file(locale, page): 127 if self.source.has_localizable_file(locale, page):
130 return page, self.source.read_localizable_file(locale, page) 128 return page, self.source.read_localizable_file(locale, page)
131 129
132 return None, None 130 return None, None
133 131
134 def _has_conflicts(self, page): 132 def _has_conflicts(self, page):
135 """Check if a page has conflicts. 133 """Check if a page has conflicts.
136 134
137 A page has conflicts if there are other pages with the same name. 135 A page has conflicts if there are other pages with the same name.
136
138 Parameters 137 Parameters
Vasily Kuznetsov 2018/10/22 14:36:46 Nit: there should be an empty line before the head
Tudor Avram 2018/10/23 16:44:32 Done.
139 ---------- 138 ----------
140 page: str 139 page: str
141 The path of the page we're checking for conflicts. 140 The path of the page we're checking for conflicts.
142 141
143 Returns 142 Returns
144 ------- 143 -------
145 bool 144 bool
146 True - if the page has conflicts 145 True - if the page has conflicts
147 False - otherwise 146 False - otherwise
148 147
(...skipping 11 matching lines...) Expand all
160 def get_error_page(self, start_response, status, **kw): 159 def get_error_page(self, start_response, status, **kw):
161 """Create and display an error page. 160 """Create and display an error page.
162 161
163 Parameters 162 Parameters
164 ---------- 163 ----------
165 start_response: function 164 start_response: function
166 It will be called before constructing the error page, to setup 165 It will be called before constructing the error page, to setup
167 things like the status of the response and the headers. 166 things like the status of the response and the headers.
168 status: str 167 status: str
169 The status of the response we're sending the error page with. 168 The status of the response we're sending the error page with.
170 Needs to have the following format: "<status_code> 169 Needs to have the following format:
Vasily Kuznetsov 2018/10/22 14:36:47 It would be better to not break the line the middl
Tudor Avram 2018/10/23 16:44:30 Done.
171 <status_message>" 170 "<status_code> <status_message>"
172 kw: dict 171 kw: dict
173 Any additional arguments that will be passed onto the `stream` 172 Any additional arguments that will be passed onto the `stream`
174 method 173 method of a `jinja2 Template`.
175 of a `jinja2 Template`. 174
Vasily Kuznetsov 2018/10/22 14:36:47 Nit: redundant line break above?
Tudor Avram 2018/10/23 16:44:31 Done.
176 175 Returns
177 Returns 176 -------
178 ------- 177 generator of utf8 strings
179 generator 178 Fragments of the corresponding error HTML template.
180 of utf8 strings - fragments of the corresponding error HTML
Vasily Kuznetsov 2018/10/22 14:36:47 I think "of utf8 strings" should be part of the "t
Tudor Avram 2018/10/23 16:44:33 Done.
181 template.
182 179
183 """ 180 """
184 env = jinja2.Environment(autoescape=True) 181 env = jinja2.Environment(autoescape=True)
185 page_template = env.from_string(ERROR_TEMPLATE) 182 page_template = env.from_string(ERROR_TEMPLATE)
186 mime = 'text/html; encoding={}'.format(UNICODE_ENCODING) 183 mime = 'text/html; encoding={}'.format(UNICODE_ENCODING)
187 184
188 start_response(status, [('Content-Type', mime)]) 185 start_response(status, [('Content-Type', mime)])
189 186
190 for fragment in page_template.stream(status=status, **kw): 187 for fragment in page_template.stream(status=status, **kw):
191 yield fragment.encode(UNICODE_ENCODING) 188 yield fragment.encode(UNICODE_ENCODING)
192 189
193 def __call__(self, environ, start_response): 190 def __call__(self, environ, start_response):
194 """Execute the handler, according with the WSGI standards. 191 """Execute the handler, according to the WSGI standards.
Vasily Kuznetsov 2018/10/22 14:36:47 Nit: according to the internet the normal form is
Tudor Avram 2018/10/23 16:44:33 Done.
195 192
196 Parameters 193 Parameters
197 --------- 194 ---------
198 environ: dict 195 environ: dict
199 The environment under which the page is requested. 196 The environment under which the page is requested.
200 The requested page must be under the `PATH_INFO` key. 197 The requested page must be under the `PATH_INFO` key.
201 start_response: function 198 start_response: function
202 Used to initiate a response. Must take two arguments, in this 199 Used to initiate a response. Must take two arguments, in this
203 order: 200 order:
204 - Response status, in the format "<code> <message>". 201 - Response status, in the format "<code> <message>".
205 - Response headers, as a list of tuples. 202 - Response headers, as a list of tuples.
206 203
207 Returns 204 Returns
208 ------- 205 -------
209 list 206 list of str
Vasily Kuznetsov 2018/10/22 14:36:46 It's "list of str", right? Perhaps we can add that
Tudor Avram 2018/10/23 16:44:31 Done.
210 With the data for a specific page. 207 With the data for a specific page.
211 208
212 """ 209 """
213 path = environ.get('PATH_INFO') 210 path = environ.get('PATH_INFO')
214 211
215 data = self._get_data(path) 212 data = self._get_data(path)
216 213
217 if data is None: 214 if data is None:
218 return self.get_error_page(start_response, '404 Not Found', 215 return self.get_error_page(start_response, '404 Not Found',
219 uri=path) 216 uri=path)
220 217
221 mime = mimetypes.guess_type(path)[0] or 'text/html' 218 mime = mimetypes.guess_type(path)[0] or 'text/html'
222 219
223 if isinstance(data, unicode): 220 if isinstance(data, unicode):
224 data = data.encode(UNICODE_ENCODING) 221 data = data.encode(UNICODE_ENCODING)
225 mime = '{0}; charset={1}'.format(mime, UNICODE_ENCODING) 222 mime = '{0}; charset={1}'.format(mime, UNICODE_ENCODING)
226 223
227 start_response('200 OK', [('Content-Type', mime)]) 224 start_response('200 OK', [('Content-Type', mime)])
228 return [data] 225 return [data]
229 226
230 227
231 def get_handler(): 228 def parse_arguments():
Vasily Kuznetsov 2018/10/22 14:36:47 I was imagining a structure where we parse the arg
Tudor Avram 2018/10/23 16:44:30 Done.
232 """Set the arguments required to run the script. 229 """Set up and parse the arguments required by the script.
233 230
234 Returns 231 Returns
235 ------- 232 -------
236 TestServerHandler 233 argparse.Namespace
237 Initialised with the script parameters. 234 With the script arguments, as parsed.
238 235
239 """ 236 """
240 parser = ArgumentParser(description='CMS development server created to ' 237 parser = argparse.ArgumentParser(
241 'test pages locally and on-the-fly') 238 description='CMS development server created to test pages locally and '
239 'on-the-fly.',
240 )
242 241
243 parser.add_argument('path', default=os.curdir, nargs='?', 242 parser.add_argument('path', default=os.curdir, nargs='?',
244 help='Path to the website we intend to run. If not ' 243 help='Path to the website we intend to run. If not '
245 'provided, defaults, to the current directory.') 244 'provided, defaults, to the current directory.')
246 parser.add_argument('--host', default='localhost', 245 parser.add_argument('--host', default='localhost',
247 help='Address of the host the server will listen on. ' 246 help='Address of the host the server will listen on. '
248 'Defaults to "localhost".') 247 'Defaults to "localhost".')
249 parser.add_argument('--port', default=5000, type=int, 248 parser.add_argument('--port', default=5000, type=int,
250 help='TCP port the server will listen on. Default ' 249 help='TCP port the server will listen on. Default '
251 '5000.') 250 '5000.')
252 251
253 args = parser.parse_args() 252 return parser.parse_args()
254
255 return DynamicServerHandler(args.host, args.port, args.path)
256 253
257 254
258 def run_werkzeug_server(handler, **kw): 255 def run_werkzeug_server(handler, **kw):
259 """Set up a server that uses `werkzeug`. 256 """Set up a server that uses `werkzeug`.
260 257
261 Parameters 258 Parameters
262 ---------- 259 ----------
263 handler: DynamicServerHandler 260 handler: DynamicServerHandler
264 Defines the parameters and methods required to handle requests. 261 Defines the parameters and methods required to handle requests.
265 262
266 Raises 263 Raises
267 ------ 264 ------
268 ImportError 265 ImportError
269 If the package `werkzeug` is not installed 266 If the package `werkzeug` is not installed
270 267
271 """ 268 """
272 from werkzeug.serving import run_simple 269 from werkzeug.serving import run_simple
270 import logging
273 271
274 def run(*args, **kwargs): 272 def run(*args, **kwargs):
275 # The werkzeug logger must be configured before the 273 # The werkzeug logger must be configured before the
276 # root logger. Also we must prevent it from propagating 274 # root logger. Also we must prevent it from propagating
277 # messages, otherwise messages are logged twice. 275 # messages, otherwise messages are logged twice.
278 import logging
Vasily Kuznetsov 2018/10/22 14:36:46 Is it necessary to import logging here and not at
Tudor Avram 2018/10/22 17:29:38 It's not used anywhere else in the script, that's
Vasily Kuznetsov 2018/10/23 10:16:14 I see. Importing `logging` is probably not a big d
Tudor Avram 2018/10/23 16:44:31 Done.
279 logger = logging.getLogger('werkzeug') 276 logger = logging.getLogger('werkzeug')
280 logger.propagate = False 277 logger.propagate = False
281 logger.setLevel(logging.INFO) 278 logger.setLevel(logging.INFO)
282 logger.addHandler(logging.StreamHandler()) 279 logger.addHandler(logging.StreamHandler())
283 280
284 run_simple(threaded=True, *args, **kwargs) 281 run_simple(threaded=True, *args, **kwargs)
285 282
286 run(handler.host, handler.port, handler, **kw) 283 run(handler.host, handler.port, handler, **kw)
287 284
288 285
(...skipping 23 matching lines...) Expand all
312 ) 309 )
313 310
314 server = make_server(host, port, wrapper, ThreadedWSGIServer) 311 server = make_server(host, port, wrapper, ThreadedWSGIServer)
315 print(' * Running on {0}:{1}'.format(*server.server_address)) 312 print(' * Running on {0}:{1}'.format(*server.server_address))
316 server.serve_forever() 313 server.serve_forever()
317 314
318 run(handler.host, handler.port, handler, **kw) 315 run(handler.host, handler.port, handler, **kw)
319 316
320 317
321 def main(): 318 def main():
322 handler = get_handler() 319 args = parse_arguments()
320 handler = DynamicServerHandler(args.host, args.port, args.path)
323 321
324 try: 322 try:
325 run_werkzeug_server(handler, use_reloader=True, use_debugger=True) 323 run_werkzeug_server(handler, use_reloader=True, use_debugger=True)
326 except ImportError: 324 except ImportError:
327 run_builtins_server(handler) 325 run_builtins_server(handler)
328 326
329 327
330 if __name__ == '__main__': 328 if __name__ == '__main__':
331 main() 329 main()
LEFTRIGHT

Powered by Google App Engine
This is Rietveld