1
2 __docformat__ = 'epytext'
3 __doc__ = """Atom Publising Store loader
4
5 Synopsis
6 --------
7
8 The process of creating an Atom Publishing store is
9 a repetitive task that is common to all projects.
10
11 The loader module offers the possibility to automate
12 this task by putting required information in a
13 configuration file and let the `loader`function
14 generates the store from the configuration settings.
15
16 Consider the following:
17
18 >>> from amplee.loader import loader
19 >>> service, config = loader('/my/config.cfg')
20
21 The ``service`` object returned is an instance of
22 ``amplee.atompub.service.AtomPubService`` and is your
23 reference to all the related objects.
24
25 The ``config`` object is an instance of
26 ``amplee.loader.Config`` and is the representation
27 of your configuration file.
28
29 """
30
31 import sys
32 import os.path
33 import imp
34 import re
35 import ConfigParser
36 from warnings import warn
37
38 warn('The INI loader will be deprecated in a future release of amplee.', DeprecationWarning)
39
40 from amplee.atompub.store import *
41 from amplee.atompub.service import *
42 from amplee.atompub.workspace import *
43 from amplee.atompub.collection import *
44 from amplee.utils import strip_list
45
46 __all__ = ['loader', 'Config']
47
49 """Represents an INI file as a tree of Config instances
50
51 Say you have C{test.conf}:
52
53 [general]
54 verbose = True
55
56 >>> from amplee.loader import Config
57 >>> config = Config()
58 >>> config.from_ini('test.conf')
59 >>> print config.general.verbose
60 >>> True
61
62 Each loaded value is an unicode object except the following strings:
63 - 'True' --> True
64 - 'False' --> False
65 - 'None' --> None
66
67 An empty string will not be transformed into a None though.
68 """
69 - def from_ini(self, filepath, encoding='ISO-8859-1'):
70 """Loads the configuration file into the current instance
71 of L{Config}.
72
73 >>> from amplee.loader import Config
74 >>> conf = Config()
75 >>> conf.from_ini('./my.conf')
76
77 @type filepath: string
78 @param filepath: path to the configuration file.
79 @type encoding: string
80 @param encoding: indicates how to decode each value.
81 """
82 config = ConfigParser.ConfigParser()
83 config.readfp(file(filepath, 'rb'))
84
85 for section in config.sections():
86 section_prop = Config()
87 section_prop.keys = []
88 setattr(self, section, section_prop)
89 for option in config.options(section):
90 section_prop.keys.append(option)
91 value = self._convert_type(config.get(section, option).decode(encoding))
92 setattr(section_prop, option, value)
93
94 - def get(self, section, option, default=None, raise_error=False):
95 """Retrieve the value for a particular node in the configuration tree.
96
97 >>> print config.get('general', 'verbose', default=1)
98
99 but also:
100
101 >>> try:
102 >>> config.get('general', 'verbose', raise_error=True)
103 >>> except AttributeError:
104 >>> print "Could not find 'general.verbose'"
105
106 @type section: string
107 @param section: name of the parent node of which L{option} is a child.
108 @type option: string
109 @param option: name of the node to lookup.
110 @type default: object
111 @param default: value when the node was not found.
112 This applies when L{raise_error} is C{False}, which is the default.
113 @type raise_error: bool
114 @param raise_error: set to C{True} an L{AttributeError}
115 exception will be raised if the node is not found.
116 @rtype: object
117 @return: the retrieved value or default.
118 """
119 if hasattr(self, section):
120 obj = getattr(self, section, None)
121 if obj and hasattr(obj, option):
122 return getattr(obj, option, default)
123
124 if raise_error:
125 raise AttributeError, "%s %s" % (section, option)
126
127 return default
128
130 """Do dummy conversion of the string 'True', 'False' and 'None'
131 into their object equivalent"""
132 if value == 'True':
133 return True
134 elif value == 'False':
135 return False
136 elif value == 'None':
137 return None
138 return value
139
141 return getattr(self, section, None)
142
144 """Returns a dictionnary of all nodes which parent is the section.
145
146 @type section: string
147 @param section: name of the parent node to look for.
148 @type capitalize: bool
149 @param capitalize: indicates if the keys of the
150 dictionnary should start with a capital letter.
151 @rtype: dict
152 @return: dictionnary of all the nodes which parent is the
153 specified section.
154 """
155 values = {}
156 if hasattr(self, section):
157 obj = getattr(self, section, None)
158 for key in obj.keys:
159 if hasattr(obj, key):
160 if capitalize:
161 values[key.capitalize()] = getattr(obj, key, None)
162 else:
163 values[key] = getattr(obj, key, None)
164
165 return values
166
167 _module_callable_regex = re.compile('module:(.*),callable:(.*)')
168 _module_symbol_regex = re.compile('module:(.*),symbol:(.*)')
169
171 """Initializes a storage as described in the configuration object.
172
173 [svn_storage]
174 repository_uri = file:///var/repo
175 working_copy_path = /home/my/copy
176 username = test
177 password =
178
179 >>> from amplee.loader import loader, Config
180 >>> conf = Config()
181 >>> conf.from_ini('./my.conf')
182 >>> storage = loader.init_storage(conf, 'svn_storage')
183
184 @type config: L{Conf}
185 @param config: instance of a configuration object
186 @type storage: string
187 @param storage: section name of the storage to load.
188 @type base_path: string
189 @param base_path: only required when your configuration
190 settings use relative path in their values.
191 @rtype: object
192 @return: instance of the loaded storage
193 """
194 if storage.startswith('fs_storage'):
195 from amplee.storage import storefs
196 root_dir = config.get(storage, 'base_path')
197 if base_path and not os.path.isabs(root_dir):
198 root_dir = os.path.join(base_path, root_dir)
199 enable_lock = config.get(storage, 'enable_lock', False)
200 encoding = config.get(storage, 'encoding', 'utf-8')
201 return storefs.FilesystemStorage(root_dir, enable_lock, encoding=encoding)
202 elif storage.startswith('svn_storage'):
203 from amplee.storage import storesvn
204 repository_uri = config.get(storage, 'repository_uri')
205 working_copy_path = config.get(storage, 'working_copy_path')
206 if base_path:
207 working_copy_path = os.path.join(base_path, working_copy_path)
208 username = config.get(storage, 'username', '')
209 password = config.get(storage, 'password', '')
210 if username == '': username = None
211 if password == '': password = None
212 return storesvn.SubversionStorage(repository_uri, working_copy_path, username, password)
213 elif storage.startswith('zodb_storage'):
214 from ZODB import DB
215 from amplee.storage import storezodb
216 fs_type = config.get(storage, 'fs_type', 'filestorage')
217 if fs_type == 'filestorage':
218 from ZODB import FileStorage
219 storage_path = config.get(storage, 'fs_path')
220 if base_path:
221 storage_path = os.path.join(base_path, storage_path)
222 db = DB(FileStorage.FileStorage(storage_path))
223 elif fs_type == 'clientstorage':
224 from ZEO import ClientStorage
225 db = DB(ClientStorage.ClientStorage(config.get(storage, 'address')))
226 return storezodb.ZODBStorage(db, config.get(storage, 'top_level_node_name'))
227 elif storage.startswith('dejavu_storage'):
228 from amplee.storage import storedejavu
229 capitalize = config.get(storage, 'capitalize', True)
230 conf = config.get_all_values(storage, capitalize=capitalize)
231 return storedejavu.DejavuStorage(config.get(storage, 'db_type'), conf)
232 elif storage.startswith('sqlite_storage'):
233 from amplee.storage import storesqlite3
234 return storesqlite3.SQLite3Storage(connection=config.get(storage, 'connection'))
235 elif storage.startswith('s3_storage'):
236 from amplee.storage import stores3
237 aws_access_key_id = config.get(storage, 'access_key')
238 aws_secret_access_key = config.get(storage, 'private_key')
239 unique_prefix = config.get(storage, 'bucket_unique_prefix')
240 encoding = config.get(storage, 'encoding', 'utf-8')
241 separator = config.get(storage, 'separator', '_')
242 aws_key_lookup = config.get(storage, 'aws_key_lookup', None)
243 aws_file_path = None
244 if aws_key_lookup:
245 aws_key_lookup = _load_callable(aws_key_lookup, base_path)
246 aws_file_path = config.get(storage, 'aws_file_path', None)
247 return stores3.S3Storage(aws_access_key_id, aws_secret_access_key,
248 unique_prefix, encoding=encoding,
249 separator=separator, aws_key_lookup=aws_key_lookup,
250 aws_file_path=aws_file_path)
251 elif storage.startswith('tar_storage'):
252 from amplee.storage import storetar
253 root_dir = config.get(storage, 'base_path')
254 if base_path and not os.path.isabs(root_dir):
255 root_dir = os.path.join(base_path, root_dir)
256 compression = config.get(storage, 'compression', 'gz')
257 encoding = config.get(storage, 'encoding', 'utf-8')
258 return storetar.TarFileStorage(root_dir, compression=compression, encoding=encoding)
259 elif storage.startswith('nonbuiltin_storage'):
260 storage_module = config.get(storage, 'storage_module')
261 storage_class = config.get(storage, 'storage_class')
262 directory, name = os.path.split(storage_module)
263 if base_path:
264 directory = os.path.join(base_path, directory)
265
266 file, filename, description = imp.find_module(name, [directory])
267 mod = imp.load_module(name, file, filename, description)
268 mod_class = getattr(mod_class, storage_class, None)
269 if not mod_class:
270 raise RuntimeError, "could not load non built-in storage %s" % storage
271
272 return mod_class(config=getattr(config, storage, None))
273
275 """Initializes the Atom Publishing Protocol workspaces from the
276 provided service.
277
278 @type config: L{Conf}
279 @param config: instance of a configuration object
280 @type service: L{AtomPubService}
281 @param service: service instance
282 @type base_path: string
283 @param base_path: if provided, every relative path will be resolved to that
284 base path.
285 """
286 workspaces = strip_list(config.get('store', 'workspaces', '').split(','))
287 for workspace_name in workspaces:
288 name = config.get(workspace_name, 'name')
289 title = config.get(workspace_name, 'title')
290 xml_attrs = get_xml_attribs(config, workspace_name)
291 workspace = AtomPubWorkspace(service, name, title=title, xml_attrs=xml_attrs)
292 init_collections(config, workspace_name, workspace, base_path)
293
295 """Returns a dictionary of xml attributes with their associated value."""
296 xml_attrs = config.get(section_name, 'xml_attrs', None)
297 if xml_attrs:
298 attrs = strip_list(xml_attrs.split(';'))
299 xml_attrs = {}
300 for attr in attrs:
301 key, value = attr.split(',')
302 xml_attrs[key] = value
303 return xml_attrs or {}
304
306 """Initializes the Atom Publishing Protocol collections from the
307 provided workspace.
308
309 @type config: L{Conf}
310 @param config: instance of a configuration object
311 @type workspace_name: string
312 @param workspace_name: name of the section associated with the parent workspace
313 @type workspace: L{AtomPubWorkspace}
314 @param workspace: loaded workspace
315 @type base_path: string
316 @param base_path: if provided, every relative path will be resolved to that
317 base path.
318 """
319 collections = strip_list(config.get(workspace_name, 'collections', '').split(','))
320 for collection_name in collections:
321 base_uri = config.get(collection_name, 'base_uri', u'')
322 id = config.get(collection_name, 'name', raise_error=True)
323 title = config.get(collection_name, 'title', raise_error=True)
324 base_edit_uri = config.get(collection_name, 'base_edit_uri')
325 base_media_edit_uri = config.get(collection_name, 'base_media_edit_uri', None)
326 accept_media_types = config.get(collection_name, 'accept_media_types', [])
327 editable_media_types = config.get(collection_name, 'editable_media_types', None)
328 member_media_type = config.get(collection_name, 'member_media_type', u'application/atom+xml;type=entry')
329 member_extension = config.get(collection_name, 'member_media_extension', u'atom')
330 if accept_media_types:
331 accept_media_types = strip_list(accept_media_types.split(','))
332 if editable_media_types:
333 editable_media_types = strip_list(editable_media_types.split(','))
334 favorite = config.get(collection_name, 'favorite', False)
335 fixed_categories = config.get(collection_name, 'fixed_categories', False)
336 xml_attrs = get_xml_attribs(config, collection_name)
337
338 cats = handle_categories(config, collection_name)
339
340 collection = AtomPubCollection(workspace, name_or_id=id, title=title,
341 xml_attrs=xml_attrs, favorite=favorite,
342 base_uri=base_uri, base_edit_uri=base_edit_uri,
343 base_media_edit_uri=base_media_edit_uri,
344 accept_media_types=accept_media_types,
345 fixed_categories=fixed_categories,
346 categories=cats,
347 member_media_type=member_media_type,
348 member_extension=member_extension,
349 editable_media_types=editable_media_types)
350 read_only = config.get(collection_name, 'read_only', False)
351 collection.is_read_only = read_only
352
353 set_collection_attributes(config, workspace_name, workspace, collection_name, collection, base_path)
354
357 with_cache = config.get(collection_name, 'enable_cache')
358 if with_cache == None:
359 with_cache = config.get(workspace_name, 'enable_cache', True)
360
361 if with_cache:
362 collection.enable_cache()
363
364 member_class = config.get(collection_name, 'member_class')
365 if not member_class:
366 member_class = config.get(workspace_name, 'member_class', None)
367
368 if member_class:
369 member_class = _load_symbol(member_class, base_path)
370 collection.set_member_class(member_class)
371
372 autoreload = config.get(collection_name, 'reload_members')
373 if autoreload ==<