bundlerextensions.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. # Copyright (c) Jupyter Development Team.
  2. # Distributed under the terms of the Modified BSD License.
  3. import sys
  4. import os
  5. from ..extensions import BaseExtensionApp, _get_config_dir, GREEN_ENABLED, RED_DISABLED
  6. from .._version import __version__
  7. from notebook.config_manager import BaseJSONConfigManager
  8. from jupyter_core.paths import jupyter_config_path
  9. from traitlets.utils.importstring import import_item
  10. from traitlets import Bool
  11. BUNDLER_SECTION = "notebook"
  12. BUNDLER_SUBSECTION = "bundlerextensions"
  13. def _get_bundler_metadata(module):
  14. """Gets the list of bundlers associated with a Python package.
  15. Returns a tuple of (the module, [{
  16. 'name': 'unique name of the bundler',
  17. 'label': 'file menu item label for the bundler',
  18. 'module_name': 'dotted package/module name containing the bundler',
  19. 'group': 'download or deploy parent menu item'
  20. }])
  21. Parameters
  22. ----------
  23. module : str
  24. Importable Python module exposing the
  25. magic-named `_jupyter_bundlerextension_paths` function
  26. """
  27. m = import_item(module)
  28. if not hasattr(m, '_jupyter_bundlerextension_paths'):
  29. raise KeyError('The Python module {} does not contain a valid bundlerextension'.format(module))
  30. bundlers = m._jupyter_bundlerextension_paths()
  31. return m, bundlers
  32. def _set_bundler_state(name, label, module_name, group, state,
  33. user=True, sys_prefix=False, logger=None):
  34. """Set whether a bundler is enabled or disabled.
  35. Returns True if the final state is the one requested.
  36. Parameters
  37. ----------
  38. name : string
  39. Unique name of the bundler
  40. label : string
  41. Human-readable label for the bundler menu item in the notebook UI
  42. module_name : string
  43. Dotted module/package name containing the bundler
  44. group : string
  45. 'download' or 'deploy' indicating the parent menu containing the label
  46. state : bool
  47. The state in which to leave the extension
  48. user : bool [default: True]
  49. Whether to update the user's .jupyter/nbconfig directory
  50. sys_prefix : bool [default: False]
  51. Whether to update the sys.prefix, i.e. environment. Will override
  52. `user`.
  53. logger : Jupyter logger [optional]
  54. Logger instance to use
  55. """
  56. user = False if sys_prefix else user
  57. config_dir = os.path.join(
  58. _get_config_dir(user=user, sys_prefix=sys_prefix), 'nbconfig')
  59. cm = BaseJSONConfigManager(config_dir=config_dir)
  60. if logger:
  61. logger.info("{} {} bundler {}...".format(
  62. "Enabling" if state else "Disabling",
  63. name,
  64. module_name
  65. ))
  66. if state:
  67. cm.update(BUNDLER_SECTION, {
  68. BUNDLER_SUBSECTION: {
  69. name: {
  70. "label": label,
  71. "module_name": module_name,
  72. "group" : group
  73. }
  74. }
  75. })
  76. else:
  77. cm.update(BUNDLER_SECTION, {
  78. BUNDLER_SUBSECTION: {
  79. name: None
  80. }
  81. })
  82. return (cm.get(BUNDLER_SECTION)
  83. .get(BUNDLER_SUBSECTION, {})
  84. .get(name) is not None) == state
  85. def _set_bundler_state_python(state, module, user, sys_prefix, logger=None):
  86. """Enables or disables bundlers defined in a Python package.
  87. Returns a list of whether the state was achieved for each bundler.
  88. Parameters
  89. ----------
  90. state : Bool
  91. Whether the extensions should be enabled
  92. module : str
  93. Importable Python module exposing the
  94. magic-named `_jupyter_bundlerextension_paths` function
  95. user : bool
  96. Whether to enable in the user's nbconfig directory.
  97. sys_prefix : bool
  98. Enable/disable in the sys.prefix, i.e. environment
  99. logger : Jupyter logger [optional]
  100. Logger instance to use
  101. """
  102. m, bundlers = _get_bundler_metadata(module)
  103. return [_set_bundler_state(name=bundler["name"],
  104. label=bundler["label"],
  105. module_name=bundler["module_name"],
  106. group=bundler["group"],
  107. state=state,
  108. user=user, sys_prefix=sys_prefix,
  109. logger=logger)
  110. for bundler in bundlers]
  111. def enable_bundler_python(module, user=True, sys_prefix=False, logger=None):
  112. """Enables bundlers defined in a Python package.
  113. Returns whether each bundle defined in the packaged was enabled or not.
  114. Parameters
  115. ----------
  116. module : str
  117. Importable Python module exposing the
  118. magic-named `_jupyter_bundlerextension_paths` function
  119. user : bool [default: True]
  120. Whether to enable in the user's nbconfig directory.
  121. sys_prefix : bool [default: False]
  122. Whether to enable in the sys.prefix, i.e. environment. Will override
  123. `user`
  124. logger : Jupyter logger [optional]
  125. Logger instance to use
  126. """
  127. return _set_bundler_state_python(True, module, user, sys_prefix,
  128. logger=logger)
  129. def disable_bundler_python(module, user=True, sys_prefix=False, logger=None):
  130. """Disables bundlers defined in a Python package.
  131. Returns whether each bundle defined in the packaged was enabled or not.
  132. Parameters
  133. ----------
  134. module : str
  135. Importable Python module exposing the
  136. magic-named `_jupyter_bundlerextension_paths` function
  137. user : bool [default: True]
  138. Whether to enable in the user's nbconfig directory.
  139. sys_prefix : bool [default: False]
  140. Whether to enable in the sys.prefix, i.e. environment. Will override
  141. `user`
  142. logger : Jupyter logger [optional]
  143. Logger instance to use
  144. """
  145. return _set_bundler_state_python(False, module, user, sys_prefix,
  146. logger=logger)
  147. class ToggleBundlerExtensionApp(BaseExtensionApp):
  148. """A base class for apps that enable/disable bundlerextensions"""
  149. name = "jupyter bundlerextension enable/disable"
  150. version = __version__
  151. description = "Enable/disable a bundlerextension in configuration."
  152. user = Bool(True, config=True, help="Apply the configuration only for the current user (default)")
  153. _toggle_value = None
  154. def _config_file_name_default(self):
  155. """The default config file name."""
  156. return 'jupyter_notebook_config'
  157. def toggle_bundler_python(self, module):
  158. """Toggle some extensions in an importable Python module.
  159. Returns a list of booleans indicating whether the state was changed as
  160. requested.
  161. Parameters
  162. ----------
  163. module : str
  164. Importable Python module exposing the
  165. magic-named `_jupyter_bundlerextension_paths` function
  166. """
  167. toggle = (enable_bundler_python if self._toggle_value
  168. else disable_bundler_python)
  169. return toggle(module,
  170. user=self.user,
  171. sys_prefix=self.sys_prefix,
  172. logger=self.log)
  173. def start(self):
  174. if not self.extra_args:
  175. sys.exit('Please specify an bundlerextension/package to enable or disable')
  176. elif len(self.extra_args) > 1:
  177. sys.exit('Please specify one bundlerextension/package at a time')
  178. if self.python:
  179. self.toggle_bundler_python(self.extra_args[0])
  180. else:
  181. raise NotImplementedError('Cannot install bundlers from non-Python packages')
  182. class EnableBundlerExtensionApp(ToggleBundlerExtensionApp):
  183. """An App that enables bundlerextensions"""
  184. name = "jupyter bundlerextension enable"
  185. description = """
  186. Enable a bundlerextension in frontend configuration.
  187. Usage
  188. jupyter bundlerextension enable [--system|--sys-prefix]
  189. """
  190. _toggle_value = True
  191. class DisableBundlerExtensionApp(ToggleBundlerExtensionApp):
  192. """An App that disables bundlerextensions"""
  193. name = "jupyter bundlerextension disable"
  194. description = """
  195. Disable a bundlerextension in frontend configuration.
  196. Usage
  197. jupyter bundlerextension disable [--system|--sys-prefix]
  198. """
  199. _toggle_value = None
  200. class ListBundlerExtensionApp(BaseExtensionApp):
  201. """An App that lists and validates nbextensions"""
  202. name = "jupyter nbextension list"
  203. version = __version__
  204. description = "List all nbextensions known by the configuration system"
  205. def list_nbextensions(self):
  206. """List all the nbextensions"""
  207. config_dirs = [os.path.join(p, 'nbconfig') for p in jupyter_config_path()]
  208. print("Known bundlerextensions:")
  209. for config_dir in config_dirs:
  210. head = u' config dir: {}'.format(config_dir)
  211. head_shown = False
  212. cm = BaseJSONConfigManager(parent=self, config_dir=config_dir)
  213. data = cm.get('notebook')
  214. if 'bundlerextensions' in data:
  215. if not head_shown:
  216. # only show heading if there is an nbextension here
  217. print(head)
  218. head_shown = True
  219. for bundler_id, info in data['bundlerextensions'].items():
  220. label = info.get('label')
  221. module = info.get('module_name')
  222. if label is None or module is None:
  223. msg = u' {} {}'.format(bundler_id, RED_DISABLED)
  224. else:
  225. msg = u' "{}" from {} {}'.format(
  226. label, module, GREEN_ENABLED
  227. )
  228. print(msg)
  229. def start(self):
  230. """Perform the App's functions as configured"""
  231. self.list_nbextensions()
  232. class BundlerExtensionApp(BaseExtensionApp):
  233. """Base jupyter bundlerextension command entry point"""
  234. name = "jupyter bundlerextension"
  235. version = __version__
  236. description = "Work with Jupyter bundler extensions"
  237. examples = """
  238. jupyter bundlerextension list # list all configured bundlers
  239. jupyter bundlerextension enable --py <packagename> # enable all bundlers in a Python package
  240. jupyter bundlerextension disable --py <packagename> # disable all bundlers in a Python package
  241. """
  242. subcommands = dict(
  243. enable=(EnableBundlerExtensionApp, "Enable a bundler extension"),
  244. disable=(DisableBundlerExtensionApp, "Disable a bundler extension"),
  245. list=(ListBundlerExtensionApp, "List bundler extensions")
  246. )
  247. def start(self):
  248. """Perform the App's functions as configured"""
  249. super(BundlerExtensionApp, self).start()
  250. # The above should have called a subcommand and raised NoStart; if we
  251. # get here, it didn't, so we should self.log.info a message.
  252. subcmds = ", ".join(sorted(self.subcommands))
  253. sys.exit("Please supply at least one subcommand: %s" % subcmds)
  254. main = BundlerExtensionApp.launch_instance
  255. if __name__ == '__main__':
  256. main()