Completed
Push — 5.2-unstable ( ee49a9 )
by Felipe A.
01:03
created

CachePluginManager.wrapped()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
1
#!/usr/bin/env python
2
# -*- coding: UTF-8 -*-
3
4
import re
5
import sys
6
import argparse
7
import warnings
8
import collections
9
import functools
10
11
import flask
12
13
from werkzeug.utils import cached_property
14
15
from . import event
16
from . import mimetype
17
from . import compat
18
from . import cache
19
from .compat import deprecated, usedoc
20
21
22
def defaultsnamedtuple(name, fields, defaults=None):
23
    '''
24
    Generate namedtuple with default values.
25
26
    :param name: name
27
    :param fields: iterable with field names
28
    :param defaults: iterable or mapping with field defaults
29
    :returns: defaultdict with given fields and given defaults
30
    :rtype: collections.defaultdict
31
    '''
32
    nt = collections.namedtuple(name, fields)
33
    nt.__new__.__defaults__ = (None,) * len(nt._fields)
34
    if isinstance(defaults, collections.Mapping):
35
        nt.__new__.__defaults__ = tuple(nt(**defaults))
36
    elif defaults:
37
        nt.__new__.__defaults__ = tuple(nt(*defaults))
38
    return nt
39
40
41
class PluginNotFoundError(ImportError):
42
    pass
43
44
45
class WidgetException(Exception):
46
    pass
47
48
49
class WidgetParameterException(WidgetException):
50
    pass
51
52
53
class InvalidArgumentError(ValueError):
54
    pass
55
56
57
class PluginManagerBase(object):
58
    '''
59
    Base plugin manager for plugin module loading and Flask extension logic.
60
    '''
61
62
    @property
63
    def namespaces(self):
64
        '''
65
        List of plugin namespaces taken from app config.
66
        '''
67
        return self.app.config['plugin_namespaces'] if self.app else []
68
69
    def __init__(self, app=None):
70
        if app is None:
71
            self.clear()
72
        else:
73
            self.init_app(app)
74
75
    def init_app(self, app):
76
        '''
77
        Initialize this Flask extension for given app.
78
        '''
79
        self.app = app
80
        if not hasattr(app, 'extensions'):
81
            app.extensions = {}
82
        app.extensions['plugin_manager'] = self
83
        self.reload()
84
85
    def reload(self):
86
        '''
87
        Clear plugin manager state and reload plugins.
88
89
        This method will make use of :meth:`clear` and :meth:`load_plugin`,
90
        so all internal state will be cleared, and all plugins defined in
91
        :data:`self.app.config['plugin_modules']` will be loaded.
92
        '''
93
        self.clear()
94
        if self.app:
95
            for plugin in self.app.config.get('plugin_modules', ()):
96
                self.load_plugin(plugin)
97
98
    def clear(self):
99
        '''
100
        Clear plugin manager state.
101
        '''
102
        pass
103
104
    def import_plugin(self, plugin):
105
        '''
106
        Import plugin by given name, looking at :attr:`namespaces`.
107
108
        :param plugin: plugin module name
109
        :type plugin: str
110
        :raises PluginNotFoundError: if not found on any namespace
111
        '''
112
        names = [
113
            '%s%s%s' % (namespace, '' if namespace[-1] == '_' else '.', plugin)
114
            if namespace else
115
            plugin
116
            for namespace in self.namespaces
117
            ]
118
119
        for name in names:
120
            if name in sys.modules:
121
                return sys.modules[name]
122
123
        for name in names:
124
            try:
125
                __import__(name)
126
                return sys.modules[name]
127
            except (ImportError, KeyError):
128
                pass
129
130
        raise PluginNotFoundError(
131
            'No plugin module %r found, tried %r' % (plugin, names),
132
            plugin, names)
133
134
    def load_plugin(self, plugin):
135
        '''
136
        Import plugin (see :meth:`import_plugin`) and load related data.
137
138
        :param plugin: plugin module name
139
        :type plugin: str
140
        :raises PluginNotFoundError: if not found on any namespace
141
        '''
