PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/Orange/canvas/registry/description.py

https://gitlab.com/zaverichintan/orange3
Python | 472 lines | 359 code | 42 blank | 71 comment | 20 complexity | 23cd79d6b0219d212527f4c7ddf4e00b MD5 | raw file
  1. """
  2. Widget meta description classes
  3. ===============================
  4. """
  5. import sys
  6. import copy
  7. import warnings
  8. # Exceptions
  9. class DescriptionError(Exception):
  10. pass
  11. class WidgetSpecificationError(DescriptionError):
  12. pass
  13. class SignalSpecificationError(DescriptionError):
  14. pass
  15. class CategorySpecificationError(DescriptionError):
  16. pass
  17. ###############
  18. # Channel flags
  19. ###############
  20. # A single signal
  21. Single = 2
  22. # Multiple signal (more then one input on the channel)
  23. Multiple = 4
  24. # Default signal (default or primary input/output)
  25. Default = 8
  26. NonDefault = 16
  27. # Explicit - only connected if specifically requested or the only possibility
  28. Explicit = 32
  29. # Dynamic type output signal
  30. Dynamic = 64
  31. # Input/output signal (channel) description
  32. class InputSignal(object):
  33. """
  34. Description of an input channel.
  35. Parameters
  36. ----------
  37. name : str
  38. Name of the channel.
  39. type : str or `type`
  40. Type of the accepted signals.
  41. handler : str
  42. Name of the handler method for the signal.
  43. flags : int, optional
  44. Channel flags.
  45. id : str
  46. A unique id of the input signal.
  47. doc : str, optional
  48. A docstring documenting the channel.
  49. """
  50. def __init__(self, name, type, handler, flags=Single + NonDefault,
  51. id=None, doc=None):
  52. self.name = name
  53. self.type = type
  54. self.handler = handler
  55. self.id = id
  56. self.doc = doc
  57. if isinstance(flags, str):
  58. # flags are stored as strings
  59. warnings.warn("Passing 'flags' as string is deprecated, use "
  60. "integer constants instead",
  61. PendingDeprecationWarning)
  62. flags = eval(flags)
  63. if not (flags & Single or flags & Multiple):
  64. flags += Single
  65. if not (flags & Default or flags & NonDefault):
  66. flags += NonDefault
  67. self.single = flags & Single
  68. self.default = flags & Default
  69. self.explicit = flags & Explicit
  70. self.flags = flags
  71. def __str__(self):
  72. fmt = ("{0.__name__}(name={name!r}, type={type!s}, "
  73. "handler={handler}, ...)")
  74. return fmt.format(type(self), **self.__dict__)
  75. __repr__ = __str__
  76. def input_channel_from_args(args):
  77. if isinstance(args, tuple):
  78. return InputSignal(*args)
  79. elif isinstance(args, InputSignal):
  80. return copy.copy(args)
  81. else:
  82. raise TypeError("invalid declaration of widget input signal")
  83. class OutputSignal(object):
  84. """
  85. Description of an output channel.
  86. Parameters
  87. ----------
  88. name : str
  89. Name of the channel.
  90. type : str or `type`
  91. Type of the output signals.
  92. flags : int, optional
  93. Channel flags.
  94. id : str
  95. A unique id of the output signal.
  96. doc : str, optional
  97. A docstring documenting the channel.
  98. """
  99. def __init__(self, name, type, flags=Single + NonDefault,
  100. id=None, doc=None):
  101. self.name = name
  102. self.type = type
  103. self.id = id
  104. self.doc = doc
  105. if isinstance(flags, str):
  106. # flags are stored as strings
  107. warnings.warn("Passing 'flags' as string is deprecated, use "
  108. "integer constants instead",
  109. PendingDeprecationWarning)
  110. flags = eval(flags)
  111. if not (flags & Single or flags & Multiple):
  112. flags += Single
  113. if not (flags & Default or flags & NonDefault):
  114. flags += NonDefault
  115. self.single = flags & Single
  116. self.default = flags & Default
  117. self.explicit = flags & Explicit
  118. self.dynamic = flags & Dynamic
  119. self.flags = flags
  120. if self.dynamic and not self.single:
  121. raise SignalSpecificationError(
  122. "Output signal can not be 'Multiple' and 'Dynamic'."
  123. )
  124. def __str__(self):
  125. fmt = ("{0.__name__}(name={name!r}, type={type!s}, "
  126. "...)")
  127. return fmt.format(type(self), **self.__dict__)
  128. __repr__ = __str__
  129. def output_channel_from_args(args):
  130. if isinstance(args, tuple):
  131. return OutputSignal(*args)
  132. elif isinstance(args, OutputSignal):
  133. return copy.copy(args)
  134. else:
  135. raise TypeError("invalid declaration of widget output signal")
  136. class WidgetDescription(object):
  137. """
  138. Description of a widget.
  139. Parameters
  140. ----------
  141. name : str
  142. A human readable name of the widget.
  143. id : str
  144. A unique identifier of the widget (in most situations this should
  145. be the full module name).
  146. category : str, optional
  147. A name of the category in which this widget belongs.
  148. version : str, optional
  149. Version of the widget. By default the widget inherits the project
  150. version.
  151. description : str, optional
  152. A short description of the widget, suitable for a tool tip.
  153. long_description : str, optional
  154. A longer description of the widget, suitable for a 'what's this?'
  155. role.
  156. qualified_name : str
  157. A qualified name (import name) of the class implementing the widget.
  158. package : str, optional
  159. A package name where the widget is implemented.
  160. project_name : str, optional
  161. The distribution name that provides the widget.
  162. inputs : list of :class:`InputSignal`, optional
  163. A list of input channels provided by the widget.
  164. outputs : list of :class:`OutputSignal`, optional
  165. A list of output channels provided by the widget.
  166. help : str, optional
  167. URL or an Resource template of a detailed widget help page.
  168. help_ref : str, optional
  169. A text reference id that can be used to identify the help
  170. page, for instance an intersphinx reference.
  171. author : str, optional
  172. Author name.
  173. author_email : str, optional
  174. Author email address.
  175. maintainer : str, optional
  176. Maintainer name
  177. maintainer_email : str, optional
  178. Maintainer email address.
  179. keywords : list-of-str, optional
  180. A list of keyword phrases.
  181. priority : int, optional
  182. Widget priority (the order of the widgets in a GUI presentation).
  183. icon : str, optional
  184. A filename of the widget icon (in relation to the package).
  185. background : str, optional
  186. Widget's background color (in the canvas GUI).
  187. replaces : list-of-str, optional
  188. A list of `id`s this widget replaces (optional).
  189. """
  190. def __init__(self, name, id, category=None, version=None,
  191. description=None, long_description=None,
  192. qualified_name=None, package=None, project_name=None,
  193. inputs=[], outputs=[],
  194. author=None, author_email=None,
  195. maintainer=None, maintainer_email=None,
  196. help=None, help_ref=None, url=None, keywords=None,
  197. priority=sys.maxsize,
  198. icon=None, background=None,
  199. replaces=None,
  200. ):
  201. if not qualified_name:
  202. # TODO: Should also check that the name is real.
  203. raise ValueError("'qualified_name' must be supplied.")
  204. self.name = name
  205. self.id = id
  206. self.category = category
  207. self.version = version
  208. self.description = description
  209. self.long_description = long_description
  210. self.qualified_name = qualified_name
  211. self.package = package
  212. self.project_name = project_name
  213. self.inputs = inputs
  214. self.outputs = outputs
  215. self.help = help
  216. self.help_ref = help_ref
  217. self.author = author
  218. self.author_email = author_email
  219. self.maintainer = maintainer
  220. self.maintainer_email = maintainer_email
  221. self.url = url
  222. self.keywords = keywords
  223. self.priority = priority
  224. self.icon = icon
  225. self.background = background
  226. self.replaces = replaces
  227. def __str__(self):
  228. return ("WidgetDescription(name=%(name)r, id=%(id)r), "
  229. "category=%(category)r, ...)") % self.__dict__
  230. def __repr__(self):
  231. return self.__str__()
  232. @classmethod
  233. def from_module(cls, module):
  234. """
  235. Get the widget description from a module.
  236. The module is inspected for global variables (upper case versions of
  237. `WidgetDescription.__init__` parameters).
  238. Parameters
  239. ----------
  240. module : `module` or str
  241. A module to inspect for widget description. Can be passed
  242. as a string (qualified import name).
  243. """
  244. if isinstance(module, str):
  245. module = __import__(module, fromlist=[""])
  246. module_name = module.__name__.rsplit(".", 1)[-1]
  247. if module.__package__:
  248. package_name = module.__package__.rsplit(".", 1)[-1]
  249. else:
  250. package_name = None
  251. default_cat_name = package_name if package_name else ""
  252. from Orange.widgets.widget import WidgetMetaClass
  253. for widget_cls_name, widget_class in module.__dict__.items():
  254. if (isinstance(widget_class, WidgetMetaClass) and
  255. widget_class.name):
  256. break
  257. else:
  258. raise WidgetSpecificationError
  259. qualified_name = "%s.%s" % (module.__name__, widget_cls_name)
  260. inputs = [input_channel_from_args(input_) for input_ in
  261. widget_class.inputs]
  262. outputs = [output_channel_from_args(output) for output in
  263. widget_class.outputs]
  264. # Convert all signal types into qualified names.
  265. # This is to prevent any possible import problems when cached
  266. # descriptions are unpickled (the relevant code using this lists
  267. # should be able to handle missing types better).
  268. for s in inputs + outputs:
  269. s.type = "%s.%s" % (s.type.__module__, s.type.__name__)
  270. return cls(
  271. name=widget_class.name,
  272. id=widget_class.id or module_name,
  273. category=widget_class.category or default_cat_name,
  274. version=widget_class.version,
  275. description=widget_class.description,
  276. long_description=widget_class.long_description,
  277. qualified_name=qualified_name,
  278. package=module.__package__,
  279. inputs=inputs,
  280. outputs=outputs,
  281. author=widget_class.author,
  282. author_email=widget_class.author_email,
  283. maintainer=widget_class.maintainer,
  284. maintainer_email=widget_class.maintainer_email,
  285. help=widget_class.help,
  286. help_ref=widget_class.help_ref,
  287. url=widget_class.url,
  288. keywords=widget_class.keywords,
  289. priority=widget_class.priority,
  290. icon=widget_class.icon,
  291. background=widget_class.background,
  292. replaces=widget_class.replaces)
  293. class CategoryDescription(object):
  294. """
  295. Description of a widget category.
  296. Parameters
  297. ----------
  298. name : str
  299. A human readable name.
  300. version : str, optional
  301. Version string.
  302. description : str, optional
  303. A short description of the category, suitable for a tool tip.
  304. long_description : str, optional
  305. A longer description.
  306. qualified_name : str,
  307. Qualified name
  308. project_name : str
  309. A project name providing the category.
  310. priority : int
  311. Priority (order in the GUI).
  312. icon : str
  313. An icon filename (a resource name retrievable using `pkg_resources`
  314. relative to `qualified_name`).
  315. background : str
  316. An background color for widgets in this category.
  317. """
  318. def __init__(self, name=None, version=None,
  319. description=None, long_description=None,
  320. qualified_name=None, package=None,
  321. project_name=None, author=None, author_email=None,
  322. maintainer=None, maintainer_email=None,
  323. url=None, help=None, keywords=None,
  324. widgets=None, priority=sys.maxsize,
  325. icon=None, background=None
  326. ):
  327. self.name = name
  328. self.version = version
  329. self.description = description
  330. self.long_description = long_description
  331. self.qualified_name = qualified_name
  332. self.package = package
  333. self.project_name = project_name
  334. self.author = author
  335. self.author_email = author_email
  336. self.maintainer = maintainer
  337. self.maintainer_email = maintainer_email
  338. self.url = url
  339. self.help = help
  340. self.keywords = keywords
  341. self.widgets = widgets or []
  342. self.priority = priority
  343. self.icon = icon
  344. self.background = background
  345. def __str__(self):
  346. return "CategoryDescription(name=%(name)r, ...)" % self.__dict__
  347. def __repr__(self):
  348. return self.__str__()
  349. @classmethod
  350. def from_package(cls, package):
  351. """
  352. Get the CategoryDescription from a package.
  353. Parameters
  354. ----------
  355. package : `module` or `str`
  356. A package containing the category.
  357. """
  358. if isinstance(package, str):
  359. package = __import__(package, fromlist=[""])
  360. package_name = package.__name__
  361. qualified_name = package_name
  362. default_name = package_name.rsplit(".", 1)[-1]
  363. name = getattr(package, "NAME", default_name)
  364. description = getattr(package, "DESCRIPTION", None)
  365. long_description = getattr(package, "LONG_DESCRIPTION", None)
  366. author = getattr(package, "AUTHOR", None)
  367. author_email = getattr(package, "AUTHOR_EMAIL", None)
  368. maintainer = getattr(package, "MAINTAINER", None)
  369. maintainer_email = getattr(package, "MAINTAINER_MAIL", None)
  370. url = getattr(package, "URL", None)
  371. help = getattr(package, "HELP", None)
  372. keywords = getattr(package, "KEYWORDS", None)
  373. widgets = getattr(package, "WIDGETS", None)
  374. priority = getattr(package, "PRIORITY", sys.maxsize - 1)
  375. icon = getattr(package, "ICON", None)
  376. background = getattr(package, "BACKGROUND", None)
  377. if priority == sys.maxsize - 1 and name.lower() == "prototypes":
  378. priority = sys.maxsize
  379. return CategoryDescription(
  380. name=name,
  381. qualified_name=qualified_name,
  382. description=description,
  383. long_description=long_description,
  384. help=help,
  385. author=author,
  386. author_email=author_email,
  387. maintainer=maintainer,
  388. maintainer_email=maintainer_email,
  389. url=url,
  390. keywords=keywords,
  391. widgets=widgets,
  392. priority=priority,
  393. icon=icon,
  394. background=background)