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')