Mercurial > piecrust2
comparison piecrust/appconfig.py @ 666:81d9c3a3a0b5
internal: Get rid of the whole "sub cache" business.
* Compute cache keys up front, so the cache directory is only chosen once.
* Buffer up config variants to apply before loading the config. Makes it
possible to cache variant-resulting configs, too.
* Make a factory class to reuse the logic that creates the `PieCrust` object
correctly for multi-process workers and such.
* Add a test.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Thu, 03 Mar 2016 08:22:41 -0800 |
parents | 3ceeca7bb71c |
children | 3df808b133f8 |
comparison
equal
deleted
inserted
replaced
665:5dc13c816045 | 666:81d9c3a3a0b5 |
---|---|
12 DEFAULT_FORMAT, DEFAULT_TEMPLATE_ENGINE, DEFAULT_POSTS_FS, | 12 DEFAULT_FORMAT, DEFAULT_TEMPLATE_ENGINE, DEFAULT_POSTS_FS, |
13 DEFAULT_DATE_FORMAT, DEFAULT_THEME_SOURCE) | 13 DEFAULT_DATE_FORMAT, DEFAULT_THEME_SOURCE) |
14 from piecrust.cache import NullCache | 14 from piecrust.cache import NullCache |
15 from piecrust.configuration import ( | 15 from piecrust.configuration import ( |
16 Configuration, ConfigurationError, ConfigurationLoader, | 16 Configuration, ConfigurationError, ConfigurationLoader, |
17 merge_dicts, visit_dict) | 17 get_dict_value, set_dict_value, merge_dicts, visit_dict) |
18 from piecrust.sources.base import REALM_USER, REALM_THEME | 18 from piecrust.sources.base import REALM_USER, REALM_THEME |
19 | 19 |
20 | 20 |
21 logger = logging.getLogger(__name__) | 21 logger = logging.getLogger(__name__) |
22 | 22 |
23 | 23 |
24 class VariantNotFoundError(Exception): | 24 class VariantNotFoundError(Exception): |
25 def __init__(self, variant_path, message=None): | 25 def __init__(self, variant_name, message=None): |
26 super(VariantNotFoundError, self).__init__( | 26 super(VariantNotFoundError, self).__init__( |
27 message or ("No such configuration variant: %s" % | 27 message or ("No such configuration variant: %s" % |
28 variant_path)) | 28 variant_name)) |
29 | |
30 | |
31 def _make_variant_fixup(variant_name, raise_if_not_found): | |
32 def _variant_fixup(index, config): | |
33 if index != -1: | |
34 return | |
35 try: | |
36 try: | |
37 v = get_dict_value(config, 'variants/%s' % variant_name) | |
38 except KeyError: | |
39 raise VariantNotFoundError(variant_name) | |
40 if not isinstance(v, dict): | |
41 raise VariantNotFoundError( | |
42 variant_name, | |
43 "Configuration variant '%s' is not an array. " | |
44 "Check your configuration file." % variant_name) | |
45 merge_dicts(config, v) | |
46 except VariantNotFoundError: | |
47 if raise_if_not_found: | |
48 raise | |
49 | |
50 return _variant_fixup | |
29 | 51 |
30 | 52 |
31 class PieCrustConfiguration(Configuration): | 53 class PieCrustConfiguration(Configuration): |
32 def __init__(self, paths=None, cache=None, values=None, validate=True, | 54 def __init__(self, paths=None, cache=None, values=None, validate=True, |
33 theme_config=False): | 55 theme_config=False): |
34 super(PieCrustConfiguration, self).__init__() | 56 super(PieCrustConfiguration, self).__init__() |
35 self.paths = paths | 57 self._paths = paths |
36 self.cache = cache or NullCache() | 58 self._cache = cache or NullCache() |
37 self.fixups = [] | 59 self._fixups = [] |
38 self.theme_config = theme_config | 60 self.theme_config = theme_config |
39 # Set the values after we set the rest, since our validation needs | 61 # Set the values after we set the rest, since our validation needs |
40 # our attributes. | 62 # our attributes. |
41 if values: | 63 if values: |
42 self.setAll(values, validate=validate) | 64 self.setAll(values, validate=validate) |
43 | 65 |
44 def applyVariant(self, variant_path, raise_if_not_found=True): | 66 def addFixup(self, f): |
45 variant = self.get(variant_path) | 67 self._ensureNotLoaded() |
46 if variant is None: | 68 self._fixups.append(f) |
47 if raise_if_not_found: | 69 |
48 raise VariantNotFoundError(variant_path) | 70 def addPath(self, p, first=False): |
49 return | 71 self._ensureNotLoaded() |
50 if not isinstance(variant, dict): | 72 if not first: |
51 raise VariantNotFoundError( | 73 self._paths.append(p) |
52 variant_path, | 74 else: |
53 "Configuration variant '%s' is not an array. " | 75 self._paths.insert(0, p) |
54 "Check your configuration file." % variant_path) | 76 |
55 self.merge(variant) | 77 def addVariant(self, variant_path, raise_if_not_found=True): |
78 self._ensureNotLoaded() | |
79 if os.path.isfile(variant_path): | |
80 self.addPath(variant_path) | |
81 else: | |
82 name, _ = os.path.splitext(os.path.basename(variant_path)) | |
83 fixup = _make_variant_fixup(name, raise_if_not_found) | |
84 self.addFixup(fixup) | |
85 | |
86 logger.warning( | |
87 "Configuration variants should now be `.yml` files located " | |
88 "in the `configs/` directory of your website.") | |
89 logger.warning( | |
90 "Variants defined in the site configuration will be " | |
91 "deprecated in a future version of PieCrust.") | |
92 | |
93 def addVariantValue(self, path, value): | |
94 def _fixup(index, config): | |
95 set_dict_value(config, path, value) | |
96 self.addFixup(_fixup) | |
97 | |
98 def _ensureNotLoaded(self): | |
99 if self._values is not None: | |
100 raise Exception("The configurations has been loaded.") | |
56 | 101 |
57 def _load(self): | 102 def _load(self): |
58 if self.paths is None: | 103 if self._paths is None: |
59 self._values = self._validateAll({}) | 104 self._values = self._validateAll({}) |
60 return | 105 return |
61 | 106 |
62 path_times = [os.path.getmtime(p) for p in self.paths] | 107 path_times = [os.path.getmtime(p) for p in self._paths] |
63 | 108 |
64 cache_key_hash = hashlib.md5( | 109 cache_key_hash = hashlib.md5( |
65 ("version=%s&cache=%d" % ( | 110 ("version=%s&cache=%d" % ( |
66 APP_VERSION, CACHE_VERSION)).encode('utf8')) | 111 APP_VERSION, CACHE_VERSION)).encode('utf8')) |
67 for p in self.paths: | 112 for p in self._paths: |
68 cache_key_hash.update(("&path=%s" % p).encode('utf8')) | 113 cache_key_hash.update(("&path=%s" % p).encode('utf8')) |
69 cache_key = cache_key_hash.hexdigest() | 114 cache_key = cache_key_hash.hexdigest() |
70 | 115 |
71 if self.cache.isValid('config.json', path_times): | 116 if self._cache.isValid('config.json', path_times): |
72 logger.debug("Loading configuration from cache...") | 117 logger.debug("Loading configuration from cache...") |
73 config_text = self.cache.read('config.json') | 118 config_text = self._cache.read('config.json') |
74 self._values = json.loads( | 119 self._values = json.loads( |
75 config_text, | 120 config_text, |
76 object_pairs_hook=collections.OrderedDict) | 121 object_pairs_hook=collections.OrderedDict) |
77 | 122 |
78 actual_cache_key = self._values.get('__cache_key') | 123 actual_cache_key = self._values.get('__cache_key') |
80 self._values['__cache_valid'] = True | 125 self._values['__cache_valid'] = True |
81 return | 126 return |
82 logger.debug("Outdated cache key '%s' (expected '%s')." % ( | 127 logger.debug("Outdated cache key '%s' (expected '%s')." % ( |
83 actual_cache_key, cache_key)) | 128 actual_cache_key, cache_key)) |
84 | 129 |
85 logger.debug("Loading configuration from: %s" % self.paths) | 130 logger.debug("Loading configuration from: %s" % self._paths) |
86 values = {} | 131 values = {} |
87 try: | 132 try: |
88 for i, p in enumerate(self.paths): | 133 for i, p in enumerate(self._paths): |
89 with open(p, 'r', encoding='utf-8') as fp: | 134 with open(p, 'r', encoding='utf-8') as fp: |
90 loaded_values = yaml.load( | 135 loaded_values = yaml.load( |
91 fp.read(), | 136 fp.read(), |
92 Loader=ConfigurationLoader) | 137 Loader=ConfigurationLoader) |
93 if loaded_values is None: | 138 if loaded_values is None: |
94 loaded_values = {} | 139 loaded_values = {} |
95 for fixup in self.fixups: | 140 for fixup in self._fixups: |
96 fixup(i, loaded_values) | 141 fixup(i, loaded_values) |
97 merge_dicts(values, loaded_values) | 142 merge_dicts(values, loaded_values) |
98 | 143 |
99 for fixup in self.fixups: | 144 for fixup in self._fixups: |
100 fixup(len(self.paths), values) | 145 fixup(-1, values) |
101 | 146 |
102 self._values = self._validateAll(values) | 147 self._values = self._validateAll(values) |
103 except Exception as ex: | 148 except Exception as ex: |
104 raise Exception("Error loading configuration from: %s" % | 149 raise Exception("Error loading configuration from: %s" % |
105 ', '.join(self.paths)) from ex | 150 ', '.join(self._paths)) from ex |
106 | 151 |
107 logger.debug("Caching configuration...") | 152 logger.debug("Caching configuration...") |
108 self._values['__cache_key'] = cache_key | 153 self._values['__cache_key'] = cache_key |
109 config_text = json.dumps(self._values) | 154 config_text = json.dumps(self._values) |
110 self.cache.write('config.json', config_text) | 155 self._cache.write('config.json', config_text) |
111 | 156 |
112 self._values['__cache_valid'] = False | 157 self._values['__cache_valid'] = False |
113 | 158 |
114 def _validateAll(self, values): | 159 def _validateAll(self, values): |
115 if values is None: | 160 if values is None: |