source: orange/Orange/OrangeCanvas/registry/description.py @ 11718:bbec0a6fdf1d

Revision 11718:bbec0a6fdf1d, 17.9 KB checked in by Ales Erjavec <ales.erjavec@…>, 7 months ago (diff)

Fixed widget qualified name.

Should always use the name used to discover it.

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):
[11418]55    """
56    Description of an input channel.
[11098]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.
[11252]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.
[11098]72
73    """
[11252]74    def __init__(self, name, type, handler, flags=Single + NonDefault,
75                 id=None, doc=None):
[11098]76        self.name = name
77        self.type = type
78        self.handler = handler
[11252]79        self.id = id
80        self.doc = doc
[11098]81
[11252]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)
[11098]88
[11252]89        if not (flags & Single or flags & Multiple):
90            flags += Single
[11098]91
[11252]92        if not (flags & Default or flags & NonDefault):
93            flags += NonDefault
[11098]94
[11252]95        self.single = flags & Single
96        self.default = flags & Default
97        self.explicit = flags & Explicit
98        self.flags = flags
99
[11267]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
[11252]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:
[11267]114        raise TypeError("tuple, dict or InputSignal expected "
115                        "(got {0!r})".format(type(args)))
[11098]116
117
118class OutputSignal(object):
[11418]119    """
120    Description of an output channel.
[11098]121
122    Parameters
123    ----------
124    name : str
125        Name of the channel.
126    type : str or `type`
127        Type of the output signals.
[11252]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.
[11098]134
135    """
[11252]136    def __init__(self, name, type, flags=Single + NonDefault,
137                 id=None, doc=None):
[11098]138        self.name = name
139        self.type = type
[11252]140        self.id = id
141        self.doc = doc
[11098]142
[11252]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)
[11098]149
[11252]150        if not (flags & Single or flags & Multiple):
151            flags += Single
[11098]152
[11252]153        if not (flags & Default or flags & NonDefault):
154            flags += NonDefault
[11098]155
[11252]156        self.single = flags & Single
157        self.default = flags & Default
158        self.explicit = flags & Explicit
159        self.dynamic = flags & Dynamic
160        self.flags = flags
[11098]161
162        if self.dynamic and not self.single:
163            raise SignalSpecificationError(
164                "Output signal can not be 'Multiple' and 'Dynamic'."
165                )
166
[11267]167    def __str__(self):
168        fmt = ("{0.__name__}(name={name!r}, type={type!s}, "
169               "...)")
170        return fmt.format(type(self), **self.__dict__)
171
[11098]172
[11252]173def output_channel_from_args(args):
174    if isinstance(args, tuple):
175        return OutputSignal(*args)
176    elif isinstance(args, dict):
177        return OutputSignal(**args)
[11267]178    elif isinstance(args, OutputSignal):
[11252]179        return args
180    else:
[11267]181        raise TypeError("tuple, dict or OutputSignal expected "
182                        "(got {0!r})".format(type(args)))
[11252]183
184
[11098]185class WidgetDescription(object):
[11418]186    """
187    Description of a widget.
[11098]188
189    Parameters
[11252]190    ----------
[11098]191    name : str
[11252]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.
[11418]212    inputs : list of :class:`InputSignal`, optional
[11098]213        A list of input channels provided by the widget.
[11418]214    outputs : list of :class:`OutputSignal`, optional
[11252]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.
[11368]218    help_ref : str, optional
[11263]219        A text reference id that can be used to identify the help
220        page, for instance an intersphinx reference.
[11252]221    author : str, optional
[11098]222        Author name.
[11252]223    author_email : str, optional
[11098]224        Author email address.
[11252]225    maintainer : str, optional
[11098]226        Maintainer name
[11252]227    maintainer_email : str, optional
[11098]228        Maintainer email address.
[11252]229    keywords : list-of-str, optional
230        A list of keyword phrases.
231    priority : int, optional
[11098]232        Widget priority (the order of the widgets in a GUI presentation).
[11252]233    icon : str, optional
[11098]234        A filename of the widget icon (in relation to the package).
[11252]235    background : str, optional
[11098]236        Widget's background color (in the canvas GUI).
[11368]237    replaces : list-of-str, optional
[11252]238        A list of `id`s this widget replaces (optional).
[11098]239
240    """
[11252]241    def __init__(self, name, id, category=None, version=None,
[11098]242                 description=None, long_description=None,
243                 qualified_name=None, package=None, project_name=None,
[11252]244                 inputs=[], outputs=[],
[11098]245                 author=None, author_email=None,
246                 maintainer=None, maintainer_email=None,
[11263]247                 help=None, help_ref=None, url=None, keywords=None,
[11252]248                 priority=sys.maxint,
[11098]249                 icon=None, background=None,
[11252]250                 replaces=None,
[11098]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
[11252]258        self.id = id
[11098]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
[11263]269        self.help_ref = help_ref
[11098]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
[11252]279        self.replaces = replaces
[11098]280
281    def __str__(self):
[11252]282        return ("WidgetDescription(name=%(name)r, id=%(id)r), "
283                "category=%(category)r, ...)") % self.__dict__
[11098]284
285    def __repr__(self):
286        return self.__str__()
287
288    @classmethod
289    def from_file(cls, filename, import_name=None):
[11418]290        """
291        Widget description from old style (2.5 version) widget
[11098]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
[11252]321        qualified_name = "%s.%s" % (import_name, widget_name)
322
[11098]323        inputs = eval(meta.inputList)
324        outputs = eval(meta.outputList)
325
[11252]326        inputs = map(input_channel_from_args, inputs)
327
328        outputs = map(output_channel_from_args, outputs)
[11098]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,
[11252]345             id=qualified_name,
[11098]346             category=meta.category,
347             description=meta.description,
[11252]348             qualified_name=qualified_name,
[11098]349             package=wmod.__package__,
350             keywords=meta.tags,
351             inputs=inputs,
352             outputs=outputs,
353             icon=meta.icon,
[11157]354             priority=int(meta.priority)
[11267]355        )
356
[11098]357        return desc
358
359    @classmethod
360    def from_module(cls, module):
[11418]361        """
362        Get the widget description from a module.
[11098]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
[11718]397        qualified_name = "%s.%s" % (module.__name__, widget_cls_name)
[11252]398
399        id = getattr(module, "ID", module_name)
[11098]400        inputs = getattr(module, "INPUTS", [])
401        outputs = getattr(module, "OUTPUTS", [])
402        category = getattr(module, "CATEGORY", default_cat_name)
[11252]403        version = getattr(module, "VERSION", None)
[11098]404        description = getattr(module, "DESCRIPTION", name)
[11252]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)
[11263]411        help_ref = getattr(module, "HELP_REF", None)
[11252]412        url = getattr(module, "URL", None)
[11098]413
414        icon = getattr(module, "ICON", None)
415        priority = getattr(module, "PRIORITY", sys.maxint)
416        keywords = getattr(module, "KEYWORDS", None)
[11252]417        background = getattr(module, "BACKGROUND", None)
418        replaces = getattr(module, "REPLACES", None)
[11098]419
[11252]420        inputs = map(input_channel_from_args, inputs)
421        outputs = map(output_channel_from_args, outputs)
[11098]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,
[11252]432            id=id,
[11098]433            category=category,
[11252]434            version=version,
[11098]435            description=description,
[11252]436            long_description=long_description,
[11098]437            qualified_name=qualified_name,
438            package=module.__package__,
439            inputs=inputs,
440            outputs=outputs,
[11252]441            author=author,
442            author_email=author_email,
443            maintainer=maintainer,
444            maintainer_email=maintainer_email,
445            help=help,
[11263]446            help_ref=help_ref,
[11252]447            url=url,
448            keywords=keywords,
449            priority=priority,
[11098]450            icon=icon,
[11252]451            background=background,
452            replaces=replaces)
[11098]453
454
455class CategoryDescription(object):
[11418]456    """
457    Description of a widget category.
[11098]458
459    Parameters
[11252]460    ----------
[11098]461
462    name : str
463        A human readable name.
[11418]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
[11098]469        A longer description.
[11418]470    qualified_name : str,
[11098]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
[11418]477        An icon filename (a resource name retrievable using `pkg_resources`
478        relative to `qualified_name`).
[11098]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,
[11252]486                 project_name=None, author=None, author_email=None,
[11098]487                 maintainer=None, maintainer_email=None,
[11252]488                 url=None, help=None, keywords=None,
[11098]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
[11252]505        self.help = help
[11098]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):
[11418]520        """
521        Get the CategoryDescription from a package.
[11098]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)
[11252]543        help = getattr(package, "HELP", None)
[11098]544        keywords = getattr(package, "KEYWORDS", None)
545        widgets = getattr(package, "WIDGETS", None)
[11171]546        priority = getattr(package, "PRIORITY", sys.maxint - 1)
[11098]547        icon = getattr(package, "ICON", None)
548        background = getattr(package, "BACKGROUND", None)
549
[11171]550        if priority == sys.maxint - 1 and name.lower() == "prototypes":
551            priority = sys.maxint
552
[11098]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.