142
        return self.import_plugin(plugin)
143
144
145
class RegistrablePluginManager(PluginManagerBase):
146
    '''
147
    Base plugin manager for plugin registration via :func:`register_plugin`
148
    functions at plugin module level.
149
    '''
150
    def load_plugin(self, plugin):
151
        '''
152
        Import plugin (see :meth:`import_plugin`) and load related data.
153
154
        If available, plugin's module-level :func:`register_plugin` function
155
        will be called with current plugin manager instance as first argument.
156
157
        :param plugin: plugin module name
158
        :type plugin: str
159
        :raises PluginNotFoundError: if not found on any namespace
160
        '''
161
        module = super(RegistrablePluginManager, self).load_plugin(plugin)
162
        if hasattr(module, 'register_plugin'):
163
            module.register_plugin(self)
164
        return module
165
166
167
class BlueprintPluginManager(RegistrablePluginManager):
168
    '''
169
    Manager for blueprint registration via :meth:`register_plugin` calls.
170
171
    Note: blueprints are not removed on `clear` nor reloaded on `reload`
172
    as flask does not allow it.
173
    '''
174
    def __init__(self, app=None):
175
        self._blueprint_known = set()
176
        super(BlueprintPluginManager, self).__init__(app=app)
177
178
    def register_blueprint(self, blueprint):
179
        '''
180
        Register given blueprint on curren app.
181
182
        This method is provided for using inside plugin's module-level
183
        :func:`register_plugin` functions.
184
185
        :param blueprint: blueprint object with plugin endpoints
186
        :type blueprint: flask.Blueprint
187
        '''
188
        if blueprint not in self._blueprint_known:
189
            self.app.register_blueprint(blueprint)
190
            self._blueprint_known.add(blueprint)
191
192
193
class WidgetPluginManager(RegistrablePluginManager):
194
    '''
195
    Plugin manager for widget registration.
196
197
    This class provides a dictionary of widget types at its
198
    :attr:`widget_types` attribute. They can be referenced by their keys on
199
    both :meth:`create_widget` and :meth:`register_widget` methods' `type`
200
    parameter, or instantiated directly and passed to :meth:`register_widget`
201
    via `widget` parameter.
202
    '''
203
    widget_types = {
204
        'base': defaultsnamedtuple(
205
            'Widget',
206
            ('place', 'type')),
207
        'link': defaultsnamedtuple(
208
            'Link',
209
            ('place', 'type', 'css', 'icon', 'text', 'endpoint', 'href'),
210
            {
211
                'type': 'link',
212
                'text': lambda f: f.name,
213
                'icon': lambda f: f.category
214
            }),
215
        'button': defaultsnamedtuple(
216
            'Button',
217
            ('place', 'type', 'css', 'text', 'endpoint', 'href'),
218
            {'type': 'button'}),
219
        'upload': defaultsnamedtuple(
220
            'Upload',
221
            ('place', 'type', 'css', 'text', 'endpoint', 'action'),
222
            {'type': 'upload'}),
223
        'stylesheet': defaultsnamedtuple(
224
            'Stylesheet',
225
            ('place', 'type', 'endpoint', 'filename', 'href'),
226
            {'type': 'stylesheet'}),
227
        'script': defaultsnamedtuple(
228
            'Script',
229
            ('place', 'type', 'endpoint', 'filename', 'src'),
230
            {'type': 'script'}),
231
        'html': defaultsnamedtuple(
232
            'Html',
233
            ('place', 'type', 'html'),
234
            {'type': 'html'}),
235
    }
236
237
    def clear(self):
238
        '''
239
        Clear plugin manager state.
240
241
        Registered widgets will be disposed.
242
        '''
243
        self._widgets = []
244
        super(WidgetPluginManager, self).clear()
245
246
    def get_widgets(self, file=None, place=None):
247
        '''
248
        List registered widgets, optionally matching given criteria.
249
250
        :param file: optional file object will be passed to widgets' filter
251
                     functions.
252
        :type file: browsepy.file.Node or None
253
        :param place: optional template place hint.
254
        :type place: str
255
        :returns: list of widget instances
256
        :rtype: list of objects
257
        '''
