source: orange/Orange/OrangeCanvas/registry/description.py @ 11171:5a8a8d697c94

Revision 11171:5a8a8d697c94, 14.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 18 months ago (diff)

Using 'sys.maxint - 1' as the default category priority ('sys.maxint' is used for prototypes).

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