[3] | 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 |
---|