258
        return list(self.iter_widgets(file, place))
259
260
    @classmethod
261
    def _resolve_widget(cls, file, widget):
262
        '''
263
        Resolve widget callable properties into static ones.
264
265
        :param file: file will be used to resolve callable properties.
266
        :type file: browsepy.file.Node
267
        :param widget: widget instance optionally with callable properties
268
        :type widget: object
269
        :returns: a new widget instance of the same type as widget parameter
270
        :rtype: object
271
        '''
272
        return widget.__class__(*[
273
            value(file) if callable(value) else value
274
            for value in widget
275
            ])
276
277
    def iter_widgets(self, file=None, place=None):
278
        '''
279
        Iterate registered widgets, optionally matching given criteria.
280
281
        :param file: optional file object will be passed to widgets' filter
282
                     functions.
283
        :type file: browsepy.file.Node or None
284
        :param place: optional template place hint.
285
        :type place: str
286
        :yields: widget instances
287
        :ytype: object
288
        '''
289
        for filter, dynamic, cwidget in self._widgets:
290
            try:
291
                if file and filter and not filter(file):
292
                    continue
293
            except BaseException as e:
294
                # Exception is handled  as this method execution is deffered,
295
                # making hard to debug for plugin developers.
296
                warnings.warn(
297
                    'Plugin action filtering failed with error: %s' % e,
298
                    RuntimeWarning
299
                    )
300
                continue
301
            if place and place != cwidget.place:
302
                continue
303
            if file and dynamic:
304
                cwidget = self._resolve_widget(file, cwidget)
305
            yield cwidget
306
307
    def create_widget(self, place, type, file=None, **kwargs):
308
        '''
309
        Create a widget object based on given arguments.
310
311
        If file object is provided, callable arguments will be resolved:
312
        its return value will be used after calling them with file as first
313
        parameter.
314
315
        All extra `kwargs` parameters will be passed to widget constructor.
316
317
        :param place: place hint where widget should be shown.
318
        :type place: str
319
        :param type: widget type name as taken from :attr:`widget_types` dict
320
                     keys.
321
        :type type: str
322
        :param file: optional file object for widget attribute resolving
323
        :type type: browsepy.files.Node or None
324
        :returns: widget instance
325
        :rtype: object
326
        '''
327
        widget_class = self.widget_types.get(type, self.widget_types['base'])
328
        kwargs.update(place=place, type=type)
329
        try:
330
            element = widget_class(**kwargs)
331
        except TypeError as e:
332
            message = e.args[0] if e.args else ''
333
            if (
334
              'unexpected keyword argument' in message or
335
              'required positional argument' in message
336
              ):
337
                raise WidgetParameterException(
338
                    'type %s; %s; available: %r'
339
                    % (type, message, widget_class._fields)
340
                    )
341
            raise e
342
        if file and any(map(callable, element)):
343
            return self._resolve_widget(file, element)
344
        return element
345
346
    def register_widget(self, place=None, type=None, widget=None, filter=None,
347
                        **kwargs):
348
        '''
349
        Create (see :meth:`create_widget`) or use provided widget and register
350
        it.
351
352
        This method provides this dual behavior in order to simplify widget
353
        creation-registration on an functional single step without sacrifycing
354
        the reusability of a object-oriented approach.
355
356
        :param place: where widget should be placed. This param conflicts
357
                      with `widget` argument.
358
        :type place: str or None
359
        :param type: widget type name as taken from :attr:`widget_types` dict
360
                     keys. This param conflicts with `widget` argument.
361
        :type type: str or None
362
        :param widget: optional widget object will be used as is. This param
363
                       conflicts with both place and type arguments.
364
        :type widget: object or None
365
        :raises TypeError: if both widget and place or type are provided at
366
                           the same time (they're mutually exclusive).
367
        :returns: created or given widget object
368
        :rtype: object
369
        '''
370
        if bool(widget) == bool(place or type):
