application.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. # encoding: utf-8
  2. """
  3. A base Application class for Jupyter applications.
  4. All Jupyter applications should inherit from this.
  5. """
  6. # Copyright (c) Jupyter Development Team.
  7. # Distributed under the terms of the Modified BSD License.
  8. from __future__ import print_function
  9. from copy import deepcopy
  10. import logging
  11. import os
  12. import sys
  13. try:
  14. # py3
  15. from shutil import which
  16. except ImportError:
  17. from .utils.shutil_which import which
  18. try:
  19. raw_input
  20. except NameError:
  21. # py3
  22. raw_input = input
  23. from traitlets.config.application import Application, catch_config_error
  24. from traitlets.config.loader import ConfigFileNotFound
  25. from traitlets import Unicode, Bool, List, observe
  26. from .utils import ensure_dir_exists
  27. from ipython_genutils import py3compat
  28. from .paths import (
  29. jupyter_config_dir, jupyter_data_dir, jupyter_runtime_dir,
  30. jupyter_path, jupyter_config_path, allow_insecure_writes,
  31. issue_insecure_write_warning
  32. )
  33. # aliases and flags
  34. base_aliases = {
  35. 'log-level' : 'Application.log_level',
  36. 'config' : 'JupyterApp.config_file',
  37. }
  38. base_flags = {
  39. 'debug': ({'Application' : {'log_level' : logging.DEBUG}},
  40. "set log level to logging.DEBUG (maximize logging output)"),
  41. 'generate-config': ({'JupyterApp': {'generate_config': True}},
  42. "generate default config file"),
  43. 'y': ({'JupyterApp': {'answer_yes': True}},
  44. "Answer yes to any questions instead of prompting."),
  45. }
  46. class NoStart(Exception):
  47. """Exception to raise when an application shouldn't start"""
  48. class JupyterApp(Application):
  49. """Base class for Jupyter applications"""
  50. name = 'jupyter' # override in subclasses
  51. description = "A Jupyter Application"
  52. aliases = base_aliases
  53. flags = base_flags
  54. def _log_level_default(self):
  55. return logging.INFO
  56. jupyter_path = List(Unicode())
  57. def _jupyter_path_default(self):
  58. return jupyter_path()
  59. config_dir = Unicode()
  60. def _config_dir_default(self):
  61. return jupyter_config_dir()
  62. @property
  63. def config_file_paths(self):
  64. path = jupyter_config_path()
  65. if self.config_dir not in path:
  66. path.insert(0, self.config_dir)
  67. path.insert(0, py3compat.getcwd())
  68. return path
  69. data_dir = Unicode()
  70. def _data_dir_default(self):
  71. d = jupyter_data_dir()
  72. ensure_dir_exists(d, mode=0o700)
  73. return d
  74. runtime_dir = Unicode()
  75. def _runtime_dir_default(self):
  76. rd = jupyter_runtime_dir()
  77. ensure_dir_exists(rd, mode=0o700)
  78. return rd
  79. @observe('runtime_dir')
  80. def _runtime_dir_changed(self, change):
  81. ensure_dir_exists(change['new'], mode=0o700)
  82. generate_config = Bool(False, config=True,
  83. help="""Generate default config file."""
  84. )
  85. config_file_name = Unicode(config=True,
  86. help="Specify a config file to load."
  87. )
  88. def _config_file_name_default(self):
  89. if not self.name:
  90. return ''
  91. return self.name.replace('-','_') + u'_config'
  92. config_file = Unicode(config=True,
  93. help="""Full path of a config file.""",
  94. )
  95. answer_yes = Bool(False, config=True,
  96. help="""Answer yes to any prompts."""
  97. )
  98. def write_default_config(self):
  99. """Write our default config to a .py config file"""
  100. if self.config_file:
  101. config_file = self.config_file
  102. else:
  103. config_file = os.path.join(self.config_dir, self.config_file_name + '.py')
  104. if os.path.exists(config_file) and not self.answer_yes:
  105. answer = ''
  106. def ask():
  107. prompt = "Overwrite %s with default config? [y/N]" % config_file
  108. try:
  109. return raw_input(prompt).lower() or 'n'
  110. except KeyboardInterrupt:
  111. print('') # empty line
  112. return 'n'
  113. answer = ask()
  114. while not answer.startswith(('y', 'n')):
  115. print("Please answer 'yes' or 'no'")
  116. answer = ask()
  117. if answer.startswith('n'):
  118. return
  119. config_text = self.generate_config_file()
  120. if isinstance(config_text, bytes):
  121. config_text = config_text.decode('utf8')
  122. print("Writing default config to: %s" % config_file)
  123. ensure_dir_exists(os.path.abspath(os.path.dirname(config_file)), 0o700)
  124. with open(config_file, mode='w') as f:
  125. f.write(config_text)
  126. def migrate_config(self):
  127. """Migrate config/data from IPython 3"""
  128. if os.path.exists(os.path.join(self.config_dir, 'migrated')):
  129. # already migrated
  130. return
  131. from .migrate import get_ipython_dir, migrate
  132. # No IPython dir, nothing to migrate
  133. if not os.path.exists(get_ipython_dir()):
  134. return
  135. migrate()
  136. def load_config_file(self, suppress_errors=True):
  137. """Load the config file.
  138. By default, errors in loading config are handled, and a warning
  139. printed on screen. For testing, the suppress_errors option is set
  140. to False, so errors will make tests fail.
  141. """
  142. self.log.debug("Searching %s for config files", self.config_file_paths)
  143. base_config = 'jupyter_config'
  144. try:
  145. super(JupyterApp, self).load_config_file(
  146. base_config,
  147. path=self.config_file_paths,
  148. )
  149. except ConfigFileNotFound:
  150. # ignore errors loading parent
  151. self.log.debug("Config file %s not found", base_config)
  152. pass
  153. if self.config_file:
  154. path, config_file_name = os.path.split(self.config_file)
  155. else:
  156. path = self.config_file_paths
  157. config_file_name = self.config_file_name
  158. if not config_file_name or (config_file_name == base_config):
  159. return
  160. try:
  161. super(JupyterApp, self).load_config_file(
  162. config_file_name,
  163. path=path
  164. )
  165. except ConfigFileNotFound:
  166. self.log.debug("Config file not found, skipping: %s", config_file_name)
  167. except Exception:
  168. # Reraise errors for testing purposes, or if set in
  169. # self.raise_config_file_errors
  170. if (not suppress_errors) or self.raise_config_file_errors:
  171. raise
  172. self.log.warning("Error loading config file: %s" %
  173. config_file_name, exc_info=True)
  174. # subcommand-related
  175. def _find_subcommand(self, name):
  176. name = '{}-{}'.format(self.name, name)
  177. return which(name)
  178. @property
  179. def _dispatching(self):
  180. """Return whether we are dispatching to another command
  181. or running ourselves.
  182. """
  183. return bool(self.generate_config or self.subapp or self.subcommand)
  184. subcommand = Unicode()
  185. @catch_config_error
  186. def initialize(self, argv=None):
  187. # don't hook up crash handler before parsing command-line
  188. if argv is None:
  189. argv = sys.argv[1:]
  190. if argv:
  191. subc = self._find_subcommand(argv[0])
  192. if subc:
  193. self.argv = argv
  194. self.subcommand = subc
  195. return
  196. self.parse_command_line(argv)
  197. cl_config = deepcopy(self.config)
  198. if self._dispatching:
  199. return
  200. self.migrate_config()
  201. self.load_config_file()
  202. # enforce cl-opts override configfile opts:
  203. self.update_config(cl_config)
  204. if allow_insecure_writes:
  205. issue_insecure_write_warning()
  206. def start(self):
  207. """Start the whole thing"""
  208. if self.subcommand:
  209. os.execv(self.subcommand, [self.subcommand] + self.argv[1:])
  210. raise NoStart()
  211. if self.subapp:
  212. self.subapp.start()
  213. raise NoStart()
  214. if self.generate_config:
  215. self.write_default_config()
  216. raise NoStart()
  217. @classmethod
  218. def launch_instance(cls, argv=None, **kwargs):
  219. """Launch an instance of a Jupyter Application"""
  220. try:
  221. return super(JupyterApp, cls).launch_instance(argv=argv, **kwargs)
  222. except NoStart:
  223. return
  224. if __name__ == '__main__':
  225. JupyterApp.launch_instance()