##// END OF EJS Templates
Minor changes to a few files to reflect design discussion.
Brian Granger -
Show More
@@ -1,198 +1,198 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """A factory for creating configuration objects.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2008-2009 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 import os
18 18 import sys
19 19
20 20 from IPython.external import argparse
21 21 from IPython.utils.ipstruct import Struct
22 22
23 23 #-----------------------------------------------------------------------------
24 24 # Code
25 25 #-----------------------------------------------------------------------------
26 26
27 27
28 28 class ConfigLoaderError(Exception):
29 29 pass
30 30
31 31
32 32 class ConfigLoader(object):
33 33 """A object for loading configurations from just about anywhere.
34 34
35 35 The resulting configuration is packaged as a :class:`Struct`.
36 36
37 37 Notes
38 38 -----
39 39 A :class:`ConfigLoader` does one thing: load a config from a source
40 40 (file, command line arguments) and returns the data as a :class:`Struct`.
41 41 There are lots of things that :class:`ConfigLoader` does not do. It does
42 42 not implement complex logic for finding config files. It does not handle
43 43 default values or merge multiple configs. These things need to be
44 44 handled elsewhere.
45 45 """
46 46
47 47 def __init__(self):
48 48 """A base class for config loaders.
49 49
50 50 Examples
51 51 --------
52 52
53 53 >>> cl = ConfigLoader()
54 54 >>> config = cl.load_config()
55 55 >>> config
56 56 {}
57 57 """
58 58 self.clear()
59 59
60 60 def clear(self):
61 61 self.config = Struct()
62 62
63 63 def load_config(self):
64 64 """Load a config from somewhere, return a Struct.
65 65
66 66 Usually, this will cause self.config to be set and then returned.
67 67 """
68 68 return self.config
69 69
70 70
71 71 class FileConfigLoader(ConfigLoader):
72 72 """A base class for file based configurations.
73 73
74 74 As we add more file based config loaders, the common logic should go
75 75 here.
76 76 """
77 77 pass
78 78
79 79
80 80 class PyFileConfigLoader(FileConfigLoader):
81 81 """A config loader for pure python files.
82 82
83 83 This calls execfile on a plain python file and looks for attributes
84 84 that are all caps. These attribute are added to the config Struct.
85 85 """
86 86
87 87 def __init__(self, filename, path='.'):
88 88 """Build a config loader for a filename and path.
89 89
90 90 Parameters
91 91 ----------
92 92 filename : str
93 93 The file name of the config file.
94 94 path : str, list, tuple
95 95 The path to search for the config file on, or a sequence of
96 96 paths to try in order.
97 97 """
98 98 super(PyFileConfigLoader, self).__init__()
99 99 self.filename = filename
100 100 self.path = path
101 101 self.full_filename = ''
102 102 self.data = None
103 103
104 104 def load_config(self):
105 105 """Load the config from a file and return it as a Struct."""
106 106 self._find_file()
107 107 self._read_file_as_dict()
108 108 self._convert_to_struct()
109 109 return self.config
110 110
111 111 def _find_file(self):
112 112 """Try to find the file by searching the paths."""
113 113 if os.path.isfile(os.path.expanduser(self.filename)):
114 114 self.full_filename = os.path.expanduser(self.filename)
115 115 return
116 116 if self.path == '.':
117 117 self.path = [os.getcwd()]
118 118 if not isinstance(path, (list, tuple)):
119 119 raise TypeError("path must be a list or tuple, got: %r" % self.path)
120 120 for p in self.path:
121 121 if p == '.': p = os.getcwd()
122 122 full_filename = os.path.expanduser(os.path.join(p, self.filename))
123 123 if os.path.isfile(full_filename):
124 124 self.full_filename = full_filename
125 125 return
126 126 raise IOError("Config file does not exist in any "
127 127 "of the search paths: %r, %r" % \
128 128 (self.filename, self.path))
129 129
130 130 def _read_file_as_dict(self):
131 131 self.data = {}
132 132 execfile(self.full_filename, self.data)
133 133
134 134 def _convert_to_struct(self):
135 135 if self.data is None:
136 136 ConfigLoaderError('self.data does not exist')
137 137 for k, v in self.data.iteritems():
138 138 if k == k.upper():
139 139 self.config[k] = v
140 140
141 141
142 142 class CommandLineConfigLoader(ConfigLoader):
143 143 """A config loader for command line arguments.
144 144
145 145 As we add more command line based loaders, the common logic should go
146 146 here.
147 147 """
148 148
149 149
150 150 class NoDefault(object): pass
151 151 NoDefault = NoDefault()
152 152
153 153 class ArgParseConfigLoader(CommandLineConfigLoader):
154 154
155 155 # arguments = [(('-f','--file'),dict(type=str,dest='file'))]
156 arguments = []
156 arguments = ()
157 157
158 158 def __init__(self, *args, **kw):
159 159 """Create a config loader for use with argparse.
160 160
161 161 The args and kwargs arguments here are passed onto the constructor
162 162 of :class:`argparse.ArgumentParser`.
163 163 """
164 164 super(CommandLineConfigLoader, self).__init__()
165 165 self.args = args
166 166 self.kw = kw
167 167
168 168 def load_config(self, args=None):
169 169 """Parse command line arguments and return as a Struct."""
170 170 self._create_parser()
171 171 self._parse_args(args)
172 172 self._convert_to_struct()
173 173 return self.config
174 174
175 175 def _create_parser(self):
176 176 self.parser = argparse.ArgumentParser(*self.args, **self.kw)
177 177 self._add_arguments()
178 178
179 179 def _add_arguments(self):
180 180 for argument in self.arguments:
181 181 if not argument[1].has_key('default'):
182 182 argument[1]['default'] = NoDefault
183 183 self.parser.add_argument(*argument[0],**argument[1])
184 184
185 185 def _parse_args(self, args=None):
186 186 """self.parser->self.parsed_data"""
187 187 if args is None:
188 188 self.parsed_data = self.parser.parse_args()
189 189 else:
190 190 self.parsed_data = self.parser.parse_args(args)
191 191
192 192 def _convert_to_struct(self):
193 193 """self.parsed_data->self.config"""
194 194 self.config = Struct()
195 195 for k, v in vars(self.parsed_data).items():
196 196 if v is not NoDefault:
197 197 setattr(self.config, k, v)
198 198
@@ -1,92 +1,93 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 Tests for IPython.config.loader
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * Fernando Perez (design help)
10 10 """
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2008-2009 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 import os
24 24 from tempfile import mkstemp
25 25 from unittest import TestCase
26 26
27 27 from IPython.config.loader import PyFileConfigLoader, ArgParseConfigLoader
28 28
29 29 #-----------------------------------------------------------------------------
30 30 # Actual tests
31 31 #-----------------------------------------------------------------------------
32 32
33 33
34 34 pyfile = """
35 35 A = 10
36 36 B = range(10)
37 37 C = True
38 38 D = 'hi there'
39 39 """
40 40
41 41 class TestPyFileCL(TestCase):
42 42
43 43 def test_basic(self):
44 44 fd, fname = mkstemp()
45 45 f = os.fdopen(fd, 'w')
46 46 f.write(pyfile)
47 47 f.close()
48 # Unlink the file
48 49 cl = PyFileConfigLoader(fname)
49 50 config = cl.load_config()
50 51 self.assertEquals(config.A, 10)
51 52 self.assertEquals(config.B, range(10))
52 53 self.assertEquals(config.C, True)
53 54 self.assertEquals(config.D, 'hi there')
54 55
55 56
56 57 class TestArgParseCL(TestCase):
57 58
58 59 def test_basic(self):
59 60
60 61 class MyLoader(ArgParseConfigLoader):
61 arguments = [
62 arguments = (
62 63 (('-f','--foo'), dict(dest='FOO', type=str)),
63 64 (('-b',), dict(dest='BAR', type=int)),
64 65 (('-n',), dict(dest='N', action='store_true')),
65 66 (('BAM',), dict(type=str))
66 ]
67 )
67 68
68 69 cl = MyLoader()
69 70 config = cl.load_config('-f hi -b 10 -n wow'.split())
70 71 self.assertEquals(config.FOO, 'hi')
71 72 self.assertEquals(config.BAR, 10)
72 73 self.assertEquals(config.N, True)
73 74 self.assertEquals(config.BAM, 'wow')
74 75
75 76 def test_add_arguments(self):
76 77
77 78 class MyLoader(ArgParseConfigLoader):
78 79 def _add_arguments(self):
79 80 subparsers = self.parser.add_subparsers(dest='subparser_name')
80 81 subparser1 = subparsers.add_parser('1')
81 82 subparser1.add_argument('-x')
82 83 subparser2 = subparsers.add_parser('2')
83 84 subparser2.add_argument('y')
84 85
85 86 cl = MyLoader()
86 87 config = cl.load_config('2 frobble'.split())
87 88 self.assertEquals(config.subparser_name, '2')
88 89 self.assertEquals(config.y, 'frobble')
89 90 config = cl.load_config('1 -x frobble'.split())
90 91 self.assertEquals(config.subparser_name, '1')
91 92 self.assertEquals(config.x, 'frobble')
92 93
@@ -1,159 +1,161 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 An application for IPython
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * Fernando Perez
10 10
11 11 Notes
12 12 -----
13 13
14 14 The following directories are relevant in the startup of an app:
15 15
16 16 * The ipythondir. This has a default, but can be set by IPYTHONDIR or at
17 17 the command line.
18 18 * The current working directory.
19 19 * Another runtime directory. With some applications (engine, controller) we
20 20 need the ability to have different cluster configs. Each of these needs
21 21 to have its own config, security dir and log dir. We could simply treat
22 22 these as regular ipython dirs.
23 23
24 24 There are number of ways in which these directories are used:
25 25
26 26 * For config files.
27 27 * For other assets and resources needed to run. These include
28 28 plugins, magics, furls files.
29 29 * For writing various things created at runtime like logs, furl files, etc.
30 30
31 31 Questions:
32 32
33 33
34 34 * Can we limit ourselves to 1 config file or do we want to have a sequence
35 of them like IPYTHONDIR->RUNTIMEDIR->CWD?
35 of them like IPYTHONDIR->RUNTIMEDIR->CWD? [1]
36 36 * Do we need a debug mode that has custom exception handling and can drop
37 into pdb upno startup?
37 into pdb upno startup? N
38 38 * Do we need to use an OutputTrap to capture output and then present it
39 to a user if startup fails?
39 to a user if startup fails? N
40 40 * Do we want the location of the config file(s) to be independent of the
41 41 ipython/runtime dir or coupled to it. In other words, can the user select
42 42 a config file that is outside their runtime/ipython dir. One model is
43 43 that we could have a very strict model of IPYTHONDIR=runtimed dir=
44 44 dir used for all config.
45 * Do we install default config files or not?
45 * Do we install default config files or not? N
46
47 * attempt needs to either clash or to die
46 48 """
47 49
48 50 #-----------------------------------------------------------------------------
49 51 # Copyright (C) 2008-2009 The IPython Development Team
50 52 #
51 53 # Distributed under the terms of the BSD License. The full license is in
52 54 # the file COPYING, distributed as part of this software.
53 55 #-----------------------------------------------------------------------------
54 56
55 57 #-----------------------------------------------------------------------------
56 58 # Imports
57 59 #-----------------------------------------------------------------------------
58 60
59 61 import sys
60 62 from copy import deepcopy
61 63 from IPython.utils.ipstruct import Struct
62 64
63 65 #-----------------------------------------------------------------------------
64 66 # Classes and functions
65 67 #-----------------------------------------------------------------------------
66 68
67 69
68 70 class ApplicationError(Exception):
69 71 pass
70 72
71 73
72 74 class Application(object):
73 75
74 76 runtime_dirs = []
75 77 default_config = Struct()
76 78 runtime_dir = ''
77 79 config_file = ''
78 80 name = ''
79 81
80 82 def __init__(self):
81 83 pass
82 84
83 85 def start(self):
84 86 """Start the application."""
85 87 self.attempt(self.create_command_line_config)
86 88 self.attempt(self.find_runtime_dirs)
87 89 self.attempt(self.create_runtime_dirs)
88 90 self.attempt(self.find_config_files)
89 91 self.attempt(self.create_file_configs)
90 92 self.attempt(self.merge_configs)
91 93 self.attempt(self.construct)
92 94 self.attempt(self.start_logging)
93 95 self.attempt(self.start_app)
94 96
95 97 #-------------------------------------------------------------------------
96 98 # Various stages of Application creation
97 99 #-------------------------------------------------------------------------
98 100
99 101 def create_command_line_config(self):
100 102 """Read the command line args and return its config object."""
101 103 self.command_line_config = Struct()
102 104
103 105 def find_runtime_dirs(self):
104 106 """Find the runtime directory for this application.
105 107
106 108 This should set self.runtime_dir.
107 109 """
108 110 pass
109 111
110 112 def create_runtime_dirs(self):
111 113 """Create the runtime dirs if they don't exist."""
112 114 pass
113 115
114 116 def find_config_files(self):
115 117 """Find the config file for this application."""
116 118 pass
117 119
118 120 def create_file_configs(self):
119 121 self.file_configs = [Struct()]
120 122
121 123 def merge_configs(self):
122 124 config = Struct()
123 125 all_configs = self.file_configs + self.command_line_config
124 126 for c in all_configs:
125 127 config.update(c)
126 128 self.master_config = config
127 129
128 130 def construct(self, config):
129 131 """Construct the main components that make up this app."""
130 132 pass
131 133
132 134 def start_logging(self):
133 135 """Start logging, if needed, at the last possible moment."""
134 136 pass
135 137
136 138 def start_app(self):
137 139 """Actually start the app."""
138 140 pass
139 141
140 142 #-------------------------------------------------------------------------
141 143 # Utility methods
142 144 #-------------------------------------------------------------------------
143 145
144 146 def abort(self):
145 147 """Abort the starting of the application."""
146 148 print "Aborting application: ", self.name
147 149 sys.exit(1)
148 150
149 151 def attempt(self, func):
150 152 try:
151 153 func()
152 154 except:
153 155 self.handle_error()
154 156 self.abort()
155 157
156 158 def handle_error(self):
157 159 print "I am dying!"
158 160
159 161 No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now