123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- # encoding: utf-8
- """
- A base Application class for Jupyter applications.
- All Jupyter applications should inherit from this.
- """
- # Copyright (c) Jupyter Development Team.
- # Distributed under the terms of the Modified BSD License.
- from __future__ import print_function
- from copy import deepcopy
- import logging
- import os
- import sys
- try:
- # py3
- from shutil import which
- except ImportError:
- from .utils.shutil_which import which
- try:
- raw_input
- except NameError:
- # py3
- raw_input = input
- from traitlets.config.application import Application, catch_config_error
- from traitlets.config.loader import ConfigFileNotFound
- from traitlets import Unicode, Bool, List, observe
- from .utils import ensure_dir_exists
- from ipython_genutils import py3compat
- from .paths import (
- jupyter_config_dir, jupyter_data_dir, jupyter_runtime_dir,
- jupyter_path, jupyter_config_path, allow_insecure_writes,
- issue_insecure_write_warning
- )
- # aliases and flags
- base_aliases = {
- 'log-level' : 'Application.log_level',
- 'config' : 'JupyterApp.config_file',
- }
- base_flags = {
- 'debug': ({'Application' : {'log_level' : logging.DEBUG}},
- "set log level to logging.DEBUG (maximize logging output)"),
- 'generate-config': ({'JupyterApp': {'generate_config': True}},
- "generate default config file"),
- 'y': ({'JupyterApp': {'answer_yes': True}},
- "Answer yes to any questions instead of prompting."),
- }
- class NoStart(Exception):
- """Exception to raise when an application shouldn't start"""
- class JupyterApp(Application):
- """Base class for Jupyter applications"""
- name = 'jupyter' # override in subclasses
- description = "A Jupyter Application"
-
- aliases = base_aliases
- flags = base_flags
- def _log_level_default(self):
- return logging.INFO
-
- jupyter_path = List(Unicode())
- def _jupyter_path_default(self):
- return jupyter_path()
-
- config_dir = Unicode()
-
- def _config_dir_default(self):
- return jupyter_config_dir()
-
- @property
- def config_file_paths(self):
- path = jupyter_config_path()
- if self.config_dir not in path:
- path.insert(0, self.config_dir)
- path.insert(0, py3compat.getcwd())
- return path
-
- data_dir = Unicode()
-
- def _data_dir_default(self):
- d = jupyter_data_dir()
- ensure_dir_exists(d, mode=0o700)
- return d
- runtime_dir = Unicode()
-
- def _runtime_dir_default(self):
- rd = jupyter_runtime_dir()
- ensure_dir_exists(rd, mode=0o700)
- return rd
- @observe('runtime_dir')
- def _runtime_dir_changed(self, change):
- ensure_dir_exists(change['new'], mode=0o700)
- generate_config = Bool(False, config=True,
- help="""Generate default config file."""
- )
-
- config_file_name = Unicode(config=True,
- help="Specify a config file to load."
- )
- def _config_file_name_default(self):
- if not self.name:
- return ''
- return self.name.replace('-','_') + u'_config'
-
- config_file = Unicode(config=True,
- help="""Full path of a config file.""",
- )
-
- answer_yes = Bool(False, config=True,
- help="""Answer yes to any prompts."""
- )
-
- def write_default_config(self):
- """Write our default config to a .py config file"""
- if self.config_file:
- config_file = self.config_file
- else:
- config_file = os.path.join(self.config_dir, self.config_file_name + '.py')
-
- if os.path.exists(config_file) and not self.answer_yes:
- answer = ''
- def ask():
- prompt = "Overwrite %s with default config? [y/N]" % config_file
- try:
- return raw_input(prompt).lower() or 'n'
- except KeyboardInterrupt:
- print('') # empty line
- return 'n'
- answer = ask()
- while not answer.startswith(('y', 'n')):
- print("Please answer 'yes' or 'no'")
- answer = ask()
- if answer.startswith('n'):
- return
-
- config_text = self.generate_config_file()
- if isinstance(config_text, bytes):
- config_text = config_text.decode('utf8')
- print("Writing default config to: %s" % config_file)
- ensure_dir_exists(os.path.abspath(os.path.dirname(config_file)), 0o700)
- with open(config_file, mode='w') as f:
- f.write(config_text)
-
- def migrate_config(self):
- """Migrate config/data from IPython 3"""
- if os.path.exists(os.path.join(self.config_dir, 'migrated')):
- # already migrated
- return
- from .migrate import get_ipython_dir, migrate
- # No IPython dir, nothing to migrate
- if not os.path.exists(get_ipython_dir()):
- return
- migrate()
- def load_config_file(self, suppress_errors=True):
- """Load the config file.
- By default, errors in loading config are handled, and a warning
- printed on screen. For testing, the suppress_errors option is set
- to False, so errors will make tests fail.
- """
- self.log.debug("Searching %s for config files", self.config_file_paths)
- base_config = 'jupyter_config'
- try:
- super(JupyterApp, self).load_config_file(
- base_config,
- path=self.config_file_paths,
- )
- except ConfigFileNotFound:
- # ignore errors loading parent
- self.log.debug("Config file %s not found", base_config)
- pass
- if self.config_file:
- path, config_file_name = os.path.split(self.config_file)
- else:
- path = self.config_file_paths
- config_file_name = self.config_file_name
- if not config_file_name or (config_file_name == base_config):
- return
- try:
- super(JupyterApp, self).load_config_file(
- config_file_name,
- path=path
- )
- except ConfigFileNotFound:
- self.log.debug("Config file not found, skipping: %s", config_file_name)
- except Exception:
- # Reraise errors for testing purposes, or if set in
- # self.raise_config_file_errors
- if (not suppress_errors) or self.raise_config_file_errors:
- raise
- self.log.warning("Error loading config file: %s" %
- config_file_name, exc_info=True)
- # subcommand-related
- def _find_subcommand(self, name):
- name = '{}-{}'.format(self.name, name)
- return which(name)
-
- @property
- def _dispatching(self):
- """Return whether we are dispatching to another command
-
- or running ourselves.
- """
- return bool(self.generate_config or self.subapp or self.subcommand)
-
- subcommand = Unicode()
-
- @catch_config_error
- def initialize(self, argv=None):
- # don't hook up crash handler before parsing command-line
- if argv is None:
- argv = sys.argv[1:]
- if argv:
- subc = self._find_subcommand(argv[0])
- if subc:
- self.argv = argv
- self.subcommand = subc
- return
- self.parse_command_line(argv)
- cl_config = deepcopy(self.config)
- if self._dispatching:
- return
- self.migrate_config()
- self.load_config_file()
- # enforce cl-opts override configfile opts:
- self.update_config(cl_config)
- if allow_insecure_writes:
- issue_insecure_write_warning()
- def start(self):
- """Start the whole thing"""
- if self.subcommand:
- os.execv(self.subcommand, [self.subcommand] + self.argv[1:])
- raise NoStart()
-
- if self.subapp:
- self.subapp.start()
- raise NoStart()
-
- if self.generate_config:
- self.write_default_config()
- raise NoStart()
-
- @classmethod
- def launch_instance(cls, argv=None, **kwargs):
- """Launch an instance of a Jupyter Application"""
- try:
- return super(JupyterApp, cls).launch_instance(argv=argv, **kwargs)
- except NoStart:
- return
- if __name__ == '__main__':
- JupyterApp.launch_instance()
|