Completed
Push — file-actions ( 3ab188...6cd94b )
by Felipe A.
28s
created

ExcludePluginManager.__init__()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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