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