371
            raise InvalidArgumentError(
372
                'register_widget takes either place and type or widget'
373
                )
374
        widget = widget or self.create_widget(place, type, **kwargs)
375
        dynamic = any(map(callable, widget))
376
        self._widgets.append((filter, dynamic, widget))
377
        return widget
378
379
380
class MimetypePluginManager(RegistrablePluginManager):
381
    '''
382
    Plugin manager for mimetype-function registration.
383
    '''
384
    _default_mimetype_functions = (
385
        mimetype.by_python,
386
        mimetype.by_file,
387
        mimetype.by_default,
388
    )
389
390
    def clear(self):
391
        '''
392
        Clear plugin manager state.
393
394
        Registered mimetype functions will be disposed.
395
        '''
396
        self._mimetype_functions = list(self._default_mimetype_functions)
397
        super(MimetypePluginManager, self).clear()
398
399
    def get_mimetype(self, path):
400
        '''
401
        Get mimetype of given path calling all registered mime functions (and
402
        default ones).
403
404
        :param path: filesystem path of file
405
        :type path: str
406
        :returns: mimetype
407
        :rtype: str
408
        '''
409
        for fnc in self._mimetype_functions:
410
            mime = fnc(path)
411
            if mime:
412
                return mime
413
        return mimetype.by_default(path)
414
415
    def register_mimetype_function(self, fnc):
416
        '''
417
        Register mimetype function.
418
419
        Given function must accept a filesystem path as string and return
420
        a mimetype string or None.
421
422
        :param fnc: callable accepting a path string
423
        :type fnc: collections.abc.Callable
424
        '''
425
        self._mimetype_functions.insert(0, fnc)
426
427
428
class ArgumentPluginManager(PluginManagerBase):
429
    '''
430
    Plugin manager for command-line argument registration.
431
432
    This function is used by browsepy's :mod:`__main__` module in order
433
    to attach extra arguments at argument-parsing time.
434
435
    This is done by :meth:`load_arguments` which imports all plugin modules
436
    and calls their respective :func:`register_arguments` module-level
437
    function.
438
    '''
439
    _argparse_kwargs = {'add_help': False}
440
    _argparse_arguments = argparse.Namespace()
441
442
    def load_arguments(self, argv, base=None):
443
        '''
444
        Process given argument list based on registered arguments and given
445
        optional base :class:`argparse.ArgumentParser` instance.
446
447
        This method saves processed arguments on itself, and this state won't
448
        be lost after :meth:`clean` calls.
449
450
        Processed argument state will be available via :meth:`get_argument`
451
        method.
452
453
        :param argv: command-line arguments (without command itself)
454
        :type argv: iterable of str
455
        :param base: optional base :class:`argparse.ArgumentParser` instance.
456
        :type base: argparse.ArgumentParser or None
457
        :returns: argparse.Namespace instance with processed arguments as
458
                  given by :meth:`argparse.ArgumentParser.parse_args`.
459
        :rtype: argparse.Namespace
460
        '''
461
        plugin_parser = argparse.ArgumentParser(add_help=False)
462
        plugin_parser.add_argument(
463
            '--plugin',
464
            type=lambda x: x.split(',') if x else [],
465
            default=[]
466
            )
467
        parser = argparse.ArgumentParser(
468
            parents=(base or plugin_parser,),
469
            add_help=False
470
            )
471
        for plugin in plugin_parser.parse_known_args(argv)[0].plugin:
472
            module = self.import_plugin(plugin)
473
            if hasattr(module, 'register_arguments'):
474
                manager = ArgumentPluginManager()
475
                module.register_arguments(manager)
476
                group = parser.add_argument_group('%s arguments' % plugin)
477
                for argargs, argkwargs in manager._argparse_argkwargs:
478
                    group.add_argument(*argargs, **argkwargs)
479
        self._argparse_arguments = parser.parse_args(argv)
480
        return self._argparse_arguments
481
482
    def clear(self):
483
        '''
484
        Clear plugin manager state.
485
486
        Registered command-line arguments will be disposed.
487
        '''
