source: orange/Orange/OrangeCanvas/registry/description.py @ 11368:7de87de13b64

Revision 11368:7de87de13b64, 17.7 KB checked in by Ales Erjavec <ales.erjavec@…>, 14 months ago (diff)

Added rst documentation for the registry package.

Fixing docstrings in the process.

RevLine 
[11098]1"""
[11368]2Widget meta description classes
3===============================
[11098]4
5"""
6
7import os
8import sys
[11252]9import warnings
[11098]10
11# Exceptions
12
[11252]13
[11098]14class DescriptionError(Exception):
15    pass
16
17
18class WidgetSpecificationError(DescriptionError):
19    pass
20
21
22class SignalSpecificationError(DescriptionError):
23    pass
24
25
26class CategorySpecificationError(DescriptionError):
27    pass
28
29
[11252]30###############
31# Channel flags
32###############
[11098]33
34# A single signal
35Single = 2
36
37# Multiple signal (more then one input on the channel)
38Multiple = 4
39
40# Default signal (default or primary input/output)
41Default = 8
42NonDefault = 16
43
44# Explicit - only connected if specifically requested or the only possibility
45Explicit = 32
46
47# Dynamic type output signal
48Dynamic = 64
49
50
51# Input/output signal (channel) description
52
53
54class InputSignal(object):
55    """Description of an input channel.
56
57    Parameters
58    ----------
59    name : str
60        Name of the channel.
61    type : str or `type`
62        Type of the accepted signals.
63    handler : str
64        Name of the handler method for the signal.
[11252]65    flags : int, optional
66        Channel flags.
67    id : str
68        A unique id of the input signal.
69    doc : str, optional
70        A docstring documenting the channel.
[11098]71
72    """
[11252]73    def __init__(self, name, type, handler, flags=Single + NonDefault,
74                 id=None, doc=None):
[11098]75        self.name = name
76        self.type = type
77        self.handler = handler
[11252]78        self.id = id
79        self.doc = doc
[11098]80
[11252]81        if isinstance(flags, basestring):
82            # flags are stored as strings
83            warnings.warn("Passing 'flags' as string is deprecated, use "
84                          "integer constants instead",
85                          PendingDeprecationWarning)
86            flags = eval(flags)
[11098]87
[11252]88        if not (flags & Single or flags & Multiple):
89            flags += Single
[11098]90
[11252]91        if not (flags & Default or flags & NonDefault):
92            flags += NonDefault
[11098]93
[11252]94        self.single = flags & Single
95        self.default = flags & Default
96        self.explicit = flags & Explicit
97        self.flags = flags
98
[11267]99    def __str__(self):
100        fmt = ("{0.__name__}(name={name!r}, type={type!s}, "
101               "handler={handler}, ...)")
102        return fmt.format(type(self), **self.__dict__)
103
[11252]104
105def input_channel_from_args(args):
106    if isinstance(args, tuple):
107        return InputSignal(*args)
108    elif isinstance(args, dict):
109        return InputSignal(**args)
110    elif isinstance(args, InputSignal):
111        return args
112    else:
[11267]113        raise TypeError("tuple, dict or InputSignal expected "
114                        "(got {0!r})".format(type(args)))
[11098]115
116
117class OutputSignal(object):
118    """Description of an output channel.
119
120    Parameters
121    ----------
122    name : str
123        Name of the channel.
124    type : str or `type`
125        Type of the output signals.
[11252]126    flags : int, optional
127        Channel flags.
128    id : str
129        A unique id of the output signal.
130    doc : str, optional
131        A docstring documenting the channel.
[11098]132
133    """
[11252]134    def __init__(self, name, type, flags=Single + NonDefault,
135                 id=None, doc=None):
[11098]136        self.name = name
137        self.type = type
[11252]138        self.id = id
139        self.doc = doc
[11098]140
[11252]141        if isinstance(flags, basestring):
142            # flags are stored as strings
143            warnings.warn("Passing 'flags' as string is deprecated, use "
144                          "integer constants instead",
145                          PendingDeprecationWarning)
146            flags = eval(flags)
[11098]147
[11252]148        if not (flags & Single or flags & Multiple):
149            flags += Single
[11098]150
[11252]151        if not (flags & Default or flags & NonDefault):
152            flags += NonDefault
[11098]153
[11252]154        self.single = flags & Single
155        self.default = flags & Default
156        self.explicit = flags & Explicit
157        self.dynamic = flags & Dynamic
158        self.flags = flags
[11098]159
160        if self.dynamic and not self.single:
161            raise SignalSpecificationError(
162                "Output signal can not be 'Multiple' and 'Dynamic'."
163                )
164
[11267]165    def __str__(self):
166        fmt = ("{0.__name__}(name={name!r}, type={type!s}, "
167               "...)")
168        return fmt.format(type(self), **self.__dict__)
169
[11098]170
[11252]171def output_channel_from_args(args):
172    if isinstance(args, tuple):
173        return OutputSignal(*args)
174    elif isinstance(args, dict):
175        return OutputSignal(**args)
[11267]176    elif isinstance(args, OutputSignal):
[11252]177        return args
178    else:
[11267]179        raise TypeError("tuple, dict or OutputSignal expected "
180                        "(got {0!r})".format(type(args)))
[11252]181
182
[11098]183class WidgetDescription(object):
184    """Description of a widget.
185
186    Parameters
[11252]187    ----------
[11098]188    name : str
[11252]189        A human readable name of the widget.
190    id : str
191        A unique identifier of the widget (in most situations this should
192        be the full module name).
193    category : str, optional
194        A name of the category in which this widget belongs.
195    version : str, optional
196        Version of the widget. By default the widget inherits the project
197        version.
198    description : str, optional
199        A short description of the widget, suitable for a tool tip.
200    long_description : str, optional
201        A longer description of the widget, suitable for a 'what's this?'
202        role.
203    qualified_name : str
204        A qualified name (import name) of the class implementing the widget.
205    package : str, optional
206        A package name where the widget is implemented.
207    project_name : str, optional
208        The distribution name that provides the widget.
209    inputs : list of `InputSignal`, optional
[11098]210        A list of input channels provided by the widget.
[11252]211    outputs : list of `OutputSignal`, optional
212        A list of output channels provided by the widget.
213    help : str, optional
214        URL or an Resource template of a detailed widget help page.
[11368]215    help_ref : str, optional
[11263]216        A text reference id that can be used to identify the help
217        page, for instance an intersphinx reference.
[11252]218    author : str, optional
[11098]219        Author name.
[11252]220    author_email : str, optional
[11098]221        Author email address.
[11252]222    maintainer : str, optional
[11098]223        Maintainer name
[11252]224    maintainer_email : str, optional
[11098]225        Maintainer email address.
[11252]226    keywords : list-of-str, optional
227        A list of keyword phrases.
228    priority : int, optional
[11098]229        Widget priority (the order of the widgets in a GUI presentation).
[11252]230    icon : str, optional
[11098]231        A filename of the widget icon (in relation to the package).
[11252]232    background : str, optional
[11098]233        Widget's background color (in the canvas GUI).
[11368]234    replaces : list-of-str, optional
[11252]235        A list of `id`s this widget replaces (optional).
[11098]236
237    """
[11252]238    def __init__(self, name, id, category=None, version=None,
[11098]239                 description=None, long_description=None,
240                 qualified_name=None, package=None, project_name=None,
[11252]241                 inputs=[], outputs=[],
[11098]242                 author=None, author_email=None,
243                 maintainer=None, maintainer_email=None,
[11263]244                 help=None, help_ref=None, url=None, keywords=None,
[11252]245                 priority=sys.maxint,
[11098]246                 icon=None, background=None,
[11252]247                 replaces=None,
[11098]248                 ):
249
250        if not qualified_name:
251            # TODO: Should also check that the name is real.
252            raise ValueError("'qualified_name' must be supplied.")
253
254        self.name = name
[11252]255        self.id = id
[11098]256        self.category = category
257        self.version = version
258        self.description = description
259        self.long_description = long_description
260        self.qualified_name = qualified_name
261        self.package = package
262        self.project_name = project_name
263        self.inputs = inputs
264        self.outputs = outputs
265        self.help = help
[11263]266        self.help_ref = help_ref
[11098]267        self.author = author
268        self.author_email = author_email
269        self.maintainer = maintainer
270        self.maintainer_email = maintainer_email
271        self.url = url
272        self.keywords = keywords
273        self.priority = priority
274        self.icon = icon
275        self.background = background
[11252]276        self.replaces = replaces
[11098]277
278    def __str__(self):
[11252]279        return ("WidgetDescription(name=%(name)r, id=%(id)r), "
280                "category=%(category)r, ...)") % self.__dict__
[11098]281
282    def __repr__(self):
283        return self.__str__()
284
285    @classmethod
286    def from_file(cls, filename, import_name=None):
287        """Widget description from old style (2.5 version) widget
288        descriptions.
289
290        """
291        from Orange.orng.widgetParser import WidgetMetaData
292        from ..orngSignalManager import resolveSignal
293
294        rest, ext = os.path.splitext(filename)
295        if ext in [".pyc", ".pyo"]:
296            filename = filename[:-1]
297
298        contents = open(filename, "rb").read()
299
300        dirname, basename = os.path.split(filename)
301        default_cat = os.path.basename(dirname)
302
303        try:
304            meta = WidgetMetaData(contents, default_cat)
305        except Exception, ex:
306            if "Not an Orange widget module." in str(ex):
307                raise WidgetSpecificationError
308            else:
309                raise
310
311        widget_name, ext = os.path.splitext(basename)
312        if import_name is None:
313            import_name = widget_name
314
315        wmod = __import__(import_name, fromlist=[""])
316
[11252]317        qualified_name = "%s.%s" % (import_name, widget_name)
318
[11098]319        inputs = eval(meta.inputList)
320        outputs = eval(meta.outputList)
321
[11252]322        inputs = map(input_channel_from_args, inputs)
323
324        outputs = map(output_channel_from_args, outputs)
[11098]325
326        # Resolve signal type names into concrete type instances
327        inputs = [resolveSignal(input, globals=wmod.__dict__)
328                  for input in inputs]
329        outputs = [resolveSignal(output, globals=wmod.__dict__)
330                  for output in outputs]
331
332        # Convert all signal types back into qualified names.
333        # This is to prevent any possible import problems when cached
334        # descriptions are unpickled (the relevant code using this lists
335        # should be able to handle missing types better).
336        for s in inputs + outputs:
337            s.type = "%s.%s" % (s.type.__module__, s.type.__name__)
338
339        desc = WidgetDescription(
340             name=meta.name,
[11252]341             id=qualified_name,
[11098]342             category=meta.category,
343             description=meta.description,
[11252]344             qualified_name=qualified_name,
[11098]345             package=wmod.__package__,
346             keywords=meta.tags,
347             inputs=inputs,
348             outputs=outputs,
349             icon=meta.icon,
[11157]350             priority=int(meta.priority)
[11267]351        )
352
[11098]353        return desc
354
355    @classmethod
356    def from_module(cls, module):
357        """Get the widget description from a module.
358
359        The module is inspected for global variables (upper case versions of
360        `WidgetDescription.__init__` parameters).
361
362        Parameters
363        ----------
364        module : `module` or str
365            A module to inspect for widget description. Can be passed
366            as a string (qualified import name).
367
368        """
369        if isinstance(module, basestring):
370            module = __import__(module, fromlist=[""])
371
372        module_name = module.__name__.rsplit(".", 1)[-1]
373        if module.__package__:
374            package_name = module.__package__.rsplit(".", 1)[-1]
375        else:
376            package_name = None
377
378        # Default widget class name unless otherwise specified is the
379        # module name, and default category the package name
380        default_cls_name = module_name
381        default_cat_name = package_name if package_name else ""
382
383        widget_cls_name = getattr(module, "WIDGET_CLASS", default_cls_name)
384        try:
385            widget_class = getattr(module, widget_cls_name)
386            name = getattr(module, "NAME")
387        except AttributeError:
388            # The module does not have a widget class implementation or the
389            # widget name.
390            raise WidgetSpecificationError
391
[11252]392        qualified_name = "%s.%s" % (module.__name__, widget_class.__name__)
393
394        id = getattr(module, "ID", module_name)
[11098]395        inputs = getattr(module, "INPUTS", [])
396        outputs = getattr(module, "OUTPUTS", [])
397        category = getattr(module, "CATEGORY", default_cat_name)
[11252]398        version = getattr(module, "VERSION", None)
[11098]399        description = getattr(module, "DESCRIPTION", name)
[11252]400        long_description = getattr(module, "LONG_DESCRIPTION", None)
401        author = getattr(module, "AUTHOR", None)
402        author_email = getattr(module, "AUTHOR_EMAIL", None)
403        maintainer = getattr(module, "MAINTAINER", None)
404        maintainer_email = getattr(module, "MAINTAINER_EMAIL", None)
405        help = getattr(module, "HELP", None)
[11263]406        help_ref = getattr(module, "HELP_REF", None)
[11252]407        url = getattr(module, "URL", None)
[11098]408
409        icon = getattr(module, "ICON", None)
410        priority = getattr(module, "PRIORITY", sys.maxint)
411        keywords = getattr(module, "KEYWORDS", None)
[11252]412        background = getattr(module, "BACKGROUND", None)
413        replaces = getattr(module, "REPLACES", None)
[11098]414
[11252]415        inputs = map(input_channel_from_args, inputs)
416        outputs = map(output_channel_from_args, outputs)
[11098]417
418        # Convert all signal types into qualified names.
419        # This is to prevent any possible import problems when cached
420        # descriptions are unpickled (the relevant code using this lists
421        # should be able to handle missing types better).
422        for s in inputs + outputs:
423            s.type = "%s.%s" % (s.type.__module__, s.type.__name__)
424
425        return WidgetDescription(
426            name=name,
[11252]427            id=id,
[11098]428            category=category,
[11252]429            version=version,
[11098]430            description=description,
[11252]431            long_description=long_description,
[11098]432            qualified_name=qualified_name,
433            package=module.__package__,
434            inputs=inputs,
435            outputs=outputs,
[11252]436            author=author,
437            author_email=author_email,
438            maintainer=maintainer,
439            maintainer_email=maintainer_email,
440            help=help,
[11263]441            help_ref=help_ref,
[11252]442            url=url,
443            keywords=keywords,
444            priority=priority,
[11098]445            icon=icon,
[11252]446            background=background,
447            replaces=replaces)
[11098]448
449
450class CategoryDescription(object):
451    """Description of a widget category.
452
453    Parameters
[11252]454    ----------
[11098]455
456    name : str
457        A human readable name.
458    version : str
459        Version string (optional).
460    description : str
461        A short description of the category, suitable for a tool
462        tip (optional).
463    long_description : str
464        A longer description.
465    qualified_name : str
466        Qualified name
467    project_name : str
468        A project name providing the category.
469    priority : int
470        Priority (order in the GUI).
471    icon : str
472        An icon filename
473    background : str
474        An background color for widgets in this category.
475
476    """
477    def __init__(self, name=None, version=None,
478                 description=None, long_description=None,
479                 qualified_name=None, package=None,
[11252]480                 project_name=None, author=None, author_email=None,
[11098]481                 maintainer=None, maintainer_email=None,
[11252]482                 url=None, help=None, keywords=None,
[11098]483                 widgets=None, priority=sys.maxint,
484                 icon=None, background=None
485                 ):
486
487        self.name = name
488        self.version = version
489        self.description = description
490        self.long_description = long_description
491        self.qualified_name = qualified_name
492        self.package = package
493        self.project_name = project_name
494        self.author = author
495        self.author_email = author_email
496        self.maintainer = maintainer
497        self.maintainer_email = maintainer_email
498        self.url = url
[11252]499        self.help = help
[11098]500        self.keywords = keywords
501        self.widgets = widgets or []
502        self.priority = priority
503        self.icon = icon
504        self.background = background
505
506    def __str__(self):
507        return "CategoryDescription(name=%(name)r, ...)" % self.__dict__
508
509    def __repr__(self):
510        return self.__str__()
511
512    @classmethod
513    def from_package(cls, package):
514        """Get the CategoryDescription from a package.
515
516        Parameters
517        ----------
518        package : `module` or `str`
519            A package containing the category.
520
521        """
522        if isinstance(package, basestring):
523            package = __import__(package, fromlist=[""])
524        package_name = package.__name__
525        qualified_name = package_name
526        default_name = package_name.rsplit(".", 1)[-1]
527
528        name = getattr(package, "NAME", default_name)
529        description = getattr(package, "DESCRIPTION", None)
530        long_description = getattr(package, "LONG_DESCRIPTION", None)
531        author = getattr(package, "AUTHOR", None)
532        author_email = getattr(package, "AUTHOR_EMAIL", None)
533        maintainer = getattr(package, "MAINTAINER", None)
534        maintainer_email = getattr(package, "MAINTAINER_MAIL", None)
535        url = getattr(package, "URL", None)
[11252]536        help = getattr(package, "HELP", None)
[11098]537        keywords = getattr(package, "KEYWORDS", None)
538        widgets = getattr(package, "WIDGETS", None)
[11171]539        priority = getattr(package, "PRIORITY", sys.maxint - 1)
[11098]540        icon = getattr(package, "ICON", None)
541        background = getattr(package, "BACKGROUND", None)
542
[11171]543        if priority == sys.maxint - 1 and name.lower() == "prototypes":
544            priority = sys.maxint
545
[11098]546        return CategoryDescription(
547            name=name,
548            qualified_name=qualified_name,
549            description=description,
550            long_description=long_description,
551            help=help,
552            author=author,
553            author_email=author_email,
554            maintainer=maintainer,
555            maintainer_email=maintainer_email,
556            url=url,
557            keywords=keywords,
558            widgets=widgets,
559            priority=priority,
560            icon=icon,
561            background=background)
Note: See TracBrowser for help on using the repository browser.