From ade94907399e91198ffd15aeb6807d891dd172c3 2009-08-11 17:20:54
From: Brian Granger <ellisonbg@gmail.com>
Date: 2009-08-11 17:20:54
Subject: [PATCH] Working version of the new config loaders for .py files and argparse.

---

diff --git a/IPython/config/loader.py b/IPython/config/loader.py
index 8a143b1..0e060f2 100644
--- a/IPython/config/loader.py
+++ b/IPython/config/loader.py
@@ -15,7 +15,9 @@
 #-----------------------------------------------------------------------------
 
 import os
+import sys
 
+from IPython.external import argparse
 from IPython.utils.ipstruct import Struct
 
 #-----------------------------------------------------------------------------
@@ -31,6 +33,15 @@ class ConfigLoader(object):
     """A object for loading configurations from just about anywhere.
     
     The resulting configuration is packaged as a :class:`Struct`.
+    
+    Notes
+    -----
+    A :class:`ConfigLoader` does one thing: load a config from a source 
+    (file, command line arguments) and returns the data as a :class:`Struct`.
+    There are lots of things that :class:`ConfigLoader` does not do.  It does
+    not implement complex logic for finding config files.  It does not handle
+    default values or merge multiple configs.  These things need to be 
+    handled elsewhere.
     """
 
     def __init__(self):
@@ -58,6 +69,15 @@ class ConfigLoader(object):
 
 
 class FileConfigLoader(ConfigLoader):
+    """A base class for file based configurations.
+
+    As we add more file based config loaders, the common logic should go
+    here.
+    """
+    pass
+
+
+class PyFileConfigLoader(FileConfigLoader):
     """A config loader for pure python files.
     
     This calls execfile on a plain python file and looks for attributes
@@ -73,114 +93,103 @@ class FileConfigLoader(ConfigLoader):
             The file name of the config file.
         path : str, list, tuple
             The path to search for the config file on, or a sequence of
-            paths to try in order
-
-        Examples
-        --------
-        
-        
+            paths to try in order.
         """
+        super(PyFileConfigLoader, self).__init__()
         self.filename = filename
         self.path = path
         self.full_filename = ''
         self.data = None
-        ConfigLoader.__init__(self)
 
-    def find_file(self):
-        """Implement file finding logic here."""
-        self.full_filename = self.filename
+    def load_config(self):
+        """Load the config from a file and return it as a Struct."""
+        self._find_file()
+        self._read_file_as_dict()
+        self._convert_to_struct()
+        return self.config
 
-    def read_file_as_dict(self):
-        if not os.path.isfile(self.full_filename):
-            raise IOError("config file does not exist: %r" % self.fullfilename)
+    def _find_file(self):
+        """Try to find the file by searching the paths."""
+        if self.path == '.':
+            self.path = [os.getcwd()]
+        if not isinstance(path, (list, tuple)):
+            raise TypeError("path must be a list or tuple, got: %r" % self.path)
+        for p in self.path:
+            if p == '.': p = os.getcwd()
+            full_filename = os.path.join(p, self.filename)
+            if os.path.isfile(full_filename):
+                self.full_filename = full_filename
+                return
+        raise IOError("Config file does not exist in any "
+                      "of the search paths: %r, %r" % \
+                      (self.filename, self.path))
+
+    def _read_file_as_dict(self):
         self.data = {}
         execfile(self.full_filename, self.data)
 
-    def convert_to_struct(self):
+    def _convert_to_struct(self):
         if self.data is None:
             ConfigLoaderError('self.data does not exist')
         for k, v in self.data.iteritems():
             if k == k.upper():
                 self.config[k] = v
 
-    def load_config(self):
-        self.find_file()
-        self.read_file_as_dict()
-        self.convert_to_struct()
-        return self.config
-
-class PyConfigLoader(object):
-    pass
-
-
-class DefaultFileConfigLoader(object):
-
-    def __init__(self, filename, install_location):
-        pass
-
-    def load_config(self):
-        pass
-
-    def install(self, force=False):
-        pass
-
 
 class CommandLineConfigLoader(ConfigLoader):
+    """A config loader for command line arguments.
 
-    def __init__(self):
-        self.parser = None
-        self.parsed_data = None
-        self.clear()
-        
-
-    def clear(self):
-        self.config = Struct()
-
-    def load_config(self, args=None):
-        self.create_parser()
-        self.parse_args(args)
-        self.convert_to_struct()
-        return self.config
-
-    def create_parser(self):
-        """Create self.parser"""
-
-    def parse_args(self, args=None):
-        """self.parser->self.parsed_data"""
-        if self.parser is None:
-            raise ConfigLoaderError('self.parser does not exist')
-        if args is None:
-            self.parsed_data = parser.parse_args()
-        else:
-            self.parse_data = parser.parse_args(args)
+    As we add more command line based loaders, the common logic should go
+    here.
+    """
 
-    def convert_to_struct(self):
-        """self.parsed_data->self.config"""
-        if self.parsed_data is None:
-            raise ConfigLoaderError('self.parsed_data does not exist')
-        self.config = Struct(vars(self.parsed_data))
 
+class NoDefault(object): pass
+NoDefault = NoDefault()
 
 class ArgParseConfigLoader(CommandLineConfigLoader):
-
+    
     # arguments = [(('-f','--file'),dict(type=str,dest='file'))]
     arguments = []
 
     def __init__(self, *args, **kw):
         """Create a config loader for use with argparse.
-        
+
         The args and kwargs arguments here are passed onto the constructor
         of :class:`argparse.ArgumentParser`.
         """
+        super(CommandLineConfigLoader, self).__init__()
         self.args = args
         self.kw = kw
