##// END OF EJS Templates
move shortname to Application...
MinRK -
Show More
@@ -1,134 +1,190 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A base class for a configurable application.
3 A base class for a configurable application.
4
4
5 Authors:
5 Authors:
6
6
7 * Brian Granger
7 * Brian Granger
8 """
8 """
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2008-2011 The IPython Development Team
11 # Copyright (C) 2008-2011 The IPython Development Team
12 #
12 #
13 # Distributed under the terms of the BSD License. The full license is in
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
14 # the file COPYING, distributed as part of this software.
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 from copy import deepcopy
21 from copy import deepcopy
22 import logging
22 import logging
23 import sys
23 import sys
24
24
25 from IPython.config.configurable import SingletonConfigurable
25 from IPython.config.configurable import SingletonConfigurable
26 from IPython.utils.traitlets import (
27 Unicode, List, Int, Enum
28 )
29 from IPython.config.loader import (
26 from IPython.config.loader import (
30 KeyValueConfigLoader, PyFileConfigLoader
27 KeyValueConfigLoader, PyFileConfigLoader
31 )
28 )
32
29
30 from IPython.utils.traitlets import (
31 Unicode, List, Int, Enum, Dict
32 )
33 from IPython.utils.text import indent
34
33 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
34 # Application class
36 # Application class
35 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
36
38
37
39
38 class Application(SingletonConfigurable):
40 class Application(SingletonConfigurable):
39 """A singleton application with full configuration support."""
41 """A singleton application with full configuration support."""
40
42
41 # The name of the application, will usually match the name of the command
43 # The name of the application, will usually match the name of the command
42 # line application
44 # line application
43 app_name = Unicode(u'application')
45 app_name = Unicode(u'application')
44
46
45 # The description of the application that is printed at the beginning
47 # The description of the application that is printed at the beginning
46 # of the help.
48 # of the help.
47 description = Unicode(u'This is an application.')
49 description = Unicode(u'This is an application.')
48
50
49 # A sequence of Configurable subclasses whose config=True attributes will
51 # A sequence of Configurable subclasses whose config=True attributes will
50 # be exposed at the command line (shortnames and help).
52 # be exposed at the command line (shortnames and help).
51 classes = List([])
53 classes = List([])
52
54
53 # The version string of this application.
55 # The version string of this application.
54 version = Unicode(u'0.0')
56 version = Unicode(u'0.0')
55
57
56 # The log level for the application
58 # The log level for the application
57 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
59 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
58 config=True, shortname="log_level",
60 config=True,
59 help="Set the log level (0,10,20,30,40,50).")
61 help="Set the log level (0,10,20,30,40,50).")
62
63 # the shortname map for configurables
64 shortnames = Dict(dict(log_level='Application.log_level'))
65
66 # macros for loading Configurables or store_const style flags
67 # macros are loaded from this dict by '--key' flags
68 macros = Dict()
69 # macro_help dict keys must match macros
70 macro_help = Dict()
71
60
72
61 def __init__(self, **kwargs):
73 def __init__(self, **kwargs):
62 SingletonConfigurable.__init__(self, **kwargs)
74 SingletonConfigurable.__init__(self, **kwargs)
63 # Add my class to self.classes so my attributes appear in command line
75 # Add my class to self.classes so my attributes appear in command line
64 # options.
76 # options.
65 self.classes.insert(0, self.__class__)
77 self.classes.insert(0, self.__class__)
78
79 # check that macro_help has the right keys
80 # there is probably a better way to do this that doesn't use 2 dicts
81 keys = set(self.macros.keys())
82 badkeys = keys.difference_update(set(self.macro_help.keys()))
83 if badkeys:
84 raise KeyError("macro %r has no help in `macro_help`!"%badkeys.pop())
66 self.init_logging()
85 self.init_logging()
67
86
68 def init_logging(self):
87 def init_logging(self):
69 """Start logging for this application.
88 """Start logging for this application.
70
89
71 The default is to log to stdout using a StreaHandler. The log level
90 The default is to log to stdout using a StreaHandler. The log level
72 starts at loggin.WARN, but this can be adjusted by setting the
91 starts at loggin.WARN, but this can be adjusted by setting the
73 ``log_level`` attribute.
92 ``log_level`` attribute.
74 """
93 """
75 self.log = logging.getLogger(self.__class__.__name__)
94 self.log = logging.getLogger(self.__class__.__name__)
76 self.log.setLevel(self.log_level)
95 self.log.setLevel(self.log_level)
77 self._log_handler = logging.StreamHandler()
96 self._log_handler = logging.StreamHandler()
78 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
97 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
79 self._log_handler.setFormatter(self._log_formatter)
98 self._log_handler.setFormatter(self._log_formatter)
80 self.log.addHandler(self._log_handler)
99 self.log.addHandler(self._log_handler)
81
100
82 def _log_level_changed(self, name, old, new):
101 def _log_level_changed(self, name, old, new):
83 """Adjust the log level when log_level is set."""
102 """Adjust the log level when log_level is set."""
84 self.log.setLevel(new)
103 self.log.setLevel(new)
85
104
105 def print_shortname_help(self):
106 """print the shortname part of the help"""
107 if not self.shortnames:
108 return
109
110 print "Aliases"
111 print "-------"
112 classdict = {}
113 for c in self.classes:
114 classdict[c.__name__] = c
115 for shortname, longname in self.shortnames.iteritems():
116 classname, traitname = longname.split('.',1)
117 cls = classdict[classname]
118
119 trait = cls.class_traits(config=True)[traitname]
120 help = trait.get_metadata('help')
121 print shortname, "(%s)"%longname, ':', trait.__class__.__name__
122 if help:
123 print indent(help)
124 print
125
126 def print_macro_help(self):
127 """print the the macro part of the help"""
128 if not self.macros:
129 return
130
131 print "Flags"
132 print "-----"
133
134 for m, cfg in self.macros.iteritems():
135 print '--'+m
136 print indent(self.macro_help[m])
137 print
138
86 def print_help(self):
139 def print_help(self):
87 """Print the help for each Configurable class in self.classes."""
140 """Print the help for each Configurable class in self.classes."""
141 self.print_macro_help()
142 self.print_shortname_help()
88 for cls in self.classes:
143 for cls in self.classes:
89 cls.class_print_help()
144 cls.class_print_help()
90 print
145 print
91
146
92 def print_description(self):
147 def print_description(self):
93 """Print the application description."""
148 """Print the application description."""
94 print self.description
149 print self.description
95 print
150 print
96
151
97 def print_version(self):
152 def print_version(self):
98 """Print the version string."""
153 """Print the version string."""
99 print self.version
154 print self.version
100
155
101 def update_config(self, config):
156 def update_config(self, config):
102 """Fire the traits events when the config is updated."""
157 """Fire the traits events when the config is updated."""
103 # Save a copy of the current config.
158 # Save a copy of the current config.
104 newconfig = deepcopy(self.config)
159 newconfig = deepcopy(self.config)
105 # Merge the new config into the current one.
160 # Merge the new config into the current one.
106 newconfig._merge(config)
161 newconfig._merge(config)
107 # Save the combined config as self.config, which triggers the traits
162 # Save the combined config as self.config, which triggers the traits
108 # events.
163 # events.
109 self.config = config
164 self.config = config
110
165
111 def parse_command_line(self, argv=None):
166 def parse_command_line(self, argv=None):
112 """Parse the command line arguments."""
167 """Parse the command line arguments."""
113 argv = sys.argv[1:] if argv is None else argv
168 argv = sys.argv[1:] if argv is None else argv
114
169
115 if '-h' in argv or '--h' in argv:
170 if '-h' in argv or '--help' in argv:
116 self.print_description()
171 self.print_description()
117 self.print_help()
172 self.print_help()
118 sys.exit(1)
173 sys.exit(1)
119
174
120 if '--version' in argv:
175 if '--version' in argv:
121 self.print_version()
176 self.print_version()
122 sys.exit(1)
177 sys.exit(1)
123
178
124 loader = KeyValueConfigLoader(argv=argv, classes=self.classes)
179 loader = KeyValueConfigLoader(argv=argv, shortnames=self.shortnames,
180 macros=self.macros)
125 config = loader.load_config()
181 config = loader.load_config()
126 self.update_config(config)
182 self.update_config(config)
127
183
128 def load_config_file(self, filename, path=None):
184 def load_config_file(self, filename, path=None):
129 """Load a .py based config file by filename and path."""
185 """Load a .py based config file by filename and path."""
130 # TODO: this raises IOError if filename does not exist.
186 # TODO: this raises IOError if filename does not exist.
131 loader = PyFileConfigLoader(filename, path=path)
187 loader = PyFileConfigLoader(filename, path=path)
132 config = loader.load_config()
188 config = loader.load_config()
133 self.update_config(config)
189 self.update_config(config)
134
190
@@ -1,240 +1,225 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
25
26 from loader import Config
26 from loader import Config
27 from IPython.utils.traitlets import HasTraits, Instance
27 from IPython.utils.traitlets import HasTraits, Instance
28 from IPython.utils.text import indent
28 from IPython.utils.text import indent
29
29
30
30
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32 # Helper classes for Configurables
32 # Helper classes for Configurables
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34
34
35
35
36 class ConfigurableError(Exception):
36 class ConfigurableError(Exception):
37 pass
37 pass
38
38
39
39
40 class MultipleInstanceError(ConfigurableError):
40 class MultipleInstanceError(ConfigurableError):
41 pass
41 pass
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Configurable implementation
44 # Configurable implementation
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47
47
48 class Configurable(HasTraits):
48 class Configurable(HasTraits):
49
49
50 config = Instance(Config,(),{})
50 config = Instance(Config,(),{})
51 created = None
51 created = None
52
52
53 def __init__(self, **kwargs):
53 def __init__(self, **kwargs):
54 """Create a conigurable given a config config.
54 """Create a conigurable given a config config.
55
55
56 Parameters
56 Parameters
57 ----------
57 ----------
58 config : Config
58 config : Config
59 If this is empty, default values are used. If config is a
59 If this is empty, default values are used. If config is a
60 :class:`Config` instance, it will be used to configure the
60 :class:`Config` instance, it will be used to configure the
61 instance.
61 instance.
62
62
63 Notes
63 Notes
64 -----
64 -----
65 Subclasses of Configurable must call the :meth:`__init__` method of
65 Subclasses of Configurable must call the :meth:`__init__` method of
66 :class:`Configurable` *before* doing anything else and using
66 :class:`Configurable` *before* doing anything else and using
67 :func:`super`::
67 :func:`super`::
68
68
69 class MyConfigurable(Configurable):
69 class MyConfigurable(Configurable):
70 def __init__(self, config=None):
70 def __init__(self, config=None):
71 super(MyConfigurable, self).__init__(config)
71 super(MyConfigurable, self).__init__(config)
72 # Then any other code you need to finish initialization.
72 # Then any other code you need to finish initialization.
73
73
74 This ensures that instances will be configured properly.
74 This ensures that instances will be configured properly.
75 """
75 """
76 config = kwargs.pop('config', None)
76 config = kwargs.pop('config', None)
77 if config is not None:
77 if config is not None:
78 # We used to deepcopy, but for now we are trying to just save
78 # We used to deepcopy, but for now we are trying to just save
79 # by reference. This *could* have side effects as all components
79 # by reference. This *could* have side effects as all components
80 # will share config. In fact, I did find such a side effect in
80 # will share config. In fact, I did find such a side effect in
81 # _config_changed below. If a config attribute value was a mutable type
81 # _config_changed below. If a config attribute value was a mutable type
82 # all instances of a component were getting the same copy, effectively
82 # all instances of a component were getting the same copy, effectively
83 # making that a class attribute.
83 # making that a class attribute.
84 # self.config = deepcopy(config)
84 # self.config = deepcopy(config)
85 self.config = config
85 self.config = config
86 # This should go second so individual keyword arguments override
86 # This should go second so individual keyword arguments override
87 # the values in config.
87 # the values in config.
88 super(Configurable, self).__init__(**kwargs)
88 super(Configurable, self).__init__(**kwargs)
89 self.created = datetime.datetime.now()
89 self.created = datetime.datetime.now()
90
90
91 #-------------------------------------------------------------------------
91 #-------------------------------------------------------------------------
92 # Static trait notifiations
92 # Static trait notifiations
93 #-------------------------------------------------------------------------
93 #-------------------------------------------------------------------------
94
94
95 def _config_changed(self, name, old, new):
95 def _config_changed(self, name, old, new):
96 """Update all the class traits having ``config=True`` as metadata.
96 """Update all the class traits having ``config=True`` as metadata.
97
97
98 For any class trait with a ``config`` metadata attribute that is
98 For any class trait with a ``config`` metadata attribute that is
99 ``True``, we update the trait with the value of the corresponding
99 ``True``, we update the trait with the value of the corresponding
100 config entry.
100 config entry.
101 """
101 """
102 # Get all traits with a config metadata entry that is True
102 # Get all traits with a config metadata entry that is True
103 traits = self.traits(config=True)
103 traits = self.traits(config=True)
104
104
105 # We auto-load config section for this class as well as any parent
105 # We auto-load config section for this class as well as any parent
106 # classes that are Configurable subclasses. This starts with Configurable
106 # classes that are Configurable subclasses. This starts with Configurable
107 # and works down the mro loading the config for each section.
107 # and works down the mro loading the config for each section.
108 section_names = [cls.__name__ for cls in \
108 section_names = [cls.__name__ for cls in \
109 reversed(self.__class__.__mro__) if
109 reversed(self.__class__.__mro__) if
110 issubclass(cls, Configurable) and issubclass(self.__class__, cls)]
110 issubclass(cls, Configurable) and issubclass(self.__class__, cls)]
111
111
112 for sname in section_names:
112 for sname in section_names:
113 # Don't do a blind getattr as that would cause the config to
113 # Don't do a blind getattr as that would cause the config to
114 # dynamically create the section with name self.__class__.__name__.
114 # dynamically create the section with name self.__class__.__name__.
115 if new._has_section(sname):
115 if new._has_section(sname):
116 my_config = new[sname]
116 my_config = new[sname]
117 for k, v in traits.iteritems():
117 for k, v in traits.iteritems():
118 # Don't allow traitlets with config=True to start with
118 # Don't allow traitlets with config=True to start with
119 # uppercase. Otherwise, they are confused with Config
119 # uppercase. Otherwise, they are confused with Config
120 # subsections. But, developers shouldn't have uppercase
120 # subsections. But, developers shouldn't have uppercase
121 # attributes anyways! (PEP 6)
121 # attributes anyways! (PEP 6)
122 if k[0].upper()==k[0] and not k.startswith('_'):
122 if k[0].upper()==k[0] and not k.startswith('_'):
123 raise ConfigurableError('Configurable traitlets with '
123 raise ConfigurableError('Configurable traitlets with '
124 'config=True must start with a lowercase so they are '
124 'config=True must start with a lowercase so they are '
125 'not confused with Config subsections: %s.%s' % \
125 'not confused with Config subsections: %s.%s' % \
126 (self.__class__.__name__, k))
126 (self.__class__.__name__, k))
127 try:
127 try:
128 # Here we grab the value from the config
128 # Here we grab the value from the config
129 # If k has the naming convention of a config
129 # If k has the naming convention of a config
130 # section, it will be auto created.
130 # section, it will be auto created.
131 config_value = my_config[k]
131 config_value = my_config[k]
132 except KeyError:
132 except KeyError:
133 pass
133 pass
134 else:
134 else:
135 # print "Setting %s.%s from %s.%s=%r" % \
135 # print "Setting %s.%s from %s.%s=%r" % \
136 # (self.__class__.__name__,k,sname,k,config_value)
136 # (self.__class__.__name__,k,sname,k,config_value)
137 # We have to do a deepcopy here if we don't deepcopy the entire
137 # We have to do a deepcopy here if we don't deepcopy the entire
138 # config object. If we don't, a mutable config_value will be
138 # config object. If we don't, a mutable config_value will be
139 # shared by all instances, effectively making it a class attribute.
139 # shared by all instances, effectively making it a class attribute.
140 setattr(self, k, deepcopy(config_value))
140 setattr(self, k, deepcopy(config_value))
141
141
142 @classmethod
142 @classmethod
143 def class_get_shortnames(cls):
144 """Return the shortname to fullname dict for config=True traits."""
145 cls_traits = cls.class_traits(config=True)
146 shortnames = {}
147 for k, v in cls_traits.items():
148 shortname = v.get_metadata('shortname')
149 if shortname is not None:
150 longname = cls.__name__ + '.' + k
151 shortnames[shortname] = longname
152 return shortnames
153
154 @classmethod
155 def class_get_help(cls):
143 def class_get_help(cls):
156 """Get the help string for this class in ReST format."""
144 """Get the help string for this class in ReST format."""
157 cls_traits = cls.class_traits(config=True)
145 cls_traits = cls.class_traits(config=True)
158 final_help = []
146 final_help = []
159 final_help.append(u'%s options' % cls.__name__)
147 final_help.append(u'%s options' % cls.__name__)
160 final_help.append(len(final_help[0])*u'-')
148 final_help.append(len(final_help[0])*u'-')
161 for k, v in cls_traits.items():
149 for k, v in cls_traits.items():
162 help = v.get_metadata('help')
150 help = v.get_metadata('help')
163 shortname = v.get_metadata('shortname')
164 header = "%s.%s : %s" % (cls.__name__, k, v.__class__.__name__)
151 header = "%s.%s : %s" % (cls.__name__, k, v.__class__.__name__)
165 if shortname is not None:
166 header += " (shortname=" + shortname + ")"
167 final_help.append(header)
152 final_help.append(header)
168 if help is not None:
153 if help is not None:
169 final_help.append(indent(help))
154 final_help.append(indent(help))
170 return '\n'.join(final_help)
155 return '\n'.join(final_help)
171
156
172 @classmethod
157 @classmethod
173 def class_print_help(cls):
158 def class_print_help(cls):
174 print cls.class_get_help()
159 print cls.class_get_help()
175
160
176
161
177 class SingletonConfigurable(Configurable):
162 class SingletonConfigurable(Configurable):
178 """A configurable that only allows one instance.
163 """A configurable that only allows one instance.
179
164
180 This class is for classes that should only have one instance of itself
165 This class is for classes that should only have one instance of itself
181 or *any* subclass. To create and retrieve such a class use the
166 or *any* subclass. To create and retrieve such a class use the
182 :meth:`SingletonConfigurable.instance` method.
167 :meth:`SingletonConfigurable.instance` method.
183 """
168 """
184
169
185 _instance = None
170 _instance = None
186
171
187 @classmethod
172 @classmethod
188 def instance(cls, *args, **kwargs):
173 def instance(cls, *args, **kwargs):
189 """Returns a global instance of this class.
174 """Returns a global instance of this class.
190
175
191 This method create a new instance if none have previously been created
176 This method create a new instance if none have previously been created
192 and returns a previously created instance is one already exists.
177 and returns a previously created instance is one already exists.
193
178
194 The arguments and keyword arguments passed to this method are passed
179 The arguments and keyword arguments passed to this method are passed
195 on to the :meth:`__init__` method of the class upon instantiation.
180 on to the :meth:`__init__` method of the class upon instantiation.
196
181
197 Examples
182 Examples
198 --------
183 --------
199
184
200 Create a singleton class using instance, and retrieve it::
185 Create a singleton class using instance, and retrieve it::
201
186
202 >>> from IPython.config.configurable import SingletonConfigurable
187 >>> from IPython.config.configurable import SingletonConfigurable
203 >>> class Foo(SingletonConfigurable): pass
188 >>> class Foo(SingletonConfigurable): pass
204 >>> foo = Foo.instance()
189 >>> foo = Foo.instance()
205 >>> foo == Foo.instance()
190 >>> foo == Foo.instance()
206 True
191 True
207
192
208 Create a subclass that is retrived using the base class instance::
193 Create a subclass that is retrived using the base class instance::
209
194
210 >>> class Bar(SingletonConfigurable): pass
195 >>> class Bar(SingletonConfigurable): pass
211 >>> class Bam(Bar): pass
196 >>> class Bam(Bar): pass
212 >>> bam = Bam.instance()
197 >>> bam = Bam.instance()
213 >>> bam == Bar.instance()
198 >>> bam == Bar.instance()
214 True
199 True
215 """
200 """
216 # Create and save the instance
201 # Create and save the instance
217 if cls._instance is None:
202 if cls._instance is None:
218 inst = cls(*args, **kwargs)
203 inst = cls(*args, **kwargs)
219 # Now make sure that the instance will also be returned by
204 # Now make sure that the instance will also be returned by
220 # the subclasses instance attribute.
205 # the subclasses instance attribute.
221 for subclass in cls.mro():
206 for subclass in cls.mro():
222 if issubclass(cls, subclass) and \
207 if issubclass(cls, subclass) and \
223 issubclass(subclass, SingletonConfigurable) and \
208 issubclass(subclass, SingletonConfigurable) and \
224 subclass != SingletonConfigurable:
209 subclass != SingletonConfigurable:
225 subclass._instance = inst
210 subclass._instance = inst
226 else:
211 else:
227 break
212 break
228 if isinstance(cls._instance, cls):
213 if isinstance(cls._instance, cls):
229 return cls._instance
214 return cls._instance
230 else:
215 else:
231 raise MultipleInstanceError(
216 raise MultipleInstanceError(
232 'Multiple incompatible subclass instances of '
217 'Multiple incompatible subclass instances of '
233 '%s are being created.' % cls.__name__
218 '%s are being created.' % cls.__name__
234 )
219 )
235
220
236 @classmethod
221 @classmethod
237 def initialized(cls):
222 def initialized(cls):
238 """Has an instance been created?"""
223 """Has an instance been created?"""
239 return hasattr(cls, "_instance") and cls._instance is not None
224 return hasattr(cls, "_instance") and cls._instance is not None
240
225
@@ -1,491 +1,506 b''
1 """A simple configuration system.
1 """A simple configuration system.
2
2
3 Authors
3 Authors
4 -------
4 -------
5 * Brian Granger
5 * Brian Granger
6 * Fernando Perez
6 * Fernando Perez
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2008-2009 The IPython Development Team
10 # Copyright (C) 2008-2009 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 import __builtin__
20 import __builtin__
21 import re
21 import sys
22 import sys
22
23
23 from IPython.external import argparse
24 from IPython.external import argparse
24 from IPython.utils.path import filefind
25 from IPython.utils.path import filefind
25
26
26 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
27 # Exceptions
28 # Exceptions
28 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
29
30
30
31
31 class ConfigError(Exception):
32 class ConfigError(Exception):
32 pass
33 pass
33
34
34
35
35 class ConfigLoaderError(ConfigError):
36 class ConfigLoaderError(ConfigError):
36 pass
37 pass
37
38
38 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
39 # Argparse fix
40 # Argparse fix
40 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
41
42
42 # Unfortunately argparse by default prints help messages to stderr instead of
43 # Unfortunately argparse by default prints help messages to stderr instead of
43 # stdout. This makes it annoying to capture long help screens at the command
44 # stdout. This makes it annoying to capture long help screens at the command
44 # line, since one must know how to pipe stderr, which many users don't know how
45 # line, since one must know how to pipe stderr, which many users don't know how
45 # to do. So we override the print_help method with one that defaults to
46 # to do. So we override the print_help method with one that defaults to
46 # stdout and use our class instead.
47 # stdout and use our class instead.
47
48
48 class ArgumentParser(argparse.ArgumentParser):
49 class ArgumentParser(argparse.ArgumentParser):
49 """Simple argparse subclass that prints help to stdout by default."""
50 """Simple argparse subclass that prints help to stdout by default."""
50
51
51 def print_help(self, file=None):
52 def print_help(self, file=None):
52 if file is None:
53 if file is None:
53 file = sys.stdout
54 file = sys.stdout
54 return super(ArgumentParser, self).print_help(file)
55 return super(ArgumentParser, self).print_help(file)
55
56
56 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
57 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
57
58
58 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
59 # Config class for holding config information
60 # Config class for holding config information
60 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
61
62
62
63
63 class Config(dict):
64 class Config(dict):
64 """An attribute based dict that can do smart merges."""
65 """An attribute based dict that can do smart merges."""
65
66
66 def __init__(self, *args, **kwds):
67 def __init__(self, *args, **kwds):
67 dict.__init__(self, *args, **kwds)
68 dict.__init__(self, *args, **kwds)
68 # This sets self.__dict__ = self, but it has to be done this way
69 # This sets self.__dict__ = self, but it has to be done this way
69 # because we are also overriding __setattr__.
70 # because we are also overriding __setattr__.
70 dict.__setattr__(self, '__dict__', self)
71 dict.__setattr__(self, '__dict__', self)
71
72
72 def _merge(self, other):
73 def _merge(self, other):
73 to_update = {}
74 to_update = {}
74 for k, v in other.iteritems():
75 for k, v in other.iteritems():
75 if not self.has_key(k):
76 if not self.has_key(k):
76 to_update[k] = v
77 to_update[k] = v
77 else: # I have this key
78 else: # I have this key
78 if isinstance(v, Config):
79 if isinstance(v, Config):
79 # Recursively merge common sub Configs
80 # Recursively merge common sub Configs
80 self[k]._merge(v)
81 self[k]._merge(v)
81 else:
82 else:
82 # Plain updates for non-Configs
83 # Plain updates for non-Configs
83 to_update[k] = v
84 to_update[k] = v
84
85
85 self.update(to_update)
86 self.update(to_update)
86
87
87 def _is_section_key(self, key):
88 def _is_section_key(self, key):
88 if key[0].upper()==key[0] and not key.startswith('_'):
89 if key[0].upper()==key[0] and not key.startswith('_'):
89 return True
90 return True
90 else:
91 else:
91 return False
92 return False
92
93
93 def __contains__(self, key):
94 def __contains__(self, key):
94 if self._is_section_key(key):
95 if self._is_section_key(key):
95 return True
96 return True
96 else:
97 else:
97 return super(Config, self).__contains__(key)
98 return super(Config, self).__contains__(key)
98 # .has_key is deprecated for dictionaries.
99 # .has_key is deprecated for dictionaries.
99 has_key = __contains__
100 has_key = __contains__
100
101
101 def _has_section(self, key):
102 def _has_section(self, key):
102 if self._is_section_key(key):
103 if self._is_section_key(key):
103 if super(Config, self).__contains__(key):
104 if super(Config, self).__contains__(key):
104 return True
105 return True
105 return False
106 return False
106
107
107 def copy(self):
108 def copy(self):
108 return type(self)(dict.copy(self))
109 return type(self)(dict.copy(self))
109
110
110 def __copy__(self):
111 def __copy__(self):
111 return self.copy()
112 return self.copy()
112
113
113 def __deepcopy__(self, memo):
114 def __deepcopy__(self, memo):
114 import copy
115 import copy
115 return type(self)(copy.deepcopy(self.items()))
116 return type(self)(copy.deepcopy(self.items()))
116
117
117 def __getitem__(self, key):
118 def __getitem__(self, key):
118 # We cannot use directly self._is_section_key, because it triggers
119 # We cannot use directly self._is_section_key, because it triggers
119 # infinite recursion on top of PyPy. Instead, we manually fish the
120 # infinite recursion on top of PyPy. Instead, we manually fish the
120 # bound method.
121 # bound method.
121 is_section_key = self.__class__._is_section_key.__get__(self)
122 is_section_key = self.__class__._is_section_key.__get__(self)
122
123
123 # Because we use this for an exec namespace, we need to delegate
124 # Because we use this for an exec namespace, we need to delegate
124 # the lookup of names in __builtin__ to itself. This means
125 # the lookup of names in __builtin__ to itself. This means
125 # that you can't have section or attribute names that are
126 # that you can't have section or attribute names that are
126 # builtins.
127 # builtins.
127 try:
128 try:
128 return getattr(__builtin__, key)
129 return getattr(__builtin__, key)
129 except AttributeError:
130 except AttributeError:
130 pass
131 pass
131 if is_section_key(key):
132 if is_section_key(key):
132 try:
133 try:
133 return dict.__getitem__(self, key)
134 return dict.__getitem__(self, key)
134 except KeyError:
135 except KeyError:
135 c = Config()
136 c = Config()
136 dict.__setitem__(self, key, c)
137 dict.__setitem__(self, key, c)
137 return c
138 return c
138 else:
139 else:
139 return dict.__getitem__(self, key)
140 return dict.__getitem__(self, key)
140
141
141 def __setitem__(self, key, value):
142 def __setitem__(self, key, value):
142 # Don't allow names in __builtin__ to be modified.
143 # Don't allow names in __builtin__ to be modified.
143 if hasattr(__builtin__, key):
144 if hasattr(__builtin__, key):
144 raise ConfigError('Config variable names cannot have the same name '
145 raise ConfigError('Config variable names cannot have the same name '
145 'as a Python builtin: %s' % key)
146 'as a Python builtin: %s' % key)
146 if self._is_section_key(key):
147 if self._is_section_key(key):
147 if not isinstance(value, Config):
148 if not isinstance(value, Config):
148 raise ValueError('values whose keys begin with an uppercase '
149 raise ValueError('values whose keys begin with an uppercase '
149 'char must be Config instances: %r, %r' % (key, value))
150 'char must be Config instances: %r, %r' % (key, value))
150 else:
151 else:
151 dict.__setitem__(self, key, value)
152 dict.__setitem__(self, key, value)
152
153
153 def __getattr__(self, key):
154 def __getattr__(self, key):
154 try:
155 try:
155 return self.__getitem__(key)
156 return self.__getitem__(key)
156 except KeyError, e:
157 except KeyError, e:
157 raise AttributeError(e)
158 raise AttributeError(e)
158
159
159 def __setattr__(self, key, value):
160 def __setattr__(self, key, value):
160 try:
161 try:
161 self.__setitem__(key, value)
162 self.__setitem__(key, value)
162 except KeyError, e:
163 except KeyError, e:
163 raise AttributeError(e)
164 raise AttributeError(e)
164
165
165 def __delattr__(self, key):
166 def __delattr__(self, key):
166 try:
167 try:
167 dict.__delitem__(self, key)
168 dict.__delitem__(self, key)
168 except KeyError, e:
169 except KeyError, e:
169 raise AttributeError(e)
170 raise AttributeError(e)
170
171
171
172
172 #-----------------------------------------------------------------------------
173 #-----------------------------------------------------------------------------
173 # Config loading classes
174 # Config loading classes
174 #-----------------------------------------------------------------------------
175 #-----------------------------------------------------------------------------
175
176
176
177
177 class ConfigLoader(object):
178 class ConfigLoader(object):
178 """A object for loading configurations from just about anywhere.
179 """A object for loading configurations from just about anywhere.
179
180
180 The resulting configuration is packaged as a :class:`Struct`.
181 The resulting configuration is packaged as a :class:`Struct`.
181
182
182 Notes
183 Notes
183 -----
184 -----
184 A :class:`ConfigLoader` does one thing: load a config from a source
185 A :class:`ConfigLoader` does one thing: load a config from a source
185 (file, command line arguments) and returns the data as a :class:`Struct`.
186 (file, command line arguments) and returns the data as a :class:`Struct`.
186 There are lots of things that :class:`ConfigLoader` does not do. It does
187 There are lots of things that :class:`ConfigLoader` does not do. It does
187 not implement complex logic for finding config files. It does not handle
188 not implement complex logic for finding config files. It does not handle
188 default values or merge multiple configs. These things need to be
189 default values or merge multiple configs. These things need to be
189 handled elsewhere.
190 handled elsewhere.
190 """
191 """
191
192
192 def __init__(self):
193 def __init__(self):
193 """A base class for config loaders.
194 """A base class for config loaders.
194
195
195 Examples
196 Examples
196 --------
197 --------
197
198
198 >>> cl = ConfigLoader()
199 >>> cl = ConfigLoader()
199 >>> config = cl.load_config()
200 >>> config = cl.load_config()
200 >>> config
201 >>> config
201 {}
202 {}
202 """
203 """
203 self.clear()
204 self.clear()
204
205
205 def clear(self):
206 def clear(self):
206 self.config = Config()
207 self.config = Config()
207
208
208 def load_config(self):
209 def load_config(self):
209 """Load a config from somewhere, return a :class:`Config` instance.
210 """Load a config from somewhere, return a :class:`Config` instance.
210
211
211 Usually, this will cause self.config to be set and then returned.
212 Usually, this will cause self.config to be set and then returned.
212 However, in most cases, :meth:`ConfigLoader.clear` should be called
213 However, in most cases, :meth:`ConfigLoader.clear` should be called
213 to erase any previous state.
214 to erase any previous state.
214 """
215 """
215 self.clear()
216 self.clear()
216 return self.config
217 return self.config
217
218
218
219
219 class FileConfigLoader(ConfigLoader):
220 class FileConfigLoader(ConfigLoader):
220 """A base class for file based configurations.
221 """A base class for file based configurations.
221
222
222 As we add more file based config loaders, the common logic should go
223 As we add more file based config loaders, the common logic should go
223 here.
224 here.
224 """
225 """
225 pass
226 pass
226
227
227
228
228 class PyFileConfigLoader(FileConfigLoader):
229 class PyFileConfigLoader(FileConfigLoader):
229 """A config loader for pure python files.
230 """A config loader for pure python files.
230
231
231 This calls execfile on a plain python file and looks for attributes
232 This calls execfile on a plain python file and looks for attributes
232 that are all caps. These attribute are added to the config Struct.
233 that are all caps. These attribute are added to the config Struct.
233 """
234 """
234
235
235 def __init__(self, filename, path=None):
236 def __init__(self, filename, path=None):
236 """Build a config loader for a filename and path.
237 """Build a config loader for a filename and path.
237
238
238 Parameters
239 Parameters
239 ----------
240 ----------
240 filename : str
241 filename : str
241 The file name of the config file.
242 The file name of the config file.
242 path : str, list, tuple
243 path : str, list, tuple
243 The path to search for the config file on, or a sequence of
244 The path to search for the config file on, or a sequence of
244 paths to try in order.
245 paths to try in order.
245 """
246 """
246 super(PyFileConfigLoader, self).__init__()
247 super(PyFileConfigLoader, self).__init__()
247 self.filename = filename
248 self.filename = filename
248 self.path = path
249 self.path = path
249 self.full_filename = ''
250 self.full_filename = ''
250 self.data = None
251 self.data = None
251
252
252 def load_config(self):
253 def load_config(self):
253 """Load the config from a file and return it as a Struct."""
254 """Load the config from a file and return it as a Struct."""
254 self.clear()
255 self.clear()
255 self._find_file()
256 self._find_file()
256 self._read_file_as_dict()
257 self._read_file_as_dict()
257 self._convert_to_config()
258 self._convert_to_config()
258 return self.config
259 return self.config
259
260
260 def _find_file(self):
261 def _find_file(self):
261 """Try to find the file by searching the paths."""
262 """Try to find the file by searching the paths."""
262 self.full_filename = filefind(self.filename, self.path)
263 self.full_filename = filefind(self.filename, self.path)
263
264
264 def _read_file_as_dict(self):
265 def _read_file_as_dict(self):
265 """Load the config file into self.config, with recursive loading."""
266 """Load the config file into self.config, with recursive loading."""
266 # This closure is made available in the namespace that is used
267 # This closure is made available in the namespace that is used
267 # to exec the config file. This allows users to call
268 # to exec the config file. This allows users to call
268 # load_subconfig('myconfig.py') to load config files recursively.
269 # load_subconfig('myconfig.py') to load config files recursively.
269 # It needs to be a closure because it has references to self.path
270 # It needs to be a closure because it has references to self.path
270 # and self.config. The sub-config is loaded with the same path
271 # and self.config. The sub-config is loaded with the same path
271 # as the parent, but it uses an empty config which is then merged
272 # as the parent, but it uses an empty config which is then merged
272 # with the parents.
273 # with the parents.
273 def load_subconfig(fname):
274 def load_subconfig(fname):
274 loader = PyFileConfigLoader(fname, self.path)
275 loader = PyFileConfigLoader(fname, self.path)
275 try:
276 try:
276 sub_config = loader.load_config()
277 sub_config = loader.load_config()
277 except IOError:
278 except IOError:
278 # Pass silently if the sub config is not there. This happens
279 # Pass silently if the sub config is not there. This happens
279 # when a user us using a profile, but not the default config.
280 # when a user us using a profile, but not the default config.
280 pass
281 pass
281 else:
282 else:
282 self.config._merge(sub_config)
283 self.config._merge(sub_config)
283
284
284 # Again, this needs to be a closure and should be used in config
285 # Again, this needs to be a closure and should be used in config
285 # files to get the config being loaded.
286 # files to get the config being loaded.
286 def get_config():
287 def get_config():
287 return self.config
288 return self.config
288
289
289 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
290 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
290 fs_encoding = sys.getfilesystemencoding() or 'ascii'
291 fs_encoding = sys.getfilesystemencoding() or 'ascii'
291 conf_filename = self.full_filename.encode(fs_encoding)
292 conf_filename = self.full_filename.encode(fs_encoding)
292 execfile(conf_filename, namespace)
293 execfile(conf_filename, namespace)
293
294
294 def _convert_to_config(self):
295 def _convert_to_config(self):
295 if self.data is None:
296 if self.data is None:
296 ConfigLoaderError('self.data does not exist')
297 ConfigLoaderError('self.data does not exist')
297
298
298
299
299 class CommandLineConfigLoader(ConfigLoader):
300 class CommandLineConfigLoader(ConfigLoader):
300 """A config loader for command line arguments.
301 """A config loader for command line arguments.
301
302
302 As we add more command line based loaders, the common logic should go
303 As we add more command line based loaders, the common logic should go
303 here.
304 here.
304 """
305 """
305
306
307 kv_pattern = re.compile(r'[A-Za-z]\w*(\.\w+)*\=.+')
308 macro_pattern = re.compile(r'\-\-\w+(\-\w)*')
306
309
307 class KeyValueConfigLoader(CommandLineConfigLoader):
310 class KeyValueConfigLoader(CommandLineConfigLoader):
308 """A config loader that loads key value pairs from the command line.
311 """A config loader that loads key value pairs from the command line.
309
312
310 This allows command line options to be gives in the following form::
313 This allows command line options to be gives in the following form::
311
314
312 ipython Global.profile="foo" InteractiveShell.autocall=False
315 ipython Global.profile="foo" InteractiveShell.autocall=False
313 """
316 """
314
317
315 def __init__(self, argv=None, classes=None):
318 def __init__(self, argv=None, shortnames=None, macros=None):
316 """Create a key value pair config loader.
319 """Create a key value pair config loader.
317
320
318 Parameters
321 Parameters
319 ----------
322 ----------
320 argv : list
323 argv : list
321 A list that has the form of sys.argv[1:] which has unicode
324 A list that has the form of sys.argv[1:] which has unicode
322 elements of the form u"key=value". If this is None (default),
325 elements of the form u"key=value". If this is None (default),
323 then sys.argv[1:] will be used.
326 then sys.argv[1:] will be used.
324 classes : (list, tuple) of Configurables
327 shortnames : dict
325 A sequence of Configurable classes that will be used to map
328 A dict of aliases for configurable traits.
326 shortnames to longnames.
329 Keys are the short aliases, Values are the resolved trait.
330 Of the form: `{'shortname' : 'Configurable.trait'}`
331 macros : dict
332 A dict of macros, keyed by str name. Vaues can be Config objects,
333 dicts, or "key=value" strings. If Config or dict, when the macro
334 is triggered, The macro is loaded as `self.config.update(m)`.
327
335
328 Returns
336 Returns
329 -------
337 -------
330 config : Config
338 config : Config
331 The resulting Config object.
339 The resulting Config object.
332
340
333 Examples
341 Examples
334 --------
342 --------
335
343
336 >>> from IPython.config.loader import KeyValueConfigLoader
344 >>> from IPython.config.loader import KeyValueConfigLoader
337 >>> cl = KeyValueConfigLoader()
345 >>> cl = KeyValueConfigLoader()
338 >>> cl.load_config(["foo='bar'","A.name='brian'","B.number=0"])
346 >>> cl.load_config(["foo='bar'","A.name='brian'","B.number=0"])
339 {'A': {'name': 'brian'}, 'B': {'number': 0}, 'foo': 'bar'}
347 {'A': {'name': 'brian'}, 'B': {'number': 0}, 'foo': 'bar'}
340 """
348 """
341 if argv is None:
349 if argv is None:
342 argv = sys.argv[1:]
350 argv = sys.argv[1:]
343 if classes is None:
344 classes = ()
345 self.argv = argv
351 self.argv = argv
346 self.classes = classes
352 self.shortnames = shortnames or {}
353 self.macros = macros or {}
347
354
348 def load_config(self, argv=None, classes=None):
355 def load_config(self, argv=None, shortnames=None, macros=None):
349 """Parse the configuration and generate the Config object.
356 """Parse the configuration and generate the Config object.
350
357
351 Parameters
358 Parameters
352 ----------
359 ----------
353 argv : list, optional
360 argv : list, optional
354 A list that has the form of sys.argv[1:] which has unicode
361 A list that has the form of sys.argv[1:] which has unicode
355 elements of the form u"key=value". If this is None (default),
362 elements of the form u"key=value". If this is None (default),
356 then self.argv will be used.
363 then self.argv will be used.
357 classes : (list, tuple) of Configurables
364 shortnames : dict
358 A sequence of Configurable classes that will be used to map
365 A dict of aliases for configurable traits.
359 shortnames to longnames.
366 Keys are the short aliases, Values are the resolved trait.
367 Of the form: `{'shortname' : 'Configurable.trait'}`
368 macros : dict
369 A dict of macros, keyed by str name. Vaues can be Config objects,
370 dicts, or "key=value" strings. If Config or dict, when the macro
371 is triggered, The macro is loaded as `self.config.update(m)`.
360 """
372 """
361 from IPython.config.configurable import Configurable
373 from IPython.config.configurable import Configurable
362
374
363 self.clear()
375 self.clear()
364 if argv is None:
376 if argv is None:
365 argv = self.argv
377 argv = self.argv
366 if classes is None:
378 if shortnames is None:
367 classes = self.classes
379 shortnames = self.shortnames
368
380 if macros is None:
369 # Create the mapping between shortnames and longnames.
381 macros = self.macros
370 shortnames = {}
371 for cls in classes:
372 if issubclass(cls, Configurable):
373 sn = cls.class_get_shortnames()
374 # Check for duplicate shortnames and raise if found.
375 for k, v in sn.items():
376 if k in shortnames:
377 raise KeyError(
378 'Duplicate shortname: both %s and %s use the shortname: "%s"' %\
379 (v, shortnames[k], k)
380 )
381 shortnames.update(sn)
382
382
383 for item in argv:
383 for item in argv:
384 pair = tuple(item.split("="))
384 if kv_pattern.match(item):
385 if len(pair) == 2:
385 lhs,rhs = item.split('=',1)
386 lhs = pair[0]
387 rhs = pair[1]
388 # Substitute longnames for shortnames.
386 # Substitute longnames for shortnames.
389 if lhs in shortnames:
387 if lhs in shortnames:
390 lhs = shortnames[lhs]
388 lhs = shortnames[lhs]
391 exec_str = 'self.config.' + lhs + '=' + rhs
389 exec_str = 'self.config.' + lhs + '=' + rhs
392 try:
390 try:
393 # Try to see if regular Python syntax will work. This
391 # Try to see if regular Python syntax will work. This
394 # won't handle strings as the quote marks are removed
392 # won't handle strings as the quote marks are removed
395 # by the system shell.
393 # by the system shell.
396 exec exec_str in locals(), globals()
394 exec exec_str in locals(), globals()
397 except (NameError, SyntaxError):
395 except (NameError, SyntaxError):
398 # This case happens if the rhs is a string but without
396 # This case happens if the rhs is a string but without
399 # the quote marks. We add the quote marks and see if
397 # the quote marks. We add the quote marks and see if
400 # it succeeds. If it still fails, we let it raise.
398 # it succeeds. If it still fails, we let it raise.
401 exec_str = 'self.config.' + lhs + '="' + rhs + '"'
399 exec_str = 'self.config.' + lhs + '="' + rhs + '"'
402 exec exec_str in locals(), globals()
400 exec exec_str in locals(), globals()
401 elif macro_pattern.match(item):
402 # trim leading '--'
403 m = item[2:]
404 macro = macros.get(m, None)
405 if macro is None:
406 raise ValueError("Unrecognized argument: %r"%item)
407 macro = macros[m]
408 if isinstance(macro, basestring):
409 # macro is simply a 'Class.trait=value' string
410 exec_str = 'self.config.' + macro
411 exec exec_str in locals(), globals()
412 elif isinstance(macro, (dict, Configurable)):
413 # update self.config with Config:
414 self.config.update(macros[m])
415 else:
416 raise ValueError("Invalid macro: %r"%macro)
417 else:
418 raise ValueError("Invalid argument: %r"%item)
403 return self.config
419 return self.config
404
420
405
406 class ArgParseConfigLoader(CommandLineConfigLoader):
421 class ArgParseConfigLoader(CommandLineConfigLoader):
407 """A loader that uses the argparse module to load from the command line."""
422 """A loader that uses the argparse module to load from the command line."""
408
423
409 def __init__(self, argv=None, *parser_args, **parser_kw):
424 def __init__(self, argv=None, *parser_args, **parser_kw):
410 """Create a config loader for use with argparse.
425 """Create a config loader for use with argparse.
411
426
412 Parameters
427 Parameters
413 ----------
428 ----------
414
429
415 argv : optional, list
430 argv : optional, list
416 If given, used to read command-line arguments from, otherwise
431 If given, used to read command-line arguments from, otherwise
417 sys.argv[1:] is used.
432 sys.argv[1:] is used.
418
433
419 parser_args : tuple
434 parser_args : tuple
420 A tuple of positional arguments that will be passed to the
435 A tuple of positional arguments that will be passed to the
421 constructor of :class:`argparse.ArgumentParser`.
436 constructor of :class:`argparse.ArgumentParser`.
422
437
423 parser_kw : dict
438 parser_kw : dict
424 A tuple of keyword arguments that will be passed to the
439 A tuple of keyword arguments that will be passed to the
425 constructor of :class:`argparse.ArgumentParser`.
440 constructor of :class:`argparse.ArgumentParser`.
426
441
427 Returns
442 Returns
428 -------
443 -------
429 config : Config
444 config : Config
430 The resulting Config object.
445 The resulting Config object.
431 """
446 """
432 super(CommandLineConfigLoader, self).__init__()
447 super(CommandLineConfigLoader, self).__init__()
433 if argv == None:
448 if argv == None:
434 argv = sys.argv[1:]
449 argv = sys.argv[1:]
435 self.argv = argv
450 self.argv = argv
436 self.parser_args = parser_args
451 self.parser_args = parser_args
437 self.version = parser_kw.pop("version", None)
452 self.version = parser_kw.pop("version", None)
438 kwargs = dict(argument_default=argparse.SUPPRESS)
453 kwargs = dict(argument_default=argparse.SUPPRESS)
439 kwargs.update(parser_kw)
454 kwargs.update(parser_kw)
440 self.parser_kw = kwargs
455 self.parser_kw = kwargs
441
456
442 def load_config(self, argv=None):
457 def load_config(self, argv=None):
443 """Parse command line arguments and return as a Config object.
458 """Parse command line arguments and return as a Config object.
444
459
445 Parameters
460 Parameters
446 ----------
461 ----------
447
462
448 args : optional, list
463 args : optional, list
449 If given, a list with the structure of sys.argv[1:] to parse
464 If given, a list with the structure of sys.argv[1:] to parse
450 arguments from. If not given, the instance's self.argv attribute
465 arguments from. If not given, the instance's self.argv attribute
451 (given at construction time) is used."""
466 (given at construction time) is used."""
452 self.clear()
467 self.clear()
453 if argv is None:
468 if argv is None:
454 argv = self.argv
469 argv = self.argv
455 self._create_parser()
470 self._create_parser()
456 self._parse_args(argv)
471 self._parse_args(argv)
457 self._convert_to_config()
472 self._convert_to_config()
458 return self.config
473 return self.config
459
474
460 def get_extra_args(self):
475 def get_extra_args(self):
461 if hasattr(self, 'extra_args'):
476 if hasattr(self, 'extra_args'):
462 return self.extra_args
477 return self.extra_args
463 else:
478 else:
464 return []
479 return []
465
480
466 def _create_parser(self):
481 def _create_parser(self):
467 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
482 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
468 self._add_arguments()
483 self._add_arguments()
469
484
470 def _add_arguments(self):
485 def _add_arguments(self):
471 raise NotImplementedError("subclasses must implement _add_arguments")
486 raise NotImplementedError("subclasses must implement _add_arguments")
472
487
473 def _parse_args(self, args):
488 def _parse_args(self, args):
474 """self.parser->self.parsed_data"""
489 """self.parser->self.parsed_data"""
475 # decode sys.argv to support unicode command-line options
490 # decode sys.argv to support unicode command-line options
476 uargs = []
491 uargs = []
477 for a in args:
492 for a in args:
478 if isinstance(a, str):
493 if isinstance(a, str):
479 # don't decode if we already got unicode
494 # don't decode if we already got unicode
480 a = a.decode(sys.stdin.encoding or
495 a = a.decode(sys.stdin.encoding or
481 sys.getdefaultencoding())
496 sys.getdefaultencoding())
482 uargs.append(a)
497 uargs.append(a)
483 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
498 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
484
499
485 def _convert_to_config(self):
500 def _convert_to_config(self):
486 """self.parsed_data->self.config"""
501 """self.parsed_data->self.config"""
487 for k, v in vars(self.parsed_data).iteritems():
502 for k, v in vars(self.parsed_data).iteritems():
488 exec_str = 'self.config.' + k + '= v'
503 exec_str = 'self.config.' + k + '= v'
489 exec exec_str in locals(), globals()
504 exec exec_str in locals(), globals()
490
505
491
506
@@ -1,90 +1,108 b''
1 """
1 """
2 Tests for IPython.config.application.Application
2 Tests for IPython.config.application.Application
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2008-2011 The IPython Development Team
10 # Copyright (C) 2008-2011 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 from unittest import TestCase
20 from unittest import TestCase
21
21
22 from IPython.config.configurable import Configurable
22 from IPython.config.configurable import Configurable
23
23
24 from IPython.config.application import (
24 from IPython.config.application import (
25 Application
25 Application
26 )
26 )
27
27
28 from IPython.utils.traitlets import (
28 from IPython.utils.traitlets import (
29 Bool, Unicode, Int, Float, List
29 Bool, Unicode, Int, Float, List
30 )
30 )
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Code
33 # Code
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 class Foo(Configurable):
36 class Foo(Configurable):
37
37
38 i = Int(0, config=True, shortname='i', help="The integer i.")
38 i = Int(0, config=True, help="The integer i.")
39 j = Int(1, config=True, shortname='j', help="The integer j.")
39 j = Int(1, config=True, help="The integer j.")
40 name = Unicode(u'Brian', config=True, shortname='name', help="First name.")
40 name = Unicode(u'Brian', config=True, help="First name.")
41
41
42
42
43 class Bar(Configurable):
43 class Bar(Configurable):
44
44
45 enabled = Bool(True, config=True, shortname="enabled", help="Enable bar.")
45 enabled = Bool(True, config=True, help="Enable bar.")
46
46
47
47
48 class MyApp(Application):
48 class MyApp(Application):
49
49
50 app_name = Unicode(u'myapp')
50 app_name = Unicode(u'myapp')
51 running = Bool(False, config=True, shortname="running",
51 running = Bool(False, config=True, shortname="running",
52 help="Is the app running?")
52 help="Is the app running?")
53 classes = List([Bar, Foo])
53 classes = List([Bar, Foo])
54 config_file = Unicode(u'', config=True, shortname="config_file",
54 config_file = Unicode(u'', config=True, shortname="config_file",
55 help="Load this config file")
55 help="Load this config file")
56
56
57 shortnames = dict(i='Foo.i',j='Foo.j',name='Foo.name',
58 enabled='Bar.enabled', log_level='MyApp.log_level')
59
60 macros = dict(enable='Bar.enabled=True', disable='Bar.enabled=False')
61
62 macro_help = dict(
63 enable="""Enable bar""",
64 disable="""Disable bar"""
65 )
57 def init_foo(self):
66 def init_foo(self):
58 self.foo = Foo(config=self.config)
67 self.foo = Foo(config=self.config)
59
68
60 def init_bar(self):
69 def init_bar(self):
61 self.bar = Bar(config=self.config)
70 self.bar = Bar(config=self.config)
62
71
63
72
64 class TestApplication(TestCase):
73 class TestApplication(TestCase):
65
74
66 def test_basic(self):
75 def test_basic(self):
67 app = MyApp()
76 app = MyApp()
68 self.assertEquals(app.app_name, u'myapp')
77 self.assertEquals(app.app_name, u'myapp')
69 self.assertEquals(app.running, False)
78 self.assertEquals(app.running, False)
70 self.assertEquals(app.classes, [MyApp,Bar,Foo])
79 self.assertEquals(app.classes, [MyApp,Bar,Foo])
71 self.assertEquals(app.config_file, u'')
80 self.assertEquals(app.config_file, u'')
72
81
73 def test_config(self):
82 def test_config(self):
74 app = MyApp()
83 app = MyApp()
75 app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=0"])
84 app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=0"])
76 config = app.config
85 config = app.config
77 self.assertEquals(config.Foo.i, 10)
86 self.assertEquals(config.Foo.i, 10)
78 self.assertEquals(config.Foo.j, 10)
87 self.assertEquals(config.Foo.j, 10)
79 self.assertEquals(config.Bar.enabled, False)
88 self.assertEquals(config.Bar.enabled, False)
80 self.assertEquals(config.MyApp.log_level,0)
89 self.assertEquals(config.MyApp.log_level,0)
81
90
82 def test_config_propagation(self):
91 def test_config_propagation(self):
83 app = MyApp()
92 app = MyApp()
84 app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=0"])
93 app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=0"])
85 app.init_foo()
94 app.init_foo()
86 app.init_bar()
95 app.init_bar()
87 self.assertEquals(app.foo.i, 10)
96 self.assertEquals(app.foo.i, 10)
88 self.assertEquals(app.foo.j, 10)
97 self.assertEquals(app.foo.j, 10)
89 self.assertEquals(app.bar.enabled, False)
98 self.assertEquals(app.bar.enabled, False)
90
99
100 def test_macro(self):
101 app = MyApp()
102 app.parse_command_line(["--disable"])
103 app.init_bar()
104 self.assertEquals(app.bar.enabled, False)
105 app.parse_command_line(["--enable"])
106 app.init_bar()
107 self.assertEquals(app.bar.enabled, True)
108
@@ -1,172 +1,164 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 Tests for IPython.config.configurable
4 Tests for IPython.config.configurable
5
5
6 Authors:
6 Authors:
7
7
8 * Brian Granger
8 * Brian Granger
9 * Fernando Perez (design help)
9 * Fernando Perez (design help)
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 unittest import TestCase
23 from unittest import TestCase
24
24
25 from IPython.config.configurable import (
25 from IPython.config.configurable import (
26 Configurable,
26 Configurable,
27 SingletonConfigurable
27 SingletonConfigurable
28 )
28 )
29
29
30 from IPython.utils.traitlets import (
30 from IPython.utils.traitlets import (
31 Int, Float, Str
31 Int, Float, Str
32 )
32 )
33
33
34 from IPython.config.loader import Config
34 from IPython.config.loader import Config
35
35
36
36
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # Test cases
38 # Test cases
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40
40
41
41
42 class MyConfigurable(Configurable):
42 class MyConfigurable(Configurable):
43 a = Int(1, config=True, shortname="a", help="The integer a.")
43 a = Int(1, config=True, help="The integer a.")
44 b = Float(1.0, config=True, shortname="b", help="The integer b.")
44 b = Float(1.0, config=True, help="The integer b.")
45 c = Str('no config')
45 c = Str('no config')
46
46
47
47
48 mc_help=u"""MyConfigurable options
48 mc_help=u"""MyConfigurable options
49 ----------------------
49 ----------------------
50 MyConfigurable.a : Int (shortname=a)
50 MyConfigurable.a : Int
51 The integer a.
51 The integer a.
52 MyConfigurable.b : Float (shortname=b)
52 MyConfigurable.b : Float
53 The integer b."""
53 The integer b."""
54
54
55 class Foo(Configurable):
55 class Foo(Configurable):
56 a = Int(0, config=True, shortname="a", help="The integer a.")
56 a = Int(0, config=True, help="The integer a.")
57 b = Str('nope', config=True)
57 b = Str('nope', config=True)
58
58
59
59
60 class Bar(Foo):
60 class Bar(Foo):
61 b = Str('gotit', config=False, shortname="b", help="The string b.")
61 b = Str('gotit', config=False, help="The string b.")
62 c = Float(config=True, shortname="c", help="The string c.")
62 c = Float(config=True, help="The string c.")
63
63
64
64
65 class TestConfigurable(TestCase):
65 class TestConfigurable(TestCase):
66
66
67 def test_default(self):
67 def test_default(self):
68 c1 = Configurable()
68 c1 = Configurable()
69 c2 = Configurable(config=c1.config)
69 c2 = Configurable(config=c1.config)
70 c3 = Configurable(config=c2.config)
70 c3 = Configurable(config=c2.config)
71 self.assertEquals(c1.config, c2.config)
71 self.assertEquals(c1.config, c2.config)
72 self.assertEquals(c2.config, c3.config)
72 self.assertEquals(c2.config, c3.config)
73
73
74 def test_custom(self):
74 def test_custom(self):
75 config = Config()
75 config = Config()
76 config.foo = 'foo'
76 config.foo = 'foo'
77 config.bar = 'bar'
77 config.bar = 'bar'
78 c1 = Configurable(config=config)
78 c1 = Configurable(config=config)
79 c2 = Configurable(config=c1.config)
79 c2 = Configurable(config=c1.config)
80 c3 = Configurable(config=c2.config)
80 c3 = Configurable(config=c2.config)
81 self.assertEquals(c1.config, config)
81 self.assertEquals(c1.config, config)
82 self.assertEquals(c2.config, config)
82 self.assertEquals(c2.config, config)
83 self.assertEquals(c3.config, config)
83 self.assertEquals(c3.config, config)
84 # Test that copies are not made
84 # Test that copies are not made
85 self.assert_(c1.config is config)
85 self.assert_(c1.config is config)
86 self.assert_(c2.config is config)
86 self.assert_(c2.config is config)
87 self.assert_(c3.config is config)
87 self.assert_(c3.config is config)
88 self.assert_(c1.config is c2.config)
88 self.assert_(c1.config is c2.config)
89 self.assert_(c2.config is c3.config)
89 self.assert_(c2.config is c3.config)
90
90
91 def test_inheritance(self):
91 def test_inheritance(self):
92 config = Config()
92 config = Config()
93 config.MyConfigurable.a = 2
93 config.MyConfigurable.a = 2
94 config.MyConfigurable.b = 2.0
94 config.MyConfigurable.b = 2.0
95 c1 = MyConfigurable(config=config)
95 c1 = MyConfigurable(config=config)
96 c2 = MyConfigurable(config=c1.config)
96 c2 = MyConfigurable(config=c1.config)
97 self.assertEquals(c1.a, config.MyConfigurable.a)
97 self.assertEquals(c1.a, config.MyConfigurable.a)
98 self.assertEquals(c1.b, config.MyConfigurable.b)
98 self.assertEquals(c1.b, config.MyConfigurable.b)
99 self.assertEquals(c2.a, config.MyConfigurable.a)
99 self.assertEquals(c2.a, config.MyConfigurable.a)
100 self.assertEquals(c2.b, config.MyConfigurable.b)
100 self.assertEquals(c2.b, config.MyConfigurable.b)
101
101
102 def test_parent(self):
102 def test_parent(self):
103 config = Config()
103 config = Config()
104 config.Foo.a = 10
104 config.Foo.a = 10
105 config.Foo.b = "wow"
105 config.Foo.b = "wow"
106 config.Bar.b = 'later'
106 config.Bar.b = 'later'
107 config.Bar.c = 100.0
107 config.Bar.c = 100.0
108 f = Foo(config=config)
108 f = Foo(config=config)
109 b = Bar(config=f.config)
109 b = Bar(config=f.config)
110 self.assertEquals(f.a, 10)
110 self.assertEquals(f.a, 10)
111 self.assertEquals(f.b, 'wow')
111 self.assertEquals(f.b, 'wow')
112 self.assertEquals(b.b, 'gotit')
112 self.assertEquals(b.b, 'gotit')
113 self.assertEquals(b.c, 100.0)
113 self.assertEquals(b.c, 100.0)
114
114
115 def test_override1(self):
115 def test_override1(self):
116 config = Config()
116 config = Config()
117 config.MyConfigurable.a = 2
117 config.MyConfigurable.a = 2
118 config.MyConfigurable.b = 2.0
118 config.MyConfigurable.b = 2.0
119 c = MyConfigurable(a=3, config=config)
119 c = MyConfigurable(a=3, config=config)
120 self.assertEquals(c.a, 3)
120 self.assertEquals(c.a, 3)
121 self.assertEquals(c.b, config.MyConfigurable.b)
121 self.assertEquals(c.b, config.MyConfigurable.b)
122 self.assertEquals(c.c, 'no config')
122 self.assertEquals(c.c, 'no config')
123
123
124 def test_override2(self):
124 def test_override2(self):
125 config = Config()
125 config = Config()
126 config.Foo.a = 1
126 config.Foo.a = 1
127 config.Bar.b = 'or' # Up above b is config=False, so this won't do it.
127 config.Bar.b = 'or' # Up above b is config=False, so this won't do it.
128 config.Bar.c = 10.0
128 config.Bar.c = 10.0
129 c = Bar(config=config)
129 c = Bar(config=config)
130 self.assertEquals(c.a, config.Foo.a)
130 self.assertEquals(c.a, config.Foo.a)
131 self.assertEquals(c.b, 'gotit')
131 self.assertEquals(c.b, 'gotit')
132 self.assertEquals(c.c, config.Bar.c)
132 self.assertEquals(c.c, config.Bar.c)
133 c = Bar(a=2, b='and', c=20.0, config=config)
133 c = Bar(a=2, b='and', c=20.0, config=config)
134 self.assertEquals(c.a, 2)
134 self.assertEquals(c.a, 2)
135 self.assertEquals(c.b, 'and')
135 self.assertEquals(c.b, 'and')
136 self.assertEquals(c.c, 20.0)
136 self.assertEquals(c.c, 20.0)
137
137
138 def test_shortnames(self):
139 sn = MyConfigurable.class_get_shortnames()
140 self.assertEquals(sn, {'a': 'MyConfigurable.a', 'b': 'MyConfigurable.b'})
141 sn = Foo.class_get_shortnames()
142 self.assertEquals(sn, {'a': 'Foo.a'})
143 sn = Bar.class_get_shortnames()
144 self.assertEquals(sn, {'a': 'Bar.a', 'c': 'Bar.c'})
145
146 def test_help(self):
138 def test_help(self):
147 self.assertEquals(MyConfigurable.class_get_help(), mc_help)
139 self.assertEquals(MyConfigurable.class_get_help(), mc_help)
148
140
149
141
150 class TestSingletonConfigurable(TestCase):
142 class TestSingletonConfigurable(TestCase):
151
143
152 def test_instance(self):
144 def test_instance(self):
153 from IPython.config.configurable import SingletonConfigurable
145 from IPython.config.configurable import SingletonConfigurable
154 class Foo(SingletonConfigurable): pass
146 class Foo(SingletonConfigurable): pass
155 self.assertEquals(Foo.initialized(), False)
147 self.assertEquals(Foo.initialized(), False)
156 foo = Foo.instance()
148 foo = Foo.instance()
157 self.assertEquals(Foo.initialized(), True)
149 self.assertEquals(Foo.initialized(), True)
158 self.assertEquals(foo, Foo.instance())
150 self.assertEquals(foo, Foo.instance())
159 self.assertEquals(SingletonConfigurable._instance, None)
151 self.assertEquals(SingletonConfigurable._instance, None)
160
152
161 def test_inheritance(self):
153 def test_inheritance(self):
162 class Bar(SingletonConfigurable): pass
154 class Bar(SingletonConfigurable): pass
163 class Bam(Bar): pass
155 class Bam(Bar): pass
164 self.assertEquals(Bar.initialized(), False)
156 self.assertEquals(Bar.initialized(), False)
165 self.assertEquals(Bam.initialized(), False)
157 self.assertEquals(Bam.initialized(), False)
166 bam = Bam.instance()
158 bam = Bam.instance()
167 bam == Bar.instance()
159 bam == Bar.instance()
168 self.assertEquals(Bar.initialized(), True)
160 self.assertEquals(Bar.initialized(), True)
169 self.assertEquals(Bam.initialized(), True)
161 self.assertEquals(Bam.initialized(), True)
170 self.assertEquals(bam, Bam._instance)
162 self.assertEquals(bam, Bam._instance)
171 self.assertEquals(bam, Bar._instance)
163 self.assertEquals(bam, Bar._instance)
172 self.assertEquals(SingletonConfigurable._instance, None)
164 self.assertEquals(SingletonConfigurable._instance, None)
@@ -1,207 +1,190 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 Tests for IPython.config.loader
4 Tests for IPython.config.loader
5
5
6 Authors:
6 Authors:
7
7
8 * Brian Granger
8 * Brian Granger
9 * Fernando Perez (design help)
9 * Fernando Perez (design help)
10 """
10 """
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2009 The IPython Development Team
13 # Copyright (C) 2008-2009 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 import os
23 import os
24 from tempfile import mkstemp
24 from tempfile import mkstemp
25 from unittest import TestCase
25 from unittest import TestCase
26
26
27 from IPython.utils.traitlets import Int, Unicode
27 from IPython.utils.traitlets import Int, Unicode
28 from IPython.config.configurable import Configurable
28 from IPython.config.configurable import Configurable
29 from IPython.config.loader import (
29 from IPython.config.loader import (
30 Config,
30 Config,
31 PyFileConfigLoader,
31 PyFileConfigLoader,
32 KeyValueConfigLoader,
32 KeyValueConfigLoader,
33 ArgParseConfigLoader,
33 ArgParseConfigLoader,
34 ConfigError
34 ConfigError
35 )
35 )
36
36
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # Actual tests
38 # Actual tests
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40
40
41
41
42 pyfile = """
42 pyfile = """
43 c = get_config()
43 c = get_config()
44 c.a=10
44 c.a=10
45 c.b=20
45 c.b=20
46 c.Foo.Bar.value=10
46 c.Foo.Bar.value=10
47 c.Foo.Bam.value=range(10)
47 c.Foo.Bam.value=range(10)
48 c.D.C.value='hi there'
48 c.D.C.value='hi there'
49 """
49 """
50
50
51 class TestPyFileCL(TestCase):
51 class TestPyFileCL(TestCase):
52
52
53 def test_basic(self):
53 def test_basic(self):
54 fd, fname = mkstemp('.py')
54 fd, fname = mkstemp('.py')
55 f = os.fdopen(fd, 'w')
55 f = os.fdopen(fd, 'w')
56 f.write(pyfile)
56 f.write(pyfile)
57 f.close()
57 f.close()
58 # Unlink the file
58 # Unlink the file
59 cl = PyFileConfigLoader(fname)
59 cl = PyFileConfigLoader(fname)
60 config = cl.load_config()
60 config = cl.load_config()
61 self.assertEquals(config.a, 10)
61 self.assertEquals(config.a, 10)
62 self.assertEquals(config.b, 20)
62 self.assertEquals(config.b, 20)
63 self.assertEquals(config.Foo.Bar.value, 10)
63 self.assertEquals(config.Foo.Bar.value, 10)
64 self.assertEquals(config.Foo.Bam.value, range(10))
64 self.assertEquals(config.Foo.Bam.value, range(10))
65 self.assertEquals(config.D.C.value, 'hi there')
65 self.assertEquals(config.D.C.value, 'hi there')
66
66
67 class MyLoader1(ArgParseConfigLoader):
67 class MyLoader1(ArgParseConfigLoader):
68 def _add_arguments(self):
68 def _add_arguments(self):
69 p = self.parser
69 p = self.parser
70 p.add_argument('-f', '--foo', dest='Global.foo', type=str)
70 p.add_argument('-f', '--foo', dest='Global.foo', type=str)
71 p.add_argument('-b', dest='MyClass.bar', type=int)
71 p.add_argument('-b', dest='MyClass.bar', type=int)
72 p.add_argument('-n', dest='n', action='store_true')
72 p.add_argument('-n', dest='n', action='store_true')
73 p.add_argument('Global.bam', type=str)
73 p.add_argument('Global.bam', type=str)
74
74
75 class MyLoader2(ArgParseConfigLoader):
75 class MyLoader2(ArgParseConfigLoader):
76 def _add_arguments(self):
76 def _add_arguments(self):
77 subparsers = self.parser.add_subparsers(dest='subparser_name')
77 subparsers = self.parser.add_subparsers(dest='subparser_name')
78 subparser1 = subparsers.add_parser('1')
78 subparser1 = subparsers.add_parser('1')
79 subparser1.add_argument('-x',dest='Global.x')
79 subparser1.add_argument('-x',dest='Global.x')
80 subparser2 = subparsers.add_parser('2')
80 subparser2 = subparsers.add_parser('2')
81 subparser2.add_argument('y')
81 subparser2.add_argument('y')
82
82
83 class TestArgParseCL(TestCase):
83 class TestArgParseCL(TestCase):
84
84
85 def test_basic(self):
85 def test_basic(self):
86 cl = MyLoader1()
86 cl = MyLoader1()
87 config = cl.load_config('-f hi -b 10 -n wow'.split())
87 config = cl.load_config('-f hi -b 10 -n wow'.split())
88 self.assertEquals(config.Global.foo, 'hi')
88 self.assertEquals(config.Global.foo, 'hi')
89 self.assertEquals(config.MyClass.bar, 10)
89 self.assertEquals(config.MyClass.bar, 10)
90 self.assertEquals(config.n, True)
90 self.assertEquals(config.n, True)
91 self.assertEquals(config.Global.bam, 'wow')
91 self.assertEquals(config.Global.bam, 'wow')
92 config = cl.load_config(['wow'])
92 config = cl.load_config(['wow'])
93 self.assertEquals(config.keys(), ['Global'])
93 self.assertEquals(config.keys(), ['Global'])
94 self.assertEquals(config.Global.keys(), ['bam'])
94 self.assertEquals(config.Global.keys(), ['bam'])
95 self.assertEquals(config.Global.bam, 'wow')
95 self.assertEquals(config.Global.bam, 'wow')
96
96
97 def test_add_arguments(self):
97 def test_add_arguments(self):
98 cl = MyLoader2()
98 cl = MyLoader2()
99 config = cl.load_config('2 frobble'.split())
99 config = cl.load_config('2 frobble'.split())
100 self.assertEquals(config.subparser_name, '2')
100 self.assertEquals(config.subparser_name, '2')
101 self.assertEquals(config.y, 'frobble')
101 self.assertEquals(config.y, 'frobble')
102 config = cl.load_config('1 -x frobble'.split())
102 config = cl.load_config('1 -x frobble'.split())
103 self.assertEquals(config.subparser_name, '1')
103 self.assertEquals(config.subparser_name, '1')
104 self.assertEquals(config.Global.x, 'frobble')
104 self.assertEquals(config.Global.x, 'frobble')
105
105
106 def test_argv(self):
106 def test_argv(self):
107 cl = MyLoader1(argv='-f hi -b 10 -n wow'.split())
107 cl = MyLoader1(argv='-f hi -b 10 -n wow'.split())
108 config = cl.load_config()
108 config = cl.load_config()
109 self.assertEquals(config.Global.foo, 'hi')
109 self.assertEquals(config.Global.foo, 'hi')
110 self.assertEquals(config.MyClass.bar, 10)
110 self.assertEquals(config.MyClass.bar, 10)
111 self.assertEquals(config.n, True)
111 self.assertEquals(config.n, True)
112 self.assertEquals(config.Global.bam, 'wow')
112 self.assertEquals(config.Global.bam, 'wow')
113
113
114
114
115 class TestKeyValueCL(TestCase):
115 class TestKeyValueCL(TestCase):
116
116
117 def test_basic(self):
117 def test_basic(self):
118 cl = KeyValueConfigLoader()
118 cl = KeyValueConfigLoader()
119 argv = [s.strip('c.') for s in pyfile.split('\n')[2:-1]]
119 argv = [s.strip('c.') for s in pyfile.split('\n')[2:-1]]
120 config = cl.load_config(argv)
120 config = cl.load_config(argv)
121 self.assertEquals(config.a, 10)
121 self.assertEquals(config.a, 10)
122 self.assertEquals(config.b, 20)
122 self.assertEquals(config.b, 20)
123 self.assertEquals(config.Foo.Bar.value, 10)
123 self.assertEquals(config.Foo.Bar.value, 10)
124 self.assertEquals(config.Foo.Bam.value, range(10))
124 self.assertEquals(config.Foo.Bam.value, range(10))
125 self.assertEquals(config.D.C.value, 'hi there')
125 self.assertEquals(config.D.C.value, 'hi there')
126
126
127 def test_shortname(self):
128 class Foo(Configurable):
129 i = Int(0, config=True, shortname="i")
130 s = Unicode('hi', config=True, shortname="s")
131 cl = KeyValueConfigLoader()
132 config = cl.load_config(["i=20", "s=there"], classes=[Foo])
133 self.assertEquals(config.Foo.i, 20)
134 self.assertEquals(config.Foo.s, "there")
135
136 def test_duplicate(self):
137 class Foo(Configurable):
138 i = Int(0, config=True, shortname="i")
139 class Bar(Configurable):
140 i = Int(0, config=True, shortname="i")
141 cl = KeyValueConfigLoader()
142 self.assertRaises(KeyError, cl.load_config, ["i=20", "s=there"], classes=[Foo, Bar])
143
144
127
145 class TestConfig(TestCase):
128 class TestConfig(TestCase):
146
129
147 def test_setget(self):
130 def test_setget(self):
148 c = Config()
131 c = Config()
149 c.a = 10
132 c.a = 10
150 self.assertEquals(c.a, 10)
133 self.assertEquals(c.a, 10)
151 self.assertEquals(c.has_key('b'), False)
134 self.assertEquals(c.has_key('b'), False)
152
135
153 def test_auto_section(self):
136 def test_auto_section(self):
154 c = Config()
137 c = Config()
155 self.assertEquals(c.has_key('A'), True)
138 self.assertEquals(c.has_key('A'), True)
156 self.assertEquals(c._has_section('A'), False)
139 self.assertEquals(c._has_section('A'), False)
157 A = c.A
140 A = c.A
158 A.foo = 'hi there'
141 A.foo = 'hi there'
159 self.assertEquals(c._has_section('A'), True)
142 self.assertEquals(c._has_section('A'), True)
160 self.assertEquals(c.A.foo, 'hi there')
143 self.assertEquals(c.A.foo, 'hi there')
161 del c.A
144 del c.A
162 self.assertEquals(len(c.A.keys()),0)
145 self.assertEquals(len(c.A.keys()),0)
163
146
164 def test_merge_doesnt_exist(self):
147 def test_merge_doesnt_exist(self):
165 c1 = Config()
148 c1 = Config()
166 c2 = Config()
149 c2 = Config()
167 c2.bar = 10
150 c2.bar = 10
168 c2.Foo.bar = 10
151 c2.Foo.bar = 10
169 c1._merge(c2)
152 c1._merge(c2)
170 self.assertEquals(c1.Foo.bar, 10)
153 self.assertEquals(c1.Foo.bar, 10)
171 self.assertEquals(c1.bar, 10)
154 self.assertEquals(c1.bar, 10)
172 c2.Bar.bar = 10
155 c2.Bar.bar = 10
173 c1._merge(c2)
156 c1._merge(c2)
174 self.assertEquals(c1.Bar.bar, 10)
157 self.assertEquals(c1.Bar.bar, 10)
175
158
176 def test_merge_exists(self):
159 def test_merge_exists(self):
177 c1 = Config()
160 c1 = Config()
178 c2 = Config()
161 c2 = Config()
179 c1.Foo.bar = 10
162 c1.Foo.bar = 10
180 c1.Foo.bam = 30
163 c1.Foo.bam = 30
181 c2.Foo.bar = 20
164 c2.Foo.bar = 20
182 c2.Foo.wow = 40
165 c2.Foo.wow = 40
183 c1._merge(c2)
166 c1._merge(c2)
184 self.assertEquals(c1.Foo.bam, 30)
167 self.assertEquals(c1.Foo.bam, 30)
185 self.assertEquals(c1.Foo.bar, 20)
168 self.assertEquals(c1.Foo.bar, 20)
186 self.assertEquals(c1.Foo.wow, 40)
169 self.assertEquals(c1.Foo.wow, 40)
187 c2.Foo.Bam.bam = 10
170 c2.Foo.Bam.bam = 10
188 c1._merge(c2)
171 c1._merge(c2)
189 self.assertEquals(c1.Foo.Bam.bam, 10)
172 self.assertEquals(c1.Foo.Bam.bam, 10)
190
173
191 def test_deepcopy(self):
174 def test_deepcopy(self):
192 c1 = Config()
175 c1 = Config()
193 c1.Foo.bar = 10
176 c1.Foo.bar = 10
194 c1.Foo.bam = 30
177 c1.Foo.bam = 30
195 c1.a = 'asdf'
178 c1.a = 'asdf'
196 c1.b = range(10)
179 c1.b = range(10)
197 import copy
180 import copy
198 c2 = copy.deepcopy(c1)
181 c2 = copy.deepcopy(c1)
199 self.assertEquals(c1, c2)
182 self.assertEquals(c1, c2)
200 self.assert_(c1 is not c2)
183 self.assert_(c1 is not c2)
201 self.assert_(c1.Foo is not c2.Foo)
184 self.assert_(c1.Foo is not c2.Foo)
202
185
203 def test_builtin(self):
186 def test_builtin(self):
204 c1 = Config()
187 c1 = Config()
205 exec 'foo = True' in c1
188 exec 'foo = True' in c1
206 self.assertEquals(c1.foo, True)
189 self.assertEquals(c1.foo, True)
207 self.assertRaises(ConfigError, setattr, c1, 'ValueError', 10)
190 self.assertRaises(ConfigError, setattr, c1, 'ValueError', 10)
@@ -1,88 +1,97 b''
1 """A simple example of how to use IPython.config.application.Application.
1 """A simple example of how to use IPython.config.application.Application.
2
2
3 This should serve as a simple example that shows how the IPython config
3 This should serve as a simple example that shows how the IPython config
4 system works. The main classes are:
4 system works. The main classes are:
5
5
6 * IPython.config.configurable.Configurable
6 * IPython.config.configurable.Configurable
7 * IPython.config.configurable.SingletonConfigurable
7 * IPython.config.configurable.SingletonConfigurable
8 * IPython.config.loader.Config
8 * IPython.config.loader.Config
9 * IPython.config.application.Application
9 * IPython.config.application.Application
10
10
11 To see the command line option help, run this program from the command line::
11 To see the command line option help, run this program from the command line::
12
12
13 $ python appconfig.py -h
13 $ python appconfig.py -h
14
14
15 To make one of your classes configurable (from the command line and config
15 To make one of your classes configurable (from the command line and config
16 files) inherit from Configurable and declare class attributes as traits (see
16 files) inherit from Configurable and declare class attributes as traits (see
17 classes Foo and Bar below). To make the traits configurable, you will need
17 classes Foo and Bar below). To make the traits configurable, you will need
18 to set the following options:
18 to set the following options:
19
19
20 * ``config``: set to ``True`` to make the attribute configurable.
20 * ``config``: set to ``True`` to make the attribute configurable.
21 * ``shortname``: by default, configurable attributes are set using the syntax
21 * ``shortname``: by default, configurable attributes are set using the syntax
22 "Classname.attributename". At the command line, this is a bit verbose, so
22 "Classname.attributename". At the command line, this is a bit verbose, so
23 we allow "shortnames" to be declared. Setting a shortname is optional, but
23 we allow "shortnames" to be declared. Setting a shortname is optional, but
24 when you do this, you can set the option at the command line using the
24 when you do this, you can set the option at the command line using the
25 syntax: "shortname=value".
25 syntax: "shortname=value".
26 * ``help``: set the help string to display a help message when the ``-h``
26 * ``help``: set the help string to display a help message when the ``-h``
27 option is given at the command line. The help string should be valid ReST.
27 option is given at the command line. The help string should be valid ReST.
28
28
29 When the config attribute of an Application is updated, it will fire all of
29 When the config attribute of an Application is updated, it will fire all of
30 the trait's events for all of the config=True attributes.
30 the trait's events for all of the config=True attributes.
31 """
31 """
32
32
33 import sys
33 import sys
34
34
35 from IPython.config.configurable import Configurable
35 from IPython.config.configurable import Configurable
36 from IPython.config.application import Application
36 from IPython.config.application import Application
37 from IPython.utils.traitlets import (
37 from IPython.utils.traitlets import (
38 Bool, Unicode, Int, Float, List
38 Bool, Unicode, Int, Float, List
39 )
39 )
40
40
41
41
42 class Foo(Configurable):
42 class Foo(Configurable):
43 """A class that has configurable, typed attributes.
43 """A class that has configurable, typed attributes.
44
44
45 """
45 """
46
46
47 i = Int(0, config=True, shortname='i', help="The integer i.")
47 i = Int(0, config=True, shortname='i', help="The integer i.")
48 j = Int(1, config=True, shortname='j', help="The integer j.")
48 j = Int(1, config=True, shortname='j', help="The integer j.")
49 name = Unicode(u'Brian', config=True, shortname='name', help="First name.")
49 name = Unicode(u'Brian', config=True, shortname='name', help="First name.")
50
50
51
51
52 class Bar(Configurable):
52 class Bar(Configurable):
53
53
54 enabled = Bool(True, config=True, shortname="enabled", help="Enable bar.")
54 enabled = Bool(True, config=True, shortname="enabled", help="Enable bar.")
55
55
56
56
57 class MyApp(Application):
57 class MyApp(Application):
58
58
59 app_name = Unicode(u'myapp')
59 app_name = Unicode(u'myapp')
60 running = Bool(False, config=True, shortname="running",
60 running = Bool(False, config=True, shortname="running",
61 help="Is the app running?")
61 help="Is the app running?")
62 classes = List([Bar, Foo])
62 classes = List([Bar, Foo])
63 config_file = Unicode(u'', config=True, shortname="config_file",
63 config_file = Unicode(u'', config=True, shortname="config_file",
64 help="Load this config file")
64 help="Load this config file")
65
66 shortnames = dict(i='Foo.i',j='Foo.j',name='Foo.name',
67 enabled='Bar.enabled')
68
69 macros = dict(enable='Bar.enabled=True', disable='Bar.enabled=False')
70 macro_help = dict(
71 enable="""Set Bar.enabled to True""",
72 disable="""Set Bar.enabled to False"""
73 )
65
74
66 def init_foo(self):
75 def init_foo(self):
67 # Pass config to other classes for them to inherit the config.
76 # Pass config to other classes for them to inherit the config.
68 self.foo = Foo(config=self.config)
77 self.foo = Foo(config=self.config)
69
78
70 def init_bar(self):
79 def init_bar(self):
71 # Pass config to other classes for them to inherit the config.
80 # Pass config to other classes for them to inherit the config.
72 self.bar = Bar(config=self.config)
81 self.bar = Bar(config=self.config)
73
82
74
83
75
84
76 def main():
85 def main():
77 app = MyApp()
86 app = MyApp()
78 app.parse_command_line()
87 app.parse_command_line()
79 if app.config_file:
88 if app.config_file:
80 app.load_config_file(app.config_file)
89 app.load_config_file(app.config_file)
81 app.init_foo()
90 app.init_foo()
82 app.init_bar()
91 app.init_bar()
83 print "app.config:"
92 print "app.config:"
84 print app.config
93 print app.config
85
94
86
95
87 if __name__ == "__main__":
96 if __name__ == "__main__":
88 main()
97 main()
General Comments 0
You need to be logged in to leave comments. Login now