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_ |
|
|
88 |
self.attempt(self. |
|
|
89 |
self.attempt(self. |
|
|
90 |
self.attempt(self. |
|
|
91 |
self.attempt(self. |
|
|
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. |
|
|
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 |
""" |
|
|
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 |
|
|
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 |
|
|
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_ |
|
|
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 |
|
|
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 |
|
|
|
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 |
|
|
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 st |
|
|
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 |
|
|
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. |
|
|
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 | ||
|
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 |
|
|
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 = |
|
|
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 |
|
|
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