source: orange/Orange/OrangeCanvas/registry/description.py @ 11263:9f8fc6d26e58

Revision 11263:9f8fc6d26e58, 17.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 15 months ago (diff)

Added canvas help search system.

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