application.py
303 lines
| 11.0 KiB
| text/x-python
|
PythonLexer
Brian Granger
|
r2185 | #!/usr/bin/env python | |
# encoding: utf-8 | |||
""" | |||
An application for IPython | |||
Authors: | |||
* Brian Granger | |||
* Fernando Perez | |||
Notes | |||
----- | |||
""" | |||
#----------------------------------------------------------------------------- | |||
# Copyright (C) 2008-2009 The IPython Development Team | |||
# | |||
# Distributed under the terms of the BSD License. The full license is in | |||
# the file COPYING, distributed as part of this software. | |||
#----------------------------------------------------------------------------- | |||
#----------------------------------------------------------------------------- | |||
# Imports | |||
#----------------------------------------------------------------------------- | |||
Brian Granger
|
r2252 | import logging | |
Brian Granger
|
r2200 | import os | |
Brian Granger
|
r2185 | import sys | |
Brian Granger
|
r2200 | import traceback | |
Brian Granger
|
r2185 | from copy import deepcopy | |
Brian Granger
|
r2245 | ||
Brian Granger
|
r2200 | from IPython.utils.genutils import get_ipython_dir, filefind | |
from IPython.config.loader import ( | |||
Brian Granger
|
r2245 | PyFileConfigLoader, | |
ArgParseConfigLoader, | |||
Config, | |||
NoConfigDefault | |||
Brian Granger
|
r2200 | ) | |
Brian Granger
|
r2185 | ||
#----------------------------------------------------------------------------- | |||
# Classes and functions | |||
#----------------------------------------------------------------------------- | |||
Brian Granger
|
r2245 | class IPythonArgParseConfigLoader(ArgParseConfigLoader): | |
"""Default command line options for IPython based applications.""" | |||
def _add_other_arguments(self): | |||
Brian Granger
|
r2287 | self.parser.add_argument('-ipythondir', '--ipythondir', | |
dest='Global.ipythondir',type=str, | |||
Brian Granger
|
r2245 | help='Set to override default location of Global.ipythondir.', | |
default=NoConfigDefault, | |||
metavar='Global.ipythondir') | |||
Brian Granger
|
r2287 | self.parser.add_argument('-p','-profile', '--profile', | |
dest='Global.profile',type=str, | |||
Brian Granger
|
r2245 | help='The string name of the ipython profile to be used.', | |
default=NoConfigDefault, | |||
metavar='Global.profile') | |||
Brian Granger
|
r2287 | self.parser.add_argument('-log_level', '--log-level', | |
dest="Global.log_level",type=int, | |||
Brian Granger
|
r2252 | help='Set the log level (0,10,20,30,40,50). Default is 30.', | |
Brian Granger
|
r2245 | default=NoConfigDefault) | |
Brian Granger
|
r2287 | self.parser.add_argument('-config_file', '--config-file', | |
dest='Global.config_file',type=str, | |||
Brian Granger
|
r2245 | help='Set the config file name to override default.', | |
default=NoConfigDefault, | |||
metavar='Global.config_file') | |||
Brian Granger
|
r2185 | class ApplicationError(Exception): | |
pass | |||
class Application(object): | |||
Brian Granger
|
r2200 | """Load a config, construct an app and run it. | |
""" | |||
Brian Granger
|
r2185 | ||
Brian Granger
|
r2200 | config_file_name = 'ipython_config.py' | |
name = 'ipython' | |||
Brian Granger
|
r2185 | ||
def __init__(self): | |||
Brian Granger
|
r2252 | self.init_logger() | |
Brian Granger
|
r2257 | self.default_config_file_name = self.config_file_name | |
Brian Granger
|
r2252 | ||
def init_logger(self): | |||
self.log = logging.getLogger(self.__class__.__name__) | |||
# This is used as the default until the command line arguments are read. | |||
self.log.setLevel(logging.WARN) | |||
self._log_handler = logging.StreamHandler() | |||
self._log_formatter = logging.Formatter("[%(name)s] %(message)s") | |||
self._log_handler.setFormatter(self._log_formatter) | |||
self.log.addHandler(self._log_handler) | |||
def _set_log_level(self, level): | |||
self.log.setLevel(level) | |||
def _get_log_level(self): | |||
return self.log.level | |||
log_level = property(_get_log_level, _set_log_level) | |||
Brian Granger
|
r2185 | ||
Brian Granger
|
r2187 | def start(self): | |
"""Start the application.""" | |||
Brian Granger
|
r2200 | self.attempt(self.create_default_config) | |
self.attempt(self.pre_load_command_line_config) | |||
Brian Granger
|
r2203 | self.attempt(self.load_command_line_config, action='abort') | |
Brian Granger
|
r2200 | self.attempt(self.post_load_command_line_config) | |
self.attempt(self.find_ipythondir) | |||
self.attempt(self.find_config_file_name) | |||
self.attempt(self.find_config_file_paths) | |||
self.attempt(self.pre_load_file_config) | |||
self.attempt(self.load_file_config) | |||
self.attempt(self.post_load_file_config) | |||
Brian Granger
|
r2187 | self.attempt(self.merge_configs) | |
Brian Granger
|
r2200 | self.attempt(self.pre_construct) | |
Brian Granger
|
r2187 | self.attempt(self.construct) | |
Brian Granger
|
r2200 | self.attempt(self.post_construct) | |
Brian Granger
|
r2187 | self.attempt(self.start_app) | |
#------------------------------------------------------------------------- | |||
# Various stages of Application creation | |||
#------------------------------------------------------------------------- | |||
Brian Granger
|
r2200 | def create_default_config(self): | |
Brian Granger
|
r2253 | """Create defaults that can't be set elsewhere. | |
For the most part, we try to set default in the class attributes | |||
of Components. But, defaults the top-level Application (which is | |||
not a HasTraitlets or Component) are not set in this way. Instead | |||
we set them here. The Global section is for variables like this that | |||
don't belong to a particular component. | |||
""" | |||
Brian Granger
|
r2245 | self.default_config = Config() | |
self.default_config.Global.ipythondir = get_ipython_dir() | |||
Brian Granger
|
r2252 | self.log.debug('Default config loaded:') | |
self.log.debug(repr(self.default_config)) | |||
Brian Granger
|
r2200 | ||
Brian Granger
|
r2187 | def create_command_line_config(self): | |
Brian Granger
|
r2200 | """Create and return a command line config loader.""" | |
return IPythonArgParseConfigLoader(description=self.name) | |||
def pre_load_command_line_config(self): | |||
"""Do actions just before loading the command line config.""" | |||
pass | |||
Brian Granger
|
r2187 | ||
Brian Granger
|
r2200 | def load_command_line_config(self): | |
"""Load the command line config. | |||
Brian Granger
|
r2185 | ||
Brian Granger
|
r2200 | This method also sets ``self.debug``. | |
Brian Granger
|
r2185 | """ | |
Brian Granger
|
r2200 | loader = self.create_command_line_config() | |
self.command_line_config = loader.load_config() | |||
Brian Granger
|
r2253 | self.extra_args = loader.get_extra_args() | |
Brian Granger
|
r2252 | ||
Brian Granger
|
r2200 | try: | |
Brian Granger
|
r2252 | self.log_level = self.command_line_config.Global.log_level | |
Brian Granger
|
r2200 | except AttributeError: | |
Brian Granger
|
r2252 | pass # Use existing value which is set in Application.init_logger. | |
self.log.debug("Command line config loaded:") | |||
self.log.debug(repr(self.command_line_config)) | |||
Brian Granger
|
r2200 | ||
def post_load_command_line_config(self): | |||
"""Do actions just after loading the command line config.""" | |||
Brian Granger
|
r2185 | pass | |
Brian Granger
|
r2200 | def find_ipythondir(self): | |
"""Set the IPython directory. | |||
This sets ``self.ipythondir``, but the actual value that is passed | |||
to the application is kept in either ``self.default_config`` or | |||
``self.command_line_config``. This also added ``self.ipythondir`` to | |||
``sys.path`` so config files there can be references by other config | |||
files. | |||
""" | |||
try: | |||
Brian Granger
|
r2245 | self.ipythondir = self.command_line_config.Global.ipythondir | |
Brian Granger
|
r2200 | except AttributeError: | |
Brian Granger
|
r2245 | self.ipythondir = self.default_config.Global.ipythondir | |
Brian Granger
|
r2200 | sys.path.append(os.path.abspath(self.ipythondir)) | |
Brian Granger
|
r2201 | if not os.path.isdir(self.ipythondir): | |
os.makedirs(self.ipythondir, mode = 0777) | |||
Brian Granger
|
r2252 | self.log.debug("IPYTHONDIR set to: %s" % self.ipythondir) | |
Brian Granger
|
r2200 | ||
def find_config_file_name(self): | |||
"""Find the config file name for this application. | |||
If a profile has been set at the command line, this will resolve | |||
it. The search paths for the config file are set in | |||
:meth:`find_config_file_paths` and then passed to the config file | |||
loader where they are resolved to an absolute path. | |||
""" | |||
Brian Granger
|
r2203 | try: | |
Brian Granger
|
r2245 | self.config_file_name = self.command_line_config.Global.config_file | |
Brian Granger
|
r2203 | except AttributeError: | |
pass | |||
try: | |||
Brian Granger
|
r2245 | self.profile_name = self.command_line_config.Global.profile | |
Brian Granger
|
r2200 | name_parts = self.config_file_name.split('.') | |
name_parts.insert(1, '_' + self.profile_name + '.') | |||
self.config_file_name = ''.join(name_parts) | |||
Brian Granger
|
r2203 | except AttributeError: | |
pass | |||
Brian Granger
|
r2200 | ||
def find_config_file_paths(self): | |||
"""Set the search paths for resolving the config file.""" | |||
self.config_file_paths = (os.getcwd(), self.ipythondir) | |||
def pre_load_file_config(self): | |||
"""Do actions before the config file is loaded.""" | |||
Brian Granger
|
r2185 | pass | |
Brian Granger
|
r2200 | def load_file_config(self): | |
"""Load the config file. | |||
This tries to load the config file from disk. If successful, the | |||
``CONFIG_FILE`` config variable is set to the resolved config file | |||
location. If not successful, an empty config is used. | |||
""" | |||
Brian Granger
|
r2252 | self.log.debug("Attempting to load config file: <%s>" % self.config_file_name) | |
Brian Granger
|
r2245 | loader = PyFileConfigLoader(self.config_file_name, | |
path=self.config_file_paths) | |||
Brian Granger
|
r2200 | try: | |
self.file_config = loader.load_config() | |||
Brian Granger
|
r2245 | self.file_config.Global.config_file = loader.full_filename | |
Brian Granger
|
r2200 | except IOError: | |
Brian Granger
|
r2257 | # Only warn if the default config file was NOT being used. | |
if not self.config_file_name==self.default_config_file_name: | |||
self.log.warn("Config file not found, skipping: <%s>" % \ | |||
self.config_file_name, exc_info=True) | |||
Brian Granger
|
r2252 | self.file_config = Config() | |
except: | |||
self.log.warn("Error loading config file: <%s>" % \ | |||
self.config_file_name, exc_info=True) | |||
Brian Granger
|
r2245 | self.file_config = Config() | |
Brian Granger
|
r2200 | else: | |
Brian Granger
|
r2252 | self.log.debug("Config file loaded: <%s>" % loader.full_filename) | |
self.log.debug(repr(self.file_config)) | |||
Brian Granger
|
r2270 | # We need to keeep self.log_level updated. But we only use the value | |
# of the file_config if a value was not specified at the command | |||
# line. | |||
if not hasattr(self.command_line_config.Global, 'log_level'): | |||
try: | |||
self.log_level = self.file_config.Global.log_level | |||
except AttributeError: | |||
pass # Use existing value | |||
Brian Granger
|
r2200 | ||
def post_load_file_config(self): | |||
"""Do actions after the config file is loaded.""" | |||
pass | |||
Brian Granger
|
r2185 | ||
Brian Granger
|
r2187 | def merge_configs(self): | |
Brian Granger
|
r2200 | """Merge the default, command line and file config objects.""" | |
Brian Granger
|
r2245 | config = Config() | |
config._merge(self.default_config) | |||
config._merge(self.file_config) | |||
config._merge(self.command_line_config) | |||
Brian Granger
|
r2187 | self.master_config = config | |
Brian Granger
|
r2252 | self.log.debug("Master config created:") | |
self.log.debug(repr(self.master_config)) | |||
Brian Granger
|
r2185 | ||
Brian Granger
|
r2200 | def pre_construct(self): | |
"""Do actions after the config has been built, but before construct.""" | |||
Brian Granger
|
r2185 | pass | |
Brian Granger
|
r2200 | def construct(self): | |
"""Construct the main components that make up this app.""" | |||
Brian Granger
|
r2252 | self.log.debug("Constructing components for application") | |
Brian Granger
|
r2200 | ||
def post_construct(self): | |||
"""Do actions after construct, but before starting the app.""" | |||
Brian Granger
|
r2185 | pass | |
def start_app(self): | |||
"""Actually start the app.""" | |||
Brian Granger
|
r2252 | self.log.debug("Starting application") | |
Brian Granger
|
r2185 | ||
Brian Granger
|
r2187 | #------------------------------------------------------------------------- | |
# Utility methods | |||
#------------------------------------------------------------------------- | |||
Brian Granger
|
r2185 | def abort(self): | |
"""Abort the starting of the application.""" | |||
Brian Granger
|
r2252 | self.log.critical("Aborting application: %s" % self.name, exc_info=True) | |
Brian Granger
|
r2185 | sys.exit(1) | |
Brian Granger
|
r2200 | def exit(self): | |
Brian Granger
|
r2252 | self.log.critical("Aborting application: %s" % self.name) | |
Brian Granger
|
r2200 | sys.exit(1) | |
def attempt(self, func, action='abort'): | |||
Brian Granger
|
r2185 | try: | |
func() | |||
Brian Granger
|
r2245 | except SystemExit: | |
self.exit() | |||
Brian Granger
|
r2185 | except: | |
Brian Granger
|
r2200 | if action == 'abort': | |
self.abort() | |||
elif action == 'exit': | |||
self.exit() | |||
Brian Granger
|
r2252 |