488
        self._argparse_argkwargs = []
489
        super(ArgumentPluginManager, self).clear()
490
491
    def register_argument(self, *args, **kwargs):
492
        '''
493
        Register command-line argument.
494
495
        All given arguments will be passed directly to
496
        :meth:`argparse.ArgumentParser.add_argument` calls by
497
        :meth:`load_arguments` method.
498
499
        See :meth:`argparse.ArgumentParser.add_argument` documentation for
500
        further information.
501
        '''
502
        self._argparse_argkwargs.append((args, kwargs))
503
504
    def get_argument(self, name, default=None):
505
        '''
506
        Get argument value from last :meth:`load_arguments` call.
507
508
        Keep in mind :meth:`argparse.ArgumentParser.parse_args` generates
509
        its own command-line arguments if `dest` kwarg is not provided,
510
        so ie. `--my-option` became available as `my_option`.
511
512
        :param name: command-line argument name
513
        :type name: str
514
        :param default: default value if parameter is not found
515
        :returns: command-line argument or default value
516
        '''
517
        return getattr(self._argparse_arguments, name, default)
518
519
520
class EventPluginManager(RegistrablePluginManager):
521
    '''
522
    Plugin manager for event manager with pluggable event sources.
523
    '''
524
    _default_event_sources = (
525
        event.WathdogEventSource,
526
        )
527
528
    def __init__(self, app=None):
529
        self.event = event.EventManager()
530
        self._event_sources = []
531
        super(EventPluginManager, self).__init__(app=app)
532
533
    def reload(self):
534
        '''
535
        Clear plugin manager state and reload plugins.
536
537
        This method will make use of :meth:`clear` and :meth:`load_plugin`,
538
        so all internal state will be cleared, and all plugins defined in
539
        :data:`self.app.config['plugin_modules']` will be loaded.
540
541
        Also, event sources will be reset and will be registered again by
542
        plugins.
543
        '''
544
        super(EventPluginManager, self).reload()
545
        if self.app:
546
            event = self.event
547
            app = self.app
548
            self._event_sources[:] = [
549
                source_class(event, app)
550
                for source_class in self._default_event_sources
551
                ]
552
553
    def register_event_source(self, source):
554
        '''
555
        Register an event source.
556
557
        Event source must be a type with a constructor accepting both an
558
        :class:`browsepy.event.EventManager` instance and a
559
        :class:`flask.Flask` app instance, and must have a :meth:`clear`
560
        method.
561
562
        :param source: class accepting two parameters with a clear method.
563
        :type source: type
564
        '''
565
        self._event_sources.append(source(self.event, self.app))
566
567
    def clear(self):
568
        '''
569
        Clear plugin manager state.
570
571
        Registered events and event sources will be disposed.
572
        '''
573
        super(EventPluginManager, self).clear()
574
        for source in self._event_sources:
575
            source.clear()
576
        del self._event_sources[:]
577
        self.event.clear()
578
579
580
class CachePluginManager(RegistrablePluginManager):
581
    '''
582
    Plugin manager for cache backend registration.
583
    '''
584
    multi_cache_class = cache.MultiCache
585
    fallback_cache_class = cache.LRUCache
586
587
    def __init__(self, app=None):
588
        self._cache_fallback = self.fallback_cache_class()
589
        self.cache = cache.MultiCache()
590
        super(CachePluginManager, self).__init__(app=app)
591
592
    def register_cache_backend(self, cache_backend):
593
        '''
594
        Register a cache backend.
595
596
        Cache backends must be a type with a constructor accepting
597
        a :class:`flask.Flask` app instance, and must expose a dict-like
598
        interface.
599
600
        :param source: werkzeug-cache-compatible object
601
        :type source: werkzeug.contrib.cache.BaseCache
602
        '''
603
        self.cache.backends.append(cache_backend)
604
605
    def clear(self):
606
        '''
607
        Clear plugin manager state.
608
609
        * Registered events and event sources will be disposed.
610
        * Registered cache backends will be disposed.
611
        '''
