Mercurial > piecrust2
comparison piecrust/serving/server.py @ 555:daf8df5ade7d
serve: Refactor the server to make pieces usable by the debugging middleware.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Wed, 12 Aug 2015 23:04:46 -0700 |
parents | 155c7e20414f |
children | 657384f08ca3 |
comparison
equal
deleted
inserted
replaced
554:155c7e20414f | 555:daf8df5ade7d |
---|---|
8 from werkzeug.exceptions import ( | 8 from werkzeug.exceptions import ( |
9 NotFound, MethodNotAllowed, InternalServerError, HTTPException) | 9 NotFound, MethodNotAllowed, InternalServerError, HTTPException) |
10 from werkzeug.wrappers import Request, Response | 10 from werkzeug.wrappers import Request, Response |
11 from jinja2 import FileSystemLoader, Environment | 11 from jinja2 import FileSystemLoader, Environment |
12 from piecrust import CACHE_DIR, RESOURCES_DIR | 12 from piecrust import CACHE_DIR, RESOURCES_DIR |
13 from piecrust.app import PieCrust | 13 from piecrust.rendering import PageRenderingContext, render_page |
14 from piecrust.rendering import QualifiedPage, PageRenderingContext, render_page | 14 from piecrust.routing import RouteNotFoundError |
15 from piecrust.serving.util import content_type_map, make_wrapped_file_response | 15 from piecrust.serving.util import ( |
16 from piecrust.sources.base import MODE_PARSING | 16 content_type_map, make_wrapped_file_response, get_requested_page, |
17 from piecrust.uriutil import split_sub_uri | 17 get_app_for_server) |
18 from piecrust.sources.base import SourceNotFoundError | |
18 | 19 |
19 | 20 |
20 logger = logging.getLogger(__name__) | 21 logger = logging.getLogger(__name__) |
21 | 22 |
22 | 23 |
61 def get_description(self, environ=None): | 62 def get_description(self, environ=None): |
62 from werkzeug.utils import escape | 63 from werkzeug.utils import escape |
63 desc = '<p>' + self.description + '</p>' | 64 desc = '<p>' + self.description + '</p>' |
64 desc += '<p>' | 65 desc += '<p>' |
65 for nfe in self._nfes: | 66 for nfe in self._nfes: |
66 desc += '<li>' + escape(nfe.description) + '</li>' | 67 desc += '<li>' + escape(str(nfe)) + '</li>' |
67 desc += '</p>' | 68 desc += '</p>' |
68 return desc | 69 return desc |
69 | 70 |
70 | 71 |
71 class Server(object): | 72 class Server(object): |
105 response = self._try_serve_asset(environ, request) | 106 response = self._try_serve_asset(environ, request) |
106 if response is not None: | 107 if response is not None: |
107 return response | 108 return response |
108 | 109 |
109 # Create the app for this request. | 110 # Create the app for this request. |
110 app = PieCrust(root_dir=self.root_dir, debug=self.debug) | 111 app = get_app_for_server(self.root_dir, debug=self.debug, |
111 if self.sub_cache_dir: | 112 sub_cache_dir=self.sub_cache_dir) |
112 app._useSubCacheDir(self.sub_cache_dir) | |
113 app.config.set('site/root', '/') | |
114 app.config.set('server/is_serving', True) | |
115 if (app.config.get('site/enable_debug_info') and | 113 if (app.config.get('site/enable_debug_info') and |
116 self.enable_debug_info and | 114 self.enable_debug_info and |
117 '!debug' in request.args): | 115 '!debug' in request.args): |
118 app.config.set('site/show_debug_info', True) | 116 app.config.set('site/show_debug_info', True) |
119 | 117 |
166 return None | 164 return None |
167 | 165 |
168 return make_wrapped_file_response(environ, request, full_path) | 166 return make_wrapped_file_response(environ, request, full_path) |
169 | 167 |
170 def _try_serve_page(self, app, environ, request): | 168 def _try_serve_page(self, app, environ, request): |
171 # Try to find what matches the requested URL. | 169 # Find a matching page. |
172 req_path, page_num = split_sub_uri(app, request.path) | 170 req_page = get_requested_page(app, request.path) |
173 | 171 |
174 routes = find_routes(app.routes, req_path) | 172 # If we haven't found any good match, report all the places we didn't |
175 if len(routes) == 0: | 173 # find it at. |
176 raise RouteNotFoundError("Can't find route for: %s" % req_path) | 174 qp = req_page.qualified_page |
177 | 175 if qp is None: |
178 rendered_page = None | 176 msg = "Can't find path for '%s':" % request.path |
179 not_found_errors = [] | 177 raise MultipleNotFound(msg, req_page.not_found_errors) |
180 for route, route_metadata in routes: | 178 |
181 try: | 179 # We have a page, let's try to render it. |
182 logger.debug("Trying to render match from source '%s'." % | 180 render_ctx = PageRenderingContext(qp, |
183 route.source_name) | 181 page_num=req_page.page_num, |
184 rendered_page = self._try_render_page( | 182 force_render=True) |
185 app, route, route_metadata, page_num, req_path) | 183 if qp.route.taxonomy_name is not None: |
186 if rendered_page is not None: | 184 taxonomy = app.getTaxonomy(qp.route.taxonomy_name) |
187 break | 185 tax_terms = qp.route.getTaxonomyTerms(qp.route_metadata) |
188 except NotFound as nfe: | 186 render_ctx.setTaxonomyFilter(tax_terms, needs_slugifier=True) |
189 not_found_errors.append(nfe) | 187 |
190 | 188 # See if this page is known to use sources. If that's the case, |
191 # If we haven't found any good match, raise whatever exception we | 189 # just don't use cached rendered segments for that page (but still |
192 # first got. Otherwise, raise a generic exception. | 190 # use them for pages that are included in it). |
193 if rendered_page is None: | 191 uri = qp.getUri() |
194 msg = ("Can't find path for '%s', looked in sources: %s" % | 192 entry = self._page_record.getEntry(uri, req_page.page_num) |
195 (req_path, | 193 if (qp.route.taxonomy_name is not None or entry is None or |
196 ', '.join([r.source_name for r, _ in routes]))) | 194 entry.used_source_names): |
197 raise MultipleNotFound(msg, not_found_errors) | 195 cache_key = '%s:%s' % (uri, req_page.page_num) |
196 app.env.rendered_segments_repository.invalidate(cache_key) | |
197 | |
198 # Render the page. | |
199 rendered_page = render_page(render_ctx) | |
200 | |
201 # Check if this page is a taxonomy page that actually doesn't match | |
202 # anything. | |
203 if qp.route.taxonomy_name is not None: | |
204 paginator = rendered_page.data.get('pagination') | |
205 if (paginator and paginator.is_loaded and | |
206 len(paginator.items) == 0): | |
207 taxonomy = app.getTaxonomy(qp.route.taxonomy_name) | |
208 message = ("This URL matched a route for taxonomy '%s' but " | |
209 "no pages have been found to have it. This page " | |
210 "won't be generated by a bake." % taxonomy.name) | |
211 raise NotFound(message) | |
212 | |
213 # Remember stuff for next time. | |
214 if entry is None: | |
215 entry = ServeRecordPageEntry(req_page.req_path, req_page.page_num) | |
216 self._page_record.addEntry(entry) | |
217 for p, pinfo in render_ctx.render_passes.items(): | |
218 entry.used_source_names |= pinfo.used_source_names | |
198 | 219 |
199 # Start doing stuff. | 220 # Start doing stuff. |
200 page = rendered_page.page | 221 page = rendered_page.page |
201 rp_content = rendered_page.content | 222 rp_content = rendered_page.content |
202 | 223 |
253 "falling back to uncompressed.") | 274 "falling back to uncompressed.") |
254 response.set_data(rp_content) | 275 response.set_data(rp_content) |
255 | 276 |
256 return response | 277 return response |
257 | 278 |
258 def _try_render_page(self, app, route, route_metadata, page_num, req_path): | |
259 # Match the route to an actual factory. | |
260 taxonomy_info = None | |
261 source = app.getSource(route.source_name) | |
262 if route.taxonomy_name is None: | |
263 factory = source.findPageFactory(route_metadata, MODE_PARSING) | |
264 if factory is None: | |
265 raise NotFound("No path found for '%s' in source '%s'." % | |
266 (req_path, source.name)) | |
267 else: | |
268 taxonomy = app.getTaxonomy(route.taxonomy_name) | |
269 tax_terms = route.getTaxonomyTerms(route_metadata) | |
270 taxonomy_info = (taxonomy, tax_terms) | |
271 | |
272 tax_page_ref = taxonomy.getPageRef(source) | |
273 factory = tax_page_ref.getFactory() | |
274 | |
275 # Build the page. | |
276 page = factory.buildPage() | |
277 # We force the rendering of the page because it could not have | |
278 # changed, but include pages that did change. | |
279 qp = QualifiedPage(page, route, route_metadata) | |
280 render_ctx = PageRenderingContext(qp, | |
281 page_num=page_num, | |
282 force_render=True) | |
283 if taxonomy_info is not None: | |
284 _, tax_terms = taxonomy_info | |
285 render_ctx.setTaxonomyFilter(tax_terms, needs_slugifier=True) | |
286 | |
287 # See if this page is known to use sources. If that's the case, | |
288 # just don't use cached rendered segments for that page (but still | |
289 # use them for pages that are included in it). | |
290 uri = qp.getUri() | |
291 entry = self._page_record.getEntry(uri, page_num) | |
292 if (taxonomy_info is not None or entry is None or | |
293 entry.used_source_names): | |
294 cache_key = '%s:%s' % (uri, page_num) | |
295 app.env.rendered_segments_repository.invalidate(cache_key) | |
296 | |
297 # Render the page. | |
298 rendered_page = render_page(render_ctx) | |
299 | |
300 # Check if this page is a taxonomy page that actually doesn't match | |
301 # anything. | |
302 if taxonomy_info is not None: | |
303 paginator = rendered_page.data.get('pagination') | |
304 if (paginator and paginator.is_loaded and | |
305 len(paginator.items) == 0): | |
306 taxonomy = taxonomy_info[0] | |
307 message = ("This URL matched a route for taxonomy '%s' but " | |
308 "no pages have been found to have it. This page " | |
309 "won't be generated by a bake." % taxonomy.name) | |
310 raise NotFound(message) | |
311 | |
312 # Remember stuff for next time. | |
313 if entry is None: | |
314 entry = ServeRecordPageEntry(req_path, page_num) | |
315 self._page_record.addEntry(entry) | |
316 for p, pinfo in render_ctx.render_passes.items(): | |
317 entry.used_source_names |= pinfo.used_source_names | |
318 | |
319 # Ok all good. | |
320 return rendered_page | |
321 | |
322 def _handle_error(self, exception, environ, start_response): | 279 def _handle_error(self, exception, environ, start_response): |
323 code = 500 | 280 code = 500 |
324 if isinstance(exception, HTTPException): | 281 if isinstance(exception, HTTPException): |
325 code = exception.code | 282 code = exception.code |
326 | 283 |
352 inner_ex = exception.__context__ | 309 inner_ex = exception.__context__ |
353 exception = inner_ex | 310 exception = inner_ex |
354 return desc | 311 return desc |
355 | 312 |
356 | 313 |
357 class RouteNotFoundError(Exception): | |
358 pass | |
359 | |
360 | |
361 class SourceNotFoundError(Exception): | |
362 pass | |
363 | |
364 | |
365 def find_routes(routes, uri): | |
366 res = [] | |
367 tax_res = [] | |
368 for route in routes: | |
369 metadata = route.matchUri(uri) | |
370 if metadata is not None: | |
371 if route.is_taxonomy_route: | |
372 tax_res.append((route, metadata)) | |
373 else: | |
374 res.append((route, metadata)) | |
375 return res + tax_res | |
376 | |
377 | |
378 class ErrorMessageLoader(FileSystemLoader): | 314 class ErrorMessageLoader(FileSystemLoader): |
379 def __init__(self): | 315 def __init__(self): |
380 base_dir = os.path.join(RESOURCES_DIR, 'messages') | 316 base_dir = os.path.join(RESOURCES_DIR, 'messages') |
381 super(ErrorMessageLoader, self).__init__(base_dir) | 317 super(ErrorMessageLoader, self).__init__(base_dir) |
382 | 318 |