-        CommandLineConfigLoader.__init__(self)
 
-    def create_parser(self):
+    def load_config(self, args=None):
+        """Parse command line arguments and return as a Struct."""
+        self._create_parser()
+        self._parse_args(args)
+        self._convert_to_struct()
+        return self.config
+
+    def _create_parser(self):
         self.parser = argparse.ArgumentParser(*self.args, **self.kw)
-        self.add_arguments()
+        self._add_arguments()
 
-    def add_arguments(self):
+    def _add_arguments(self):
         for argument in self.arguments:
+            argument[1]['default'] = NoDefault
+            print argument
             self.parser.add_argument(*argument[0],**argument[1])
 
+    def _parse_args(self, args=None):
+        """self.parser->self.parsed_data"""
+        if args is None:
+            self.parsed_data = self.parser.parse_args()
+        else:
+            self.parsed_data = self.parser.parse_args(args)
+
+    def _convert_to_struct(self):
+        """self.parsed_data->self.config"""
+        self.config = Struct()
+        for k, v in vars(self.parsed_data).items():
+            if v is not NoDefault:
+                setattr(self.config, k, v)
+
diff --git a/IPython/core/application.py b/IPython/core/application.py
new file mode 100644
index 0000000..9a90a94
--- /dev/null
+++ b/IPython/core/application.py
@@ -0,0 +1,174 @@
+#!/usr/bin/env python
+# encoding: utf-8
+"""
+An application for IPython
+
+Authors:
+
+* Brian Granger
+* Fernando Perez
+
+Notes
+-----
+
+The following directories are relevant in the startup of an app:
+
+* The ipythondir.  This has a default, but can be set by IPYTHONDIR or at
+  the command line.
+* The current working directory.
+* Another runtime directory.  With some applications (engine, controller) we
+  need the ability to have different cluster configs.  Each of these needs
+  to have its own config, security dir and log dir.  We could simply treat
+  these as regular ipython dirs.  
+
+There are number of ways in which these directories are used:
+
+* For config files.
+* For other assets and resources needed to run.  These include 
+  plugins, magics, furls files.
+* For writing various things created at runtime like logs, furl files, etc.
+
+Questions:
+
+1. Can we limit ourselves to 1 config file or do we want to have a sequence
+   of them like IPYTHONDIR->RUNTIMEDIR->CWD?
+2. Do we need a debug mode that has custom exception handling and can drop
+   into pdb upno startup?
+3. Do we need to use an OutputTrap to capture output and then present it
+   to a user if startup fails?
+4. Do we want the location of the config file(s) to be independent of the
+   ipython/runtime dir or coupled to it.  In other words, can the user select
+   a config file that is outside their runtime/ipython dir.  One model is 
+   that we could have a very strict model of IPYTHONDIR=runtimed dir=
+   dir used for all config.
+"""
+
+#-----------------------------------------------------------------------------
+#  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
+#-----------------------------------------------------------------------------
+
+import sys
+from copy import deepcopy
+from IPython.utils.ipstruct import Struct
+
+#-----------------------------------------------------------------------------
+# Classes and functions
+#-----------------------------------------------------------------------------
+
+
+class ApplicationError(Exception):
+    pass
+
+
+class Application(object):
+
+    runtime_dirs = []
+    default_config = Struct()
+    runtime_dir = ''
+    config_file = ''
+    name = ''
+
+    def __init__(self):
+        pass
+
+    def find_runtime_dir(self):
+        """Find the runtime directory for this application.
+
+        This should set self.runtime_dir.
+        """
+        pass
+
+    def create_runtime_dirs(self):
+        """Create the runtime dirs if they dont exist."""
+        pass
+
+    def find_config_file(self):
+        """Find the config file for this application."""
+        pass
+
+    def create_config(self):
+        self.config = deepcopy(self.default_config)
+
+        self.pre_file_config()
+        self.file_config = self.create_file_config()
+        self.post_file_config()
+
+        self.pre_command_line_config()
+        self.command_line_config = create_command_line_config()
+        self.post_command_line_config()
+
+        master_config = self.merge_configs(config, file_config, cl_config)
+        self.master_config = master_config
+        return master_config
+
+    def pre_file_config(self):
+        pass
+
+    def create_file_config(self):
+        """Read the config file and return its config object."""
+        return Struct()
+
+    def post_file_config(self):
+        pass
+
+    def pre_command_line_config(self):
+        pass
+
+    def create_command_line_config(self):
+        """Read the command line args and return its config object."""
+        return Struct()
+
+    def post_command_line_config(self):
+        pass
+
+    def merge_configs(self, config, file_config, cl_config):
+        config.update(file_config)
+        config.update(cl_config)
+        return config
+
+    def start(self):
+        """Start the application."""
+        self.attempt(self.find_runtime_dir)
+        self.attempt(self.find_runtime_dir)
+        self.attempt(self.create_runtime_dirs)
+        self.attempt(self.find_config_file)
+        self.attempt(self.create_config)
+        self.attempt(self.construct)
+        self.attempt(self.start_logging)
+        self.attempt(self.start_app)
+
+    def construct(self, config):
+        """Construct the main components that make up this app."""
+        pass
+
+    def start_logging(self):
+        """Start logging, if needed, at the last possible moment."""
+        pass
+
+    def start_app(self):
+        """Actually start the app."""
+        pass
+
+    def abort(self):
+        """Abort the starting of the application."""
+        print "Aborting application: ", self.name
+        sys.exit(1)
+
+    def attempt(self, func):
+        try:
+            func()
+        except:
+            self.handle_error()
+            self.abort()
+
+    def handle_error(self):
+        print "I am dying!"
+     
+        
\ No newline at end of file