##// 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 from IPython.external import argparse
20 from IPython.external import argparse
21 from IPython.utils.ipstruct import Struct
21 from IPython.utils.ipstruct import Struct
22 from IPython.utils.genutils import filefind
22
23
23 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
24 # Code
25 # Code
@@ -84,7 +85,7 b' class PyFileConfigLoader(FileConfigLoader):'
84 that are all caps. These attribute are added to the config Struct.
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 """Build a config loader for a filename and path.
89 """Build a config loader for a filename and path.
89
90
90 Parameters
91 Parameters
@@ -110,22 +111,7 b' class PyFileConfigLoader(FileConfigLoader):'
110
111
111 def _find_file(self):
112 def _find_file(self):
112 """Try to find the file by searching the paths."""
113 """Try to find the file by searching the paths."""
113 if os.path.isfile(os.path.expanduser(self.filename)):
114 self.full_filename = filefind(self.filename, self.path)
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))
129
115
130 def _read_file_as_dict(self):
116 def _read_file_as_dict(self):
131 self.data = {}
117 self.data = {}
@@ -175,6 +161,10 b' class ArgParseConfigLoader(CommandLineConfigLoader):'
175 def _create_parser(self):
161 def _create_parser(self):
176 self.parser = argparse.ArgumentParser(*self.args, **self.kw)
162 self.parser = argparse.ArgumentParser(*self.args, **self.kw)
177 self._add_arguments()
163 self._add_arguments()
164 self._add_other_arguments()
165
166 def _add_other_arguments():
167 pass
178
168
179 def _add_arguments(self):
169 def _add_arguments(self):
180 for argument in self.arguments:
170 for argument in self.arguments:
@@ -196,3 +186,15 b' class ArgParseConfigLoader(CommandLineConfigLoader):'
196 if v is not NoDefault:
186 if v is not NoDefault:
197 setattr(self.config, k, v)
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 Notes
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 # Imports
23 # Imports
59 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
60
25
26 import os
61 import sys
27 import sys
28 import traceback
29
62 from copy import deepcopy
30 from copy import deepcopy
63 from IPython.utils.ipstruct import Struct
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 # Classes and functions
39 # Classes and functions
@@ -72,72 +45,157 b' class ApplicationError(Exception):'
72
45
73
46
74 class Application(object):
47 class Application(object):
48 """Load a config, construct an app and run it.
49 """
75
50
76 runtime_dirs = []
51 config_file_name = 'ipython_config.py'
77 default_config = Struct()
52 name = 'ipython'
78 runtime_dir = ''
53 debug = False
79 config_file = ''
80 name = ''
81
54
82 def __init__(self):
55 def __init__(self):
83 pass
56 pass
84
57
85 def start(self):
58 def start(self):
86 """Start the application."""
59 """Start the application."""
87 self.attempt(self.create_command_line_config)
60 self.attempt(self.create_default_config)
88 self.attempt(self.find_runtime_dirs)
61 self.attempt(self.pre_load_command_line_config)
89 self.attempt(self.create_runtime_dirs)
62 self.attempt(self.load_command_line_config, action='exit')
90 self.attempt(self.find_config_files)
63 self.attempt(self.post_load_command_line_config)
91 self.attempt(self.create_file_configs)
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 self.attempt(self.merge_configs)
70 self.attempt(self.merge_configs)
71 self.attempt(self.pre_construct)
93 self.attempt(self.construct)
72 self.attempt(self.construct)
94 self.attempt(self.start_logging)
73 self.attempt(self.post_construct)
95 self.attempt(self.start_app)
74 self.attempt(self.start_app)
96
75
97 #-------------------------------------------------------------------------
76 #-------------------------------------------------------------------------
98 # Various stages of Application creation
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 def create_command_line_config(self):
85 def create_command_line_config(self):
102 """Read the command line args and return its config object."""
86 """Create and return a command line config loader."""
103 self.command_line_config = Struct()
87 return IPythonArgParseConfigLoader(description=self.name)
104
88
105 def find_runtime_dirs(self):
89 def pre_load_command_line_config(self):
106 """Find the runtime directory for this application.
90 """Do actions just before loading the command line config."""
91 pass
107
92
108 This should set self.runtime_dir.
93 def load_command_line_config(self):
94 """Load the command line config.
95
96 This method also sets ``self.debug``.
109 """
97 """
110 pass
111
98
112 def create_runtime_dirs(self):
99 loader = self.create_command_line_config()
113 """Create the runtime dirs if they don't exist."""
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 pass
110 pass
115
111
116 def find_config_files(self):
112 def find_ipythondir(self):
117 """Find the config file for this application."""
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 pass
150 pass
119
151
120 def create_file_configs(self):
152 def load_file_config(self):
121 self.file_configs = [Struct()]
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 def merge_configs(self):
175 def merge_configs(self):
176 """Merge the default, command line and file config objects."""
124 config = Struct()
177 config = Struct()
125 all_configs = self.file_configs + self.command_line_config
178 config.update(self.default_config)
126 for c in all_configs:
179 config.update(self.command_line_config)
127 config.update(c)
180 config.update(self.file_config)
128 self.master_config = config
181 self.master_config = config
182 self.log("Master config created:", self.master_config)
129
183
130 def construct(self, config):
184 def pre_construct(self):
131 """Construct the main components that make up this app."""
185 """Do actions after the config has been built, but before construct."""
132 pass
186 pass
133
187
134 def start_logging(self):
188 def construct(self):
135 """Start logging, if needed, at the last possible moment."""
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 pass
194 pass
137
195
138 def start_app(self):
196 def start_app(self):
139 """Actually start the app."""
197 """Actually start the app."""
140 pass
198 self.log("Starting application...")
141
199
142 #-------------------------------------------------------------------------
200 #-------------------------------------------------------------------------
143 # Utility methods
201 # Utility methods
@@ -148,14 +206,26 b' class Application(object):'
148 print "Aborting application: ", self.name
206 print "Aborting application: ", self.name
149 sys.exit(1)
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 try:
214 try:
153 func()
215 func()
154 except:
216 except:
155 self.handle_error()
217 if action == 'abort':
218 self.print_traceback()
156 self.abort()
219 self.abort()
157
220 elif action == 'exit':
158 def handle_error(self):
221 self.exit()
159 print "I am dying!"
222
160
223 def print_traceback(self):
161 No newline at end of file
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 def _config_changed(self, name, old, new):
197 def _config_changed(self, name, old, new):
198 # Get all traitlets with a config_key metadata entry
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 for k, v in traitlets.items():
200 for k, v in traitlets.items():
201 try:
201 try:
202 config_value = new[v.get_metadata('config_key')]
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 # Defaults and initialization
93 # Defaults and initialization
94
94
95 # For developer debugging, deactivates crash handler and uses pdb.
95 # For developer debugging, deactivates crash handler and uses pdb.
96 DEVDEBUG = False
96 DEVDEBUG = True
97
97
98 if argv is None:
98 if argv is None:
99 argv = sys.argv
99 argv = sys.argv
@@ -446,6 +446,8 b" object? -> Details about 'object'. ?object also works, ?? prints more."
446 warn('Configuration file %s not found. Ignoring request.'
446 warn('Configuration file %s not found. Ignoring request.'
447 % (opts_all.rcfile) )
447 % (opts_all.rcfile) )
448
448
449 print opts_all.rcfile, opts_all.ipythondir
450
449 # 'profiles' are a shorthand notation for config filenames
451 # 'profiles' are a shorthand notation for config filenames
450 profile_handled_by_legacy = False
452 profile_handled_by_legacy = False
451 if opts_all.profile:
453 if opts_all.profile:
@@ -528,32 +528,55 b' def get_py_filename(name):'
528 raise IOError,'File `%s` not found.' % name
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:
536 This iterates through a sequence of paths looking for a file and returns
540 try:
537 the full, absolute path of the first occurence of the file. If no set of
541 alt_dirs = get_home_dir()
538 path dirs is given, the filename is tested as is, after running through
542 except HomeDirError:
539 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
543 alt_dirs = os.getcwd()
540
544 search = [fname] + list_strings(alt_dirs)
541 filefind('myfile.txt')
545 search = map(os.path.expanduser,search)
542
546 #print 'search list for',fname,'list:',search # dbg
543 will find the file in the current working dir, but::
547 fname = search[0]
544
548 if os.path.isfile(fname):
545 filefind('~/myfile.txt')
549 return fname
546
550 for direc in search[1:]:
547 Will find the file in the users home directory. This function does not
551 testname = os.path.join(direc,fname)
548 automatically try any paths, such as the cwd or the user's home directory.
552 #print 'testname',testname # dbg
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 if os.path.isfile(testname):
574 if os.path.isfile(testname):
554 return testname
575 return os.path.abspath(testname)
555 raise IOError,'File' + `fname` + \
576 raise IOError("File does not exist in any "
556 ' not found in current or supplied directories:' + `alt_dirs`
577 "of the search paths: %r, %r" % \
578 (filename, path_dirs))
579
557
580
558 #----------------------------------------------------------------------------
581 #----------------------------------------------------------------------------
559 def file_read(filename):
582 def file_read(filename):
@@ -354,10 +354,13 b' class TestHasTraitlets(TestCase):'
354 i = Int(config_key='VALUE1', other_thing='VALUE2')
354 i = Int(config_key='VALUE1', other_thing='VALUE2')
355 f = Float(config_key='VALUE3', other_thing='VALUE2')
355 f = Float(config_key='VALUE3', other_thing='VALUE2')
356 a = A()
356 a = A()
357 # traitlets = a.traitlets(config_key=lambda v: True)
357 self.assertEquals(a.traitlets(), dict(i=A.i, f=A.f))
358 # self.assertEquals(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 traitlets = a.traitlets(config_key='VALUE1', other_thing='VALUE2')
360 traitlets = a.traitlets(config_key='VALUE1', other_thing='VALUE2')
360 self.assertEquals(traitlets, dict(i=A.i))
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 # Tests for specific traitlet types
366 # Tests for specific traitlet types
@@ -438,7 +438,7 b' class HasTraitlets(object):'
438 """Get a list of all the names of this classes traitlets."""
438 """Get a list of all the names of this classes traitlets."""
439 return self.traitlets(**metadata).keys()
439 return self.traitlets(**metadata).keys()
440
440
441 def traitlets(self, **metadata):
441 def traitlets(self, *args, **metadata):
442 """Get a list of all the traitlets of this class.
442 """Get a list of all the traitlets of this class.
443
443
444 The TraitletTypes returned don't know anything about the values
444 The TraitletTypes returned don't know anything about the values
@@ -446,9 +446,12 b' class HasTraitlets(object):'
446 """
446 """
447 traitlets = dict([memb for memb in inspect.getmembers(self.__class__) if \
447 traitlets = dict([memb for memb in inspect.getmembers(self.__class__) if \
448 isinstance(memb[1], TraitletType)])
448 isinstance(memb[1], TraitletType)])
449 if len(metadata) == 0:
449 if len(metadata) == 0 and len(args) == 0:
450 return traitlets
450 return traitlets
451
451
452 for meta_name in args:
453 metadata[meta_name] = lambda _: True
454
452 for meta_name, meta_eval in metadata.items():
455 for meta_name, meta_eval in metadata.items():
453 if type(meta_eval) is not FunctionType:
456 if type(meta_eval) is not FunctionType:
454 metadata[meta_name] = _SimpleTest(meta_eval)
457 metadata[meta_name] = _SimpleTest(meta_eval)
General Comments 0
You need to be logged in to leave comments. Login now