612
        super(CachePluginManager, self).clear()
613
        self.cache.backends[:] = (self._cache_fallback,)
614
615
    def memoize(self, fnc):
616
        '''
617
        Decorate given function and memoize its results using currente cache.
618
619
        :param fnc: function to decorate
620
        :type fnc: collections.abc.Callable
621
        :param maxsize: optional maximum size (defaults to 1024)
622
        :type maxsize: int
623
        :returns: wrapped cached function
624
        :rtype: function
625
        '''
626
        manager = self.memoize_class(self)
627
628
        @functools.wraps(fnc)
629
        def wrapped(*args, **kwargs):
630
            return manager.run(fnc, *args, **kwargs)
631
        wrapped.cache = manager
632
        return wrapped
633
634
    def cached(timeout_or_fnc=5 * 60, key='view/{path}'):
635
        timeout = 5 * 60 if callable(timeout_or_fnc) else timeout_or_fnc
636
637
        def decorator(f):
638
            @functools.wraps(f)
639
            def decorated_function(*args, **kwargs):
640
                cache_key = key.format(path=flask.request.path)
641
                rv = cache.get(cache_key)
642
                if rv is not None:
643
                    return rv
644
                rv = f(*args, **kwargs)
645
                cache.set(cache_key, rv, timeout=timeout)
646
                return rv
647
            return decorated_function
648
        return decorator(timeout_or_fnc) if callable(timeout_or_fnc) else decorator
649
650
651
class MimetypeActionPluginManager(WidgetPluginManager, MimetypePluginManager):
652
    '''
653
    Deprecated plugin API
654
    '''
655
656
    _deprecated_places = {
657
        'javascript': 'scripts',
658
        'style': 'styles',
659
        'button': 'entry-actions',
660
        'link': 'entry-link',
661
        }
662
663
    @classmethod
664
    def _mimetype_filter(cls, mimetypes):
665
        widget_mimetype_re = re.compile(
666
            '^%s$' % '$|^'.join(
667
                map(re.escape, mimetypes)
668
                ).replace('\\*', '[^/]+')
669
            )
670
671
        def handler(f):
672
            return widget_mimetype_re.match(f.type) is not None
673
674
        return handler
675
676
    def _widget_attrgetter(self, widget, name):
677
        def handler(f):
678
            app = f.app or self.app or flask.current_app
679
            with app.app_context():
680
                return getattr(widget.for_file(f), name)
681
        return handler
682
683
    def _widget_props(self, widget, endpoint=None, mimetypes=(),
684
                      dynamic=False):
685
        type = getattr(widget, '_type', 'base')
686
        fields = self.widget_types[type]._fields
687
        with self.app.app_context():
688
            props = {
689
                name: self._widget_attrgetter(widget, name)
690
                for name in fields
691
                if hasattr(widget, name)
692
                }
693
        props.update(
694
            type=type,
695
            place=self._deprecated_places.get(widget.place),
696
            )
697
        if dynamic:
698
            props['filter'] = self._mimetype_filter(mimetypes)
699
        if 'endpoint' in fields:
700
            props['endpoint'] = endpoint
701
        return props
702
703
    @usedoc(WidgetPluginManager.__init__)
704
    def __init__(self, app=None):
705
        self._action_widgets = []
706
        super(MimetypeActionPluginManager, self).__init__(app=app)
707
708
    @usedoc(WidgetPluginManager.clear)
709
    def clear(self):
710
        self._action_widgets[:] = ()
711
        super(MimetypeActionPluginManager, self).clear()
712
713
    @cached_property
714
    def _widget(self):
715
        with warnings.catch_warnings():
716
            warnings.filterwarnings('ignore', category=DeprecationWarning)
717
            from . import widget
718
        return widget
719
720
    @cached_property
721
    @deprecated('Deprecated attribute action_class')
722
    def action_class(self):
723
        return collections.namedtuple(
724
            'MimetypeAction',
725
            ('endpoint', 'widget')
726
            )
727
728
    @cached_property
