##// END OF EJS Templates
Created config.application and updated Configurable.
Brian Granger -
Show More
@@ -0,0 +1,108 b''
1 # encoding: utf-8
2 """
3 A base class for a configurable application.
4
5 Authors:
6
7 * Brian Granger
8 """
9
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2008-2011 The IPython Development Team
12 #
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
15 #-----------------------------------------------------------------------------
16
17 #-----------------------------------------------------------------------------
18 # Imports
19 #-----------------------------------------------------------------------------
20
21 from copy import deepcopy
22 import sys
23
24 from IPython.config.configurable import Configurable
25 from IPython.utils.traitlets import (
26 Unicode, List
27 )
28 from IPython.config.loader import (
29 KeyValueConfigLoader, PyFileConfigLoader
30 )
31
32 #-----------------------------------------------------------------------------
33 # Application class
34 #-----------------------------------------------------------------------------
35
36
37 class Application(Configurable):
38
39 # The name of the application, will usually match the name of the command
40 # line application
41 app_name = Unicode(u'application')
42
43 # The description of the application that is printed at the beginning
44 # of the help.
45 description = Unicode(u'This is an application.')
46
47 # A sequence of Configurable subclasses whose config=True attributes will
48 # be exposed at the command line (shortnames and help).
49 classes = List([])
50
51 # The version string of this application.
52 version = Unicode(u'0.0')
53
54 def __init__(self, **kwargs):
55 Configurable.__init__(self, **kwargs)
56 # Add my class to self.classes so my attributes appear in command line
57 # options.
58 self.classes.insert(0, self.__class__)
59
60 def print_help(self):
61 """Print the help for each Configurable class in self.classes."""
62 for cls in self.classes:
63 cls.class_print_help()
64 print
65
66 def print_description(self):
67 """Print the application description."""
68 print self.description
69 print
70
71 def print_version(self):
72 """Print the version string."""
73 print self.version
74
75 def update_config(self, config):
76 # Save a copy of the current config.
77 newconfig = deepcopy(self.config)
78 # Merge the new config into the current one.
79 newconfig._merge(config)
80 # Save the combined config as self.config, which triggers the traits
81 # events.
82 self.config = config
83
84 def parse_command_line(self, argv=None):
85 """Parse the command line arguments."""
86 if argv is None:
87 argv = sys.argv[1:]
88
89 if '-h' in argv or '--h' in argv:
90 self.print_description()
91 self.print_help()
92 sys.exit(1)
93
94 if '--version' in argv:
95 self.print_version()
96 sys.exit(1)
97
98 loader = KeyValueConfigLoader(argv=argv, classes=self.classes)
99 config = loader.load_config()
100 self.update_config(config)
101
102 def load_config_file(self, filename, path=None):
103 """Load a .py based config file by filename and path."""
104 # TODO: this raises IOError if filename does not exist.
105 loader = PyFileConfigLoader(filename, path=path)
106 config = loader.load_config()
107 self.update_config(config)
108
@@ -1,170 +1,173 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 A base class for objects that are configurable.
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * Fernando Perez
10 10 """
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2008-2010 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 from copy import deepcopy
24 24 import datetime
25 from weakref import WeakValueDictionary
26 25
27 from IPython.utils.importstring import import_item
28 26 from loader import Config
29 27 from IPython.utils.traitlets import HasTraits, Instance
30 28 from IPython.utils.text import indent
31 29
32 30
33 31 #-----------------------------------------------------------------------------
34 32 # Helper classes for Configurables
35 33 #-----------------------------------------------------------------------------
36 34
37 35
38 36 class ConfigurableError(Exception):
39 37 pass
40 38
41 39
42 40 #-----------------------------------------------------------------------------
43 41 # Configurable implementation
44 42 #-----------------------------------------------------------------------------
45 43
46 44
47 45 class Configurable(HasTraits):
48 46
49 47 config = Instance(Config,(),{})
50 48 created = None
51 49
52 50 def __init__(self, **kwargs):
53 51 """Create a conigurable given a config config.
54 52
55 53 Parameters
56 54 ----------
57 55 config : Config
58 56 If this is empty, default values are used. If config is a
59 57 :class:`Config` instance, it will be used to configure the
60 58 instance.
61 59
62 60 Notes
63 61 -----
64 62 Subclasses of Configurable must call the :meth:`__init__` method of
65 63 :class:`Configurable` *before* doing anything else and using
66 64 :func:`super`::
67 65
68 66 class MyConfigurable(Configurable):
69 67 def __init__(self, config=None):
70 68 super(MyConfigurable, self).__init__(config)
71 69 # Then any other code you need to finish initialization.
72 70
73 71 This ensures that instances will be configured properly.
74 72 """
75 73 config = kwargs.pop('config', None)
76 74 if config is not None:
77 75 # We used to deepcopy, but for now we are trying to just save
78 76 # by reference. This *could* have side effects as all components
79 77 # will share config. In fact, I did find such a side effect in
80 78 # _config_changed below. If a config attribute value was a mutable type
81 79 # all instances of a component were getting the same copy, effectively
82 80 # making that a class attribute.
83 81 # self.config = deepcopy(config)
84 82 self.config = config
85 83 # This should go second so individual keyword arguments override
86 84 # the values in config.
87 85 super(Configurable, self).__init__(**kwargs)
88 86 self.created = datetime.datetime.now()
89 87
90 88 #-------------------------------------------------------------------------
91 89 # Static trait notifiations
92 90 #-------------------------------------------------------------------------
93 91
94 92 def _config_changed(self, name, old, new):
95 93 """Update all the class traits having ``config=True`` as metadata.
96 94
97 95 For any class trait with a ``config`` metadata attribute that is
98 96 ``True``, we update the trait with the value of the corresponding
99 97 config entry.
100 98 """
101 99 # Get all traits with a config metadata entry that is True
102 100 traits = self.traits(config=True)
103 101
104 102 # We auto-load config section for this class as well as any parent
105 103 # classes that are Configurable subclasses. This starts with Configurable
106 104 # and works down the mro loading the config for each section.
107 105 section_names = [cls.__name__ for cls in \
108 106 reversed(self.__class__.__mro__) if
109 107 issubclass(cls, Configurable) and issubclass(self.__class__, cls)]
110 108
111 109 for sname in section_names:
112 110 # Don't do a blind getattr as that would cause the config to
113 111 # dynamically create the section with name self.__class__.__name__.
114 112 if new._has_section(sname):
115 113 my_config = new[sname]
116 114 for k, v in traits.iteritems():
117 115 # Don't allow traitlets with config=True to start with
118 116 # uppercase. Otherwise, they are confused with Config
119 117 # subsections. But, developers shouldn't have uppercase
120 118 # attributes anyways! (PEP 6)
121 119 if k[0].upper()==k[0] and not k.startswith('_'):
122 120 raise ConfigurableError('Configurable traitlets with '
123 121 'config=True must start with a lowercase so they are '
124 122 'not confused with Config subsections: %s.%s' % \
125 123 (self.__class__.__name__, k))
126 124 try:
127 125 # Here we grab the value from the config
128 126 # If k has the naming convention of a config
129 127 # section, it will be auto created.
130 128 config_value = my_config[k]
131 129 except KeyError:
132 130 pass
133 131 else:
134 132 # print "Setting %s.%s from %s.%s=%r" % \
135 133 # (self.__class__.__name__,k,sname,k,config_value)
136 134 # We have to do a deepcopy here if we don't deepcopy the entire
137 135 # config object. If we don't, a mutable config_value will be
138 136 # shared by all instances, effectively making it a class attribute.
139 137 setattr(self, k, deepcopy(config_value))
140 138
141 139 @classmethod
142 140 def class_get_shortnames(cls):
143 141 """Return the shortname to fullname dict for config=True traits."""
144 142 cls_traits = cls.class_traits(config=True)
145 143 shortnames = {}
146 144 for k, v in cls_traits.items():
147 145 shortname = v.get_metadata('shortname')
148 146 if shortname is not None:
149 147 longname = cls.__name__ + '.' + k
150 148 shortnames[shortname] = longname
151 149 return shortnames
152 150
153 151 @classmethod
154 152 def class_get_help(cls):
153 """Get the help string for this class in ReST format."""
155 154 cls_traits = cls.class_traits(config=True)
156 155 final_help = []
157 final_help.append('%s options' % cls.__name__)
158 final_help.append(len(final_help[0])*'-')
156 final_help.append(u'%s options' % cls.__name__)
157 final_help.append(len(final_help[0])*u'-')
159 158 for k, v in cls_traits.items():
160 159 help = v.get_metadata('help')
161 final_help.append(k + " : " + v.__class__.__name__)
160 shortname = v.get_metadata('shortname')
161 header = "%s.%s : %s" % (cls.__name__, k, v.__class__.__name__)
162 if shortname is not None:
163 header += " (shortname=" + shortname + ")"
164 final_help.append(header)
162 165 if help is not None:
163 166 final_help.append(indent(help))
164 167 return '\n'.join(final_help)
165 168
166 169 @classmethod
167 170 def class_print_help(cls):
168 171 print cls.class_get_help()
169 172
170 173 No newline at end of file
@@ -1,55 +1,39 b''
1 1 import sys
2 2
3 3 from IPython.config.configurable import Configurable
4 from IPython.config.application import Application
4 5 from IPython.utils.traitlets import (
5 6 Bool, Unicode, Int, Float, List
6 7 )
7 from IPython.config.loader import KeyValueConfigLoader
8 8
9 9 class Foo(Configurable):
10 10
11 11 i = Int(0, config=True, shortname='i', help="The integer i.")
12 12 j = Int(1, config=True, shortname='j', help="The integer j.")
13 13 name = Unicode(u'Brian', config=True, shortname='name', help="First name.")
14 14
15 15
16 16 class Bar(Configurable):
17 17
18 18 enabled = Bool(True, config=True, shortname="bar-enabled", help="Enable bar.")
19 19
20 20
21 class MyApp(Configurable):
21 class MyApp(Application):
22 22
23 app_name = Unicode(u'myapp', config=True, shortname="myapp", help="The app name.")
23 app_name = Unicode(u'myapp')
24 24 running = Bool(False, config=True, shortname="running", help="Is the app running?")
25 25 classes = List([Bar, Foo])
26
27 def __init__(self, **kwargs):
28 Configurable.__init__(self, **kwargs)
29 self.classes.insert(0, self.__class__)
30
31 def print_help(self):
32 for cls in self.classes:
33 cls.class_print_help()
34 print
35
36 def parse_command_line(self, argv=None):
37 if argv is None:
38 argv = sys.argv[1:]
39 if '-h' in argv or '--h' in argv:
40 self.print_help()
41 sys.exit(1)
42 loader = KeyValueConfigLoader(argv=argv, classes=self.classes)
43 config = loader.load_config()
44 self.config = config
26 config_file = Unicode(u'', config=True, shortname="config-file", help="Load this config file")
45 27
46 28
47 29 def main():
48 30 app = MyApp()
49 31 app.parse_command_line()
32 if app.config_file:
33 app.load_config_file(app.config_file)
50 34 print "app.config:"
51 35 print app.config
52 36
53 37
54 38 if __name__ == "__main__":
55 39 main()
General Comments 0
You need to be logged in to leave comments. Login now