##// END OF EJS Templates
Semi-final Application and minor work on traitlets.
Brian Granger -
Show More
@@ -19,6 +19,7 b' import sys'
19 19
20 20 from IPython.external import argparse
21 21 from IPython.utils.ipstruct import Struct
22 from IPython.utils.genutils import filefind
22 23
23 24 #-----------------------------------------------------------------------------
24 25 # Code
@@ -84,7 +85,7 b' class PyFileConfigLoader(FileConfigLoader):'
84 85 that are all caps. These attribute are added to the config Struct.
85 86 """
86 87
87 def __init__(self, filename, path='.'):
88 def __init__(self, filename, path=None):
88 89 """Build a config loader for a filename and path.
89 90
90 91 Parameters
@@ -110,22 +111,7 b' class PyFileConfigLoader(FileConfigLoader):'
110 111
111 112 def _find_file(self):
112 113 """Try to find the file by searching the paths."""
113 if os.path.isfile(os.path.expanduser(self.filename)):
114 self.full_filename = os.path.expanduser(self.filename)
115 return
116 if self.path == '.':
117 self.path = [os.getcwd()]
118 if not isinstance(path, (list, tuple)):
119 raise TypeError("path must be a list or tuple, got: %r" % self.path)
120 for p in self.path:
121 if p == '.': p = os.getcwd()
122 full_filename = os.path.expanduser(os.path.join(p, self.filename))
123 if os.path.isfile(full_filename):
124 self.full_filename = full_filename
125 return
126 raise IOError("Config file does not exist in any "
127 "of the search paths: %r, %r" % \
128 (self.filename, self.path))
114 self.full_filename = filefind(self.filename, self.path)
129 115
130 116 def _read_file_as_dict(self):
131 117 self.data = {}
@@ -175,6 +161,10 b' class ArgParseConfigLoader(CommandLineConfigLoader):'
175 161 def _create_parser(self):
176 162 self.parser = argparse.ArgumentParser(*self.args, **self.kw)
177 163 self._add_arguments()
164 self._add_other_arguments()
165
166 def _add_other_arguments():
167 pass
178 168
179 169 def _add_arguments(self):
180 170 for argument in self.arguments:
@@ -196,3 +186,15 b' class ArgParseConfigLoader(CommandLineConfigLoader):'
196 186 if v is not NoDefault:
197 187 setattr(self.config, k, v)
198 188
189 class IPythonArgParseConfigLoader(ArgParseConfigLoader):
190
191 def _add_other_arguments(self):
192 self.parser.add_argument('--ipythondir',dest='IPYTHONDIR',type=str,
193 help='set to override default location of IPYTHONDIR',
194 default=NoDefault)
195 self.parser.add_argument('-p','--p',dest='PROFILE_NAME',type=str,
196 help='the string name of the ipython profile to be used',
197 default=None)
198 self.parser.add_argument('--debug',dest="DEBUG",action='store_true',
199 help='debug the application startup process',
200 default=NoDefault)
@@ -10,41 +10,6 b' Authors:'
10 10
11 11 Notes
12 12 -----
13
14 The following directories are relevant in the startup of an app:
15
16 * The ipythondir. This has a default, but can be set by IPYTHONDIR or at
17 the command line.
18 * The current working directory.
19 * Another runtime directory. With some applications (engine, controller) we
20 need the ability to have different cluster configs. Each of these needs
21 to have its own config, security dir and log dir. We could simply treat
22 these as regular ipython dirs.
23
24 There are number of ways in which these directories are used:
25
26 * For config files.
27 * For other assets and resources needed to run. These include
28 plugins, magics, furls files.
29 * For writing various things created at runtime like logs, furl files, etc.
30
31 Questions:
32
33
34 * Can we limit ourselves to 1 config file or do we want to have a sequence
35 of them like IPYTHONDIR->RUNTIMEDIR->CWD? [1]
36 * Do we need a debug mode that has custom exception handling and can drop
37 into pdb upno startup? N
38 * Do we need to use an OutputTrap to capture output and then present it
39 to a user if startup fails? N
40 * Do we want the location of the config file(s) to be independent of the
41 ipython/runtime dir or coupled to it. In other words, can the user select
42 a config file that is outside their runtime/ipython dir. One model is
43 that we could have a very strict model of IPYTHONDIR=runtimed dir=
44 dir used for all config.
45 * Do we install default config files or not? N
46
47 * attempt needs to either clash or to die
48 13 """
49 14
50 15 #-----------------------------------------------------------------------------
@@ -58,9 +23,17 b' Questions:'
58 23 # Imports
59 24 #-----------------------------------------------------------------------------
60 25
26 import os
61 27 import sys
28 import traceback
29
62 30 from copy import deepcopy
63 31 from IPython.utils.ipstruct import Struct
32 from IPython.utils.genutils import get_ipython_dir, filefind
33 from IPython.config.loader import (
34 IPythonArgParseConfigLoader,
35 PyFileConfigLoader
36 )
64 37
65 38 #-----------------------------------------------------------------------------
66 39 # Classes and functions
@@ -72,72 +45,157 b' class ApplicationError(Exception):'
72 45
73 46
74 47 class Application(object):
48 """Load a config, construct an app and run it.
49 """
75 50
76 runtime_dirs = []
77 default_config = Struct()
78 runtime_dir = ''
79 config_file = ''
80 name = ''
51 config_file_name = 'ipython_config.py'
52 name = 'ipython'
53 debug = False
81 54
82 55 def __init__(self):
83 56 pass
84 57
85 58 def start(self):
86 59 """Start the application."""
87 self.attempt(self.create_command_line_config)
88 self.attempt(self.find_runtime_dirs)
89 self.attempt(self.create_runtime_dirs)
90 self.attempt(self.find_config_files)
91 self.attempt(self.create_file_configs)
60 self.attempt(self.create_default_config)
61 self.attempt(self.pre_load_command_line_config)
62 self.attempt(self.load_command_line_config, action='exit')
63 self.attempt(self.post_load_command_line_config)
64 self.attempt(self.find_ipythondir)
65 self.attempt(self.find_config_file_name)
66 self.attempt(self.find_config_file_paths)
67 self.attempt(self.pre_load_file_config)
68 self.attempt(self.load_file_config)
69 self.attempt(self.post_load_file_config)
92 70 self.attempt(self.merge_configs)
71 self.attempt(self.pre_construct)
93 72 self.attempt(self.construct)
94 self.attempt(self.start_logging)
73 self.attempt(self.post_construct)
95 74 self.attempt(self.start_app)
96 75
97 76 #-------------------------------------------------------------------------
98 77 # Various stages of Application creation
99 78 #-------------------------------------------------------------------------
100 79
80 def create_default_config(self):
81 """Create defaults that can't be set elsewhere."""
82 self.default_config = Struct()
83 self.default_config.IPYTHONDIR = get_ipython_dir()
84
101 85 def create_command_line_config(self):
102 """Read the command line args and return its config object."""
103 self.command_line_config = Struct()
86 """Create and return a command line config loader."""
87 return IPythonArgParseConfigLoader(description=self.name)
88
89 def pre_load_command_line_config(self):
90 """Do actions just before loading the command line config."""
91 pass
104 92
105 def find_runtime_dirs(self):
106 """Find the runtime directory for this application.
93 def load_command_line_config(self):
94 """Load the command line config.
107 95
108 This should set self.runtime_dir.
96 This method also sets ``self.debug``.
109 97 """
110 pass
111 98
112 def create_runtime_dirs(self):
113 """Create the runtime dirs if they don't exist."""
99 loader = self.create_command_line_config()
100 self.command_line_config = loader.load_config()
101 try:
102 self.debug = self.command_line_config.DEBUG
103 except AttributeError:
104 pass # use class default
105 self.log("Default config loaded:", self.default_config)
106 self.log("Command line config loaded:", self.command_line_config)
107
108 def post_load_command_line_config(self):
109 """Do actions just after loading the command line config."""
114 110 pass
115 111
116 def find_config_files(self):
117 """Find the config file for this application."""
112 def find_ipythondir(self):
113 """Set the IPython directory.
114
115 This sets ``self.ipythondir``, but the actual value that is passed
116 to the application is kept in either ``self.default_config`` or
117 ``self.command_line_config``. This also added ``self.ipythondir`` to
118 ``sys.path`` so config files there can be references by other config
119 files.
120 """
121
122 try:
123 self.ipythondir = self.command_line_config.IPYTHONDIR
124 except AttributeError:
125 self.ipythondir = self.default_config.IPYTHONDIR
126 sys.path.append(os.path.abspath(self.ipythondir))
127 self.log("IPYTHONDIR set to: %s" % self.ipythondir)
128
129 def find_config_file_name(self):
130 """Find the config file name for this application.
131
132 If a profile has been set at the command line, this will resolve
133 it. The search paths for the config file are set in
134 :meth:`find_config_file_paths` and then passed to the config file
135 loader where they are resolved to an absolute path.
136 """
137
138 if self.command_line_config.PROFILE_NAME is not None:
139 self.profile_name = self.command_line_config.PROFILE_NAME
140 name_parts = self.config_file_name.split('.')
141 name_parts.insert(1, '_' + self.profile_name + '.')
142 self.config_file_name = ''.join(name_parts)
143
144 def find_config_file_paths(self):
145 """Set the search paths for resolving the config file."""
146 self.config_file_paths = (os.getcwd(), self.ipythondir)
147
148 def pre_load_file_config(self):
149 """Do actions before the config file is loaded."""
118 150 pass
119 151
120 def create_file_configs(self):
121 self.file_configs = [Struct()]
152 def load_file_config(self):
153 """Load the config file.
154
155 This tries to load the config file from disk. If successful, the
156 ``CONFIG_FILE`` config variable is set to the resolved config file
157 location. If not successful, an empty config is used.
158 """
159 loader = PyFileConfigLoader(self.config_file_name,
160 self.config_file_paths)
161 try:
162 self.file_config = loader.load_config()
163 self.file_config.CONFIG_FILE = loader.full_filename
164 except IOError:
165 self.log("Config file not found, skipping: %s" % \
166 self.config_file_name)
167 self.file_config = Struct()
168 else:
169 self.log("Config file loaded: %s" % loader.full_filename)
170
171 def post_load_file_config(self):
172 """Do actions after the config file is loaded."""
173 pass
122 174
123 175 def merge_configs(self):
176 """Merge the default, command line and file config objects."""
124 177 config = Struct()
125 all_configs = self.file_configs + self.command_line_config
126 for c in all_configs:
127 config.update(c)
178 config.update(self.default_config)
179 config.update(self.command_line_config)
180 config.update(self.file_config)
128 181 self.master_config = config
182 self.log("Master config created:", self.master_config)
129 183
130 def construct(self, config):
131 """Construct the main components that make up this app."""
184 def pre_construct(self):
185 """Do actions after the config has been built, but before construct."""
132 186 pass
133 187
134 def start_logging(self):
135 """Start logging, if needed, at the last possible moment."""
188 def construct(self):
189 """Construct the main components that make up this app."""
190 self.log("Constructing components for application...")
191
192 def post_construct(self):
193 """Do actions after construct, but before starting the app."""
136 194 pass
137 195
138 196 def start_app(self):
139 197 """Actually start the app."""
140 pass
198 self.log("Starting application...")
141 199
142 200 #-------------------------------------------------------------------------
143 201 # Utility methods
@@ -148,14 +206,26 b' class Application(object):'
148 206 print "Aborting application: ", self.name
149 207 sys.exit(1)
150 208
151 def attempt(self, func):
209 def exit(self):
210 print "Exiting application: ", self.name
211 sys.exit(1)
212
213 def attempt(self, func, action='abort'):
152 214 try:
153 215 func()
154 216 except:
155 self.handle_error()
156 self.abort()
157
158 def handle_error(self):
159 print "I am dying!"
160
161 No newline at end of file
217 if action == 'abort':
218 self.print_traceback()
219 self.abort()
220 elif action == 'exit':
221 self.exit()
222
223 def print_traceback(self):
224 print "Error in appliction startup: ", self.name
225 print
226 traceback.print_exc()
227
228 def log(self, *args):
229 if self.debug:
230 for arg in args:
231 print "[%s] %s" % (self.name, arg) No newline at end of file
@@ -196,7 +196,7 b' class Component(HasTraitlets):'
196 196
197 197 def _config_changed(self, name, old, new):
198 198 # Get all traitlets with a config_key metadata entry
199 traitlets = self.traitlets(config_key=lambda v: True)
199 traitlets = self.traitlets('config_key')
200 200 for k, v in traitlets.items():
201 201 try:
202 202 config_value = new[v.get_metadata('config_key')]
@@ -93,7 +93,7 b' def make_IPython(argv=None,user_ns=None,user_global_ns=None,debug=1,'
93 93 # Defaults and initialization
94 94
95 95 # For developer debugging, deactivates crash handler and uses pdb.
96 DEVDEBUG = False
96 DEVDEBUG = True
97 97
98 98 if argv is None:
99 99 argv = sys.argv
@@ -446,6 +446,8 b" object? -> Details about 'object'. ?object also works, ?? prints more."
446 446 warn('Configuration file %s not found. Ignoring request.'
447 447 % (opts_all.rcfile) )
448 448
449 print opts_all.rcfile, opts_all.ipythondir
450
449 451 # 'profiles' are a shorthand notation for config filenames
450 452 profile_handled_by_legacy = False
451 453 if opts_all.profile:
@@ -528,32 +528,55 b' def get_py_filename(name):'
528 528 raise IOError,'File `%s` not found.' % name
529 529
530 530 #-----------------------------------------------------------------------------
531 def filefind(fname,alt_dirs = None):
532 """Return the given filename either in the current directory, if it
533 exists, or in a specified list of directories.
534 531
535 ~ expansion is done on all file and directory names.
536 532
537 Upon an unsuccessful search, raise an IOError exception."""
533 def filefind(filename, path_dirs=None):
534 """Find a file by looking through a sequence of paths.
538 535
539 if alt_dirs is None:
540 try:
541 alt_dirs = get_home_dir()
542 except HomeDirError:
543 alt_dirs = os.getcwd()
544 search = [fname] + list_strings(alt_dirs)
545 search = map(os.path.expanduser,search)
546 #print 'search list for',fname,'list:',search # dbg
547 fname = search[0]
548 if os.path.isfile(fname):
549 return fname
550 for direc in search[1:]:
551 testname = os.path.join(direc,fname)
552 #print 'testname',testname # dbg
536 This iterates through a sequence of paths looking for a file and returns
537 the full, absolute path of the first occurence of the file. If no set of
538 path dirs is given, the filename is tested as is, after running through
539 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
540
541 filefind('myfile.txt')
542
543 will find the file in the current working dir, but::
544
545 filefind('~/myfile.txt')
546
547 Will find the file in the users home directory. This function does not
548 automatically try any paths, such as the cwd or the user's home directory.
549
550 Parameters
551 ----------
552 filename : str
553 The filename to look for.
554 path_dirs : str, None or sequence of str
555 The sequence of paths to look for the file in. If None, the filename
556 need to be absolute or be in the cwd. If a string, the string is
557 put into a sequence and the searched. If a sequence, walk through
558 each element and join with ``filename``, calling :func:`expandvars`
559 and :func:`expanduser` before testing for existence.
560
561 Returns
562 -------
563 Raises :exc:`IOError` or returns absolute path to file.
564 """
565 if path_dirs is None:
566 path_dirs = ("",)
567 elif isinstance(path_dirs, basestring):
568 path_dirs = (path_dirs,)
569 for path in path_dirs:
570 if path == '.': path = os.getcwd()
571 testname = os.path.expandvars(
572 os.path.expanduser(
573 os.path.join(path, filename)))
553 574 if os.path.isfile(testname):
554 return testname
555 raise IOError,'File' + `fname` + \
556 ' not found in current or supplied directories:' + `alt_dirs`
575 return os.path.abspath(testname)
576 raise IOError("File does not exist in any "
577 "of the search paths: %r, %r" % \
578 (filename, path_dirs))
579
557 580
558 581 #----------------------------------------------------------------------------
559 582 def file_read(filename):
@@ -354,10 +354,13 b' class TestHasTraitlets(TestCase):'
354 354 i = Int(config_key='VALUE1', other_thing='VALUE2')
355 355 f = Float(config_key='VALUE3', other_thing='VALUE2')
356 356 a = A()
357 # traitlets = a.traitlets(config_key=lambda v: True)
358 # self.assertEquals(traitlets, dict(i=A.i, f=A.f))
357 self.assertEquals(a.traitlets(), dict(i=A.i, f=A.f))
358 traitlets = a.traitlets(config_key=lambda v: True)
359 self.assertEquals(traitlets, dict(i=A.i, f=A.f))
359 360 traitlets = a.traitlets(config_key='VALUE1', other_thing='VALUE2')
360 361 self.assertEquals(traitlets, dict(i=A.i))
362 traitlets = a.traitlets('config_key')
363 self.assertEquals(traitlets, dict(i=A.i, f=A.f))
361 364
362 365 #-----------------------------------------------------------------------------
363 366 # Tests for specific traitlet types
@@ -438,7 +438,7 b' class HasTraitlets(object):'
438 438 """Get a list of all the names of this classes traitlets."""
439 439 return self.traitlets(**metadata).keys()
440 440
441 def traitlets(self, **metadata):
441 def traitlets(self, *args, **metadata):
442 442 """Get a list of all the traitlets of this class.
443 443
444 444 The TraitletTypes returned don't know anything about the values
@@ -446,9 +446,12 b' class HasTraitlets(object):'
446 446 """
447 447 traitlets = dict([memb for memb in inspect.getmembers(self.__class__) if \
448 448 isinstance(memb[1], TraitletType)])
449 if len(metadata) == 0:
449 if len(metadata) == 0 and len(args) == 0:
450 450 return traitlets
451 451
452 for meta_name in args:
453 metadata[meta_name] = lambda _: True
454
452 455 for meta_name, meta_eval in metadata.items():
453 456 if type(meta_eval) is not FunctionType:
454 457 metadata[meta_name] = _SimpleTest(meta_eval)
General Comments 0
You need to be logged in to leave comments. Login now