729
    @deprecated('Deprecated attribute style_class')
730
    def style_class(self):
731
        return self._widget.StyleWidget
732
733
    @cached_property
734
    @deprecated('Deprecated attribute button_class')
735
    def button_class(self):
736
        return self._widget.ButtonWidget
737
738
    @cached_property
739
    @deprecated('Deprecated attribute javascript_class')
740
    def javascript_class(self):
741
        return self._widget.JavascriptWidget
742
743
    @cached_property
744
    @deprecated('Deprecated attribute link_class')
745
    def link_class(self):
746
        return self._widget.LinkWidget
747
748
    @deprecated('Deprecated method register_action')
749
    def register_action(self, endpoint, widget, mimetypes=(), **kwargs):
750
        props = self._widget_props(widget, endpoint, mimetypes, True)
751
        self.register_widget(**props)
752
        self._action_widgets.append((widget, props['filter'], endpoint))
753
754
    @deprecated('Deprecated method get_actions')
755
    def get_actions(self, file):
756
        return [
757
            self.action_class(endpoint, deprecated.for_file(file))
758
            for deprecated, filter, endpoint in self._action_widgets
759
            if endpoint and filter(file)
760
            ]
761
762
    @usedoc(WidgetPluginManager.register_widget)
763
    def register_widget(self, place=None, type=None, widget=None, filter=None,
764
                        **kwargs):
765
        if isinstance(place or widget, self._widget.WidgetBase):
766
            warnings.warn(
767
                'Deprecated use of register_widget',
768
                category=DeprecationWarning
769
                )
770
            widget = place or widget
771
            props = self._widget_props(widget)
772
            self.register_widget(**props)
773
            self._action_widgets.append((widget, None, None))
774
            return
775
        return super(MimetypeActionPluginManager, self).register_widget(
776
            place=place, type=type, widget=widget, filter=filter, **kwargs)
777
778
    @usedoc(WidgetPluginManager.get_widgets)
779
    def get_widgets(self, file=None, place=None):
780
        if isinstance(file, compat.basestring) or \
781
          place in self._deprecated_places:
782
            warnings.warn(
783
                'Deprecated use of get_widgets',
784
                category=DeprecationWarning
785
                )
786
            place = file or place
787
            return [
788
                widget
789
                for widget, filter, endpoint in self._action_widgets
790
                if not (filter or endpoint) and place == widget.place
791
                ]
792
        return super(MimetypeActionPluginManager, self).get_widgets(
793
            file=file, place=place)
794
795
796
class PluginManager(
797
  CachePluginManager,
798
  EventPluginManager,
799
  MimetypeActionPluginManager,
800
  BlueprintPluginManager,
801
  WidgetPluginManager,
802
  MimetypePluginManager,
803
  ArgumentPluginManager
804
  ):
805
    '''
806
    Main plugin manager
807
808
    Provides:
809
        * Plugin module loading and Flask extension logic.
810
        * Plugin registration via :func:`register_plugin` functions at plugin
811
          module level.
812
        * Plugin blueprint registration via :meth:`register_plugin` calls.
813
        * Widget registration via :meth:`register_widget` method.
814
        * Mimetype function registration via :meth:`register_mimetype_function`
815
          method.
816
        * Command-line argument registration calling :func:`register_arguments`
817
          at plugin module level and providing :meth:`register_argument`
818
          method.
819
820
    This class also provides a dictionary of widget types at its
821
    :attr:`widget_types` attribute. They can be referenced by their keys on
822
    both :meth:`create_widget` and :meth:`register_widget` methods' `type`
823
    parameter, or instantiated directly and passed to :meth:`register_widget`
824
    via `widget` parameter.
825
    '''
826
    def clear(self):
827
        '''
828
        Clear plugin manager state.
829
830
        * Registered widgets will be disposed.
831
        * Registered mimetype functions will be disposed.
832
        * Registered events and event sources will be disposed.
833
        * Registered cache backends will be disposed.
834
        * Registered command-line arguments will be disposed
835
        '''
836
        super(PluginManager, self).clear()
837