comparison planemo/lib/python3.7/site-packages/galaxy/util/pastescript/loadwsgi.py @ 1:56ad4e20f292 draft

"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author guerler
date Fri, 31 Jul 2020 00:32:28 -0400
parents
children
comparison
equal deleted inserted replaced
0:d30785e31577 1:56ad4e20f292
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
4 # Mostly taken from PasteDeploy and stripped down for Galaxy
5
6 import inspect
7 import os
8 import re
9 import sys
10
11 import pkg_resources
12 from six import iteritems
13 from six.moves.urllib.parse import unquote
14
15 from galaxy.util.getargspec import getfullargspec
16 from galaxy.util.properties import NicerConfigParser
17
18
19 __all__ = ('loadapp', 'loadserver', 'loadfilter', 'appconfig')
20
21 # ---- from paste.deploy.compat --------------------------------------
22
23 """Python 2<->3 compatibility module"""
24
25
26 def print_(template, *args, **kwargs):
27 template = str(template)
28 if args:
29 template = template % args
30 elif kwargs:
31 template = template % kwargs
32 sys.stdout.writelines(template)
33
34
35 if sys.version_info < (3, 0):
36 def reraise(t, e, tb):
37 exec('raise t, e, tb', dict(t=t, e=e, tb=tb))
38 else:
39 def reraise(t, e, tb):
40 exec('raise e from tb', dict(e=e, tb=tb))
41
42 # ---- from paste.deploy.util ----------------------------------------
43
44
45 def fix_type_error(exc_info, callable, varargs, kwargs):
46 """
47 Given an exception, this will test if the exception was due to a
48 signature error, and annotate the error with better information if
49 so.
50
51 Usage::
52
53 try:
54 val = callable(*args, **kw)
55 except TypeError:
56 exc_info = fix_type_error(None, callable, args, kw)
57 raise exc_info[0], exc_info[1], exc_info[2]
58 """
59 if exc_info is None:
60 exc_info = sys.exc_info()
61 if (exc_info[0] != TypeError or
62 str(exc_info[1]).find('argument') == -1 or
63 getattr(exc_info[1], '_type_error_fixed', False)):
64 return exc_info
65 exc_info[1]._type_error_fixed = True
66 argspec = inspect.formatargspec(*getfullargspec(callable))
67 args = ', '.join(map(_short_repr, varargs))
68 if kwargs and args:
69 args += ', '
70 if kwargs:
71 kwargs = sorted(kwargs.keys())
72 args += ', '.join('%s=...' % n for n in kwargs)
73 gotspec = '(%s)' % args
74 msg = '%s; got %s, wanted %s' % (exc_info[1], gotspec, argspec)
75 exc_info[1].args = (msg,)
76 return exc_info
77
78
79 def _short_repr(v):
80 v = repr(v)
81 if len(v) > 12:
82 v = v[:8] + '...' + v[-4:]
83 return v
84
85
86 def fix_call(callable, *args, **kw):
87 """
88 Call ``callable(*args, **kw)`` fixing any type errors that come out.
89 """
90 try:
91 val = callable(*args, **kw)
92 except TypeError:
93 exc_info = fix_type_error(None, callable, args, kw)
94 reraise(*exc_info)
95 return val
96
97
98 def lookup_object(spec):
99 """
100 Looks up a module or object from a some.module:func_name specification.
101 To just look up a module, omit the colon and everything after it.
102 """
103 parts, target = spec.split(':') if ':' in spec else (spec, None)
104 module = __import__(parts)
105
106 for part in parts.split('.')[1:] + ([target] if target else []):
107 module = getattr(module, part)
108
109 return module
110
111 # ---- from paste.deploy.loadwsgi ------------------------------------
112
113 ############################################################
114 # Utility functions
115 ############################################################
116
117
118 def import_string(s):
119 return pkg_resources.EntryPoint.parse("x=" + s).load(False)
120
121
122 def _aslist(obj):
123 """
124 Turn object into a list; lists and tuples are left as-is, None
125 becomes [], and everything else turns into a one-element list.
126 """
127 if obj is None:
128 return []
129 elif isinstance(obj, (list, tuple)):
130 return obj
131 else:
132 return [obj]
133
134
135 def _flatten(lst):
136 """
137 Flatten a nested list.
138 """
139 if not isinstance(lst, (list, tuple)):
140 return [lst]
141 result = []
142 for item in lst:
143 result.extend(_flatten(item))
144 return result
145
146
147 ############################################################
148 # Object types
149 ############################################################
150
151
152 class _ObjectType(object):
153
154 name = None
155 egg_protocols = None
156 config_prefixes = None
157
158 def __init__(self):
159 # Normalize these variables:
160 self.egg_protocols = [_aslist(p) for p in _aslist(self.egg_protocols)]
161 self.config_prefixes = [_aslist(p) for p in _aslist(self.config_prefixes)]
162
163 def __repr__(self):
164 return '<%s protocols=%r prefixes=%r>' % (
165 self.name, self.egg_protocols, self.config_prefixes)
166
167 def invoke(self, context):
168 assert context.protocol in _flatten(self.egg_protocols)
169 return fix_call(context.object,
170 context.global_conf, **context.local_conf)
171
172
173 class _App(_ObjectType):
174
175 name = 'application'
176 egg_protocols = ['paste.app_factory', 'paste.composite_factory',
177 'paste.composit_factory']
178 config_prefixes = [['app', 'application'], ['composite', 'composit'],
179 'pipeline', 'filter-app']
180
181 def invoke(self, context):
182 if context.protocol in ('paste.composit_factory',
183 'paste.composite_factory'):
184 return fix_call(context.object,
185 context.loader, context.global_conf,
186 **context.local_conf)
187 elif context.protocol == 'paste.app_factory':
188 return fix_call(context.object, context.global_conf, **context.local_conf)
189 else:
190 assert 0, "Protocol %r unknown" % context.protocol
191
192
193 APP = _App()
194
195
196 class _Filter(_ObjectType):
197 name = 'filter'
198 egg_protocols = [['paste.filter_factory', 'paste.filter_app_factory']]
199 config_prefixes = ['filter']
200
201 def invoke(self, context):
202 if context.protocol == 'paste.filter_factory':
203 return fix_call(context.object,
204 context.global_conf, **context.local_conf)
205 elif context.protocol == 'paste.filter_app_factory':
206 def filter_wrapper(wsgi_app):
207 # This should be an object, so it has a nicer __repr__
208 return fix_call(context.object,
209 wsgi_app, context.global_conf,
210 **context.local_conf)
211 return filter_wrapper
212 else:
213 assert 0, "Protocol %r unknown" % context.protocol
214
215
216 FILTER = _Filter()
217
218
219 class _Server(_ObjectType):
220 name = 'server'
221 egg_protocols = [['paste.server_factory', 'paste.server_runner']]
222 config_prefixes = ['server']
223
224 def invoke(self, context):
225 if context.protocol == 'paste.server_factory':
226 return fix_call(context.object,
227 context.global_conf, **context.local_conf)
228 elif context.protocol == 'paste.server_runner':
229 def server_wrapper(wsgi_app):
230 # This should be an object, so it has a nicer __repr__
231 return fix_call(context.object,
232 wsgi_app, context.global_conf,
233 **context.local_conf)
234 return server_wrapper
235 else:
236 assert 0, "Protocol %r unknown" % context.protocol
237
238
239 SERVER = _Server()
240
241
242 # Virtual type: (@@: There's clearly something crufty here;
243 # this probably could be more elegant)
244 class _PipeLine(_ObjectType):
245 name = 'pipeline'
246
247 def invoke(self, context):
248 app = context.app_context.create()
249 filters = [c.create() for c in context.filter_contexts]
250 filters.reverse()
251 for filter_ in filters:
252 app = filter_(app)
253 return app
254
255
256 PIPELINE = _PipeLine()
257
258
259 class _FilterApp(_ObjectType):
260 name = 'filter_app'
261
262 def invoke(self, context):
263 next_app = context.next_context.create()
264 filter_ = context.filter_context.create()
265 return filter_(next_app)
266
267
268 FILTER_APP = _FilterApp()
269
270
271 class _FilterWith(_App):
272 name = 'filtered_with'
273
274 def invoke(self, context):
275 filter_ = context.filter_context.create()
276 filtered = context.next_context.create()
277 if context.next_context.object_type is APP:
278 return filter_(filtered)
279 else:
280 # filtering a filter
281 def composed(app):
282 return filter_(filtered(app))
283 return composed
284
285
286 FILTER_WITH = _FilterWith()
287
288 ############################################################
289 # Loaders
290 ############################################################
291
292
293 def loadapp(uri, name=None, **kw):
294 return loadobj(APP, uri, name=name, **kw)
295
296
297 def loadfilter(uri, name=None, **kw):
298 return loadobj(FILTER, uri, name=name, **kw)
299
300
301 def loadserver(uri, name=None, **kw):
302 return loadobj(SERVER, uri, name=name, **kw)
303
304
305 def appconfig(uri, name=None, relative_to=None, global_conf=None):
306 context = loadcontext(APP, uri, name=name,
307 relative_to=relative_to,
308 global_conf=global_conf)
309 return context.config()
310
311
312 _loaders = {}
313
314
315 def loadobj(object_type, uri, name=None, relative_to=None,
316 global_conf=None):
317 context = loadcontext(
318 object_type, uri, name=name, relative_to=relative_to,
319 global_conf=global_conf)
320 return context.create()
321
322
323 def loadcontext(object_type, uri, name=None, relative_to=None,
324 global_conf=None):
325 if '#' in uri:
326 if name is None:
327 uri, name = uri.split('#', 1)
328 else:
329 # @@: Ignore fragment or error?
330 uri = uri.split('#', 1)[0]
331 if name is None:
332 name = 'main'
333 if ':' not in uri:
334 raise LookupError("URI has no scheme: %r" % uri)
335 scheme, path = uri.split(':', 1)
336 scheme = scheme.lower()
337 if scheme not in _loaders:
338 raise LookupError(
339 "URI scheme not known: %r (from %s)"
340 % (scheme, ', '.join(_loaders.keys())))
341 return _loaders[scheme](
342 object_type,
343 uri, path, name=name, relative_to=relative_to,
344 global_conf=global_conf)
345
346
347 def _loadconfig(object_type, uri, path, name, relative_to,
348 global_conf):
349 isabs = os.path.isabs(path)
350 # De-Windowsify the paths:
351 path = path.replace('\\', '/')
352 if not isabs:
353 if not relative_to:
354 raise ValueError(
355 "Cannot resolve relative uri %r; no relative_to keyword "
356 "argument given" % uri)
357 relative_to = relative_to.replace('\\', '/')
358 if relative_to.endswith('/'):
359 path = relative_to + path
360 else:
361 path = relative_to + '/' + path
362 if path.startswith('///'):
363 path = path[2:]
364 path = unquote(path)
365 loader = ConfigLoader(path)
366 if global_conf:
367 loader.update_defaults(global_conf, overwrite=False)
368 return loader.get_context(object_type, name, global_conf)
369
370
371 _loaders['config'] = _loadconfig
372
373
374 def _loadegg(object_type, uri, spec, name, relative_to,
375 global_conf):
376 loader = EggLoader(spec)
377 return loader.get_context(object_type, name, global_conf)
378
379
380 _loaders['egg'] = _loadegg
381
382
383 def _loadfunc(object_type, uri, spec, name, relative_to,
384 global_conf):
385
386 loader = FuncLoader(spec)
387 return loader.get_context(object_type, name, global_conf)
388
389
390 _loaders['call'] = _loadfunc
391
392 ############################################################
393 # Loaders
394 ############################################################
395
396
397 class _Loader(object):
398
399 def get_app(self, name=None, global_conf=None):
400 return self.app_context(
401 name=name, global_conf=global_conf).create()
402
403 def get_filter(self, name=None, global_conf=None):
404 return self.filter_context(
405 name=name, global_conf=global_conf).create()
406
407 def get_server(self, name=None, global_conf=None):
408 return self.server_context(
409 name=name, global_conf=global_conf).create()
410
411 def app_context(self, name=None, global_conf=None):
412 return self.get_context(
413 APP, name=name, global_conf=global_conf)
414
415 def filter_context(self, name=None, global_conf=None):
416 return self.get_context(
417 FILTER, name=name, global_conf=global_conf)
418
419 def server_context(self, name=None, global_conf=None):
420 return self.get_context(
421 SERVER, name=name, global_conf=global_conf)
422
423 _absolute_re = re.compile(r'^[a-zA-Z]+:')
424
425 def absolute_name(self, name):
426 """
427 Returns true if the name includes a scheme
428 """
429 if name is None:
430 return False
431 return self._absolute_re.search(name)
432
433
434 class ConfigLoader(_Loader):
435
436 def __init__(self, filename):
437 self.filename = filename = filename.strip()
438 defaults = {
439 'here': os.path.dirname(os.path.abspath(filename)),
440 '__file__': os.path.abspath(filename)
441 }
442 self.parser = NicerConfigParser(filename, defaults=defaults)
443 self.parser.optionxform = str # Don't lower-case keys
444 with open(filename) as f:
445 self.parser.read_file(f)
446
447 def update_defaults(self, new_defaults, overwrite=True):
448 for key, value in iteritems(new_defaults):
449 if not overwrite and key in self.parser._defaults:
450 continue
451 self.parser._defaults[key] = value
452
453 def get_context(self, object_type, name=None, global_conf=None):
454 if self.absolute_name(name):
455 return loadcontext(object_type, name,
456 relative_to=os.path.dirname(self.filename),
457 global_conf=global_conf)
458 section = self.find_config_section(
459 object_type, name=name)
460 if global_conf is None:
461 global_conf = {}
462 else:
463 global_conf = global_conf.copy()
464 defaults = self.parser.defaults()
465 global_conf.update(defaults)
466 local_conf = {}
467 global_additions = {}
468 get_from_globals = {}
469 for option in self.parser.options(section):
470 if option.startswith('set '):
471 name = option[4:].strip()
472 global_additions[name] = global_conf[name] = (
473 self.parser.get(section, option))
474 elif option.startswith('get '):
475 name = option[4:].strip()
476 get_from_globals[name] = self.parser.get(section, option)
477 else:
478 if option in defaults:
479 # @@: It's a global option (?), so skip it
480 continue
481 local_conf[option] = self.parser.get(section, option)
482 for local_var, glob_var in get_from_globals.items():
483 local_conf[local_var] = global_conf[glob_var]
484 if object_type in (APP, FILTER) and 'filter-with' in local_conf:
485 filter_with = local_conf.pop('filter-with')
486 else:
487 filter_with = None
488 if 'require' in local_conf:
489 for spec in local_conf['require'].split():
490 pkg_resources.require(spec)
491 del local_conf['require']
492 if section.startswith('filter-app:'):
493 context = self._filter_app_context(
494 object_type, section, name=name,
495 global_conf=global_conf, local_conf=local_conf,
496 global_additions=global_additions)
497 elif section.startswith('pipeline:'):
498 context = self._pipeline_app_context(
499 object_type, section, name=name,
500 global_conf=global_conf, local_conf=local_conf,
501 global_additions=global_additions)
502 elif 'use' in local_conf:
503 context = self._context_from_use(
504 object_type, local_conf, global_conf, global_additions,
505 section)
506 else:
507 context = self._context_from_explicit(
508 object_type, local_conf, global_conf, global_additions,
509 section)
510 if filter_with is not None:
511 filter_with_context = LoaderContext(
512 obj=None,
513 object_type=FILTER_WITH,
514 protocol=None,
515 global_conf=global_conf, local_conf=local_conf,
516 loader=self)
517 filter_with_context.filter_context = self.filter_context(
518 name=filter_with, global_conf=global_conf)
519 filter_with_context.next_context = context
520 return filter_with_context
521 return context
522
523 def _context_from_use(self, object_type, local_conf, global_conf,
524 global_additions, section):
525 use = local_conf.pop('use')
526 context = self.get_context(
527 object_type, name=use, global_conf=global_conf)
528 context.global_conf.update(global_additions)
529 context.local_conf.update(local_conf)
530 if '__file__' in global_conf:
531 # use sections shouldn't overwrite the original __file__
532 context.global_conf['__file__'] = global_conf['__file__']
533 # @@: Should loader be overwritten?
534 context.loader = self
535
536 if context.protocol is None:
537 # Determine protocol from section type
538 section_protocol = section.split(':', 1)[0]
539 if section_protocol in ('application', 'app'):
540 context.protocol = 'paste.app_factory'
541 elif section_protocol in ('composit', 'composite'):
542 context.protocol = 'paste.composit_factory'
543 else:
544 # This will work with 'server' and 'filter', otherwise it
545 # could fail but there is an error message already for
546 # bad protocols
547 context.protocol = 'paste.%s_factory' % section_protocol
548
549 return context
550
551 def _context_from_explicit(self, object_type, local_conf, global_conf,
552 global_addition, section):
553 possible = []
554 for protocol_options in object_type.egg_protocols:
555 for protocol in protocol_options:
556 if protocol in local_conf:
557 possible.append((protocol, local_conf[protocol]))
558 break
559 if len(possible) > 1:
560 raise LookupError(
561 "Multiple protocols given in section %r: %s"
562 % (section, possible))
563 if not possible:
564 raise LookupError(
565 "No loader given in section %r" % section)
566 found_protocol, found_expr = possible[0]
567 del local_conf[found_protocol]
568 value = import_string(found_expr)
569 context = LoaderContext(
570 value, object_type, found_protocol,
571 global_conf, local_conf, self)
572 return context
573
574 def _filter_app_context(self, object_type, section, name,
575 global_conf, local_conf, global_additions):
576 if 'next' not in local_conf:
577 raise LookupError(
578 "The [%s] section in %s is missing a 'next' setting"
579 % (section, self.filename))
580 next_name = local_conf.pop('next')
581 context = LoaderContext(None, FILTER_APP, None, global_conf,
582 local_conf, self)
583 context.next_context = self.get_context(
584 APP, next_name, global_conf)
585 if 'use' in local_conf:
586 context.filter_context = self._context_from_use(
587 FILTER, local_conf, global_conf, global_additions,
588 section)
589 else:
590 context.filter_context = self._context_from_explicit(
591 FILTER, local_conf, global_conf, global_additions,
592 section)
593 return context
594
595 def _pipeline_app_context(self, object_type, section, name,
596 global_conf, local_conf, global_additions):
597 if 'pipeline' not in local_conf:
598 raise LookupError(
599 "The [%s] section in %s is missing a 'pipeline' setting"
600 % (section, self.filename))
601 pipeline = local_conf.pop('pipeline').split()
602 if local_conf:
603 raise LookupError(
604 "The [%s] pipeline section in %s has extra "
605 "(disallowed) settings: %s"
606 % (', '.join(local_conf.keys())))
607 context = LoaderContext(None, PIPELINE, None, global_conf,
608 local_conf, self)
609 context.app_context = self.get_context(
610 APP, pipeline[-1], global_conf)
611 context.filter_contexts = [
612 self.get_context(FILTER, pname, global_conf)
613 for pname in pipeline[:-1]]
614 return context
615
616 def find_config_section(self, object_type, name=None):
617 """
618 Return the section name with the given name prefix (following the
619 same pattern as ``protocol_desc`` in ``config``. It must have the
620 given name, or for ``'main'`` an empty name is allowed. The
621 prefix must be followed by a ``:``.
622
623 Case is *not* ignored.
624 """
625 possible = []
626 for name_options in object_type.config_prefixes:
627 for name_prefix in name_options:
628 found = self._find_sections(
629 self.parser.sections(), name_prefix, name)
630 if found:
631 possible.extend(found)
632 break
633 if not possible:
634 raise LookupError(
635 "No section %r (prefixed by %s) found in config %s"
636 % (name,
637 ' or '.join(map(repr, _flatten(object_type.config_prefixes))),
638 self.filename))
639 if len(possible) > 1:
640 raise LookupError(
641 "Ambiguous section names %r for section %r (prefixed by %s) "
642 "found in config %s"
643 % (possible, name,
644 ' or '.join(map(repr, _flatten(object_type.config_prefixes))),
645 self.filename))
646 return possible[0]
647
648 def _find_sections(self, sections, name_prefix, name):
649 found = []
650 if name is None:
651 if name_prefix in sections:
652 found.append(name_prefix)
653 name = 'main'
654 for section in sections:
655 if section.startswith(name_prefix + ':'):
656 if section[len(name_prefix) + 1:].strip() == name:
657 found.append(section)
658 return found
659
660
661 class EggLoader(_Loader):
662
663 def __init__(self, spec):
664 self.spec = spec
665
666 def get_context(self, object_type, name=None, global_conf=None):
667 if self.absolute_name(name):
668 return loadcontext(object_type, name,
669 global_conf=global_conf)
670 entry_point, protocol, ep_name = self.find_egg_entry_point(
671 object_type, name=name)
672 return LoaderContext(
673 entry_point,
674 object_type,
675 protocol,
676 global_conf or {}, {},
677 self,
678 distribution=pkg_resources.get_distribution(self.spec),
679 entry_point_name=ep_name)
680
681 def find_egg_entry_point(self, object_type, name=None):
682 """
683 Returns the (entry_point, protocol) for the with the given
684 ``name``.
685 """
686 if name is None:
687 name = 'main'
688 possible = []
689 for protocol_options in object_type.egg_protocols:
690 for protocol in protocol_options:
691 pkg_resources.require(self.spec)
692 entry = pkg_resources.get_entry_info(
693 self.spec,
694 protocol,
695 name)
696 if entry is not None:
697 possible.append((entry.load(), protocol, entry.name))
698 break
699 if not possible:
700 # Better exception
701 dist = pkg_resources.get_distribution(self.spec)
702 raise LookupError(
703 "Entry point %r not found in egg %r (dir: %s; protocols: %s; "
704 "entry_points: %s)"
705 % (name, self.spec,
706 dist.location,
707 ', '.join(_flatten(object_type.egg_protocols)),
708 ', '.join(_flatten([
709 list((pkg_resources.get_entry_info(self.spec, prot, name) or {}).keys())
710 for prot in protocol_options] or '(no entry points)'))))
711 if len(possible) > 1:
712 raise LookupError(
713 "Ambiguous entry points for %r in egg %r (protocols: %s)"
714 % (name, self.spec, ', '.join(_flatten(protocol_options))))
715 return possible[0]
716
717
718 class FuncLoader(_Loader):
719 """ Loader that supports specifying functions inside modules, without
720 using eggs at all. Configuration should be in the format:
721 use = call:my.module.path:function_name
722
723 Dot notation is supported in both the module and function name, e.g.:
724 use = call:my.module.path:object.method
725 """
726
727 def __init__(self, spec):
728 self.spec = spec
729 if ':' not in spec:
730 raise LookupError("Configuration not in format module:function")
731
732 def get_context(self, object_type, name=None, global_conf=None):
733 obj = lookup_object(self.spec)
734 return LoaderContext(
735 obj,
736 object_type,
737 None, # determine protocol from section type
738 global_conf or {},
739 {},
740 self,
741 )
742
743
744 class LoaderContext(object):
745
746 def __init__(self, obj, object_type, protocol,
747 global_conf, local_conf, loader,
748 distribution=None, entry_point_name=None):
749 self.object = obj
750 self.object_type = object_type
751 self.protocol = protocol
752 # assert protocol in _flatten(object_type.egg_protocols), (
753 # "Bad protocol %r; should be one of %s"
754 # % (protocol, ', '.join(map(repr, _flatten(object_type.egg_protocols)))))
755 self.global_conf = global_conf
756 self.local_conf = local_conf
757 self.loader = loader
758 self.distribution = distribution
759 self.entry_point_name = entry_point_name
760
761 def create(self):
762 return self.object_type.invoke(self)
763
764 def config(self):
765 conf = AttrDict(self.global_conf)
766 conf.update(self.local_conf)
767 conf.local_conf = self.local_conf
768 conf.global_conf = self.global_conf
769 conf.context = self
770 return conf
771
772
773 class AttrDict(dict):
774 """
775 A dictionary that can be assigned to.
776 """
777 pass