Completed
Push — dev-0.5.2 ( 87ea4a...b6a9d6 )
by Felipe A.
01:05
created

EventPluginManager.__init__()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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