1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) |
---|
2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php |
---|
3 | import os |
---|
4 | import re |
---|
5 | import sys |
---|
6 | import urllib |
---|
7 | from ConfigParser import ConfigParser |
---|
8 | import pkg_resources |
---|
9 | from paste.deploy.util.fixtypeerror import fix_call |
---|
10 | |
---|
11 | __all__ = ['loadapp', 'loadserver', 'loadfilter', 'appconfig'] |
---|
12 | |
---|
13 | ############################################################ |
---|
14 | ## Utility functions |
---|
15 | ############################################################ |
---|
16 | |
---|
17 | def import_string(s): |
---|
18 | return pkg_resources.EntryPoint.parse("x="+s).load(False) |
---|
19 | |
---|
20 | def _aslist(obj): |
---|
21 | """ |
---|
22 | Turn object into a list; lists and tuples are left as-is, None |
---|
23 | becomes [], and everything else turns into a one-element list. |
---|
24 | """ |
---|
25 | if obj is None: |
---|
26 | return [] |
---|
27 | elif isinstance(obj, (list, tuple)): |
---|
28 | return obj |
---|
29 | else: |
---|
30 | return [obj] |
---|
31 | |
---|
32 | def _flatten(lst): |
---|
33 | """ |
---|
34 | Flatten a nested list. |
---|
35 | """ |
---|
36 | if not isinstance(lst, (list, tuple)): |
---|
37 | return [lst] |
---|
38 | result = [] |
---|
39 | for item in lst: |
---|
40 | result.extend(_flatten(item)) |
---|
41 | return result |
---|
42 | |
---|
43 | class NicerConfigParser(ConfigParser): |
---|
44 | |
---|
45 | def __init__(self, filename, *args, **kw): |
---|
46 | ConfigParser.__init__(self, *args, **kw) |
---|
47 | self.filename = filename |
---|
48 | |
---|
49 | def defaults(self): |
---|
50 | """Return the defaults, with their values interpolated (with the |
---|
51 | defaults dict itself) |
---|
52 | |
---|
53 | Mainly to support defaults using values such as %(here)s |
---|
54 | """ |
---|
55 | defaults = ConfigParser.defaults(self).copy() |
---|
56 | for key, val in defaults.iteritems(): |
---|
57 | defaults[key] = self._interpolate('DEFAULT', key, val, defaults) |
---|
58 | return defaults |
---|
59 | |
---|
60 | def _interpolate(self, section, option, rawval, vars): |
---|
61 | try: |
---|
62 | return ConfigParser._interpolate( |
---|
63 | self, section, option, rawval, vars) |
---|
64 | except Exception, e: |
---|
65 | args = list(e.args) |
---|
66 | args[0] = 'Error in file %s, [%s] %s=%r: %s' % ( |
---|
67 | self.filename, section, option, rawval, e) |
---|
68 | e.args = tuple(args) |
---|
69 | raise |
---|
70 | |
---|
71 | ############################################################ |
---|
72 | ## Object types |
---|
73 | ############################################################ |
---|
74 | |
---|
75 | class _ObjectType(object): |
---|
76 | |
---|
77 | name = None |
---|
78 | egg_protocols = None |
---|
79 | config_prefixes = None |
---|
80 | |
---|
81 | def __init__(self): |
---|
82 | # Normalize these variables: |
---|
83 | self.egg_protocols = map(_aslist, _aslist(self.egg_protocols)) |
---|
84 | self.config_prefixes = map(_aslist, _aslist(self.config_prefixes)) |
---|
85 | |
---|
86 | def __repr__(self): |
---|
87 | return '<%s protocols=%r prefixes=%r>' % ( |
---|
88 | self.name, self.egg_protocols, self.config_prefixes) |
---|
89 | |
---|
90 | def invoke(self, context): |
---|
91 | assert context.protocol in _flatten(self.egg_protocols) |
---|
92 | return fix_call(context.object, |
---|
93 | context.global_conf, **context.local_conf) |
---|
94 | |
---|
95 | class _App(_ObjectType): |
---|
96 | |
---|
97 | name = 'application' |
---|
98 | egg_protocols = ['paste.app_factory', 'paste.composite_factory', |
---|
99 | 'paste.composit_factory'] |
---|
100 | config_prefixes = [['app', 'application'], ['composite', 'composit'], |
---|
101 | 'pipeline', 'filter-app'] |
---|
102 | |
---|
103 | def invoke(self, context): |
---|
104 | if context.protocol in ('paste.composit_factory', |
---|
105 | 'paste.composite_factory'): |
---|
106 | return fix_call(context.object, |
---|
107 | context.loader, context.global_conf, |
---|
108 | **context.local_conf) |
---|
109 | elif context.protocol == 'paste.app_factory': |
---|
110 | return fix_call(context.object, context.global_conf, **context.local_conf) |
---|
111 | else: |
---|
112 | assert 0, "Protocol %r unknown" % context.protocol |
---|
113 | |
---|
114 | APP = _App() |
---|
115 | |
---|
116 | class _Filter(_ObjectType): |
---|
117 | name = 'filter' |
---|
118 | egg_protocols = [['paste.filter_factory', 'paste.filter_app_factory']] |
---|
119 | config_prefixes = ['filter'] |
---|
120 | |
---|
121 | def invoke(self, context): |
---|
122 | if context.protocol == 'paste.filter_factory': |
---|
123 | return fix_call(context.object, |
---|
124 | context.global_conf, **context.local_conf) |
---|
125 | elif context.protocol == 'paste.filter_app_factory': |
---|
126 | def filter_wrapper(wsgi_app): |
---|
127 | # This should be an object, so it has a nicer __repr__ |
---|
128 | return fix_call(context.object, |
---|
129 | wsgi_app, context.global_conf, |
---|
130 | **context.local_conf) |
---|
131 | return filter_wrapper |
---|
132 | else: |
---|
133 | assert 0, "Protocol %r unknown" % context.protocol |
---|
134 | |
---|
135 | FILTER = _Filter() |
---|
136 | |
---|
137 | class _Server(_ObjectType): |
---|
138 | name = 'server' |
---|
139 | egg_protocols = [['paste.server_factory', 'paste.server_runner']] |
---|
140 | config_prefixes = ['server'] |
---|
141 | |
---|
142 | def invoke(self, context): |
---|
143 | if context.protocol == 'paste.server_factory': |
---|
144 | return fix_call(context.object, |
---|
145 | context.global_conf, **context.local_conf) |
---|
146 | elif context.protocol == 'paste.server_runner': |
---|
147 | def server_wrapper(wsgi_app): |
---|
148 | # This should be an object, so it has a nicer __repr__ |
---|
149 | return fix_call(context.object, |
---|
150 | wsgi_app, context.global_conf, |
---|
151 | **context.local_conf) |
---|
152 | return server_wrapper |
---|
153 | else: |
---|
154 | assert 0, "Protocol %r unknown" % context.protocol |
---|
155 | |
---|
156 | SERVER = _Server() |
---|
157 | |
---|
158 | # Virtual type: (@@: There's clearly something crufty here; |
---|
159 | # this probably could be more elegant) |
---|
160 | class _PipeLine(_ObjectType): |
---|
161 | name = 'pipeline' |
---|
162 | |
---|
163 | def invoke(self, context): |
---|
164 | app = context.app_context.create() |
---|
165 | filters = [c.create() for c in context.filter_contexts] |
---|
166 | filters.reverse() |
---|
167 | for filter in filters: |
---|
168 | app = filter(app) |
---|
169 | return app |
---|
170 | |
---|
171 | PIPELINE = _PipeLine() |
---|
172 | |
---|
173 | class _FilterApp(_ObjectType): |
---|
174 | name = 'filter_app' |
---|
175 | |
---|
176 | def invoke(self, context): |
---|
177 | next_app = context.next_context.create() |
---|
178 | filter = context.filter_context.create() |
---|
179 | return filter(next_app) |
---|
180 | |
---|
181 | FILTER_APP = _FilterApp() |
---|
182 | |
---|
183 | class _FilterWith(_App): |
---|
184 | name = 'filtered_with' |
---|
185 | |
---|
186 | def invoke(self, context): |
---|
187 | filter = context.filter_context.create() |
---|
188 | filtered = context.next_context.create() |
---|
189 | if context.next_context.object_type is APP: |
---|
190 | return filter(filtered) |
---|
191 | else: |
---|
192 | # filtering a filter |
---|
193 | def composed(app): |
---|
194 | return filter(filtered(app)) |
---|
195 | return composed |
---|
196 | |
---|
197 | FILTER_WITH = _FilterWith() |
---|
198 | |
---|
199 | ############################################################ |
---|
200 | ## Loaders |
---|
201 | ############################################################ |
---|
202 | |
---|
203 | def loadapp(uri, name=None, **kw): |
---|
204 | return loadobj(APP, uri, name=name, **kw) |
---|
205 | |
---|
206 | def loadfilter(uri, name=None, **kw): |
---|
207 | return loadobj(FILTER, uri, name=name, **kw) |
---|
208 | |
---|
209 | def loadserver(uri, name=None, **kw): |
---|
210 | return loadobj(SERVER, uri, name=name, **kw) |
---|
211 | |
---|
212 | def appconfig(uri, name=None, relative_to=None, global_conf=None): |
---|
213 | context = loadcontext(APP, uri, name=name, |
---|
214 | relative_to=relative_to, |
---|
215 | global_conf=global_conf) |
---|
216 | return context.config() |
---|
217 | |
---|
218 | _loaders = {} |
---|
219 | |
---|
220 | def loadobj(object_type, uri, name=None, relative_to=None, |
---|
221 | global_conf=None): |
---|
222 | context = loadcontext( |
---|
223 | object_type, uri, name=name, relative_to=relative_to, |
---|
224 | global_conf=global_conf) |
---|
225 | return context.create() |
---|
226 | |
---|
227 | def loadcontext(object_type, uri, name=None, relative_to=None, |
---|
228 | global_conf=None): |
---|
229 | if '#' in uri: |
---|
230 | if name is None: |
---|
231 | uri, name = uri.split('#', 1) |
---|
232 | else: |
---|
233 | # @@: Ignore fragment or error? |
---|
234 | uri = uri.split('#', 1)[0] |
---|
235 | if name is None: |
---|
236 | name = 'main' |
---|
237 | if ':' not in uri: |
---|
238 | raise LookupError("URI has no scheme: %r" % uri) |
---|
239 | scheme, path = uri.split(':', 1) |
---|
240 | scheme = scheme.lower() |
---|
241 | if scheme not in _loaders: |
---|
242 | raise LookupError( |
---|
243 | "URI scheme not known: %r (from %s)" |
---|
244 | % (scheme, ', '.join(_loaders.keys()))) |
---|
245 | return _loaders[scheme]( |
---|
246 | object_type, |
---|
247 | uri, path, name=name, relative_to=relative_to, |
---|
248 | global_conf=global_conf) |
---|
249 | |
---|
250 | def _loadconfig(object_type, uri, path, name, relative_to, |
---|
251 | global_conf): |
---|
252 | # De-Windowsify the paths: |
---|
253 | path = path.replace('\\', '/') |
---|
254 | absolute_path = True |
---|
255 | if sys.platform == 'win32': |
---|
256 | _absolute_re = re.compile(r'^[a-zA-Z]:') |
---|
257 | if not _absolute_re.search(path): |
---|
258 | absolute_path = False |
---|
259 | else: |
---|
260 | if not path.startswith('/'): |
---|
261 | absolute_path = False |
---|
262 | if not absolute_path: |
---|
263 | if not relative_to: |
---|
264 | raise ValueError( |
---|
265 | "Cannot resolve relative uri %r; no context keyword " |
---|
266 | "argument given" % uri) |
---|
267 | relative_to = relative_to.replace('\\', '/') |
---|
268 | if relative_to.endswith('/'): |
---|
269 | path = relative_to + path |
---|
270 | else: |
---|
271 | path = relative_to + '/' + path |
---|
272 | if path.startswith('///'): |
---|
273 | path = path[2:] |
---|
274 | path = urllib.unquote(path) |
---|
275 | loader = ConfigLoader(path) |
---|
276 | if global_conf: |
---|
277 | loader.update_defaults(global_conf, overwrite=False) |
---|
278 | return loader.get_context(object_type, name, global_conf) |
---|
279 | |
---|
280 | _loaders['config'] = _loadconfig |
---|
281 | |
---|
282 | def _loadegg(object_type, uri, spec, name, relative_to, |
---|
283 | global_conf): |
---|
284 | loader = EggLoader(spec) |
---|
285 | return loader.get_context(object_type, name, global_conf) |
---|
286 | |
---|
287 | _loaders['egg'] = _loadegg |
---|
288 | |
---|
289 | ############################################################ |
---|
290 | ## Loaders |
---|
291 | ############################################################ |
---|
292 | |
---|
293 | class _Loader(object): |
---|
294 | |
---|
295 | def get_app(self, name=None, global_conf=None): |
---|
296 | return self.app_context( |
---|
297 | name=name, global_conf=global_conf).create() |
---|
298 | |
---|
299 | def get_filter(self, name=None, global_conf=None): |
---|
300 | return self.filter_context( |
---|
301 | name=name, global_conf=global_conf).create() |
---|
302 | |
---|
303 | def get_server(self, name=None, global_conf=None): |
---|
304 | return self.server_context( |
---|
305 | name=name, global_conf=global_conf).create() |
---|
306 | |
---|
307 | def app_context(self, name=None, global_conf=None): |
---|
308 | return self.get_context( |
---|
309 | APP, name=name, global_conf=global_conf) |
---|
310 | |
---|
311 | def filter_context(self, name=None, global_conf=None): |
---|
312 | return self.get_context( |
---|
313 | FILTER, name=name, global_conf=global_conf) |
---|
314 | |
---|
315 | def server_context(self, name=None, global_conf=None): |
---|
316 | return self.get_context( |
---|
317 | SERVER, name=name, global_conf=global_conf) |
---|
318 | |
---|
319 | _absolute_re = re.compile(r'^[a-zA-Z]+:') |
---|
320 | def absolute_name(self, name): |
---|
321 | """ |
---|
322 | Returns true if the name includes a scheme |
---|
323 | """ |
---|
324 | if name is None: |
---|
325 | return False |
---|
326 | return self._absolute_re.search(name) |
---|
327 | |
---|
328 | class ConfigLoader(_Loader): |
---|
329 | |
---|
330 | def __init__(self, filename): |
---|
331 | self.filename = filename = filename.strip() |
---|
332 | self.parser = NicerConfigParser(self.filename) |
---|
333 | # Don't lower-case keys: |
---|
334 | self.parser.optionxform = str |
---|
335 | # Stupid ConfigParser ignores files that aren't found, so |
---|
336 | # we have to add an extra check: |
---|
337 | if not os.path.exists(filename): |
---|
338 | if filename.strip() != filename: |
---|
339 | raise OSError( |
---|
340 | "File %r not found; trailing whitespace: " |
---|
341 | "did you try to use a # on the same line as a filename? " |
---|
342 | "(comments must be on their own line)" % filename) |
---|
343 | raise OSError( |
---|
344 | "File %r not found" % filename) |
---|
345 | self.parser.read(filename) |
---|
346 | self.parser._defaults.setdefault( |
---|
347 | 'here', os.path.dirname(os.path.abspath(filename))) |
---|
348 | self.parser._defaults.setdefault( |
---|
349 | '__file__', os.path.abspath(filename)) |
---|
350 | |
---|
351 | def update_defaults(self, new_defaults, overwrite=True): |
---|
352 | for key, value in new_defaults.items(): |
---|
353 | if not overwrite and key in self.parser._defaults: |
---|
354 | continue |
---|
355 | self.parser._defaults[key] = value |
---|
356 | |
---|
357 | def get_context(self, object_type, name=None, global_conf=None): |
---|
358 | if self.absolute_name(name): |
---|
359 | return loadcontext(object_type, name, |
---|
360 | relative_to=os.path.dirname(self.filename), |
---|
361 | global_conf=global_conf) |
---|
362 | section = self.find_config_section( |
---|
363 | object_type, name=name) |
---|
364 | if global_conf is None: |
---|
365 | global_conf = {} |
---|
366 | else: |
---|
367 | global_conf = global_conf.copy() |
---|
368 | defaults = self.parser.defaults() |
---|
369 | global_conf.update(defaults) |
---|
370 | local_conf = {} |
---|
371 | global_additions = {} |
---|
372 | get_from_globals = {} |
---|
373 | for option in self.parser.options(section): |
---|
374 | if option.startswith('set '): |
---|
375 | name = option[4:].strip() |
---|
376 | global_additions[name] = global_conf[name] = ( |
---|
377 | self.parser.get(section, option)) |
---|
378 | elif option.startswith('get '): |
---|
379 | name = option[4:].strip() |
---|
380 | get_from_globals[name] = self.parser.get(section, option) |
---|
381 | else: |
---|
382 | if option in defaults: |
---|
383 | # @@: It's a global option (?), so skip it |
---|
384 | continue |
---|
385 | local_conf[option] = self.parser.get(section, option) |
---|
386 | for local_var, glob_var in get_from_globals.items(): |
---|
387 | local_conf[local_var] = global_conf[glob_var] |
---|
388 | if object_type in (APP, FILTER) and 'filter-with' in local_conf: |
---|
389 | filter_with = local_conf.pop('filter-with') |
---|
390 | else: |
---|
391 | filter_with = None |
---|
392 | if 'require' in local_conf: |
---|
393 | for spec in local_conf['require'].split(): |
---|
394 | pkg_resources.require(spec) |
---|
395 | del local_conf['require'] |
---|
396 | if section.startswith('filter-app:'): |
---|
397 | context = self._filter_app_context( |
---|
398 | object_type, section, name=name, |
---|
399 | global_conf=global_conf, local_conf=local_conf, |
---|
400 | global_additions=global_additions) |
---|
401 | elif section.startswith('pipeline:'): |
---|
402 | context = self._pipeline_app_context( |
---|
403 | object_type, section, name=name, |
---|
404 | global_conf=global_conf, local_conf=local_conf, |
---|
405 | global_additions=global_additions) |
---|
406 | elif 'use' in local_conf: |
---|
407 | context = self._context_from_use( |
---|
408 | object_type, local_conf, global_conf, global_additions, |
---|
409 | section) |
---|
410 | else: |
---|
411 | context = self._context_from_explicit( |
---|
412 | object_type, local_conf, global_conf, global_additions, |
---|
413 | section) |
---|
414 | if filter_with is not None: |
---|
415 | filter_with_context = LoaderContext( |
---|
416 | obj=None, |
---|
417 | object_type=FILTER_WITH, |
---|
418 | protocol=None, |
---|
419 | global_conf=global_conf, local_conf=local_conf, |
---|
420 | loader=self) |
---|
421 | filter_with_context.filter_context = self.filter_context( |
---|
422 | name=filter_with, global_conf=global_conf) |
---|
423 | filter_with_context.next_context = context |
---|
424 | return filter_with_context |
---|
425 | return context |
---|
426 | |
---|
427 | def _context_from_use(self, object_type, local_conf, global_conf, |
---|
428 | global_additions, section): |
---|
429 | use = local_conf.pop('use') |
---|
430 | context = self.get_context( |
---|
431 | object_type, name=use, global_conf=global_conf) |
---|
432 | context.global_conf.update(global_additions) |
---|
433 | context.local_conf.update(local_conf) |
---|
434 | if '__file__' in global_conf: |
---|
435 | # use sections shouldn't overwrite the original __file__ |
---|
436 | context.global_conf['__file__'] = global_conf['__file__'] |
---|
437 | # @@: Should loader be overwritten? |
---|
438 | context.loader = self |
---|
439 | return context |
---|
440 | |
---|
441 | def _context_from_explicit(self, object_type, local_conf, global_conf, |
---|
442 | global_addition, section): |
---|
443 | possible = [] |
---|
444 | for protocol_options in object_type.egg_protocols: |
---|
445 | for protocol in protocol_options: |
---|
446 | if protocol in local_conf: |
---|
447 | possible.append((protocol, local_conf[protocol])) |
---|
448 | break |
---|
449 | if len(possible) > 1: |
---|
450 | raise LookupError( |
---|
451 | "Multiple protocols given in section %r: %s" |
---|
452 | % (section, possible)) |
---|
453 | if not possible: |
---|
454 | raise LookupError( |
---|
455 | "No loader given in section %r" % section) |
---|
456 | found_protocol, found_expr = possible[0] |
---|
457 | del local_conf[found_protocol] |
---|
458 | value = import_string(found_expr) |
---|
459 | context = LoaderContext( |
---|
460 | value, object_type, found_protocol, |
---|
461 | global_conf, local_conf, self) |
---|
462 | return context |
---|
463 | |
---|
464 | def _filter_app_context(self, object_type, section, name, |
---|
465 | global_conf, local_conf, global_additions): |
---|
466 | if 'next' not in local_conf: |
---|
467 | raise LookupError( |
---|
468 | "The [%s] section in %s is missing a 'next' setting" |
---|
469 | % (section, self.filename)) |
---|
470 | next_name = local_conf.pop('next') |
---|
471 | context = LoaderContext(None, FILTER_APP, None, global_conf, |
---|
472 | local_conf, self) |
---|
473 | context.next_context = self.get_context( |
---|
474 | APP, next_name, global_conf) |
---|
475 | if 'use' in local_conf: |
---|
476 | context.filter_context = self._context_from_use( |
---|
477 | FILTER, local_conf, global_conf, global_additions, |
---|
478 | section) |
---|
479 | else: |
---|
480 | context.filter_context = self._context_from_explicit( |
---|
481 | FILTER, local_conf, global_conf, global_additions, |
---|
482 | section) |
---|
483 | return context |
---|
484 | |
---|
485 | def _pipeline_app_context(self, object_type, section, name, |
---|
486 | global_conf, local_conf, global_additions): |
---|
487 | if 'pipeline' not in local_conf: |
---|
488 | raise LookupError( |
---|
489 | "The [%s] section in %s is missing a 'pipeline' setting" |
---|
490 | % (section, self.filename)) |
---|
491 | pipeline = local_conf.pop('pipeline').split() |
---|
492 | if local_conf: |
---|
493 | raise LookupError( |
---|
494 | "The [%s] pipeline section in %s has extra " |
---|
495 | "(disallowed) settings: %s" |
---|
496 | % (', '.join(local_conf.keys()))) |
---|
497 | context = LoaderContext(None, PIPELINE, None, global_conf, |
---|
498 | local_conf, self) |
---|
499 | context.app_context = self.get_context( |
---|
500 | APP, pipeline[-1], global_conf) |
---|
501 | context.filter_contexts = [ |
---|
502 | self.get_context(FILTER, name, global_conf) |
---|
503 | for name in pipeline[:-1]] |
---|
504 | return context |
---|
505 | |
---|
506 | def find_config_section(self, object_type, name=None): |
---|
507 | """ |
---|
508 | Return the section name with the given name prefix (following the |
---|
509 | same pattern as ``protocol_desc`` in ``config``. It must have the |
---|
510 | given name, or for ``'main'`` an empty name is allowed. The |
---|
511 | prefix must be followed by a ``:``. |
---|
512 | |
---|
513 | Case is *not* ignored. |
---|
514 | """ |
---|
515 | possible = [] |
---|
516 | for name_options in object_type.config_prefixes: |
---|
517 | for name_prefix in name_options: |
---|
518 | found = self._find_sections( |
---|
519 | self.parser.sections(), name_prefix, name) |
---|
520 | if found: |
---|
521 | possible.extend(found) |
---|
522 | break |
---|
523 | if not possible: |
---|
524 | raise LookupError( |
---|
525 | "No section %r (prefixed by %s) found in config %s" |
---|
526 | % (name, |
---|
527 | ' or '.join(map(repr, _flatten(object_type.config_prefixes))), |
---|
528 | self.filename)) |
---|
529 | if len(possible) > 1: |
---|
530 | raise LookupError( |
---|
531 | "Ambiguous section names %r for section %r (prefixed by %s) " |
---|
532 | "found in config %s" |
---|
533 | % (possible, name, |
---|
534 | ' or '.join(map(repr, _flatten(object_type.config_prefixes))), |
---|
535 | self.filename)) |
---|
536 | return possible[0] |
---|
537 | |
---|
538 | def _find_sections(self, sections, name_prefix, name): |
---|
539 | found = [] |
---|
540 | if name is None: |
---|
541 | if name_prefix in sections: |
---|
542 | found.append(name_prefix) |
---|
543 | name = 'main' |
---|
544 | for section in sections: |
---|
545 | if section.startswith(name_prefix+':'): |
---|
546 | if section[len(name_prefix)+1:].strip() == name: |
---|
547 | found.append(section) |
---|
548 | return found |
---|
549 | |
---|
550 | |
---|
551 | class EggLoader(_Loader): |
---|
552 | |
---|
553 | def __init__(self, spec): |
---|
554 | self.spec = spec |
---|
555 | |
---|
556 | def get_context(self, object_type, name=None, global_conf=None): |
---|
557 | if self.absolute_name(name): |
---|
558 | return loadcontext(object_type, name, |
---|
559 | global_conf=global_conf) |
---|
560 | entry_point, protocol, ep_name = self.find_egg_entry_point( |
---|
561 | object_type, name=name) |
---|
562 | return LoaderContext( |
---|
563 | entry_point, |
---|
564 | object_type, |
---|
565 | protocol, |
---|
566 | global_conf or {}, {}, |
---|
567 | self, |
---|
568 | distribution=pkg_resources.get_distribution(self.spec), |
---|
569 | entry_point_name=ep_name) |
---|
570 | |
---|
571 | def find_egg_entry_point(self, object_type, name=None): |
---|
572 | """ |
---|
573 | Returns the (entry_point, protocol) for the with the given |
---|
574 | ``name``. |
---|
575 | """ |
---|
576 | if name is None: |
---|
577 | name = 'main' |
---|
578 | possible = [] |
---|
579 | for protocol_options in object_type.egg_protocols: |
---|
580 | for protocol in protocol_options: |
---|
581 | pkg_resources.require(self.spec) |
---|
582 | entry = pkg_resources.get_entry_info( |
---|
583 | self.spec, |
---|
584 | protocol, |
---|
585 | name) |
---|
586 | if entry is not None: |
---|
587 | possible.append((entry.load(), protocol, entry.name)) |
---|
588 | break |
---|
589 | if not possible: |
---|
590 | # Better exception |
---|
591 | dist = pkg_resources.get_distribution(self.spec) |
---|
592 | raise LookupError( |
---|
593 | "Entry point %r not found in egg %r (dir: %s; protocols: %s; " |
---|
594 | "entry_points: %s)" |
---|
595 | % (name, self.spec, |
---|
596 | dist.location, |
---|
597 | ', '.join(_flatten(object_type.egg_protocols)), |
---|
598 | ', '.join(_flatten([ |
---|
599 | (pkg_resources.get_entry_info(self.spec, prot, name) or {}).keys() |
---|
600 | for prot in protocol_options] or '(no entry points)')))) |
---|
601 | if len(possible) > 1: |
---|
602 | raise LookupError( |
---|
603 | "Ambiguous entry points for %r in egg %r (protocols: %s)" |
---|
604 | % (name, self.spec, ', '.join(_flatten(protocol_options)))) |
---|
605 | return possible[0] |
---|
606 | |
---|
607 | class LoaderContext(object): |
---|
608 | |
---|
609 | def __init__(self, obj, object_type, protocol, |
---|
610 | global_conf, local_conf, loader, |
---|
611 | distribution=None, entry_point_name=None): |
---|
612 | self.object = obj |
---|
613 | self.object_type = object_type |
---|
614 | self.protocol = protocol |
---|
615 | #assert protocol in _flatten(object_type.egg_protocols), ( |
---|
616 | # "Bad protocol %r; should be one of %s" |
---|
617 | # % (protocol, ', '.join(map(repr, _flatten(object_type.egg_protocols))))) |
---|
618 | self.global_conf = global_conf |
---|
619 | self.local_conf = local_conf |
---|
620 | self.loader = loader |
---|
621 | self.distribution = distribution |
---|
622 | self.entry_point_name = entry_point_name |
---|
623 | |
---|
624 | def create(self): |
---|
625 | return self.object_type.invoke(self) |
---|
626 | |
---|
627 | def config(self): |
---|
628 | conf = AttrDict(self.global_conf) |
---|
629 | conf.update(self.local_conf) |
---|
630 | conf.local_conf = self.local_conf |
---|
631 | conf.global_conf = self.global_conf |
---|
632 | conf.context = self |
---|
633 | return conf |
---|
634 | |
---|
635 | class AttrDict(dict): |
---|
636 | """ |
---|
637 | A dictionary that can be assigned to. |
---|
638 | """ |
---|
639 | pass |
---|