source: orange/Orange/OrangeCanvas/registry/description.py @ 11418:6abbcdf4ff7c

Revision 11418:6abbcdf4ff7c, 17.9 KB checked in by Ales Erjavec <ales.erjavec@…>, 13 months ago (diff)

Widget registry documentation fixup.

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