source: orange/Orange/OrangeCanvas/registry/description.py @ 11290:fff4c45473e8

Revision 11290:fff4c45473e8, 17.7 KB checked in by janezd <janez.demsar@…>, 15 months ago (diff)

Merge

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