Mercurial > piecrust2
comparison piecrust/data/builder.py @ 440:32c7c2d219d2
performance: Refactor how data is managed to reduce copying.
* Make use of `collections.abc.Mapping` to better identify things that are
supposed to look like dictionaries.
* Instead of handling "overlay" of data in a dict tree in each different data
object, make all objects `Mapping`s and handle merging at a higher level
with the new `MergedMapping` object.
* Since this new object is read-only, remove the need for deep-copying of
app and page configurations.
* Split data classes into separate modules.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Sun, 28 Jun 2015 08:22:39 -0700 |
parents | f1b759c188b0 |
children | 93b656f0af54 |
comparison
equal
deleted
inserted
replaced
439:c0700c6d9545 | 440:32c7c2d219d2 |
---|---|
1 import re | |
2 import time | |
3 import copy | |
4 import logging | 1 import logging |
5 from werkzeug.utils import cached_property | 2 from werkzeug.utils import cached_property |
6 from piecrust import APP_VERSION | |
7 from piecrust.configuration import merge_dicts | |
8 from piecrust.data.assetor import Assetor | 3 from piecrust.data.assetor import Assetor |
9 from piecrust.data.base import LazyPageConfigData | 4 from piecrust.data.base import MergedMapping |
10 from piecrust.data.debug import build_debug_info | |
11 from piecrust.data.linker import PageLinkerData | 5 from piecrust.data.linker import PageLinkerData |
6 from piecrust.data.pagedata import PageData | |
12 from piecrust.data.paginator import Paginator | 7 from piecrust.data.paginator import Paginator |
8 from piecrust.data.piecrustdata import PieCrustData | |
9 from piecrust.data.providersdata import DataProvidersData | |
13 from piecrust.uriutil import split_sub_uri | 10 from piecrust.uriutil import split_sub_uri |
14 | 11 |
15 | 12 |
16 logger = logging.getLogger(__name__) | 13 logger = logging.getLogger(__name__) |
17 | 14 |
34 | 31 |
35 def build_page_data(ctx): | 32 def build_page_data(ctx): |
36 app = ctx.app | 33 app = ctx.app |
37 page = ctx.page | 34 page = ctx.page |
38 first_uri, _ = split_sub_uri(app, ctx.uri) | 35 first_uri, _ = split_sub_uri(app, ctx.uri) |
36 pgn_source = ctx.pagination_source or get_default_pagination_source(page) | |
39 | 37 |
40 pc_data = PieCrustData() | 38 pc_data = PieCrustData() |
41 config_data = LazyPageConfigData(page) | 39 config_data = PageData(page, ctx) |
42 pgn_source = ctx.pagination_source or get_default_pagination_source(page) | |
43 paginator = Paginator(page, pgn_source, | 40 paginator = Paginator(page, pgn_source, |
44 page_num=ctx.page_num, | 41 page_num=ctx.page_num, |
45 pgn_filter=ctx.pagination_filter) | 42 pgn_filter=ctx.pagination_filter) |
46 assetor = Assetor(page, first_uri) | 43 assetor = Assetor(page, first_uri) |
47 linker = PageLinkerData(page.source, page.rel_path) | 44 linker = PageLinkerData(page.source, page.rel_path) |
51 'assets': assetor, | 48 'assets': assetor, |
52 'pagination': paginator, | 49 'pagination': paginator, |
53 'family': linker | 50 'family': linker |
54 } | 51 } |
55 | 52 |
56 for k, v in page.source_metadata.items(): | |
57 config_data.mapValue(k, copy.deepcopy(v)) | |
58 config_data.mapValue('url', ctx.uri, override_existing=True) | |
59 config_data.mapValue('timestamp', time.mktime(page.datetime.timetuple()), | |
60 override_existing=True) | |
61 date_format = app.config.get('site/date_format') | |
62 if date_format: | |
63 config_data.mapValue('date', page.datetime.strftime(date_format), | |
64 override_existing=True) | |
65 | |
66 #TODO: handle slugified taxonomy terms. | 53 #TODO: handle slugified taxonomy terms. |
67 | 54 |
68 site_data = build_site_data(page) | 55 site_data = app.config.getAll() |
69 merge_dicts(data, site_data) | 56 providers_data = DataProvidersData(page) |
57 data = MergedMapping([data, providers_data, site_data]) | |
70 | 58 |
71 # Do this at the end because we want all the data to be ready to be | 59 # Do this at the end because we want all the data to be ready to be |
72 # displayed in the debugger window. | 60 # displayed in the debugger window. |
73 if (app.config.get('site/show_debug_info') and | 61 if (app.config.get('site/show_debug_info') and |
74 not app.config.get('baker/is_baking')): | 62 not app.config.get('baker/is_baking')): |
80 def build_layout_data(page, page_data, contents): | 68 def build_layout_data(page, page_data, contents): |
81 for name, txt in contents.items(): | 69 for name, txt in contents.items(): |
82 if name in page_data: | 70 if name in page_data: |
83 logger.warning("Content segment '%s' will hide existing data." % | 71 logger.warning("Content segment '%s' will hide existing data." % |
84 name) | 72 name) |
85 page_data[name] = txt | 73 page_data._prependMapping(contents) |
86 | |
87 | |
88 class PieCrustData(object): | |
89 debug_render = ['version', 'url', 'branding', 'debug_info'] | |
90 debug_render_invoke = ['version', 'url', 'branding', 'debug_info'] | |
91 debug_render_redirect = {'debug_info': '_debugRenderDebugInfo'} | |
92 | |
93 def __init__(self): | |
94 self.version = APP_VERSION | |
95 self.url = 'http://bolt80.com/piecrust/' | |
96 self.branding = 'Baked with <em><a href="%s">PieCrust</a> %s</em>.' % ( | |
97 'http://bolt80.com/piecrust/', APP_VERSION) | |
98 self._page = None | |
99 self._data = None | |
100 | |
101 @property | |
102 def debug_info(self): | |
103 if self._page is not None and self._data is not None: | |
104 try: | |
105 return build_debug_info(self._page, self._data) | |
106 except Exception as ex: | |
107 logger.exception(ex) | |
108 return ('An error occured while generating debug info. ' | |
109 'Please check the logs.') | |
110 return '' | |
111 | |
112 def _enableDebugInfo(self, page, data): | |
113 self._page = page | |
114 self._data = data | |
115 | |
116 def _debugRenderDebugInfo(self): | |
117 return "The very thing you're looking at!" | |
118 | |
119 | |
120 re_endpoint_sep = re.compile(r'[\/\.]') | |
121 | |
122 | |
123 def build_site_data(page): | |
124 app = page.app | |
125 data = app.config.getDeepcopy(app.debug) | |
126 for source in app.sources: | |
127 endpoint_bits = re_endpoint_sep.split(source.data_endpoint) | |
128 endpoint = data | |
129 for e in endpoint_bits[:-1]: | |
130 if e not in endpoint: | |
131 endpoint[e] = {} | |
132 endpoint = endpoint[e] | |
133 user_data = endpoint.get(endpoint_bits[-1]) | |
134 provider = source.buildDataProvider(page, user_data) | |
135 endpoint[endpoint_bits[-1]] = provider | |
136 return data | |
137 | 74 |
138 | 75 |
139 def get_default_pagination_source(page): | 76 def get_default_pagination_source(page): |
140 app = page.app | 77 app = page.app |
141 source_name = page.config.get('source') or page.config.get('blog') | 78 source_name = page.config.get('source') or page.config.get('blog') |