Show More
@@ -1,351 +1,356 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """ |
|
2 | """ | |
3 | A base class for objects that are configurable. |
|
3 | A base class for objects that are configurable. | |
4 |
|
4 | |||
|
5 | Inheritance diagram: | |||
|
6 | ||||
|
7 | .. inheritance-diagram:: IPython.config.configurable | |||
|
8 | :parts: 3 | |||
|
9 | ||||
5 | Authors: |
|
10 | Authors: | |
6 |
|
11 | |||
7 | * Brian Granger |
|
12 | * Brian Granger | |
8 | * Fernando Perez |
|
13 | * Fernando Perez | |
9 | * Min RK |
|
14 | * Min RK | |
10 | """ |
|
15 | """ | |
11 |
|
16 | |||
12 | #----------------------------------------------------------------------------- |
|
17 | #----------------------------------------------------------------------------- | |
13 | # Copyright (C) 2008-2011 The IPython Development Team |
|
18 | # Copyright (C) 2008-2011 The IPython Development Team | |
14 | # |
|
19 | # | |
15 | # Distributed under the terms of the BSD License. The full license is in |
|
20 | # Distributed under the terms of the BSD License. The full license is in | |
16 | # the file COPYING, distributed as part of this software. |
|
21 | # the file COPYING, distributed as part of this software. | |
17 | #----------------------------------------------------------------------------- |
|
22 | #----------------------------------------------------------------------------- | |
18 |
|
23 | |||
19 | #----------------------------------------------------------------------------- |
|
24 | #----------------------------------------------------------------------------- | |
20 | # Imports |
|
25 | # Imports | |
21 | #----------------------------------------------------------------------------- |
|
26 | #----------------------------------------------------------------------------- | |
22 |
|
27 | |||
23 | import datetime |
|
28 | import datetime | |
24 | from copy import deepcopy |
|
29 | from copy import deepcopy | |
25 |
|
30 | |||
26 | from loader import Config |
|
31 | from loader import Config | |
27 | from IPython.utils.traitlets import HasTraits, Instance |
|
32 | from IPython.utils.traitlets import HasTraits, Instance | |
28 | from IPython.utils.text import indent, wrap_paragraphs |
|
33 | from IPython.utils.text import indent, wrap_paragraphs | |
29 |
|
34 | |||
30 |
|
35 | |||
31 | #----------------------------------------------------------------------------- |
|
36 | #----------------------------------------------------------------------------- | |
32 | # Helper classes for Configurables |
|
37 | # Helper classes for Configurables | |
33 | #----------------------------------------------------------------------------- |
|
38 | #----------------------------------------------------------------------------- | |
34 |
|
39 | |||
35 |
|
40 | |||
36 | class ConfigurableError(Exception): |
|
41 | class ConfigurableError(Exception): | |
37 | pass |
|
42 | pass | |
38 |
|
43 | |||
39 |
|
44 | |||
40 | class MultipleInstanceError(ConfigurableError): |
|
45 | class MultipleInstanceError(ConfigurableError): | |
41 | pass |
|
46 | pass | |
42 |
|
47 | |||
43 | #----------------------------------------------------------------------------- |
|
48 | #----------------------------------------------------------------------------- | |
44 | # Configurable implementation |
|
49 | # Configurable implementation | |
45 | #----------------------------------------------------------------------------- |
|
50 | #----------------------------------------------------------------------------- | |
46 |
|
51 | |||
47 | class Configurable(HasTraits): |
|
52 | class Configurable(HasTraits): | |
48 |
|
53 | |||
49 | config = Instance(Config,(),{}) |
|
54 | config = Instance(Config,(),{}) | |
50 | created = None |
|
55 | created = None | |
51 |
|
56 | |||
52 | def __init__(self, **kwargs): |
|
57 | def __init__(self, **kwargs): | |
53 | """Create a configurable given a config config. |
|
58 | """Create a configurable given a config config. | |
54 |
|
59 | |||
55 | Parameters |
|
60 | Parameters | |
56 | ---------- |
|
61 | ---------- | |
57 | config : Config |
|
62 | config : Config | |
58 | If this is empty, default values are used. If config is a |
|
63 | If this is empty, default values are used. If config is a | |
59 | :class:`Config` instance, it will be used to configure the |
|
64 | :class:`Config` instance, it will be used to configure the | |
60 | instance. |
|
65 | instance. | |
61 |
|
66 | |||
62 | Notes |
|
67 | Notes | |
63 | ----- |
|
68 | ----- | |
64 | Subclasses of Configurable must call the :meth:`__init__` method of |
|
69 | Subclasses of Configurable must call the :meth:`__init__` method of | |
65 | :class:`Configurable` *before* doing anything else and using |
|
70 | :class:`Configurable` *before* doing anything else and using | |
66 | :func:`super`:: |
|
71 | :func:`super`:: | |
67 |
|
72 | |||
68 | class MyConfigurable(Configurable): |
|
73 | class MyConfigurable(Configurable): | |
69 | def __init__(self, config=None): |
|
74 | def __init__(self, config=None): | |
70 | super(MyConfigurable, self).__init__(config) |
|
75 | super(MyConfigurable, self).__init__(config) | |
71 | # Then any other code you need to finish initialization. |
|
76 | # Then any other code you need to finish initialization. | |
72 |
|
77 | |||
73 | This ensures that instances will be configured properly. |
|
78 | This ensures that instances will be configured properly. | |
74 | """ |
|
79 | """ | |
75 | config = kwargs.pop('config', None) |
|
80 | config = kwargs.pop('config', None) | |
76 | if config is not None: |
|
81 | if config is not None: | |
77 | # We used to deepcopy, but for now we are trying to just save |
|
82 | # We used to deepcopy, but for now we are trying to just save | |
78 | # by reference. This *could* have side effects as all components |
|
83 | # by reference. This *could* have side effects as all components | |
79 | # will share config. In fact, I did find such a side effect in |
|
84 | # will share config. In fact, I did find such a side effect in | |
80 | # _config_changed below. If a config attribute value was a mutable type |
|
85 | # _config_changed below. If a config attribute value was a mutable type | |
81 | # all instances of a component were getting the same copy, effectively |
|
86 | # all instances of a component were getting the same copy, effectively | |
82 | # making that a class attribute. |
|
87 | # making that a class attribute. | |
83 | # self.config = deepcopy(config) |
|
88 | # self.config = deepcopy(config) | |
84 | self.config = config |
|
89 | self.config = config | |
85 | # This should go second so individual keyword arguments override |
|
90 | # This should go second so individual keyword arguments override | |
86 | # the values in config. |
|
91 | # the values in config. | |
87 | super(Configurable, self).__init__(**kwargs) |
|
92 | super(Configurable, self).__init__(**kwargs) | |
88 | self.created = datetime.datetime.now() |
|
93 | self.created = datetime.datetime.now() | |
89 |
|
94 | |||
90 | #------------------------------------------------------------------------- |
|
95 | #------------------------------------------------------------------------- | |
91 | # Static trait notifiations |
|
96 | # Static trait notifiations | |
92 | #------------------------------------------------------------------------- |
|
97 | #------------------------------------------------------------------------- | |
93 |
|
98 | |||
94 | def _config_changed(self, name, old, new): |
|
99 | def _config_changed(self, name, old, new): | |
95 | """Update all the class traits having ``config=True`` as metadata. |
|
100 | """Update all the class traits having ``config=True`` as metadata. | |
96 |
|
101 | |||
97 | For any class trait with a ``config`` metadata attribute that is |
|
102 | For any class trait with a ``config`` metadata attribute that is | |
98 | ``True``, we update the trait with the value of the corresponding |
|
103 | ``True``, we update the trait with the value of the corresponding | |
99 | config entry. |
|
104 | config entry. | |
100 | """ |
|
105 | """ | |
101 | # Get all traits with a config metadata entry that is True |
|
106 | # Get all traits with a config metadata entry that is True | |
102 | traits = self.traits(config=True) |
|
107 | traits = self.traits(config=True) | |
103 |
|
108 | |||
104 | # We auto-load config section for this class as well as any parent |
|
109 | # We auto-load config section for this class as well as any parent | |
105 | # classes that are Configurable subclasses. This starts with Configurable |
|
110 | # classes that are Configurable subclasses. This starts with Configurable | |
106 | # and works down the mro loading the config for each section. |
|
111 | # and works down the mro loading the config for each section. | |
107 | section_names = [cls.__name__ for cls in \ |
|
112 | section_names = [cls.__name__ for cls in \ | |
108 | reversed(self.__class__.__mro__) if |
|
113 | reversed(self.__class__.__mro__) if | |
109 | issubclass(cls, Configurable) and issubclass(self.__class__, cls)] |
|
114 | issubclass(cls, Configurable) and issubclass(self.__class__, cls)] | |
110 |
|
115 | |||
111 | for sname in section_names: |
|
116 | for sname in section_names: | |
112 | # Don't do a blind getattr as that would cause the config to |
|
117 | # Don't do a blind getattr as that would cause the config to | |
113 | # dynamically create the section with name self.__class__.__name__. |
|
118 | # dynamically create the section with name self.__class__.__name__. | |
114 | if new._has_section(sname): |
|
119 | if new._has_section(sname): | |
115 | my_config = new[sname] |
|
120 | my_config = new[sname] | |
116 | for k, v in traits.iteritems(): |
|
121 | for k, v in traits.iteritems(): | |
117 | # Don't allow traitlets with config=True to start with |
|
122 | # Don't allow traitlets with config=True to start with | |
118 | # uppercase. Otherwise, they are confused with Config |
|
123 | # uppercase. Otherwise, they are confused with Config | |
119 | # subsections. But, developers shouldn't have uppercase |
|
124 | # subsections. But, developers shouldn't have uppercase | |
120 | # attributes anyways! (PEP 6) |
|
125 | # attributes anyways! (PEP 6) | |
121 | if k[0].upper()==k[0] and not k.startswith('_'): |
|
126 | if k[0].upper()==k[0] and not k.startswith('_'): | |
122 | raise ConfigurableError('Configurable traitlets with ' |
|
127 | raise ConfigurableError('Configurable traitlets with ' | |
123 | 'config=True must start with a lowercase so they are ' |
|
128 | 'config=True must start with a lowercase so they are ' | |
124 | 'not confused with Config subsections: %s.%s' % \ |
|
129 | 'not confused with Config subsections: %s.%s' % \ | |
125 | (self.__class__.__name__, k)) |
|
130 | (self.__class__.__name__, k)) | |
126 | try: |
|
131 | try: | |
127 | # Here we grab the value from the config |
|
132 | # Here we grab the value from the config | |
128 | # If k has the naming convention of a config |
|
133 | # If k has the naming convention of a config | |
129 | # section, it will be auto created. |
|
134 | # section, it will be auto created. | |
130 | config_value = my_config[k] |
|
135 | config_value = my_config[k] | |
131 | except KeyError: |
|
136 | except KeyError: | |
132 | pass |
|
137 | pass | |
133 | else: |
|
138 | else: | |
134 | # print "Setting %s.%s from %s.%s=%r" % \ |
|
139 | # print "Setting %s.%s from %s.%s=%r" % \ | |
135 | # (self.__class__.__name__,k,sname,k,config_value) |
|
140 | # (self.__class__.__name__,k,sname,k,config_value) | |
136 | # We have to do a deepcopy here if we don't deepcopy the entire |
|
141 | # We have to do a deepcopy here if we don't deepcopy the entire | |
137 | # config object. If we don't, a mutable config_value will be |
|
142 | # config object. If we don't, a mutable config_value will be | |
138 | # shared by all instances, effectively making it a class attribute. |
|
143 | # shared by all instances, effectively making it a class attribute. | |
139 | setattr(self, k, deepcopy(config_value)) |
|
144 | setattr(self, k, deepcopy(config_value)) | |
140 |
|
145 | |||
141 | def update_config(self, config): |
|
146 | def update_config(self, config): | |
142 | """Fire the traits events when the config is updated.""" |
|
147 | """Fire the traits events when the config is updated.""" | |
143 | # Save a copy of the current config. |
|
148 | # Save a copy of the current config. | |
144 | newconfig = deepcopy(self.config) |
|
149 | newconfig = deepcopy(self.config) | |
145 | # Merge the new config into the current one. |
|
150 | # Merge the new config into the current one. | |
146 | newconfig._merge(config) |
|
151 | newconfig._merge(config) | |
147 | # Save the combined config as self.config, which triggers the traits |
|
152 | # Save the combined config as self.config, which triggers the traits | |
148 | # events. |
|
153 | # events. | |
149 | self.config = newconfig |
|
154 | self.config = newconfig | |
150 |
|
155 | |||
151 | @classmethod |
|
156 | @classmethod | |
152 | def class_get_help(cls, inst=None): |
|
157 | def class_get_help(cls, inst=None): | |
153 | """Get the help string for this class in ReST format. |
|
158 | """Get the help string for this class in ReST format. | |
154 |
|
159 | |||
155 | If `inst` is given, it's current trait values will be used in place of |
|
160 | If `inst` is given, it's current trait values will be used in place of | |
156 | class defaults. |
|
161 | class defaults. | |
157 | """ |
|
162 | """ | |
158 | assert inst is None or isinstance(inst, cls) |
|
163 | assert inst is None or isinstance(inst, cls) | |
159 | cls_traits = cls.class_traits(config=True) |
|
164 | cls_traits = cls.class_traits(config=True) | |
160 | final_help = [] |
|
165 | final_help = [] | |
161 | final_help.append(u'%s options' % cls.__name__) |
|
166 | final_help.append(u'%s options' % cls.__name__) | |
162 | final_help.append(len(final_help[0])*u'-') |
|
167 | final_help.append(len(final_help[0])*u'-') | |
163 | for k,v in sorted(cls.class_traits(config=True).iteritems()): |
|
168 | for k,v in sorted(cls.class_traits(config=True).iteritems()): | |
164 | help = cls.class_get_trait_help(v, inst) |
|
169 | help = cls.class_get_trait_help(v, inst) | |
165 | final_help.append(help) |
|
170 | final_help.append(help) | |
166 | return '\n'.join(final_help) |
|
171 | return '\n'.join(final_help) | |
167 |
|
172 | |||
168 | @classmethod |
|
173 | @classmethod | |
169 | def class_get_trait_help(cls, trait, inst=None): |
|
174 | def class_get_trait_help(cls, trait, inst=None): | |
170 | """Get the help string for a single trait. |
|
175 | """Get the help string for a single trait. | |
171 |
|
176 | |||
172 | If `inst` is given, it's current trait values will be used in place of |
|
177 | If `inst` is given, it's current trait values will be used in place of | |
173 | the class default. |
|
178 | the class default. | |
174 | """ |
|
179 | """ | |
175 | assert inst is None or isinstance(inst, cls) |
|
180 | assert inst is None or isinstance(inst, cls) | |
176 | lines = [] |
|
181 | lines = [] | |
177 | header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__) |
|
182 | header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__) | |
178 | lines.append(header) |
|
183 | lines.append(header) | |
179 | if inst is not None: |
|
184 | if inst is not None: | |
180 | lines.append(indent('Current: %r' % getattr(inst, trait.name), 4)) |
|
185 | lines.append(indent('Current: %r' % getattr(inst, trait.name), 4)) | |
181 | else: |
|
186 | else: | |
182 | try: |
|
187 | try: | |
183 | dvr = repr(trait.get_default_value()) |
|
188 | dvr = repr(trait.get_default_value()) | |
184 | except Exception: |
|
189 | except Exception: | |
185 | dvr = None # ignore defaults we can't construct |
|
190 | dvr = None # ignore defaults we can't construct | |
186 | if dvr is not None: |
|
191 | if dvr is not None: | |
187 | if len(dvr) > 64: |
|
192 | if len(dvr) > 64: | |
188 | dvr = dvr[:61]+'...' |
|
193 | dvr = dvr[:61]+'...' | |
189 | lines.append(indent('Default: %s' % dvr, 4)) |
|
194 | lines.append(indent('Default: %s' % dvr, 4)) | |
190 | if 'Enum' in trait.__class__.__name__: |
|
195 | if 'Enum' in trait.__class__.__name__: | |
191 | # include Enum choices |
|
196 | # include Enum choices | |
192 | lines.append(indent('Choices: %r' % (trait.values,))) |
|
197 | lines.append(indent('Choices: %r' % (trait.values,))) | |
193 |
|
198 | |||
194 | help = trait.get_metadata('help') |
|
199 | help = trait.get_metadata('help') | |
195 | if help is not None: |
|
200 | if help is not None: | |
196 | help = '\n'.join(wrap_paragraphs(help, 76)) |
|
201 | help = '\n'.join(wrap_paragraphs(help, 76)) | |
197 | lines.append(indent(help, 4)) |
|
202 | lines.append(indent(help, 4)) | |
198 | return '\n'.join(lines) |
|
203 | return '\n'.join(lines) | |
199 |
|
204 | |||
200 | @classmethod |
|
205 | @classmethod | |
201 | def class_print_help(cls, inst=None): |
|
206 | def class_print_help(cls, inst=None): | |
202 | """Get the help string for a single trait and print it.""" |
|
207 | """Get the help string for a single trait and print it.""" | |
203 | print cls.class_get_help(inst) |
|
208 | print cls.class_get_help(inst) | |
204 |
|
209 | |||
205 | @classmethod |
|
210 | @classmethod | |
206 | def class_config_section(cls): |
|
211 | def class_config_section(cls): | |
207 | """Get the config class config section""" |
|
212 | """Get the config class config section""" | |
208 | def c(s): |
|
213 | def c(s): | |
209 | """return a commented, wrapped block.""" |
|
214 | """return a commented, wrapped block.""" | |
210 | s = '\n\n'.join(wrap_paragraphs(s, 78)) |
|
215 | s = '\n\n'.join(wrap_paragraphs(s, 78)) | |
211 |
|
216 | |||
212 | return '# ' + s.replace('\n', '\n# ') |
|
217 | return '# ' + s.replace('\n', '\n# ') | |
213 |
|
218 | |||
214 | # section header |
|
219 | # section header | |
215 | breaker = '#' + '-'*78 |
|
220 | breaker = '#' + '-'*78 | |
216 | s = "# %s configuration"%cls.__name__ |
|
221 | s = "# %s configuration"%cls.__name__ | |
217 | lines = [breaker, s, breaker, ''] |
|
222 | lines = [breaker, s, breaker, ''] | |
218 | # get the description trait |
|
223 | # get the description trait | |
219 | desc = cls.class_traits().get('description') |
|
224 | desc = cls.class_traits().get('description') | |
220 | if desc: |
|
225 | if desc: | |
221 | desc = desc.default_value |
|
226 | desc = desc.default_value | |
222 | else: |
|
227 | else: | |
223 | # no description trait, use __doc__ |
|
228 | # no description trait, use __doc__ | |
224 | desc = getattr(cls, '__doc__', '') |
|
229 | desc = getattr(cls, '__doc__', '') | |
225 | if desc: |
|
230 | if desc: | |
226 | lines.append(c(desc)) |
|
231 | lines.append(c(desc)) | |
227 | lines.append('') |
|
232 | lines.append('') | |
228 |
|
233 | |||
229 | parents = [] |
|
234 | parents = [] | |
230 | for parent in cls.mro(): |
|
235 | for parent in cls.mro(): | |
231 | # only include parents that are not base classes |
|
236 | # only include parents that are not base classes | |
232 | # and are not the class itself |
|
237 | # and are not the class itself | |
233 | # and have some configurable traits to inherit |
|
238 | # and have some configurable traits to inherit | |
234 | if parent is not cls and issubclass(parent, Configurable) and \ |
|
239 | if parent is not cls and issubclass(parent, Configurable) and \ | |
235 | parent.class_traits(config=True): |
|
240 | parent.class_traits(config=True): | |
236 | parents.append(parent) |
|
241 | parents.append(parent) | |
237 |
|
242 | |||
238 | if parents: |
|
243 | if parents: | |
239 | pstr = ', '.join([ p.__name__ for p in parents ]) |
|
244 | pstr = ', '.join([ p.__name__ for p in parents ]) | |
240 | lines.append(c('%s will inherit config from: %s'%(cls.__name__, pstr))) |
|
245 | lines.append(c('%s will inherit config from: %s'%(cls.__name__, pstr))) | |
241 | lines.append('') |
|
246 | lines.append('') | |
242 |
|
247 | |||
243 | for name,trait in cls.class_traits(config=True).iteritems(): |
|
248 | for name,trait in cls.class_traits(config=True).iteritems(): | |
244 | help = trait.get_metadata('help') or '' |
|
249 | help = trait.get_metadata('help') or '' | |
245 | lines.append(c(help)) |
|
250 | lines.append(c(help)) | |
246 | lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value())) |
|
251 | lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value())) | |
247 | lines.append('') |
|
252 | lines.append('') | |
248 | return '\n'.join(lines) |
|
253 | return '\n'.join(lines) | |
249 |
|
254 | |||
250 |
|
255 | |||
251 |
|
256 | |||
252 | class SingletonConfigurable(Configurable): |
|
257 | class SingletonConfigurable(Configurable): | |
253 | """A configurable that only allows one instance. |
|
258 | """A configurable that only allows one instance. | |
254 |
|
259 | |||
255 | This class is for classes that should only have one instance of itself |
|
260 | This class is for classes that should only have one instance of itself | |
256 | or *any* subclass. To create and retrieve such a class use the |
|
261 | or *any* subclass. To create and retrieve such a class use the | |
257 | :meth:`SingletonConfigurable.instance` method. |
|
262 | :meth:`SingletonConfigurable.instance` method. | |
258 | """ |
|
263 | """ | |
259 |
|
264 | |||
260 | _instance = None |
|
265 | _instance = None | |
261 |
|
266 | |||
262 | @classmethod |
|
267 | @classmethod | |
263 | def _walk_mro(cls): |
|
268 | def _walk_mro(cls): | |
264 | """Walk the cls.mro() for parent classes that are also singletons |
|
269 | """Walk the cls.mro() for parent classes that are also singletons | |
265 |
|
270 | |||
266 | For use in instance() |
|
271 | For use in instance() | |
267 | """ |
|
272 | """ | |
268 |
|
273 | |||
269 | for subclass in cls.mro(): |
|
274 | for subclass in cls.mro(): | |
270 | if issubclass(cls, subclass) and \ |
|
275 | if issubclass(cls, subclass) and \ | |
271 | issubclass(subclass, SingletonConfigurable) and \ |
|
276 | issubclass(subclass, SingletonConfigurable) and \ | |
272 | subclass != SingletonConfigurable: |
|
277 | subclass != SingletonConfigurable: | |
273 | yield subclass |
|
278 | yield subclass | |
274 |
|
279 | |||
275 | @classmethod |
|
280 | @classmethod | |
276 | def clear_instance(cls): |
|
281 | def clear_instance(cls): | |
277 | """unset _instance for this class and singleton parents. |
|
282 | """unset _instance for this class and singleton parents. | |
278 | """ |
|
283 | """ | |
279 | if not cls.initialized(): |
|
284 | if not cls.initialized(): | |
280 | return |
|
285 | return | |
281 | for subclass in cls._walk_mro(): |
|
286 | for subclass in cls._walk_mro(): | |
282 | if isinstance(subclass._instance, cls): |
|
287 | if isinstance(subclass._instance, cls): | |
283 | # only clear instances that are instances |
|
288 | # only clear instances that are instances | |
284 | # of the calling class |
|
289 | # of the calling class | |
285 | subclass._instance = None |
|
290 | subclass._instance = None | |
286 |
|
291 | |||
287 | @classmethod |
|
292 | @classmethod | |
288 | def instance(cls, *args, **kwargs): |
|
293 | def instance(cls, *args, **kwargs): | |
289 | """Returns a global instance of this class. |
|
294 | """Returns a global instance of this class. | |
290 |
|
295 | |||
291 | This method create a new instance if none have previously been created |
|
296 | This method create a new instance if none have previously been created | |
292 | and returns a previously created instance is one already exists. |
|
297 | and returns a previously created instance is one already exists. | |
293 |
|
298 | |||
294 | The arguments and keyword arguments passed to this method are passed |
|
299 | The arguments and keyword arguments passed to this method are passed | |
295 | on to the :meth:`__init__` method of the class upon instantiation. |
|
300 | on to the :meth:`__init__` method of the class upon instantiation. | |
296 |
|
301 | |||
297 | Examples |
|
302 | Examples | |
298 | -------- |
|
303 | -------- | |
299 |
|
304 | |||
300 | Create a singleton class using instance, and retrieve it:: |
|
305 | Create a singleton class using instance, and retrieve it:: | |
301 |
|
306 | |||
302 | >>> from IPython.config.configurable import SingletonConfigurable |
|
307 | >>> from IPython.config.configurable import SingletonConfigurable | |
303 | >>> class Foo(SingletonConfigurable): pass |
|
308 | >>> class Foo(SingletonConfigurable): pass | |
304 | >>> foo = Foo.instance() |
|
309 | >>> foo = Foo.instance() | |
305 | >>> foo == Foo.instance() |
|
310 | >>> foo == Foo.instance() | |
306 | True |
|
311 | True | |
307 |
|
312 | |||
308 | Create a subclass that is retrived using the base class instance:: |
|
313 | Create a subclass that is retrived using the base class instance:: | |
309 |
|
314 | |||
310 | >>> class Bar(SingletonConfigurable): pass |
|
315 | >>> class Bar(SingletonConfigurable): pass | |
311 | >>> class Bam(Bar): pass |
|
316 | >>> class Bam(Bar): pass | |
312 | >>> bam = Bam.instance() |
|
317 | >>> bam = Bam.instance() | |
313 | >>> bam == Bar.instance() |
|
318 | >>> bam == Bar.instance() | |
314 | True |
|
319 | True | |
315 | """ |
|
320 | """ | |
316 | # Create and save the instance |
|
321 | # Create and save the instance | |
317 | if cls._instance is None: |
|
322 | if cls._instance is None: | |
318 | inst = cls(*args, **kwargs) |
|
323 | inst = cls(*args, **kwargs) | |
319 | # Now make sure that the instance will also be returned by |
|
324 | # Now make sure that the instance will also be returned by | |
320 | # parent classes' _instance attribute. |
|
325 | # parent classes' _instance attribute. | |
321 | for subclass in cls._walk_mro(): |
|
326 | for subclass in cls._walk_mro(): | |
322 | subclass._instance = inst |
|
327 | subclass._instance = inst | |
323 |
|
328 | |||
324 | if isinstance(cls._instance, cls): |
|
329 | if isinstance(cls._instance, cls): | |
325 | return cls._instance |
|
330 | return cls._instance | |
326 | else: |
|
331 | else: | |
327 | raise MultipleInstanceError( |
|
332 | raise MultipleInstanceError( | |
328 | 'Multiple incompatible subclass instances of ' |
|
333 | 'Multiple incompatible subclass instances of ' | |
329 | '%s are being created.' % cls.__name__ |
|
334 | '%s are being created.' % cls.__name__ | |
330 | ) |
|
335 | ) | |
331 |
|
336 | |||
332 | @classmethod |
|
337 | @classmethod | |
333 | def initialized(cls): |
|
338 | def initialized(cls): | |
334 | """Has an instance been created?""" |
|
339 | """Has an instance been created?""" | |
335 | return hasattr(cls, "_instance") and cls._instance is not None |
|
340 | return hasattr(cls, "_instance") and cls._instance is not None | |
336 |
|
341 | |||
337 |
|
342 | |||
338 | class LoggingConfigurable(Configurable): |
|
343 | class LoggingConfigurable(Configurable): | |
339 | """A parent class for Configurables that log. |
|
344 | """A parent class for Configurables that log. | |
340 |
|
345 | |||
341 | Subclasses have a log trait, and the default behavior |
|
346 | Subclasses have a log trait, and the default behavior | |
342 | is to get the logger from the currently running Application |
|
347 | is to get the logger from the currently running Application | |
343 | via Application.instance().log. |
|
348 | via Application.instance().log. | |
344 | """ |
|
349 | """ | |
345 |
|
350 | |||
346 | log = Instance('logging.Logger') |
|
351 | log = Instance('logging.Logger') | |
347 | def _log_default(self): |
|
352 | def _log_default(self): | |
348 | from IPython.config.application import Application |
|
353 | from IPython.config.application import Application | |
349 | return Application.instance().log |
|
354 | return Application.instance().log | |
350 |
|
355 | |||
351 |
|
356 |
@@ -1,696 +1,701 b'' | |||||
1 | """A simple configuration system. |
|
1 | """A simple configuration system. | |
2 |
|
2 | |||
|
3 | Inheritance diagram: | |||
|
4 | ||||
|
5 | .. inheritance-diagram:: IPython.config.loader | |||
|
6 | :parts: 3 | |||
|
7 | ||||
3 | Authors |
|
8 | Authors | |
4 | ------- |
|
9 | ------- | |
5 | * Brian Granger |
|
10 | * Brian Granger | |
6 | * Fernando Perez |
|
11 | * Fernando Perez | |
7 | * Min RK |
|
12 | * Min RK | |
8 | """ |
|
13 | """ | |
9 |
|
14 | |||
10 | #----------------------------------------------------------------------------- |
|
15 | #----------------------------------------------------------------------------- | |
11 | # Copyright (C) 2008-2011 The IPython Development Team |
|
16 | # Copyright (C) 2008-2011 The IPython Development Team | |
12 | # |
|
17 | # | |
13 | # Distributed under the terms of the BSD License. The full license is in |
|
18 | # Distributed under the terms of the BSD License. The full license is in | |
14 | # the file COPYING, distributed as part of this software. |
|
19 | # the file COPYING, distributed as part of this software. | |
15 | #----------------------------------------------------------------------------- |
|
20 | #----------------------------------------------------------------------------- | |
16 |
|
21 | |||
17 | #----------------------------------------------------------------------------- |
|
22 | #----------------------------------------------------------------------------- | |
18 | # Imports |
|
23 | # Imports | |
19 | #----------------------------------------------------------------------------- |
|
24 | #----------------------------------------------------------------------------- | |
20 |
|
25 | |||
21 | import __builtin__ as builtin_mod |
|
26 | import __builtin__ as builtin_mod | |
22 | import os |
|
27 | import os | |
23 | import re |
|
28 | import re | |
24 | import sys |
|
29 | import sys | |
25 |
|
30 | |||
26 | from IPython.external import argparse |
|
31 | from IPython.external import argparse | |
27 | from IPython.utils.path import filefind, get_ipython_dir |
|
32 | from IPython.utils.path import filefind, get_ipython_dir | |
28 | from IPython.utils import py3compat, text, warn |
|
33 | from IPython.utils import py3compat, text, warn | |
29 | from IPython.utils.encoding import DEFAULT_ENCODING |
|
34 | from IPython.utils.encoding import DEFAULT_ENCODING | |
30 |
|
35 | |||
31 | #----------------------------------------------------------------------------- |
|
36 | #----------------------------------------------------------------------------- | |
32 | # Exceptions |
|
37 | # Exceptions | |
33 | #----------------------------------------------------------------------------- |
|
38 | #----------------------------------------------------------------------------- | |
34 |
|
39 | |||
35 |
|
40 | |||
36 | class ConfigError(Exception): |
|
41 | class ConfigError(Exception): | |
37 | pass |
|
42 | pass | |
38 |
|
43 | |||
39 | class ConfigLoaderError(ConfigError): |
|
44 | class ConfigLoaderError(ConfigError): | |
40 | pass |
|
45 | pass | |
41 |
|
46 | |||
42 | class ConfigFileNotFound(ConfigError): |
|
47 | class ConfigFileNotFound(ConfigError): | |
43 | pass |
|
48 | pass | |
44 |
|
49 | |||
45 | class ArgumentError(ConfigLoaderError): |
|
50 | class ArgumentError(ConfigLoaderError): | |
46 | pass |
|
51 | pass | |
47 |
|
52 | |||
48 | #----------------------------------------------------------------------------- |
|
53 | #----------------------------------------------------------------------------- | |
49 | # Argparse fix |
|
54 | # Argparse fix | |
50 | #----------------------------------------------------------------------------- |
|
55 | #----------------------------------------------------------------------------- | |
51 |
|
56 | |||
52 | # Unfortunately argparse by default prints help messages to stderr instead of |
|
57 | # Unfortunately argparse by default prints help messages to stderr instead of | |
53 | # stdout. This makes it annoying to capture long help screens at the command |
|
58 | # stdout. This makes it annoying to capture long help screens at the command | |
54 | # line, since one must know how to pipe stderr, which many users don't know how |
|
59 | # line, since one must know how to pipe stderr, which many users don't know how | |
55 | # to do. So we override the print_help method with one that defaults to |
|
60 | # to do. So we override the print_help method with one that defaults to | |
56 | # stdout and use our class instead. |
|
61 | # stdout and use our class instead. | |
57 |
|
62 | |||
58 | class ArgumentParser(argparse.ArgumentParser): |
|
63 | class ArgumentParser(argparse.ArgumentParser): | |
59 | """Simple argparse subclass that prints help to stdout by default.""" |
|
64 | """Simple argparse subclass that prints help to stdout by default.""" | |
60 |
|
65 | |||
61 | def print_help(self, file=None): |
|
66 | def print_help(self, file=None): | |
62 | if file is None: |
|
67 | if file is None: | |
63 | file = sys.stdout |
|
68 | file = sys.stdout | |
64 | return super(ArgumentParser, self).print_help(file) |
|
69 | return super(ArgumentParser, self).print_help(file) | |
65 |
|
70 | |||
66 | print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__ |
|
71 | print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__ | |
67 |
|
72 | |||
68 | #----------------------------------------------------------------------------- |
|
73 | #----------------------------------------------------------------------------- | |
69 | # Config class for holding config information |
|
74 | # Config class for holding config information | |
70 | #----------------------------------------------------------------------------- |
|
75 | #----------------------------------------------------------------------------- | |
71 |
|
76 | |||
72 |
|
77 | |||
73 | class Config(dict): |
|
78 | class Config(dict): | |
74 | """An attribute based dict that can do smart merges.""" |
|
79 | """An attribute based dict that can do smart merges.""" | |
75 |
|
80 | |||
76 | def __init__(self, *args, **kwds): |
|
81 | def __init__(self, *args, **kwds): | |
77 | dict.__init__(self, *args, **kwds) |
|
82 | dict.__init__(self, *args, **kwds) | |
78 | # This sets self.__dict__ = self, but it has to be done this way |
|
83 | # This sets self.__dict__ = self, but it has to be done this way | |
79 | # because we are also overriding __setattr__. |
|
84 | # because we are also overriding __setattr__. | |
80 | dict.__setattr__(self, '__dict__', self) |
|
85 | dict.__setattr__(self, '__dict__', self) | |
81 |
|
86 | |||
82 | def _merge(self, other): |
|
87 | def _merge(self, other): | |
83 | to_update = {} |
|
88 | to_update = {} | |
84 | for k, v in other.iteritems(): |
|
89 | for k, v in other.iteritems(): | |
85 | if k not in self: |
|
90 | if k not in self: | |
86 | to_update[k] = v |
|
91 | to_update[k] = v | |
87 | else: # I have this key |
|
92 | else: # I have this key | |
88 | if isinstance(v, Config): |
|
93 | if isinstance(v, Config): | |
89 | # Recursively merge common sub Configs |
|
94 | # Recursively merge common sub Configs | |
90 | self[k]._merge(v) |
|
95 | self[k]._merge(v) | |
91 | else: |
|
96 | else: | |
92 | # Plain updates for non-Configs |
|
97 | # Plain updates for non-Configs | |
93 | to_update[k] = v |
|
98 | to_update[k] = v | |
94 |
|
99 | |||
95 | self.update(to_update) |
|
100 | self.update(to_update) | |
96 |
|
101 | |||
97 | def _is_section_key(self, key): |
|
102 | def _is_section_key(self, key): | |
98 | if key[0].upper()==key[0] and not key.startswith('_'): |
|
103 | if key[0].upper()==key[0] and not key.startswith('_'): | |
99 | return True |
|
104 | return True | |
100 | else: |
|
105 | else: | |
101 | return False |
|
106 | return False | |
102 |
|
107 | |||
103 | def __contains__(self, key): |
|
108 | def __contains__(self, key): | |
104 | if self._is_section_key(key): |
|
109 | if self._is_section_key(key): | |
105 | return True |
|
110 | return True | |
106 | else: |
|
111 | else: | |
107 | return super(Config, self).__contains__(key) |
|
112 | return super(Config, self).__contains__(key) | |
108 | # .has_key is deprecated for dictionaries. |
|
113 | # .has_key is deprecated for dictionaries. | |
109 | has_key = __contains__ |
|
114 | has_key = __contains__ | |
110 |
|
115 | |||
111 | def _has_section(self, key): |
|
116 | def _has_section(self, key): | |
112 | if self._is_section_key(key): |
|
117 | if self._is_section_key(key): | |
113 | if super(Config, self).__contains__(key): |
|
118 | if super(Config, self).__contains__(key): | |
114 | return True |
|
119 | return True | |
115 | return False |
|
120 | return False | |
116 |
|
121 | |||
117 | def copy(self): |
|
122 | def copy(self): | |
118 | return type(self)(dict.copy(self)) |
|
123 | return type(self)(dict.copy(self)) | |
119 |
|
124 | |||
120 | def __copy__(self): |
|
125 | def __copy__(self): | |
121 | return self.copy() |
|
126 | return self.copy() | |
122 |
|
127 | |||
123 | def __deepcopy__(self, memo): |
|
128 | def __deepcopy__(self, memo): | |
124 | import copy |
|
129 | import copy | |
125 | return type(self)(copy.deepcopy(self.items())) |
|
130 | return type(self)(copy.deepcopy(self.items())) | |
126 |
|
131 | |||
127 | def __getitem__(self, key): |
|
132 | def __getitem__(self, key): | |
128 | # We cannot use directly self._is_section_key, because it triggers |
|
133 | # We cannot use directly self._is_section_key, because it triggers | |
129 | # infinite recursion on top of PyPy. Instead, we manually fish the |
|
134 | # infinite recursion on top of PyPy. Instead, we manually fish the | |
130 | # bound method. |
|
135 | # bound method. | |
131 | is_section_key = self.__class__._is_section_key.__get__(self) |
|
136 | is_section_key = self.__class__._is_section_key.__get__(self) | |
132 |
|
137 | |||
133 | # Because we use this for an exec namespace, we need to delegate |
|
138 | # Because we use this for an exec namespace, we need to delegate | |
134 | # the lookup of names in __builtin__ to itself. This means |
|
139 | # the lookup of names in __builtin__ to itself. This means | |
135 | # that you can't have section or attribute names that are |
|
140 | # that you can't have section or attribute names that are | |
136 | # builtins. |
|
141 | # builtins. | |
137 | try: |
|
142 | try: | |
138 | return getattr(builtin_mod, key) |
|
143 | return getattr(builtin_mod, key) | |
139 | except AttributeError: |
|
144 | except AttributeError: | |
140 | pass |
|
145 | pass | |
141 | if is_section_key(key): |
|
146 | if is_section_key(key): | |
142 | try: |
|
147 | try: | |
143 | return dict.__getitem__(self, key) |
|
148 | return dict.__getitem__(self, key) | |
144 | except KeyError: |
|
149 | except KeyError: | |
145 | c = Config() |
|
150 | c = Config() | |
146 | dict.__setitem__(self, key, c) |
|
151 | dict.__setitem__(self, key, c) | |
147 | return c |
|
152 | return c | |
148 | else: |
|
153 | else: | |
149 | return dict.__getitem__(self, key) |
|
154 | return dict.__getitem__(self, key) | |
150 |
|
155 | |||
151 | def __setitem__(self, key, value): |
|
156 | def __setitem__(self, key, value): | |
152 | # Don't allow names in __builtin__ to be modified. |
|
157 | # Don't allow names in __builtin__ to be modified. | |
153 | if hasattr(builtin_mod, key): |
|
158 | if hasattr(builtin_mod, key): | |
154 | raise ConfigError('Config variable names cannot have the same name ' |
|
159 | raise ConfigError('Config variable names cannot have the same name ' | |
155 | 'as a Python builtin: %s' % key) |
|
160 | 'as a Python builtin: %s' % key) | |
156 | if self._is_section_key(key): |
|
161 | if self._is_section_key(key): | |
157 | if not isinstance(value, Config): |
|
162 | if not isinstance(value, Config): | |
158 | raise ValueError('values whose keys begin with an uppercase ' |
|
163 | raise ValueError('values whose keys begin with an uppercase ' | |
159 | 'char must be Config instances: %r, %r' % (key, value)) |
|
164 | 'char must be Config instances: %r, %r' % (key, value)) | |
160 | else: |
|
165 | else: | |
161 | dict.__setitem__(self, key, value) |
|
166 | dict.__setitem__(self, key, value) | |
162 |
|
167 | |||
163 | def __getattr__(self, key): |
|
168 | def __getattr__(self, key): | |
164 | try: |
|
169 | try: | |
165 | return self.__getitem__(key) |
|
170 | return self.__getitem__(key) | |
166 | except KeyError as e: |
|
171 | except KeyError as e: | |
167 | raise AttributeError(e) |
|
172 | raise AttributeError(e) | |
168 |
|
173 | |||
169 | def __setattr__(self, key, value): |
|
174 | def __setattr__(self, key, value): | |
170 | try: |
|
175 | try: | |
171 | self.__setitem__(key, value) |
|
176 | self.__setitem__(key, value) | |
172 | except KeyError as e: |
|
177 | except KeyError as e: | |
173 | raise AttributeError(e) |
|
178 | raise AttributeError(e) | |
174 |
|
179 | |||
175 | def __delattr__(self, key): |
|
180 | def __delattr__(self, key): | |
176 | try: |
|
181 | try: | |
177 | dict.__delitem__(self, key) |
|
182 | dict.__delitem__(self, key) | |
178 | except KeyError as e: |
|
183 | except KeyError as e: | |
179 | raise AttributeError(e) |
|
184 | raise AttributeError(e) | |
180 |
|
185 | |||
181 |
|
186 | |||
182 | #----------------------------------------------------------------------------- |
|
187 | #----------------------------------------------------------------------------- | |
183 | # Config loading classes |
|
188 | # Config loading classes | |
184 | #----------------------------------------------------------------------------- |
|
189 | #----------------------------------------------------------------------------- | |
185 |
|
190 | |||
186 |
|
191 | |||
187 | class ConfigLoader(object): |
|
192 | class ConfigLoader(object): | |
188 | """A object for loading configurations from just about anywhere. |
|
193 | """A object for loading configurations from just about anywhere. | |
189 |
|
194 | |||
190 | The resulting configuration is packaged as a :class:`Struct`. |
|
195 | The resulting configuration is packaged as a :class:`Struct`. | |
191 |
|
196 | |||
192 | Notes |
|
197 | Notes | |
193 | ----- |
|
198 | ----- | |
194 | A :class:`ConfigLoader` does one thing: load a config from a source |
|
199 | A :class:`ConfigLoader` does one thing: load a config from a source | |
195 | (file, command line arguments) and returns the data as a :class:`Struct`. |
|
200 | (file, command line arguments) and returns the data as a :class:`Struct`. | |
196 | There are lots of things that :class:`ConfigLoader` does not do. It does |
|
201 | There are lots of things that :class:`ConfigLoader` does not do. It does | |
197 | not implement complex logic for finding config files. It does not handle |
|
202 | not implement complex logic for finding config files. It does not handle | |
198 | default values or merge multiple configs. These things need to be |
|
203 | default values or merge multiple configs. These things need to be | |
199 | handled elsewhere. |
|
204 | handled elsewhere. | |
200 | """ |
|
205 | """ | |
201 |
|
206 | |||
202 | def __init__(self): |
|
207 | def __init__(self): | |
203 | """A base class for config loaders. |
|
208 | """A base class for config loaders. | |
204 |
|
209 | |||
205 | Examples |
|
210 | Examples | |
206 | -------- |
|
211 | -------- | |
207 |
|
212 | |||
208 | >>> cl = ConfigLoader() |
|
213 | >>> cl = ConfigLoader() | |
209 | >>> config = cl.load_config() |
|
214 | >>> config = cl.load_config() | |
210 | >>> config |
|
215 | >>> config | |
211 | {} |
|
216 | {} | |
212 | """ |
|
217 | """ | |
213 | self.clear() |
|
218 | self.clear() | |
214 |
|
219 | |||
215 | def clear(self): |
|
220 | def clear(self): | |
216 | self.config = Config() |
|
221 | self.config = Config() | |
217 |
|
222 | |||
218 | def load_config(self): |
|
223 | def load_config(self): | |
219 | """Load a config from somewhere, return a :class:`Config` instance. |
|
224 | """Load a config from somewhere, return a :class:`Config` instance. | |
220 |
|
225 | |||
221 | Usually, this will cause self.config to be set and then returned. |
|
226 | Usually, this will cause self.config to be set and then returned. | |
222 | However, in most cases, :meth:`ConfigLoader.clear` should be called |
|
227 | However, in most cases, :meth:`ConfigLoader.clear` should be called | |
223 | to erase any previous state. |
|
228 | to erase any previous state. | |
224 | """ |
|
229 | """ | |
225 | self.clear() |
|
230 | self.clear() | |
226 | return self.config |
|
231 | return self.config | |
227 |
|
232 | |||
228 |
|
233 | |||
229 | class FileConfigLoader(ConfigLoader): |
|
234 | class FileConfigLoader(ConfigLoader): | |
230 | """A base class for file based configurations. |
|
235 | """A base class for file based configurations. | |
231 |
|
236 | |||
232 | As we add more file based config loaders, the common logic should go |
|
237 | As we add more file based config loaders, the common logic should go | |
233 | here. |
|
238 | here. | |
234 | """ |
|
239 | """ | |
235 | pass |
|
240 | pass | |
236 |
|
241 | |||
237 |
|
242 | |||
238 | class PyFileConfigLoader(FileConfigLoader): |
|
243 | class PyFileConfigLoader(FileConfigLoader): | |
239 | """A config loader for pure python files. |
|
244 | """A config loader for pure python files. | |
240 |
|
245 | |||
241 | This calls execfile on a plain python file and looks for attributes |
|
246 | This calls execfile on a plain python file and looks for attributes | |
242 | that are all caps. These attribute are added to the config Struct. |
|
247 | that are all caps. These attribute are added to the config Struct. | |
243 | """ |
|
248 | """ | |
244 |
|
249 | |||
245 | def __init__(self, filename, path=None): |
|
250 | def __init__(self, filename, path=None): | |
246 | """Build a config loader for a filename and path. |
|
251 | """Build a config loader for a filename and path. | |
247 |
|
252 | |||
248 | Parameters |
|
253 | Parameters | |
249 | ---------- |
|
254 | ---------- | |
250 | filename : str |
|
255 | filename : str | |
251 | The file name of the config file. |
|
256 | The file name of the config file. | |
252 | path : str, list, tuple |
|
257 | path : str, list, tuple | |
253 | The path to search for the config file on, or a sequence of |
|
258 | The path to search for the config file on, or a sequence of | |
254 | paths to try in order. |
|
259 | paths to try in order. | |
255 | """ |
|
260 | """ | |
256 | super(PyFileConfigLoader, self).__init__() |
|
261 | super(PyFileConfigLoader, self).__init__() | |
257 | self.filename = filename |
|
262 | self.filename = filename | |
258 | self.path = path |
|
263 | self.path = path | |
259 | self.full_filename = '' |
|
264 | self.full_filename = '' | |
260 | self.data = None |
|
265 | self.data = None | |
261 |
|
266 | |||
262 | def load_config(self): |
|
267 | def load_config(self): | |
263 | """Load the config from a file and return it as a Struct.""" |
|
268 | """Load the config from a file and return it as a Struct.""" | |
264 | self.clear() |
|
269 | self.clear() | |
265 | try: |
|
270 | try: | |
266 | self._find_file() |
|
271 | self._find_file() | |
267 | except IOError as e: |
|
272 | except IOError as e: | |
268 | raise ConfigFileNotFound(str(e)) |
|
273 | raise ConfigFileNotFound(str(e)) | |
269 | self._read_file_as_dict() |
|
274 | self._read_file_as_dict() | |
270 | self._convert_to_config() |
|
275 | self._convert_to_config() | |
271 | return self.config |
|
276 | return self.config | |
272 |
|
277 | |||
273 | def _find_file(self): |
|
278 | def _find_file(self): | |
274 | """Try to find the file by searching the paths.""" |
|
279 | """Try to find the file by searching the paths.""" | |
275 | self.full_filename = filefind(self.filename, self.path) |
|
280 | self.full_filename = filefind(self.filename, self.path) | |
276 |
|
281 | |||
277 | def _read_file_as_dict(self): |
|
282 | def _read_file_as_dict(self): | |
278 | """Load the config file into self.config, with recursive loading.""" |
|
283 | """Load the config file into self.config, with recursive loading.""" | |
279 | # This closure is made available in the namespace that is used |
|
284 | # This closure is made available in the namespace that is used | |
280 | # to exec the config file. It allows users to call |
|
285 | # to exec the config file. It allows users to call | |
281 | # load_subconfig('myconfig.py') to load config files recursively. |
|
286 | # load_subconfig('myconfig.py') to load config files recursively. | |
282 | # It needs to be a closure because it has references to self.path |
|
287 | # It needs to be a closure because it has references to self.path | |
283 | # and self.config. The sub-config is loaded with the same path |
|
288 | # and self.config. The sub-config is loaded with the same path | |
284 | # as the parent, but it uses an empty config which is then merged |
|
289 | # as the parent, but it uses an empty config which is then merged | |
285 | # with the parents. |
|
290 | # with the parents. | |
286 |
|
291 | |||
287 | # If a profile is specified, the config file will be loaded |
|
292 | # If a profile is specified, the config file will be loaded | |
288 | # from that profile |
|
293 | # from that profile | |
289 |
|
294 | |||
290 | def load_subconfig(fname, profile=None): |
|
295 | def load_subconfig(fname, profile=None): | |
291 | # import here to prevent circular imports |
|
296 | # import here to prevent circular imports | |
292 | from IPython.core.profiledir import ProfileDir, ProfileDirError |
|
297 | from IPython.core.profiledir import ProfileDir, ProfileDirError | |
293 | if profile is not None: |
|
298 | if profile is not None: | |
294 | try: |
|
299 | try: | |
295 | profile_dir = ProfileDir.find_profile_dir_by_name( |
|
300 | profile_dir = ProfileDir.find_profile_dir_by_name( | |
296 | get_ipython_dir(), |
|
301 | get_ipython_dir(), | |
297 | profile, |
|
302 | profile, | |
298 | ) |
|
303 | ) | |
299 | except ProfileDirError: |
|
304 | except ProfileDirError: | |
300 | return |
|
305 | return | |
301 | path = profile_dir.location |
|
306 | path = profile_dir.location | |
302 | else: |
|
307 | else: | |
303 | path = self.path |
|
308 | path = self.path | |
304 | loader = PyFileConfigLoader(fname, path) |
|
309 | loader = PyFileConfigLoader(fname, path) | |
305 | try: |
|
310 | try: | |
306 | sub_config = loader.load_config() |
|
311 | sub_config = loader.load_config() | |
307 | except ConfigFileNotFound: |
|
312 | except ConfigFileNotFound: | |
308 | # Pass silently if the sub config is not there. This happens |
|
313 | # Pass silently if the sub config is not there. This happens | |
309 | # when a user s using a profile, but not the default config. |
|
314 | # when a user s using a profile, but not the default config. | |
310 | pass |
|
315 | pass | |
311 | else: |
|
316 | else: | |
312 | self.config._merge(sub_config) |
|
317 | self.config._merge(sub_config) | |
313 |
|
318 | |||
314 | # Again, this needs to be a closure and should be used in config |
|
319 | # Again, this needs to be a closure and should be used in config | |
315 | # files to get the config being loaded. |
|
320 | # files to get the config being loaded. | |
316 | def get_config(): |
|
321 | def get_config(): | |
317 | return self.config |
|
322 | return self.config | |
318 |
|
323 | |||
319 | namespace = dict(load_subconfig=load_subconfig, get_config=get_config) |
|
324 | namespace = dict(load_subconfig=load_subconfig, get_config=get_config) | |
320 | fs_encoding = sys.getfilesystemencoding() or 'ascii' |
|
325 | fs_encoding = sys.getfilesystemencoding() or 'ascii' | |
321 | conf_filename = self.full_filename.encode(fs_encoding) |
|
326 | conf_filename = self.full_filename.encode(fs_encoding) | |
322 | py3compat.execfile(conf_filename, namespace) |
|
327 | py3compat.execfile(conf_filename, namespace) | |
323 |
|
328 | |||
324 | def _convert_to_config(self): |
|
329 | def _convert_to_config(self): | |
325 | if self.data is None: |
|
330 | if self.data is None: | |
326 | ConfigLoaderError('self.data does not exist') |
|
331 | ConfigLoaderError('self.data does not exist') | |
327 |
|
332 | |||
328 |
|
333 | |||
329 | class CommandLineConfigLoader(ConfigLoader): |
|
334 | class CommandLineConfigLoader(ConfigLoader): | |
330 | """A config loader for command line arguments. |
|
335 | """A config loader for command line arguments. | |
331 |
|
336 | |||
332 | As we add more command line based loaders, the common logic should go |
|
337 | As we add more command line based loaders, the common logic should go | |
333 | here. |
|
338 | here. | |
334 | """ |
|
339 | """ | |
335 |
|
340 | |||
336 | def _exec_config_str(self, lhs, rhs): |
|
341 | def _exec_config_str(self, lhs, rhs): | |
337 | """execute self.config.<lhs> = <rhs> |
|
342 | """execute self.config.<lhs> = <rhs> | |
338 |
|
343 | |||
339 | * expands ~ with expanduser |
|
344 | * expands ~ with expanduser | |
340 | * tries to assign with raw eval, otherwise assigns with just the string, |
|
345 | * tries to assign with raw eval, otherwise assigns with just the string, | |
341 | allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not* |
|
346 | allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not* | |
342 | equivalent are `--C.a=4` and `--C.a='4'`. |
|
347 | equivalent are `--C.a=4` and `--C.a='4'`. | |
343 | """ |
|
348 | """ | |
344 | rhs = os.path.expanduser(rhs) |
|
349 | rhs = os.path.expanduser(rhs) | |
345 | try: |
|
350 | try: | |
346 | # Try to see if regular Python syntax will work. This |
|
351 | # Try to see if regular Python syntax will work. This | |
347 | # won't handle strings as the quote marks are removed |
|
352 | # won't handle strings as the quote marks are removed | |
348 | # by the system shell. |
|
353 | # by the system shell. | |
349 | value = eval(rhs) |
|
354 | value = eval(rhs) | |
350 | except (NameError, SyntaxError): |
|
355 | except (NameError, SyntaxError): | |
351 | # This case happens if the rhs is a string. |
|
356 | # This case happens if the rhs is a string. | |
352 | value = rhs |
|
357 | value = rhs | |
353 |
|
358 | |||
354 | exec u'self.config.%s = value' % lhs |
|
359 | exec u'self.config.%s = value' % lhs | |
355 |
|
360 | |||
356 | def _load_flag(self, cfg): |
|
361 | def _load_flag(self, cfg): | |
357 | """update self.config from a flag, which can be a dict or Config""" |
|
362 | """update self.config from a flag, which can be a dict or Config""" | |
358 | if isinstance(cfg, (dict, Config)): |
|
363 | if isinstance(cfg, (dict, Config)): | |
359 | # don't clobber whole config sections, update |
|
364 | # don't clobber whole config sections, update | |
360 | # each section from config: |
|
365 | # each section from config: | |
361 | for sec,c in cfg.iteritems(): |
|
366 | for sec,c in cfg.iteritems(): | |
362 | self.config[sec].update(c) |
|
367 | self.config[sec].update(c) | |
363 | else: |
|
368 | else: | |
364 | raise TypeError("Invalid flag: %r" % cfg) |
|
369 | raise TypeError("Invalid flag: %r" % cfg) | |
365 |
|
370 | |||
366 | # raw --identifier=value pattern |
|
371 | # raw --identifier=value pattern | |
367 | # but *also* accept '-' as wordsep, for aliases |
|
372 | # but *also* accept '-' as wordsep, for aliases | |
368 | # accepts: --foo=a |
|
373 | # accepts: --foo=a | |
369 | # --Class.trait=value |
|
374 | # --Class.trait=value | |
370 | # --alias-name=value |
|
375 | # --alias-name=value | |
371 | # rejects: -foo=value |
|
376 | # rejects: -foo=value | |
372 | # --foo |
|
377 | # --foo | |
373 | # --Class.trait |
|
378 | # --Class.trait | |
374 | kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*') |
|
379 | kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*') | |
375 |
|
380 | |||
376 | # just flags, no assignments, with two *or one* leading '-' |
|
381 | # just flags, no assignments, with two *or one* leading '-' | |
377 | # accepts: --foo |
|
382 | # accepts: --foo | |
378 | # -foo-bar-again |
|
383 | # -foo-bar-again | |
379 | # rejects: --anything=anything |
|
384 | # rejects: --anything=anything | |
380 | # --two.word |
|
385 | # --two.word | |
381 |
|
386 | |||
382 | flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$') |
|
387 | flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$') | |
383 |
|
388 | |||
384 | class KeyValueConfigLoader(CommandLineConfigLoader): |
|
389 | class KeyValueConfigLoader(CommandLineConfigLoader): | |
385 | """A config loader that loads key value pairs from the command line. |
|
390 | """A config loader that loads key value pairs from the command line. | |
386 |
|
391 | |||
387 | This allows command line options to be gives in the following form:: |
|
392 | This allows command line options to be gives in the following form:: | |
388 |
|
393 | |||
389 | ipython --profile="foo" --InteractiveShell.autocall=False |
|
394 | ipython --profile="foo" --InteractiveShell.autocall=False | |
390 | """ |
|
395 | """ | |
391 |
|
396 | |||
392 | def __init__(self, argv=None, aliases=None, flags=None): |
|
397 | def __init__(self, argv=None, aliases=None, flags=None): | |
393 | """Create a key value pair config loader. |
|
398 | """Create a key value pair config loader. | |
394 |
|
399 | |||
395 | Parameters |
|
400 | Parameters | |
396 | ---------- |
|
401 | ---------- | |
397 | argv : list |
|
402 | argv : list | |
398 | A list that has the form of sys.argv[1:] which has unicode |
|
403 | A list that has the form of sys.argv[1:] which has unicode | |
399 | elements of the form u"key=value". If this is None (default), |
|
404 | elements of the form u"key=value". If this is None (default), | |
400 | then sys.argv[1:] will be used. |
|
405 | then sys.argv[1:] will be used. | |
401 | aliases : dict |
|
406 | aliases : dict | |
402 | A dict of aliases for configurable traits. |
|
407 | A dict of aliases for configurable traits. | |
403 | Keys are the short aliases, Values are the resolved trait. |
|
408 | Keys are the short aliases, Values are the resolved trait. | |
404 | Of the form: `{'alias' : 'Configurable.trait'}` |
|
409 | Of the form: `{'alias' : 'Configurable.trait'}` | |
405 | flags : dict |
|
410 | flags : dict | |
406 | A dict of flags, keyed by str name. Vaues can be Config objects, |
|
411 | A dict of flags, keyed by str name. Vaues can be Config objects, | |
407 | dicts, or "key=value" strings. If Config or dict, when the flag |
|
412 | dicts, or "key=value" strings. If Config or dict, when the flag | |
408 | is triggered, The flag is loaded as `self.config.update(m)`. |
|
413 | is triggered, The flag is loaded as `self.config.update(m)`. | |
409 |
|
414 | |||
410 | Returns |
|
415 | Returns | |
411 | ------- |
|
416 | ------- | |
412 | config : Config |
|
417 | config : Config | |
413 | The resulting Config object. |
|
418 | The resulting Config object. | |
414 |
|
419 | |||
415 | Examples |
|
420 | Examples | |
416 | -------- |
|
421 | -------- | |
417 |
|
422 | |||
418 | >>> from IPython.config.loader import KeyValueConfigLoader |
|
423 | >>> from IPython.config.loader import KeyValueConfigLoader | |
419 | >>> cl = KeyValueConfigLoader() |
|
424 | >>> cl = KeyValueConfigLoader() | |
420 | >>> d = cl.load_config(["--A.name='brian'","--B.number=0"]) |
|
425 | >>> d = cl.load_config(["--A.name='brian'","--B.number=0"]) | |
421 | >>> sorted(d.items()) |
|
426 | >>> sorted(d.items()) | |
422 | [('A', {'name': 'brian'}), ('B', {'number': 0})] |
|
427 | [('A', {'name': 'brian'}), ('B', {'number': 0})] | |
423 | """ |
|
428 | """ | |
424 | self.clear() |
|
429 | self.clear() | |
425 | if argv is None: |
|
430 | if argv is None: | |
426 | argv = sys.argv[1:] |
|
431 | argv = sys.argv[1:] | |
427 | self.argv = argv |
|
432 | self.argv = argv | |
428 | self.aliases = aliases or {} |
|
433 | self.aliases = aliases or {} | |
429 | self.flags = flags or {} |
|
434 | self.flags = flags or {} | |
430 |
|
435 | |||
431 |
|
436 | |||
432 | def clear(self): |
|
437 | def clear(self): | |
433 | super(KeyValueConfigLoader, self).clear() |
|
438 | super(KeyValueConfigLoader, self).clear() | |
434 | self.extra_args = [] |
|
439 | self.extra_args = [] | |
435 |
|
440 | |||
436 |
|
441 | |||
437 | def _decode_argv(self, argv, enc=None): |
|
442 | def _decode_argv(self, argv, enc=None): | |
438 | """decode argv if bytes, using stin.encoding, falling back on default enc""" |
|
443 | """decode argv if bytes, using stin.encoding, falling back on default enc""" | |
439 | uargv = [] |
|
444 | uargv = [] | |
440 | if enc is None: |
|
445 | if enc is None: | |
441 | enc = DEFAULT_ENCODING |
|
446 | enc = DEFAULT_ENCODING | |
442 | for arg in argv: |
|
447 | for arg in argv: | |
443 | if not isinstance(arg, unicode): |
|
448 | if not isinstance(arg, unicode): | |
444 | # only decode if not already decoded |
|
449 | # only decode if not already decoded | |
445 | arg = arg.decode(enc) |
|
450 | arg = arg.decode(enc) | |
446 | uargv.append(arg) |
|
451 | uargv.append(arg) | |
447 | return uargv |
|
452 | return uargv | |
448 |
|
453 | |||
449 |
|
454 | |||
450 | def load_config(self, argv=None, aliases=None, flags=None): |
|
455 | def load_config(self, argv=None, aliases=None, flags=None): | |
451 | """Parse the configuration and generate the Config object. |
|
456 | """Parse the configuration and generate the Config object. | |
452 |
|
457 | |||
453 | After loading, any arguments that are not key-value or |
|
458 | After loading, any arguments that are not key-value or | |
454 | flags will be stored in self.extra_args - a list of |
|
459 | flags will be stored in self.extra_args - a list of | |
455 | unparsed command-line arguments. This is used for |
|
460 | unparsed command-line arguments. This is used for | |
456 | arguments such as input files or subcommands. |
|
461 | arguments such as input files or subcommands. | |
457 |
|
462 | |||
458 | Parameters |
|
463 | Parameters | |
459 | ---------- |
|
464 | ---------- | |
460 | argv : list, optional |
|
465 | argv : list, optional | |
461 | A list that has the form of sys.argv[1:] which has unicode |
|
466 | A list that has the form of sys.argv[1:] which has unicode | |
462 | elements of the form u"key=value". If this is None (default), |
|
467 | elements of the form u"key=value". If this is None (default), | |
463 | then self.argv will be used. |
|
468 | then self.argv will be used. | |
464 | aliases : dict |
|
469 | aliases : dict | |
465 | A dict of aliases for configurable traits. |
|
470 | A dict of aliases for configurable traits. | |
466 | Keys are the short aliases, Values are the resolved trait. |
|
471 | Keys are the short aliases, Values are the resolved trait. | |
467 | Of the form: `{'alias' : 'Configurable.trait'}` |
|
472 | Of the form: `{'alias' : 'Configurable.trait'}` | |
468 | flags : dict |
|
473 | flags : dict | |
469 | A dict of flags, keyed by str name. Values can be Config objects |
|
474 | A dict of flags, keyed by str name. Values can be Config objects | |
470 | or dicts. When the flag is triggered, The config is loaded as |
|
475 | or dicts. When the flag is triggered, The config is loaded as | |
471 | `self.config.update(cfg)`. |
|
476 | `self.config.update(cfg)`. | |
472 | """ |
|
477 | """ | |
473 | from IPython.config.configurable import Configurable |
|
478 | from IPython.config.configurable import Configurable | |
474 |
|
479 | |||
475 | self.clear() |
|
480 | self.clear() | |
476 | if argv is None: |
|
481 | if argv is None: | |
477 | argv = self.argv |
|
482 | argv = self.argv | |
478 | if aliases is None: |
|
483 | if aliases is None: | |
479 | aliases = self.aliases |
|
484 | aliases = self.aliases | |
480 | if flags is None: |
|
485 | if flags is None: | |
481 | flags = self.flags |
|
486 | flags = self.flags | |
482 |
|
487 | |||
483 | # ensure argv is a list of unicode strings: |
|
488 | # ensure argv is a list of unicode strings: | |
484 | uargv = self._decode_argv(argv) |
|
489 | uargv = self._decode_argv(argv) | |
485 | for idx,raw in enumerate(uargv): |
|
490 | for idx,raw in enumerate(uargv): | |
486 | # strip leading '-' |
|
491 | # strip leading '-' | |
487 | item = raw.lstrip('-') |
|
492 | item = raw.lstrip('-') | |
488 |
|
493 | |||
489 | if raw == '--': |
|
494 | if raw == '--': | |
490 | # don't parse arguments after '--' |
|
495 | # don't parse arguments after '--' | |
491 | # this is useful for relaying arguments to scripts, e.g. |
|
496 | # this is useful for relaying arguments to scripts, e.g. | |
492 | # ipython -i foo.py --pylab=qt -- args after '--' go-to-foo.py |
|
497 | # ipython -i foo.py --pylab=qt -- args after '--' go-to-foo.py | |
493 | self.extra_args.extend(uargv[idx+1:]) |
|
498 | self.extra_args.extend(uargv[idx+1:]) | |
494 | break |
|
499 | break | |
495 |
|
500 | |||
496 | if kv_pattern.match(raw): |
|
501 | if kv_pattern.match(raw): | |
497 | lhs,rhs = item.split('=',1) |
|
502 | lhs,rhs = item.split('=',1) | |
498 | # Substitute longnames for aliases. |
|
503 | # Substitute longnames for aliases. | |
499 | if lhs in aliases: |
|
504 | if lhs in aliases: | |
500 | lhs = aliases[lhs] |
|
505 | lhs = aliases[lhs] | |
501 | if '.' not in lhs: |
|
506 | if '.' not in lhs: | |
502 | # probably a mistyped alias, but not technically illegal |
|
507 | # probably a mistyped alias, but not technically illegal | |
503 | warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs) |
|
508 | warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs) | |
504 | try: |
|
509 | try: | |
505 | self._exec_config_str(lhs, rhs) |
|
510 | self._exec_config_str(lhs, rhs) | |
506 | except Exception: |
|
511 | except Exception: | |
507 | raise ArgumentError("Invalid argument: '%s'" % raw) |
|
512 | raise ArgumentError("Invalid argument: '%s'" % raw) | |
508 |
|
513 | |||
509 | elif flag_pattern.match(raw): |
|
514 | elif flag_pattern.match(raw): | |
510 | if item in flags: |
|
515 | if item in flags: | |
511 | cfg,help = flags[item] |
|
516 | cfg,help = flags[item] | |
512 | self._load_flag(cfg) |
|
517 | self._load_flag(cfg) | |
513 | else: |
|
518 | else: | |
514 | raise ArgumentError("Unrecognized flag: '%s'"%raw) |
|
519 | raise ArgumentError("Unrecognized flag: '%s'"%raw) | |
515 | elif raw.startswith('-'): |
|
520 | elif raw.startswith('-'): | |
516 | kv = '--'+item |
|
521 | kv = '--'+item | |
517 | if kv_pattern.match(kv): |
|
522 | if kv_pattern.match(kv): | |
518 | raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv)) |
|
523 | raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv)) | |
519 | else: |
|
524 | else: | |
520 | raise ArgumentError("Invalid argument: '%s'"%raw) |
|
525 | raise ArgumentError("Invalid argument: '%s'"%raw) | |
521 | else: |
|
526 | else: | |
522 | # keep all args that aren't valid in a list, |
|
527 | # keep all args that aren't valid in a list, | |
523 | # in case our parent knows what to do with them. |
|
528 | # in case our parent knows what to do with them. | |
524 | self.extra_args.append(item) |
|
529 | self.extra_args.append(item) | |
525 | return self.config |
|
530 | return self.config | |
526 |
|
531 | |||
527 | class ArgParseConfigLoader(CommandLineConfigLoader): |
|
532 | class ArgParseConfigLoader(CommandLineConfigLoader): | |
528 | """A loader that uses the argparse module to load from the command line.""" |
|
533 | """A loader that uses the argparse module to load from the command line.""" | |
529 |
|
534 | |||
530 | def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw): |
|
535 | def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw): | |
531 | """Create a config loader for use with argparse. |
|
536 | """Create a config loader for use with argparse. | |
532 |
|
537 | |||
533 | Parameters |
|
538 | Parameters | |
534 | ---------- |
|
539 | ---------- | |
535 |
|
540 | |||
536 | argv : optional, list |
|
541 | argv : optional, list | |
537 | If given, used to read command-line arguments from, otherwise |
|
542 | If given, used to read command-line arguments from, otherwise | |
538 | sys.argv[1:] is used. |
|
543 | sys.argv[1:] is used. | |
539 |
|
544 | |||
540 | parser_args : tuple |
|
545 | parser_args : tuple | |
541 | A tuple of positional arguments that will be passed to the |
|
546 | A tuple of positional arguments that will be passed to the | |
542 | constructor of :class:`argparse.ArgumentParser`. |
|
547 | constructor of :class:`argparse.ArgumentParser`. | |
543 |
|
548 | |||
544 | parser_kw : dict |
|
549 | parser_kw : dict | |
545 | A tuple of keyword arguments that will be passed to the |
|
550 | A tuple of keyword arguments that will be passed to the | |
546 | constructor of :class:`argparse.ArgumentParser`. |
|
551 | constructor of :class:`argparse.ArgumentParser`. | |
547 |
|
552 | |||
548 | Returns |
|
553 | Returns | |
549 | ------- |
|
554 | ------- | |
550 | config : Config |
|
555 | config : Config | |
551 | The resulting Config object. |
|
556 | The resulting Config object. | |
552 | """ |
|
557 | """ | |
553 | super(CommandLineConfigLoader, self).__init__() |
|
558 | super(CommandLineConfigLoader, self).__init__() | |
554 | self.clear() |
|
559 | self.clear() | |
555 | if argv is None: |
|
560 | if argv is None: | |
556 | argv = sys.argv[1:] |
|
561 | argv = sys.argv[1:] | |
557 | self.argv = argv |
|
562 | self.argv = argv | |
558 | self.aliases = aliases or {} |
|
563 | self.aliases = aliases or {} | |
559 | self.flags = flags or {} |
|
564 | self.flags = flags or {} | |
560 |
|
565 | |||
561 | self.parser_args = parser_args |
|
566 | self.parser_args = parser_args | |
562 | self.version = parser_kw.pop("version", None) |
|
567 | self.version = parser_kw.pop("version", None) | |
563 | kwargs = dict(argument_default=argparse.SUPPRESS) |
|
568 | kwargs = dict(argument_default=argparse.SUPPRESS) | |
564 | kwargs.update(parser_kw) |
|
569 | kwargs.update(parser_kw) | |
565 | self.parser_kw = kwargs |
|
570 | self.parser_kw = kwargs | |
566 |
|
571 | |||
567 | def load_config(self, argv=None, aliases=None, flags=None): |
|
572 | def load_config(self, argv=None, aliases=None, flags=None): | |
568 | """Parse command line arguments and return as a Config object. |
|
573 | """Parse command line arguments and return as a Config object. | |
569 |
|
574 | |||
570 | Parameters |
|
575 | Parameters | |
571 | ---------- |
|
576 | ---------- | |
572 |
|
577 | |||
573 | args : optional, list |
|
578 | args : optional, list | |
574 | If given, a list with the structure of sys.argv[1:] to parse |
|
579 | If given, a list with the structure of sys.argv[1:] to parse | |
575 | arguments from. If not given, the instance's self.argv attribute |
|
580 | arguments from. If not given, the instance's self.argv attribute | |
576 | (given at construction time) is used.""" |
|
581 | (given at construction time) is used.""" | |
577 | self.clear() |
|
582 | self.clear() | |
578 | if argv is None: |
|
583 | if argv is None: | |
579 | argv = self.argv |
|
584 | argv = self.argv | |
580 | if aliases is None: |
|
585 | if aliases is None: | |
581 | aliases = self.aliases |
|
586 | aliases = self.aliases | |
582 | if flags is None: |
|
587 | if flags is None: | |
583 | flags = self.flags |
|
588 | flags = self.flags | |
584 | self._create_parser(aliases, flags) |
|
589 | self._create_parser(aliases, flags) | |
585 | self._parse_args(argv) |
|
590 | self._parse_args(argv) | |
586 | self._convert_to_config() |
|
591 | self._convert_to_config() | |
587 | return self.config |
|
592 | return self.config | |
588 |
|
593 | |||
589 | def get_extra_args(self): |
|
594 | def get_extra_args(self): | |
590 | if hasattr(self, 'extra_args'): |
|
595 | if hasattr(self, 'extra_args'): | |
591 | return self.extra_args |
|
596 | return self.extra_args | |
592 | else: |
|
597 | else: | |
593 | return [] |
|
598 | return [] | |
594 |
|
599 | |||
595 | def _create_parser(self, aliases=None, flags=None): |
|
600 | def _create_parser(self, aliases=None, flags=None): | |
596 | self.parser = ArgumentParser(*self.parser_args, **self.parser_kw) |
|
601 | self.parser = ArgumentParser(*self.parser_args, **self.parser_kw) | |
597 | self._add_arguments(aliases, flags) |
|
602 | self._add_arguments(aliases, flags) | |
598 |
|
603 | |||
599 | def _add_arguments(self, aliases=None, flags=None): |
|
604 | def _add_arguments(self, aliases=None, flags=None): | |
600 | raise NotImplementedError("subclasses must implement _add_arguments") |
|
605 | raise NotImplementedError("subclasses must implement _add_arguments") | |
601 |
|
606 | |||
602 | def _parse_args(self, args): |
|
607 | def _parse_args(self, args): | |
603 | """self.parser->self.parsed_data""" |
|
608 | """self.parser->self.parsed_data""" | |
604 | # decode sys.argv to support unicode command-line options |
|
609 | # decode sys.argv to support unicode command-line options | |
605 | enc = DEFAULT_ENCODING |
|
610 | enc = DEFAULT_ENCODING | |
606 | uargs = [py3compat.cast_unicode(a, enc) for a in args] |
|
611 | uargs = [py3compat.cast_unicode(a, enc) for a in args] | |
607 | self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs) |
|
612 | self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs) | |
608 |
|
613 | |||
609 | def _convert_to_config(self): |
|
614 | def _convert_to_config(self): | |
610 | """self.parsed_data->self.config""" |
|
615 | """self.parsed_data->self.config""" | |
611 | for k, v in vars(self.parsed_data).iteritems(): |
|
616 | for k, v in vars(self.parsed_data).iteritems(): | |
612 | exec "self.config.%s = v"%k in locals(), globals() |
|
617 | exec "self.config.%s = v"%k in locals(), globals() | |
613 |
|
618 | |||
614 | class KVArgParseConfigLoader(ArgParseConfigLoader): |
|
619 | class KVArgParseConfigLoader(ArgParseConfigLoader): | |
615 | """A config loader that loads aliases and flags with argparse, |
|
620 | """A config loader that loads aliases and flags with argparse, | |
616 | but will use KVLoader for the rest. This allows better parsing |
|
621 | but will use KVLoader for the rest. This allows better parsing | |
617 | of common args, such as `ipython -c 'print 5'`, but still gets |
|
622 | of common args, such as `ipython -c 'print 5'`, but still gets | |
618 | arbitrary config with `ipython --InteractiveShell.use_readline=False`""" |
|
623 | arbitrary config with `ipython --InteractiveShell.use_readline=False`""" | |
619 |
|
624 | |||
620 | def _add_arguments(self, aliases=None, flags=None): |
|
625 | def _add_arguments(self, aliases=None, flags=None): | |
621 | self.alias_flags = {} |
|
626 | self.alias_flags = {} | |
622 | # print aliases, flags |
|
627 | # print aliases, flags | |
623 | if aliases is None: |
|
628 | if aliases is None: | |
624 | aliases = self.aliases |
|
629 | aliases = self.aliases | |
625 | if flags is None: |
|
630 | if flags is None: | |
626 | flags = self.flags |
|
631 | flags = self.flags | |
627 | paa = self.parser.add_argument |
|
632 | paa = self.parser.add_argument | |
628 | for key,value in aliases.iteritems(): |
|
633 | for key,value in aliases.iteritems(): | |
629 | if key in flags: |
|
634 | if key in flags: | |
630 | # flags |
|
635 | # flags | |
631 | nargs = '?' |
|
636 | nargs = '?' | |
632 | else: |
|
637 | else: | |
633 | nargs = None |
|
638 | nargs = None | |
634 | if len(key) is 1: |
|
639 | if len(key) is 1: | |
635 | paa('-'+key, '--'+key, type=unicode, dest=value, nargs=nargs) |
|
640 | paa('-'+key, '--'+key, type=unicode, dest=value, nargs=nargs) | |
636 | else: |
|
641 | else: | |
637 | paa('--'+key, type=unicode, dest=value, nargs=nargs) |
|
642 | paa('--'+key, type=unicode, dest=value, nargs=nargs) | |
638 | for key, (value, help) in flags.iteritems(): |
|
643 | for key, (value, help) in flags.iteritems(): | |
639 | if key in self.aliases: |
|
644 | if key in self.aliases: | |
640 | # |
|
645 | # | |
641 | self.alias_flags[self.aliases[key]] = value |
|
646 | self.alias_flags[self.aliases[key]] = value | |
642 | continue |
|
647 | continue | |
643 | if len(key) is 1: |
|
648 | if len(key) is 1: | |
644 | paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value) |
|
649 | paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value) | |
645 | else: |
|
650 | else: | |
646 | paa('--'+key, action='append_const', dest='_flags', const=value) |
|
651 | paa('--'+key, action='append_const', dest='_flags', const=value) | |
647 |
|
652 | |||
648 | def _convert_to_config(self): |
|
653 | def _convert_to_config(self): | |
649 | """self.parsed_data->self.config, parse unrecognized extra args via KVLoader.""" |
|
654 | """self.parsed_data->self.config, parse unrecognized extra args via KVLoader.""" | |
650 | # remove subconfigs list from namespace before transforming the Namespace |
|
655 | # remove subconfigs list from namespace before transforming the Namespace | |
651 | if '_flags' in self.parsed_data: |
|
656 | if '_flags' in self.parsed_data: | |
652 | subcs = self.parsed_data._flags |
|
657 | subcs = self.parsed_data._flags | |
653 | del self.parsed_data._flags |
|
658 | del self.parsed_data._flags | |
654 | else: |
|
659 | else: | |
655 | subcs = [] |
|
660 | subcs = [] | |
656 |
|
661 | |||
657 | for k, v in vars(self.parsed_data).iteritems(): |
|
662 | for k, v in vars(self.parsed_data).iteritems(): | |
658 | if v is None: |
|
663 | if v is None: | |
659 | # it was a flag that shares the name of an alias |
|
664 | # it was a flag that shares the name of an alias | |
660 | subcs.append(self.alias_flags[k]) |
|
665 | subcs.append(self.alias_flags[k]) | |
661 | else: |
|
666 | else: | |
662 | # eval the KV assignment |
|
667 | # eval the KV assignment | |
663 | self._exec_config_str(k, v) |
|
668 | self._exec_config_str(k, v) | |
664 |
|
669 | |||
665 | for subc in subcs: |
|
670 | for subc in subcs: | |
666 | self._load_flag(subc) |
|
671 | self._load_flag(subc) | |
667 |
|
672 | |||
668 | if self.extra_args: |
|
673 | if self.extra_args: | |
669 | sub_parser = KeyValueConfigLoader() |
|
674 | sub_parser = KeyValueConfigLoader() | |
670 | sub_parser.load_config(self.extra_args) |
|
675 | sub_parser.load_config(self.extra_args) | |
671 | self.config._merge(sub_parser.config) |
|
676 | self.config._merge(sub_parser.config) | |
672 | self.extra_args = sub_parser.extra_args |
|
677 | self.extra_args = sub_parser.extra_args | |
673 |
|
678 | |||
674 |
|
679 | |||
675 | def load_pyconfig_files(config_files, path): |
|
680 | def load_pyconfig_files(config_files, path): | |
676 | """Load multiple Python config files, merging each of them in turn. |
|
681 | """Load multiple Python config files, merging each of them in turn. | |
677 |
|
682 | |||
678 | Parameters |
|
683 | Parameters | |
679 | ========== |
|
684 | ========== | |
680 | config_files : list of str |
|
685 | config_files : list of str | |
681 | List of config files names to load and merge into the config. |
|
686 | List of config files names to load and merge into the config. | |
682 | path : unicode |
|
687 | path : unicode | |
683 | The full path to the location of the config files. |
|
688 | The full path to the location of the config files. | |
684 | """ |
|
689 | """ | |
685 | config = Config() |
|
690 | config = Config() | |
686 | for cf in config_files: |
|
691 | for cf in config_files: | |
687 | loader = PyFileConfigLoader(cf, path=path) |
|
692 | loader = PyFileConfigLoader(cf, path=path) | |
688 | try: |
|
693 | try: | |
689 | next_config = loader.load_config() |
|
694 | next_config = loader.load_config() | |
690 | except ConfigFileNotFound: |
|
695 | except ConfigFileNotFound: | |
691 | pass |
|
696 | pass | |
692 | except: |
|
697 | except: | |
693 | raise |
|
698 | raise | |
694 | else: |
|
699 | else: | |
695 | config._merge(next_config) |
|
700 | config._merge(next_config) | |
696 | return config |
|
701 | return config |
@@ -1,621 +1,625 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """Display formatters. |
|
2 | """Display formatters. | |
3 |
|
3 | |||
|
4 | Inheritance diagram: | |||
|
5 | ||||
|
6 | .. inheritance-diagram:: IPython.core.formatters | |||
|
7 | :parts: 3 | |||
4 |
|
8 | |||
5 | Authors: |
|
9 | Authors: | |
6 |
|
10 | |||
7 | * Robert Kern |
|
11 | * Robert Kern | |
8 | * Brian Granger |
|
12 | * Brian Granger | |
9 | """ |
|
13 | """ | |
10 | #----------------------------------------------------------------------------- |
|
14 | #----------------------------------------------------------------------------- | |
11 | # Copyright (C) 2010-2011, IPython Development Team. |
|
15 | # Copyright (C) 2010-2011, IPython Development Team. | |
12 | # |
|
16 | # | |
13 | # Distributed under the terms of the Modified BSD License. |
|
17 | # Distributed under the terms of the Modified BSD License. | |
14 | # |
|
18 | # | |
15 | # The full license is in the file COPYING.txt, distributed with this software. |
|
19 | # The full license is in the file COPYING.txt, distributed with this software. | |
16 | #----------------------------------------------------------------------------- |
|
20 | #----------------------------------------------------------------------------- | |
17 |
|
21 | |||
18 | #----------------------------------------------------------------------------- |
|
22 | #----------------------------------------------------------------------------- | |
19 | # Imports |
|
23 | # Imports | |
20 | #----------------------------------------------------------------------------- |
|
24 | #----------------------------------------------------------------------------- | |
21 |
|
25 | |||
22 | # Stdlib imports |
|
26 | # Stdlib imports | |
23 | import abc |
|
27 | import abc | |
24 | import sys |
|
28 | import sys | |
25 | # We must use StringIO, as cStringIO doesn't handle unicode properly. |
|
29 | # We must use StringIO, as cStringIO doesn't handle unicode properly. | |
26 | from StringIO import StringIO |
|
30 | from StringIO import StringIO | |
27 |
|
31 | |||
28 | # Our own imports |
|
32 | # Our own imports | |
29 | from IPython.config.configurable import Configurable |
|
33 | from IPython.config.configurable import Configurable | |
30 | from IPython.lib import pretty |
|
34 | from IPython.lib import pretty | |
31 | from IPython.utils.traitlets import Bool, Dict, Integer, Unicode, CUnicode, ObjectName |
|
35 | from IPython.utils.traitlets import Bool, Dict, Integer, Unicode, CUnicode, ObjectName | |
32 | from IPython.utils.py3compat import unicode_to_str |
|
36 | from IPython.utils.py3compat import unicode_to_str | |
33 |
|
37 | |||
34 |
|
38 | |||
35 | #----------------------------------------------------------------------------- |
|
39 | #----------------------------------------------------------------------------- | |
36 | # The main DisplayFormatter class |
|
40 | # The main DisplayFormatter class | |
37 | #----------------------------------------------------------------------------- |
|
41 | #----------------------------------------------------------------------------- | |
38 |
|
42 | |||
39 |
|
43 | |||
40 | class DisplayFormatter(Configurable): |
|
44 | class DisplayFormatter(Configurable): | |
41 |
|
45 | |||
42 | # When set to true only the default plain text formatter will be used. |
|
46 | # When set to true only the default plain text formatter will be used. | |
43 | plain_text_only = Bool(False, config=True) |
|
47 | plain_text_only = Bool(False, config=True) | |
44 |
|
48 | |||
45 | # A dict of formatter whose keys are format types (MIME types) and whose |
|
49 | # A dict of formatter whose keys are format types (MIME types) and whose | |
46 | # values are subclasses of BaseFormatter. |
|
50 | # values are subclasses of BaseFormatter. | |
47 | formatters = Dict() |
|
51 | formatters = Dict() | |
48 | def _formatters_default(self): |
|
52 | def _formatters_default(self): | |
49 | """Activate the default formatters.""" |
|
53 | """Activate the default formatters.""" | |
50 | formatter_classes = [ |
|
54 | formatter_classes = [ | |
51 | PlainTextFormatter, |
|
55 | PlainTextFormatter, | |
52 | HTMLFormatter, |
|
56 | HTMLFormatter, | |
53 | SVGFormatter, |
|
57 | SVGFormatter, | |
54 | PNGFormatter, |
|
58 | PNGFormatter, | |
55 | JPEGFormatter, |
|
59 | JPEGFormatter, | |
56 | LatexFormatter, |
|
60 | LatexFormatter, | |
57 | JSONFormatter, |
|
61 | JSONFormatter, | |
58 | JavascriptFormatter |
|
62 | JavascriptFormatter | |
59 | ] |
|
63 | ] | |
60 | d = {} |
|
64 | d = {} | |
61 | for cls in formatter_classes: |
|
65 | for cls in formatter_classes: | |
62 | f = cls(config=self.config) |
|
66 | f = cls(config=self.config) | |
63 | d[f.format_type] = f |
|
67 | d[f.format_type] = f | |
64 | return d |
|
68 | return d | |
65 |
|
69 | |||
66 | def format(self, obj, include=None, exclude=None): |
|
70 | def format(self, obj, include=None, exclude=None): | |
67 | """Return a format data dict for an object. |
|
71 | """Return a format data dict for an object. | |
68 |
|
72 | |||
69 | By default all format types will be computed. |
|
73 | By default all format types will be computed. | |
70 |
|
74 | |||
71 | The following MIME types are currently implemented: |
|
75 | The following MIME types are currently implemented: | |
72 |
|
76 | |||
73 | * text/plain |
|
77 | * text/plain | |
74 | * text/html |
|
78 | * text/html | |
75 | * text/latex |
|
79 | * text/latex | |
76 | * application/json |
|
80 | * application/json | |
77 | * application/javascript |
|
81 | * application/javascript | |
78 | * image/png |
|
82 | * image/png | |
79 | * image/jpeg |
|
83 | * image/jpeg | |
80 | * image/svg+xml |
|
84 | * image/svg+xml | |
81 |
|
85 | |||
82 | Parameters |
|
86 | Parameters | |
83 | ---------- |
|
87 | ---------- | |
84 | obj : object |
|
88 | obj : object | |
85 | The Python object whose format data will be computed. |
|
89 | The Python object whose format data will be computed. | |
86 | include : list or tuple, optional |
|
90 | include : list or tuple, optional | |
87 | A list of format type strings (MIME types) to include in the |
|
91 | A list of format type strings (MIME types) to include in the | |
88 | format data dict. If this is set *only* the format types included |
|
92 | format data dict. If this is set *only* the format types included | |
89 | in this list will be computed. |
|
93 | in this list will be computed. | |
90 | exclude : list or tuple, optional |
|
94 | exclude : list or tuple, optional | |
91 | A list of format type string (MIME types) to exclue in the format |
|
95 | A list of format type string (MIME types) to exclue in the format | |
92 | data dict. If this is set all format types will be computed, |
|
96 | data dict. If this is set all format types will be computed, | |
93 | except for those included in this argument. |
|
97 | except for those included in this argument. | |
94 |
|
98 | |||
95 | Returns |
|
99 | Returns | |
96 | ------- |
|
100 | ------- | |
97 | format_dict : dict |
|
101 | format_dict : dict | |
98 | A dictionary of key/value pairs, one or each format that was |
|
102 | A dictionary of key/value pairs, one or each format that was | |
99 | generated for the object. The keys are the format types, which |
|
103 | generated for the object. The keys are the format types, which | |
100 | will usually be MIME type strings and the values and JSON'able |
|
104 | will usually be MIME type strings and the values and JSON'able | |
101 | data structure containing the raw data for the representation in |
|
105 | data structure containing the raw data for the representation in | |
102 | that format. |
|
106 | that format. | |
103 | """ |
|
107 | """ | |
104 | format_dict = {} |
|
108 | format_dict = {} | |
105 |
|
109 | |||
106 | # If plain text only is active |
|
110 | # If plain text only is active | |
107 | if self.plain_text_only: |
|
111 | if self.plain_text_only: | |
108 | formatter = self.formatters['text/plain'] |
|
112 | formatter = self.formatters['text/plain'] | |
109 | try: |
|
113 | try: | |
110 | data = formatter(obj) |
|
114 | data = formatter(obj) | |
111 | except: |
|
115 | except: | |
112 | # FIXME: log the exception |
|
116 | # FIXME: log the exception | |
113 | raise |
|
117 | raise | |
114 | if data is not None: |
|
118 | if data is not None: | |
115 | format_dict['text/plain'] = data |
|
119 | format_dict['text/plain'] = data | |
116 | return format_dict |
|
120 | return format_dict | |
117 |
|
121 | |||
118 | for format_type, formatter in self.formatters.items(): |
|
122 | for format_type, formatter in self.formatters.items(): | |
119 | if include is not None: |
|
123 | if include is not None: | |
120 | if format_type not in include: |
|
124 | if format_type not in include: | |
121 | continue |
|
125 | continue | |
122 | if exclude is not None: |
|
126 | if exclude is not None: | |
123 | if format_type in exclude: |
|
127 | if format_type in exclude: | |
124 | continue |
|
128 | continue | |
125 | try: |
|
129 | try: | |
126 | data = formatter(obj) |
|
130 | data = formatter(obj) | |
127 | except: |
|
131 | except: | |
128 | # FIXME: log the exception |
|
132 | # FIXME: log the exception | |
129 | raise |
|
133 | raise | |
130 | if data is not None: |
|
134 | if data is not None: | |
131 | format_dict[format_type] = data |
|
135 | format_dict[format_type] = data | |
132 | return format_dict |
|
136 | return format_dict | |
133 |
|
137 | |||
134 | @property |
|
138 | @property | |
135 | def format_types(self): |
|
139 | def format_types(self): | |
136 | """Return the format types (MIME types) of the active formatters.""" |
|
140 | """Return the format types (MIME types) of the active formatters.""" | |
137 | return self.formatters.keys() |
|
141 | return self.formatters.keys() | |
138 |
|
142 | |||
139 |
|
143 | |||
140 | #----------------------------------------------------------------------------- |
|
144 | #----------------------------------------------------------------------------- | |
141 | # Formatters for specific format types (text, html, svg, etc.) |
|
145 | # Formatters for specific format types (text, html, svg, etc.) | |
142 | #----------------------------------------------------------------------------- |
|
146 | #----------------------------------------------------------------------------- | |
143 |
|
147 | |||
144 |
|
148 | |||
145 | class FormatterABC(object): |
|
149 | class FormatterABC(object): | |
146 | """ Abstract base class for Formatters. |
|
150 | """ Abstract base class for Formatters. | |
147 |
|
151 | |||
148 | A formatter is a callable class that is responsible for computing the |
|
152 | A formatter is a callable class that is responsible for computing the | |
149 | raw format data for a particular format type (MIME type). For example, |
|
153 | raw format data for a particular format type (MIME type). For example, | |
150 | an HTML formatter would have a format type of `text/html` and would return |
|
154 | an HTML formatter would have a format type of `text/html` and would return | |
151 | the HTML representation of the object when called. |
|
155 | the HTML representation of the object when called. | |
152 | """ |
|
156 | """ | |
153 | __metaclass__ = abc.ABCMeta |
|
157 | __metaclass__ = abc.ABCMeta | |
154 |
|
158 | |||
155 | # The format type of the data returned, usually a MIME type. |
|
159 | # The format type of the data returned, usually a MIME type. | |
156 | format_type = 'text/plain' |
|
160 | format_type = 'text/plain' | |
157 |
|
161 | |||
158 | # Is the formatter enabled... |
|
162 | # Is the formatter enabled... | |
159 | enabled = True |
|
163 | enabled = True | |
160 |
|
164 | |||
161 | @abc.abstractmethod |
|
165 | @abc.abstractmethod | |
162 | def __call__(self, obj): |
|
166 | def __call__(self, obj): | |
163 | """Return a JSON'able representation of the object. |
|
167 | """Return a JSON'able representation of the object. | |
164 |
|
168 | |||
165 | If the object cannot be formatted by this formatter, then return None |
|
169 | If the object cannot be formatted by this formatter, then return None | |
166 | """ |
|
170 | """ | |
167 | try: |
|
171 | try: | |
168 | return repr(obj) |
|
172 | return repr(obj) | |
169 | except TypeError: |
|
173 | except TypeError: | |
170 | return None |
|
174 | return None | |
171 |
|
175 | |||
172 |
|
176 | |||
173 | class BaseFormatter(Configurable): |
|
177 | class BaseFormatter(Configurable): | |
174 | """A base formatter class that is configurable. |
|
178 | """A base formatter class that is configurable. | |
175 |
|
179 | |||
176 | This formatter should usually be used as the base class of all formatters. |
|
180 | This formatter should usually be used as the base class of all formatters. | |
177 | It is a traited :class:`Configurable` class and includes an extensible |
|
181 | It is a traited :class:`Configurable` class and includes an extensible | |
178 | API for users to determine how their objects are formatted. The following |
|
182 | API for users to determine how their objects are formatted. The following | |
179 | logic is used to find a function to format an given object. |
|
183 | logic is used to find a function to format an given object. | |
180 |
|
184 | |||
181 | 1. The object is introspected to see if it has a method with the name |
|
185 | 1. The object is introspected to see if it has a method with the name | |
182 | :attr:`print_method`. If is does, that object is passed to that method |
|
186 | :attr:`print_method`. If is does, that object is passed to that method | |
183 | for formatting. |
|
187 | for formatting. | |
184 | 2. If no print method is found, three internal dictionaries are consulted |
|
188 | 2. If no print method is found, three internal dictionaries are consulted | |
185 | to find print method: :attr:`singleton_printers`, :attr:`type_printers` |
|
189 | to find print method: :attr:`singleton_printers`, :attr:`type_printers` | |
186 | and :attr:`deferred_printers`. |
|
190 | and :attr:`deferred_printers`. | |
187 |
|
191 | |||
188 | Users should use these dictionaries to register functions that will be |
|
192 | Users should use these dictionaries to register functions that will be | |
189 | used to compute the format data for their objects (if those objects don't |
|
193 | used to compute the format data for their objects (if those objects don't | |
190 | have the special print methods). The easiest way of using these |
|
194 | have the special print methods). The easiest way of using these | |
191 | dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name` |
|
195 | dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name` | |
192 | methods. |
|
196 | methods. | |
193 |
|
197 | |||
194 | If no function/callable is found to compute the format data, ``None`` is |
|
198 | If no function/callable is found to compute the format data, ``None`` is | |
195 | returned and this format type is not used. |
|
199 | returned and this format type is not used. | |
196 | """ |
|
200 | """ | |
197 |
|
201 | |||
198 | format_type = Unicode('text/plain') |
|
202 | format_type = Unicode('text/plain') | |
199 |
|
203 | |||
200 | enabled = Bool(True, config=True) |
|
204 | enabled = Bool(True, config=True) | |
201 |
|
205 | |||
202 | print_method = ObjectName('__repr__') |
|
206 | print_method = ObjectName('__repr__') | |
203 |
|
207 | |||
204 | # The singleton printers. |
|
208 | # The singleton printers. | |
205 | # Maps the IDs of the builtin singleton objects to the format functions. |
|
209 | # Maps the IDs of the builtin singleton objects to the format functions. | |
206 | singleton_printers = Dict(config=True) |
|
210 | singleton_printers = Dict(config=True) | |
207 | def _singleton_printers_default(self): |
|
211 | def _singleton_printers_default(self): | |
208 | return {} |
|
212 | return {} | |
209 |
|
213 | |||
210 | # The type-specific printers. |
|
214 | # The type-specific printers. | |
211 | # Map type objects to the format functions. |
|
215 | # Map type objects to the format functions. | |
212 | type_printers = Dict(config=True) |
|
216 | type_printers = Dict(config=True) | |
213 | def _type_printers_default(self): |
|
217 | def _type_printers_default(self): | |
214 | return {} |
|
218 | return {} | |
215 |
|
219 | |||
216 | # The deferred-import type-specific printers. |
|
220 | # The deferred-import type-specific printers. | |
217 | # Map (modulename, classname) pairs to the format functions. |
|
221 | # Map (modulename, classname) pairs to the format functions. | |
218 | deferred_printers = Dict(config=True) |
|
222 | deferred_printers = Dict(config=True) | |
219 | def _deferred_printers_default(self): |
|
223 | def _deferred_printers_default(self): | |
220 | return {} |
|
224 | return {} | |
221 |
|
225 | |||
222 | def __call__(self, obj): |
|
226 | def __call__(self, obj): | |
223 | """Compute the format for an object.""" |
|
227 | """Compute the format for an object.""" | |
224 | if self.enabled: |
|
228 | if self.enabled: | |
225 | obj_id = id(obj) |
|
229 | obj_id = id(obj) | |
226 | try: |
|
230 | try: | |
227 | obj_class = getattr(obj, '__class__', None) or type(obj) |
|
231 | obj_class = getattr(obj, '__class__', None) or type(obj) | |
228 | # First try to find registered singleton printers for the type. |
|
232 | # First try to find registered singleton printers for the type. | |
229 | try: |
|
233 | try: | |
230 | printer = self.singleton_printers[obj_id] |
|
234 | printer = self.singleton_printers[obj_id] | |
231 | except (TypeError, KeyError): |
|
235 | except (TypeError, KeyError): | |
232 | pass |
|
236 | pass | |
233 | else: |
|
237 | else: | |
234 | return printer(obj) |
|
238 | return printer(obj) | |
235 | # Next look for type_printers. |
|
239 | # Next look for type_printers. | |
236 | for cls in pretty._get_mro(obj_class): |
|
240 | for cls in pretty._get_mro(obj_class): | |
237 | if cls in self.type_printers: |
|
241 | if cls in self.type_printers: | |
238 | return self.type_printers[cls](obj) |
|
242 | return self.type_printers[cls](obj) | |
239 | else: |
|
243 | else: | |
240 | printer = self._in_deferred_types(cls) |
|
244 | printer = self._in_deferred_types(cls) | |
241 | if printer is not None: |
|
245 | if printer is not None: | |
242 | return printer(obj) |
|
246 | return printer(obj) | |
243 | # Finally look for special method names. |
|
247 | # Finally look for special method names. | |
244 | if hasattr(obj_class, self.print_method): |
|
248 | if hasattr(obj_class, self.print_method): | |
245 | printer = getattr(obj_class, self.print_method) |
|
249 | printer = getattr(obj_class, self.print_method) | |
246 | return printer(obj) |
|
250 | return printer(obj) | |
247 | return None |
|
251 | return None | |
248 | except Exception: |
|
252 | except Exception: | |
249 | pass |
|
253 | pass | |
250 | else: |
|
254 | else: | |
251 | return None |
|
255 | return None | |
252 |
|
256 | |||
253 | def for_type(self, typ, func): |
|
257 | def for_type(self, typ, func): | |
254 | """Add a format function for a given type. |
|
258 | """Add a format function for a given type. | |
255 |
|
259 | |||
256 | Parameters |
|
260 | Parameters | |
257 | ----------- |
|
261 | ----------- | |
258 | typ : class |
|
262 | typ : class | |
259 | The class of the object that will be formatted using `func`. |
|
263 | The class of the object that will be formatted using `func`. | |
260 | func : callable |
|
264 | func : callable | |
261 | The callable that will be called to compute the format data. The |
|
265 | The callable that will be called to compute the format data. The | |
262 | call signature of this function is simple, it must take the |
|
266 | call signature of this function is simple, it must take the | |
263 | object to be formatted and return the raw data for the given |
|
267 | object to be formatted and return the raw data for the given | |
264 | format. Subclasses may use a different call signature for the |
|
268 | format. Subclasses may use a different call signature for the | |
265 | `func` argument. |
|
269 | `func` argument. | |
266 | """ |
|
270 | """ | |
267 | oldfunc = self.type_printers.get(typ, None) |
|
271 | oldfunc = self.type_printers.get(typ, None) | |
268 | if func is not None: |
|
272 | if func is not None: | |
269 | # To support easy restoration of old printers, we need to ignore |
|
273 | # To support easy restoration of old printers, we need to ignore | |
270 | # Nones. |
|
274 | # Nones. | |
271 | self.type_printers[typ] = func |
|
275 | self.type_printers[typ] = func | |
272 | return oldfunc |
|
276 | return oldfunc | |
273 |
|
277 | |||
274 | def for_type_by_name(self, type_module, type_name, func): |
|
278 | def for_type_by_name(self, type_module, type_name, func): | |
275 | """Add a format function for a type specified by the full dotted |
|
279 | """Add a format function for a type specified by the full dotted | |
276 | module and name of the type, rather than the type of the object. |
|
280 | module and name of the type, rather than the type of the object. | |
277 |
|
281 | |||
278 | Parameters |
|
282 | Parameters | |
279 | ---------- |
|
283 | ---------- | |
280 | type_module : str |
|
284 | type_module : str | |
281 | The full dotted name of the module the type is defined in, like |
|
285 | The full dotted name of the module the type is defined in, like | |
282 | ``numpy``. |
|
286 | ``numpy``. | |
283 | type_name : str |
|
287 | type_name : str | |
284 | The name of the type (the class name), like ``dtype`` |
|
288 | The name of the type (the class name), like ``dtype`` | |
285 | func : callable |
|
289 | func : callable | |
286 | The callable that will be called to compute the format data. The |
|
290 | The callable that will be called to compute the format data. The | |
287 | call signature of this function is simple, it must take the |
|
291 | call signature of this function is simple, it must take the | |
288 | object to be formatted and return the raw data for the given |
|
292 | object to be formatted and return the raw data for the given | |
289 | format. Subclasses may use a different call signature for the |
|
293 | format. Subclasses may use a different call signature for the | |
290 | `func` argument. |
|
294 | `func` argument. | |
291 | """ |
|
295 | """ | |
292 | key = (type_module, type_name) |
|
296 | key = (type_module, type_name) | |
293 | oldfunc = self.deferred_printers.get(key, None) |
|
297 | oldfunc = self.deferred_printers.get(key, None) | |
294 | if func is not None: |
|
298 | if func is not None: | |
295 | # To support easy restoration of old printers, we need to ignore |
|
299 | # To support easy restoration of old printers, we need to ignore | |
296 | # Nones. |
|
300 | # Nones. | |
297 | self.deferred_printers[key] = func |
|
301 | self.deferred_printers[key] = func | |
298 | return oldfunc |
|
302 | return oldfunc | |
299 |
|
303 | |||
300 | def _in_deferred_types(self, cls): |
|
304 | def _in_deferred_types(self, cls): | |
301 | """ |
|
305 | """ | |
302 | Check if the given class is specified in the deferred type registry. |
|
306 | Check if the given class is specified in the deferred type registry. | |
303 |
|
307 | |||
304 | Returns the printer from the registry if it exists, and None if the |
|
308 | Returns the printer from the registry if it exists, and None if the | |
305 | class is not in the registry. Successful matches will be moved to the |
|
309 | class is not in the registry. Successful matches will be moved to the | |
306 | regular type registry for future use. |
|
310 | regular type registry for future use. | |
307 | """ |
|
311 | """ | |
308 | mod = getattr(cls, '__module__', None) |
|
312 | mod = getattr(cls, '__module__', None) | |
309 | name = getattr(cls, '__name__', None) |
|
313 | name = getattr(cls, '__name__', None) | |
310 | key = (mod, name) |
|
314 | key = (mod, name) | |
311 | printer = None |
|
315 | printer = None | |
312 | if key in self.deferred_printers: |
|
316 | if key in self.deferred_printers: | |
313 | # Move the printer over to the regular registry. |
|
317 | # Move the printer over to the regular registry. | |
314 | printer = self.deferred_printers.pop(key) |
|
318 | printer = self.deferred_printers.pop(key) | |
315 | self.type_printers[cls] = printer |
|
319 | self.type_printers[cls] = printer | |
316 | return printer |
|
320 | return printer | |
317 |
|
321 | |||
318 |
|
322 | |||
319 | class PlainTextFormatter(BaseFormatter): |
|
323 | class PlainTextFormatter(BaseFormatter): | |
320 | """The default pretty-printer. |
|
324 | """The default pretty-printer. | |
321 |
|
325 | |||
322 | This uses :mod:`IPython.lib.pretty` to compute the format data of |
|
326 | This uses :mod:`IPython.lib.pretty` to compute the format data of | |
323 | the object. If the object cannot be pretty printed, :func:`repr` is used. |
|
327 | the object. If the object cannot be pretty printed, :func:`repr` is used. | |
324 | See the documentation of :mod:`IPython.lib.pretty` for details on |
|
328 | See the documentation of :mod:`IPython.lib.pretty` for details on | |
325 | how to write pretty printers. Here is a simple example:: |
|
329 | how to write pretty printers. Here is a simple example:: | |
326 |
|
330 | |||
327 | def dtype_pprinter(obj, p, cycle): |
|
331 | def dtype_pprinter(obj, p, cycle): | |
328 | if cycle: |
|
332 | if cycle: | |
329 | return p.text('dtype(...)') |
|
333 | return p.text('dtype(...)') | |
330 | if hasattr(obj, 'fields'): |
|
334 | if hasattr(obj, 'fields'): | |
331 | if obj.fields is None: |
|
335 | if obj.fields is None: | |
332 | p.text(repr(obj)) |
|
336 | p.text(repr(obj)) | |
333 | else: |
|
337 | else: | |
334 | p.begin_group(7, 'dtype([') |
|
338 | p.begin_group(7, 'dtype([') | |
335 | for i, field in enumerate(obj.descr): |
|
339 | for i, field in enumerate(obj.descr): | |
336 | if i > 0: |
|
340 | if i > 0: | |
337 | p.text(',') |
|
341 | p.text(',') | |
338 | p.breakable() |
|
342 | p.breakable() | |
339 | p.pretty(field) |
|
343 | p.pretty(field) | |
340 | p.end_group(7, '])') |
|
344 | p.end_group(7, '])') | |
341 | """ |
|
345 | """ | |
342 |
|
346 | |||
343 | # The format type of data returned. |
|
347 | # The format type of data returned. | |
344 | format_type = Unicode('text/plain') |
|
348 | format_type = Unicode('text/plain') | |
345 |
|
349 | |||
346 | # This subclass ignores this attribute as it always need to return |
|
350 | # This subclass ignores this attribute as it always need to return | |
347 | # something. |
|
351 | # something. | |
348 | enabled = Bool(True, config=False) |
|
352 | enabled = Bool(True, config=False) | |
349 |
|
353 | |||
350 | # Look for a _repr_pretty_ methods to use for pretty printing. |
|
354 | # Look for a _repr_pretty_ methods to use for pretty printing. | |
351 | print_method = ObjectName('_repr_pretty_') |
|
355 | print_method = ObjectName('_repr_pretty_') | |
352 |
|
356 | |||
353 | # Whether to pretty-print or not. |
|
357 | # Whether to pretty-print or not. | |
354 | pprint = Bool(True, config=True) |
|
358 | pprint = Bool(True, config=True) | |
355 |
|
359 | |||
356 | # Whether to be verbose or not. |
|
360 | # Whether to be verbose or not. | |
357 | verbose = Bool(False, config=True) |
|
361 | verbose = Bool(False, config=True) | |
358 |
|
362 | |||
359 | # The maximum width. |
|
363 | # The maximum width. | |
360 | max_width = Integer(79, config=True) |
|
364 | max_width = Integer(79, config=True) | |
361 |
|
365 | |||
362 | # The newline character. |
|
366 | # The newline character. | |
363 | newline = Unicode('\n', config=True) |
|
367 | newline = Unicode('\n', config=True) | |
364 |
|
368 | |||
365 | # format-string for pprinting floats |
|
369 | # format-string for pprinting floats | |
366 | float_format = Unicode('%r') |
|
370 | float_format = Unicode('%r') | |
367 | # setter for float precision, either int or direct format-string |
|
371 | # setter for float precision, either int or direct format-string | |
368 | float_precision = CUnicode('', config=True) |
|
372 | float_precision = CUnicode('', config=True) | |
369 |
|
373 | |||
370 | def _float_precision_changed(self, name, old, new): |
|
374 | def _float_precision_changed(self, name, old, new): | |
371 | """float_precision changed, set float_format accordingly. |
|
375 | """float_precision changed, set float_format accordingly. | |
372 |
|
376 | |||
373 | float_precision can be set by int or str. |
|
377 | float_precision can be set by int or str. | |
374 | This will set float_format, after interpreting input. |
|
378 | This will set float_format, after interpreting input. | |
375 | If numpy has been imported, numpy print precision will also be set. |
|
379 | If numpy has been imported, numpy print precision will also be set. | |
376 |
|
380 | |||
377 | integer `n` sets format to '%.nf', otherwise, format set directly. |
|
381 | integer `n` sets format to '%.nf', otherwise, format set directly. | |
378 |
|
382 | |||
379 | An empty string returns to defaults (repr for float, 8 for numpy). |
|
383 | An empty string returns to defaults (repr for float, 8 for numpy). | |
380 |
|
384 | |||
381 | This parameter can be set via the '%precision' magic. |
|
385 | This parameter can be set via the '%precision' magic. | |
382 | """ |
|
386 | """ | |
383 |
|
387 | |||
384 | if '%' in new: |
|
388 | if '%' in new: | |
385 | # got explicit format string |
|
389 | # got explicit format string | |
386 | fmt = new |
|
390 | fmt = new | |
387 | try: |
|
391 | try: | |
388 | fmt%3.14159 |
|
392 | fmt%3.14159 | |
389 | except Exception: |
|
393 | except Exception: | |
390 | raise ValueError("Precision must be int or format string, not %r"%new) |
|
394 | raise ValueError("Precision must be int or format string, not %r"%new) | |
391 | elif new: |
|
395 | elif new: | |
392 | # otherwise, should be an int |
|
396 | # otherwise, should be an int | |
393 | try: |
|
397 | try: | |
394 | i = int(new) |
|
398 | i = int(new) | |
395 | assert i >= 0 |
|
399 | assert i >= 0 | |
396 | except ValueError: |
|
400 | except ValueError: | |
397 | raise ValueError("Precision must be int or format string, not %r"%new) |
|
401 | raise ValueError("Precision must be int or format string, not %r"%new) | |
398 | except AssertionError: |
|
402 | except AssertionError: | |
399 | raise ValueError("int precision must be non-negative, not %r"%i) |
|
403 | raise ValueError("int precision must be non-negative, not %r"%i) | |
400 |
|
404 | |||
401 | fmt = '%%.%if'%i |
|
405 | fmt = '%%.%if'%i | |
402 | if 'numpy' in sys.modules: |
|
406 | if 'numpy' in sys.modules: | |
403 | # set numpy precision if it has been imported |
|
407 | # set numpy precision if it has been imported | |
404 | import numpy |
|
408 | import numpy | |
405 | numpy.set_printoptions(precision=i) |
|
409 | numpy.set_printoptions(precision=i) | |
406 | else: |
|
410 | else: | |
407 | # default back to repr |
|
411 | # default back to repr | |
408 | fmt = '%r' |
|
412 | fmt = '%r' | |
409 | if 'numpy' in sys.modules: |
|
413 | if 'numpy' in sys.modules: | |
410 | import numpy |
|
414 | import numpy | |
411 | # numpy default is 8 |
|
415 | # numpy default is 8 | |
412 | numpy.set_printoptions(precision=8) |
|
416 | numpy.set_printoptions(precision=8) | |
413 | self.float_format = fmt |
|
417 | self.float_format = fmt | |
414 |
|
418 | |||
415 | # Use the default pretty printers from IPython.lib.pretty. |
|
419 | # Use the default pretty printers from IPython.lib.pretty. | |
416 | def _singleton_printers_default(self): |
|
420 | def _singleton_printers_default(self): | |
417 | return pretty._singleton_pprinters.copy() |
|
421 | return pretty._singleton_pprinters.copy() | |
418 |
|
422 | |||
419 | def _type_printers_default(self): |
|
423 | def _type_printers_default(self): | |
420 | d = pretty._type_pprinters.copy() |
|
424 | d = pretty._type_pprinters.copy() | |
421 | d[float] = lambda obj,p,cycle: p.text(self.float_format%obj) |
|
425 | d[float] = lambda obj,p,cycle: p.text(self.float_format%obj) | |
422 | return d |
|
426 | return d | |
423 |
|
427 | |||
424 | def _deferred_printers_default(self): |
|
428 | def _deferred_printers_default(self): | |
425 | return pretty._deferred_type_pprinters.copy() |
|
429 | return pretty._deferred_type_pprinters.copy() | |
426 |
|
430 | |||
427 | #### FormatterABC interface #### |
|
431 | #### FormatterABC interface #### | |
428 |
|
432 | |||
429 | def __call__(self, obj): |
|
433 | def __call__(self, obj): | |
430 | """Compute the pretty representation of the object.""" |
|
434 | """Compute the pretty representation of the object.""" | |
431 | if not self.pprint: |
|
435 | if not self.pprint: | |
432 | try: |
|
436 | try: | |
433 | return repr(obj) |
|
437 | return repr(obj) | |
434 | except TypeError: |
|
438 | except TypeError: | |
435 | return '' |
|
439 | return '' | |
436 | else: |
|
440 | else: | |
437 | # This uses use StringIO, as cStringIO doesn't handle unicode. |
|
441 | # This uses use StringIO, as cStringIO doesn't handle unicode. | |
438 | stream = StringIO() |
|
442 | stream = StringIO() | |
439 | # self.newline.encode() is a quick fix for issue gh-597. We need to |
|
443 | # self.newline.encode() is a quick fix for issue gh-597. We need to | |
440 | # ensure that stream does not get a mix of unicode and bytestrings, |
|
444 | # ensure that stream does not get a mix of unicode and bytestrings, | |
441 | # or it will cause trouble. |
|
445 | # or it will cause trouble. | |
442 | printer = pretty.RepresentationPrinter(stream, self.verbose, |
|
446 | printer = pretty.RepresentationPrinter(stream, self.verbose, | |
443 | self.max_width, unicode_to_str(self.newline), |
|
447 | self.max_width, unicode_to_str(self.newline), | |
444 | singleton_pprinters=self.singleton_printers, |
|
448 | singleton_pprinters=self.singleton_printers, | |
445 | type_pprinters=self.type_printers, |
|
449 | type_pprinters=self.type_printers, | |
446 | deferred_pprinters=self.deferred_printers) |
|
450 | deferred_pprinters=self.deferred_printers) | |
447 | printer.pretty(obj) |
|
451 | printer.pretty(obj) | |
448 | printer.flush() |
|
452 | printer.flush() | |
449 | return stream.getvalue() |
|
453 | return stream.getvalue() | |
450 |
|
454 | |||
451 |
|
455 | |||
452 | class HTMLFormatter(BaseFormatter): |
|
456 | class HTMLFormatter(BaseFormatter): | |
453 | """An HTML formatter. |
|
457 | """An HTML formatter. | |
454 |
|
458 | |||
455 | To define the callables that compute the HTML representation of your |
|
459 | To define the callables that compute the HTML representation of your | |
456 | objects, define a :meth:`_repr_html_` method or use the :meth:`for_type` |
|
460 | objects, define a :meth:`_repr_html_` method or use the :meth:`for_type` | |
457 | or :meth:`for_type_by_name` methods to register functions that handle |
|
461 | or :meth:`for_type_by_name` methods to register functions that handle | |
458 | this. |
|
462 | this. | |
459 |
|
463 | |||
460 | The return value of this formatter should be a valid HTML snippet that |
|
464 | The return value of this formatter should be a valid HTML snippet that | |
461 | could be injected into an existing DOM. It should *not* include the |
|
465 | could be injected into an existing DOM. It should *not* include the | |
462 | ```<html>`` or ```<body>`` tags. |
|
466 | ```<html>`` or ```<body>`` tags. | |
463 | """ |
|
467 | """ | |
464 | format_type = Unicode('text/html') |
|
468 | format_type = Unicode('text/html') | |
465 |
|
469 | |||
466 | print_method = ObjectName('_repr_html_') |
|
470 | print_method = ObjectName('_repr_html_') | |
467 |
|
471 | |||
468 |
|
472 | |||
469 | class SVGFormatter(BaseFormatter): |
|
473 | class SVGFormatter(BaseFormatter): | |
470 | """An SVG formatter. |
|
474 | """An SVG formatter. | |
471 |
|
475 | |||
472 | To define the callables that compute the SVG representation of your |
|
476 | To define the callables that compute the SVG representation of your | |
473 | objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type` |
|
477 | objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type` | |
474 | or :meth:`for_type_by_name` methods to register functions that handle |
|
478 | or :meth:`for_type_by_name` methods to register functions that handle | |
475 | this. |
|
479 | this. | |
476 |
|
480 | |||
477 | The return value of this formatter should be valid SVG enclosed in |
|
481 | The return value of this formatter should be valid SVG enclosed in | |
478 | ```<svg>``` tags, that could be injected into an existing DOM. It should |
|
482 | ```<svg>``` tags, that could be injected into an existing DOM. It should | |
479 | *not* include the ```<html>`` or ```<body>`` tags. |
|
483 | *not* include the ```<html>`` or ```<body>`` tags. | |
480 | """ |
|
484 | """ | |
481 | format_type = Unicode('image/svg+xml') |
|
485 | format_type = Unicode('image/svg+xml') | |
482 |
|
486 | |||
483 | print_method = ObjectName('_repr_svg_') |
|
487 | print_method = ObjectName('_repr_svg_') | |
484 |
|
488 | |||
485 |
|
489 | |||
486 | class PNGFormatter(BaseFormatter): |
|
490 | class PNGFormatter(BaseFormatter): | |
487 | """A PNG formatter. |
|
491 | """A PNG formatter. | |
488 |
|
492 | |||
489 | To define the callables that compute the PNG representation of your |
|
493 | To define the callables that compute the PNG representation of your | |
490 | objects, define a :meth:`_repr_png_` method or use the :meth:`for_type` |
|
494 | objects, define a :meth:`_repr_png_` method or use the :meth:`for_type` | |
491 | or :meth:`for_type_by_name` methods to register functions that handle |
|
495 | or :meth:`for_type_by_name` methods to register functions that handle | |
492 | this. |
|
496 | this. | |
493 |
|
497 | |||
494 | The return value of this formatter should be raw PNG data, *not* |
|
498 | The return value of this formatter should be raw PNG data, *not* | |
495 | base64 encoded. |
|
499 | base64 encoded. | |
496 | """ |
|
500 | """ | |
497 | format_type = Unicode('image/png') |
|
501 | format_type = Unicode('image/png') | |
498 |
|
502 | |||
499 | print_method = ObjectName('_repr_png_') |
|
503 | print_method = ObjectName('_repr_png_') | |
500 |
|
504 | |||
501 |
|
505 | |||
502 | class JPEGFormatter(BaseFormatter): |
|
506 | class JPEGFormatter(BaseFormatter): | |
503 | """A JPEG formatter. |
|
507 | """A JPEG formatter. | |
504 |
|
508 | |||
505 | To define the callables that compute the JPEG representation of your |
|
509 | To define the callables that compute the JPEG representation of your | |
506 | objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type` |
|
510 | objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type` | |
507 | or :meth:`for_type_by_name` methods to register functions that handle |
|
511 | or :meth:`for_type_by_name` methods to register functions that handle | |
508 | this. |
|
512 | this. | |
509 |
|
513 | |||
510 | The return value of this formatter should be raw JPEG data, *not* |
|
514 | The return value of this formatter should be raw JPEG data, *not* | |
511 | base64 encoded. |
|
515 | base64 encoded. | |
512 | """ |
|
516 | """ | |
513 | format_type = Unicode('image/jpeg') |
|
517 | format_type = Unicode('image/jpeg') | |
514 |
|
518 | |||
515 | print_method = ObjectName('_repr_jpeg_') |
|
519 | print_method = ObjectName('_repr_jpeg_') | |
516 |
|
520 | |||
517 |
|
521 | |||
518 | class LatexFormatter(BaseFormatter): |
|
522 | class LatexFormatter(BaseFormatter): | |
519 | """A LaTeX formatter. |
|
523 | """A LaTeX formatter. | |
520 |
|
524 | |||
521 | To define the callables that compute the LaTeX representation of your |
|
525 | To define the callables that compute the LaTeX representation of your | |
522 | objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type` |
|
526 | objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type` | |
523 | or :meth:`for_type_by_name` methods to register functions that handle |
|
527 | or :meth:`for_type_by_name` methods to register functions that handle | |
524 | this. |
|
528 | this. | |
525 |
|
529 | |||
526 | The return value of this formatter should be a valid LaTeX equation, |
|
530 | The return value of this formatter should be a valid LaTeX equation, | |
527 | enclosed in either ```$```, ```$$``` or another LaTeX equation |
|
531 | enclosed in either ```$```, ```$$``` or another LaTeX equation | |
528 | environment. |
|
532 | environment. | |
529 | """ |
|
533 | """ | |
530 | format_type = Unicode('text/latex') |
|
534 | format_type = Unicode('text/latex') | |
531 |
|
535 | |||
532 | print_method = ObjectName('_repr_latex_') |
|
536 | print_method = ObjectName('_repr_latex_') | |
533 |
|
537 | |||
534 |
|
538 | |||
535 | class JSONFormatter(BaseFormatter): |
|
539 | class JSONFormatter(BaseFormatter): | |
536 | """A JSON string formatter. |
|
540 | """A JSON string formatter. | |
537 |
|
541 | |||
538 | To define the callables that compute the JSON string representation of |
|
542 | To define the callables that compute the JSON string representation of | |
539 | your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type` |
|
543 | your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type` | |
540 | or :meth:`for_type_by_name` methods to register functions that handle |
|
544 | or :meth:`for_type_by_name` methods to register functions that handle | |
541 | this. |
|
545 | this. | |
542 |
|
546 | |||
543 | The return value of this formatter should be a valid JSON string. |
|
547 | The return value of this formatter should be a valid JSON string. | |
544 | """ |
|
548 | """ | |
545 | format_type = Unicode('application/json') |
|
549 | format_type = Unicode('application/json') | |
546 |
|
550 | |||
547 | print_method = ObjectName('_repr_json_') |
|
551 | print_method = ObjectName('_repr_json_') | |
548 |
|
552 | |||
549 |
|
553 | |||
550 | class JavascriptFormatter(BaseFormatter): |
|
554 | class JavascriptFormatter(BaseFormatter): | |
551 | """A Javascript formatter. |
|
555 | """A Javascript formatter. | |
552 |
|
556 | |||
553 | To define the callables that compute the Javascript representation of |
|
557 | To define the callables that compute the Javascript representation of | |
554 | your objects, define a :meth:`_repr_javascript_` method or use the |
|
558 | your objects, define a :meth:`_repr_javascript_` method or use the | |
555 | :meth:`for_type` or :meth:`for_type_by_name` methods to register functions |
|
559 | :meth:`for_type` or :meth:`for_type_by_name` methods to register functions | |
556 | that handle this. |
|
560 | that handle this. | |
557 |
|
561 | |||
558 | The return value of this formatter should be valid Javascript code and |
|
562 | The return value of this formatter should be valid Javascript code and | |
559 | should *not* be enclosed in ```<script>``` tags. |
|
563 | should *not* be enclosed in ```<script>``` tags. | |
560 | """ |
|
564 | """ | |
561 | format_type = Unicode('application/javascript') |
|
565 | format_type = Unicode('application/javascript') | |
562 |
|
566 | |||
563 | print_method = ObjectName('_repr_javascript_') |
|
567 | print_method = ObjectName('_repr_javascript_') | |
564 |
|
568 | |||
565 | FormatterABC.register(BaseFormatter) |
|
569 | FormatterABC.register(BaseFormatter) | |
566 | FormatterABC.register(PlainTextFormatter) |
|
570 | FormatterABC.register(PlainTextFormatter) | |
567 | FormatterABC.register(HTMLFormatter) |
|
571 | FormatterABC.register(HTMLFormatter) | |
568 | FormatterABC.register(SVGFormatter) |
|
572 | FormatterABC.register(SVGFormatter) | |
569 | FormatterABC.register(PNGFormatter) |
|
573 | FormatterABC.register(PNGFormatter) | |
570 | FormatterABC.register(JPEGFormatter) |
|
574 | FormatterABC.register(JPEGFormatter) | |
571 | FormatterABC.register(LatexFormatter) |
|
575 | FormatterABC.register(LatexFormatter) | |
572 | FormatterABC.register(JSONFormatter) |
|
576 | FormatterABC.register(JSONFormatter) | |
573 | FormatterABC.register(JavascriptFormatter) |
|
577 | FormatterABC.register(JavascriptFormatter) | |
574 |
|
578 | |||
575 |
|
579 | |||
576 | def format_display_data(obj, include=None, exclude=None): |
|
580 | def format_display_data(obj, include=None, exclude=None): | |
577 | """Return a format data dict for an object. |
|
581 | """Return a format data dict for an object. | |
578 |
|
582 | |||
579 | By default all format types will be computed. |
|
583 | By default all format types will be computed. | |
580 |
|
584 | |||
581 | The following MIME types are currently implemented: |
|
585 | The following MIME types are currently implemented: | |
582 |
|
586 | |||
583 | * text/plain |
|
587 | * text/plain | |
584 | * text/html |
|
588 | * text/html | |
585 | * text/latex |
|
589 | * text/latex | |
586 | * application/json |
|
590 | * application/json | |
587 | * application/javascript |
|
591 | * application/javascript | |
588 | * image/png |
|
592 | * image/png | |
589 | * image/jpeg |
|
593 | * image/jpeg | |
590 | * image/svg+xml |
|
594 | * image/svg+xml | |
591 |
|
595 | |||
592 | Parameters |
|
596 | Parameters | |
593 | ---------- |
|
597 | ---------- | |
594 | obj : object |
|
598 | obj : object | |
595 | The Python object whose format data will be computed. |
|
599 | The Python object whose format data will be computed. | |
596 |
|
600 | |||
597 | Returns |
|
601 | Returns | |
598 | ------- |
|
602 | ------- | |
599 | format_dict : dict |
|
603 | format_dict : dict | |
600 | A dictionary of key/value pairs, one or each format that was |
|
604 | A dictionary of key/value pairs, one or each format that was | |
601 | generated for the object. The keys are the format types, which |
|
605 | generated for the object. The keys are the format types, which | |
602 | will usually be MIME type strings and the values and JSON'able |
|
606 | will usually be MIME type strings and the values and JSON'able | |
603 | data structure containing the raw data for the representation in |
|
607 | data structure containing the raw data for the representation in | |
604 | that format. |
|
608 | that format. | |
605 | include : list or tuple, optional |
|
609 | include : list or tuple, optional | |
606 | A list of format type strings (MIME types) to include in the |
|
610 | A list of format type strings (MIME types) to include in the | |
607 | format data dict. If this is set *only* the format types included |
|
611 | format data dict. If this is set *only* the format types included | |
608 | in this list will be computed. |
|
612 | in this list will be computed. | |
609 | exclude : list or tuple, optional |
|
613 | exclude : list or tuple, optional | |
610 | A list of format type string (MIME types) to exclue in the format |
|
614 | A list of format type string (MIME types) to exclue in the format | |
611 | data dict. If this is set all format types will be computed, |
|
615 | data dict. If this is set all format types will be computed, | |
612 | except for those included in this argument. |
|
616 | except for those included in this argument. | |
613 | """ |
|
617 | """ | |
614 | from IPython.core.interactiveshell import InteractiveShell |
|
618 | from IPython.core.interactiveshell import InteractiveShell | |
615 |
|
619 | |||
616 | InteractiveShell.instance().display_formatter.format( |
|
620 | InteractiveShell.instance().display_formatter.format( | |
617 | obj, |
|
621 | obj, | |
618 | include, |
|
622 | include, | |
619 | exclude |
|
623 | exclude | |
620 | ) |
|
624 | ) | |
621 |
|
625 |
@@ -1,241 +1,246 b'' | |||||
1 | ''' A decorator-based method of constructing IPython magics with `argparse` |
|
1 | ''' A decorator-based method of constructing IPython magics with `argparse` | |
2 | option handling. |
|
2 | option handling. | |
3 |
|
3 | |||
4 | New magic functions can be defined like so:: |
|
4 | New magic functions can be defined like so:: | |
5 |
|
5 | |||
6 | from IPython.core.magic_arguments import (argument, magic_arguments, |
|
6 | from IPython.core.magic_arguments import (argument, magic_arguments, | |
7 | parse_argstring) |
|
7 | parse_argstring) | |
8 |
|
8 | |||
9 | @magic_arguments() |
|
9 | @magic_arguments() | |
10 | @argument('-o', '--option', help='An optional argument.') |
|
10 | @argument('-o', '--option', help='An optional argument.') | |
11 | @argument('arg', type=int, help='An integer positional argument.') |
|
11 | @argument('arg', type=int, help='An integer positional argument.') | |
12 | def magic_cool(self, arg): |
|
12 | def magic_cool(self, arg): | |
13 | """ A really cool magic command. |
|
13 | """ A really cool magic command. | |
14 |
|
14 | |||
15 | """ |
|
15 | """ | |
16 | args = parse_argstring(magic_cool, arg) |
|
16 | args = parse_argstring(magic_cool, arg) | |
17 | ... |
|
17 | ... | |
18 |
|
18 | |||
19 | The `@magic_arguments` decorator marks the function as having argparse arguments. |
|
19 | The `@magic_arguments` decorator marks the function as having argparse arguments. | |
20 | The `@argument` decorator adds an argument using the same syntax as argparse's |
|
20 | The `@argument` decorator adds an argument using the same syntax as argparse's | |
21 | `add_argument()` method. More sophisticated uses may also require the |
|
21 | `add_argument()` method. More sophisticated uses may also require the | |
22 | `@argument_group` or `@kwds` decorator to customize the formatting and the |
|
22 | `@argument_group` or `@kwds` decorator to customize the formatting and the | |
23 | parsing. |
|
23 | parsing. | |
24 |
|
24 | |||
25 | Help text for the magic is automatically generated from the docstring and the |
|
25 | Help text for the magic is automatically generated from the docstring and the | |
26 | arguments:: |
|
26 | arguments:: | |
27 |
|
27 | |||
28 | In[1]: %cool? |
|
28 | In[1]: %cool? | |
29 | %cool [-o OPTION] arg |
|
29 | %cool [-o OPTION] arg | |
30 |
|
30 | |||
31 | A really cool magic command. |
|
31 | A really cool magic command. | |
32 |
|
32 | |||
33 | positional arguments: |
|
33 | positional arguments: | |
34 | arg An integer positional argument. |
|
34 | arg An integer positional argument. | |
35 |
|
35 | |||
36 | optional arguments: |
|
36 | optional arguments: | |
37 | -o OPTION, --option OPTION |
|
37 | -o OPTION, --option OPTION | |
38 | An optional argument. |
|
38 | An optional argument. | |
39 |
|
39 | |||
|
40 | Inheritance diagram: | |||
|
41 | ||||
|
42 | .. inheritance-diagram:: IPython.core.magic_arguments | |||
|
43 | :parts: 3 | |||
|
44 | ||||
40 | ''' |
|
45 | ''' | |
41 | #----------------------------------------------------------------------------- |
|
46 | #----------------------------------------------------------------------------- | |
42 | # Copyright (C) 2010-2011, IPython Development Team. |
|
47 | # Copyright (C) 2010-2011, IPython Development Team. | |
43 | # |
|
48 | # | |
44 | # Distributed under the terms of the Modified BSD License. |
|
49 | # Distributed under the terms of the Modified BSD License. | |
45 | # |
|
50 | # | |
46 | # The full license is in the file COPYING.txt, distributed with this software. |
|
51 | # The full license is in the file COPYING.txt, distributed with this software. | |
47 | #----------------------------------------------------------------------------- |
|
52 | #----------------------------------------------------------------------------- | |
48 |
|
53 | |||
49 | # Our own imports |
|
54 | # Our own imports | |
50 | from IPython.external import argparse |
|
55 | from IPython.external import argparse | |
51 | from IPython.core.error import UsageError |
|
56 | from IPython.core.error import UsageError | |
52 | from IPython.utils.process import arg_split |
|
57 | from IPython.utils.process import arg_split | |
53 | from IPython.utils.text import dedent |
|
58 | from IPython.utils.text import dedent | |
54 |
|
59 | |||
55 | class MagicHelpFormatter(argparse.RawDescriptionHelpFormatter): |
|
60 | class MagicHelpFormatter(argparse.RawDescriptionHelpFormatter): | |
56 | """ A HelpFormatter which dedents but otherwise preserves indentation. |
|
61 | """ A HelpFormatter which dedents but otherwise preserves indentation. | |
57 | """ |
|
62 | """ | |
58 | def _fill_text(self, text, width, indent): |
|
63 | def _fill_text(self, text, width, indent): | |
59 | return argparse.RawDescriptionHelpFormatter._fill_text(self, dedent(text), width, indent) |
|
64 | return argparse.RawDescriptionHelpFormatter._fill_text(self, dedent(text), width, indent) | |
60 |
|
65 | |||
61 | class MagicArgumentParser(argparse.ArgumentParser): |
|
66 | class MagicArgumentParser(argparse.ArgumentParser): | |
62 | """ An ArgumentParser tweaked for use by IPython magics. |
|
67 | """ An ArgumentParser tweaked for use by IPython magics. | |
63 | """ |
|
68 | """ | |
64 | def __init__(self, |
|
69 | def __init__(self, | |
65 | prog=None, |
|
70 | prog=None, | |
66 | usage=None, |
|
71 | usage=None, | |
67 | description=None, |
|
72 | description=None, | |
68 | epilog=None, |
|
73 | epilog=None, | |
69 | parents=None, |
|
74 | parents=None, | |
70 | formatter_class=MagicHelpFormatter, |
|
75 | formatter_class=MagicHelpFormatter, | |
71 | prefix_chars='-', |
|
76 | prefix_chars='-', | |
72 | argument_default=None, |
|
77 | argument_default=None, | |
73 | conflict_handler='error', |
|
78 | conflict_handler='error', | |
74 | add_help=False): |
|
79 | add_help=False): | |
75 | if parents is None: |
|
80 | if parents is None: | |
76 | parents = [] |
|
81 | parents = [] | |
77 | super(MagicArgumentParser, self).__init__(prog=prog, usage=usage, |
|
82 | super(MagicArgumentParser, self).__init__(prog=prog, usage=usage, | |
78 | description=description, epilog=epilog, |
|
83 | description=description, epilog=epilog, | |
79 | parents=parents, formatter_class=formatter_class, |
|
84 | parents=parents, formatter_class=formatter_class, | |
80 | prefix_chars=prefix_chars, argument_default=argument_default, |
|
85 | prefix_chars=prefix_chars, argument_default=argument_default, | |
81 | conflict_handler=conflict_handler, add_help=add_help) |
|
86 | conflict_handler=conflict_handler, add_help=add_help) | |
82 |
|
87 | |||
83 | def error(self, message): |
|
88 | def error(self, message): | |
84 | """ Raise a catchable error instead of exiting. |
|
89 | """ Raise a catchable error instead of exiting. | |
85 | """ |
|
90 | """ | |
86 | raise UsageError(message) |
|
91 | raise UsageError(message) | |
87 |
|
92 | |||
88 | def parse_argstring(self, argstring): |
|
93 | def parse_argstring(self, argstring): | |
89 | """ Split a string into an argument list and parse that argument list. |
|
94 | """ Split a string into an argument list and parse that argument list. | |
90 | """ |
|
95 | """ | |
91 | argv = arg_split(argstring) |
|
96 | argv = arg_split(argstring) | |
92 | return self.parse_args(argv) |
|
97 | return self.parse_args(argv) | |
93 |
|
98 | |||
94 |
|
99 | |||
95 | def construct_parser(magic_func): |
|
100 | def construct_parser(magic_func): | |
96 | """ Construct an argument parser using the function decorations. |
|
101 | """ Construct an argument parser using the function decorations. | |
97 | """ |
|
102 | """ | |
98 | kwds = getattr(magic_func, 'argcmd_kwds', {}) |
|
103 | kwds = getattr(magic_func, 'argcmd_kwds', {}) | |
99 | if 'description' not in kwds: |
|
104 | if 'description' not in kwds: | |
100 | kwds['description'] = getattr(magic_func, '__doc__', None) |
|
105 | kwds['description'] = getattr(magic_func, '__doc__', None) | |
101 | arg_name = real_name(magic_func) |
|
106 | arg_name = real_name(magic_func) | |
102 | parser = MagicArgumentParser(arg_name, **kwds) |
|
107 | parser = MagicArgumentParser(arg_name, **kwds) | |
103 | # Reverse the list of decorators in order to apply them in the |
|
108 | # Reverse the list of decorators in order to apply them in the | |
104 | # order in which they appear in the source. |
|
109 | # order in which they appear in the source. | |
105 | group = None |
|
110 | group = None | |
106 | for deco in magic_func.decorators[::-1]: |
|
111 | for deco in magic_func.decorators[::-1]: | |
107 | result = deco.add_to_parser(parser, group) |
|
112 | result = deco.add_to_parser(parser, group) | |
108 | if result is not None: |
|
113 | if result is not None: | |
109 | group = result |
|
114 | group = result | |
110 |
|
115 | |||
111 | # Replace the starting 'usage: ' with IPython's %. |
|
116 | # Replace the starting 'usage: ' with IPython's %. | |
112 | help_text = parser.format_help() |
|
117 | help_text = parser.format_help() | |
113 | if help_text.startswith('usage: '): |
|
118 | if help_text.startswith('usage: '): | |
114 | help_text = help_text.replace('usage: ', '%', 1) |
|
119 | help_text = help_text.replace('usage: ', '%', 1) | |
115 | else: |
|
120 | else: | |
116 | help_text = '%' + help_text |
|
121 | help_text = '%' + help_text | |
117 |
|
122 | |||
118 | # Replace the magic function's docstring with the full help text. |
|
123 | # Replace the magic function's docstring with the full help text. | |
119 | magic_func.__doc__ = help_text |
|
124 | magic_func.__doc__ = help_text | |
120 |
|
125 | |||
121 | return parser |
|
126 | return parser | |
122 |
|
127 | |||
123 |
|
128 | |||
124 | def parse_argstring(magic_func, argstring): |
|
129 | def parse_argstring(magic_func, argstring): | |
125 | """ Parse the string of arguments for the given magic function. |
|
130 | """ Parse the string of arguments for the given magic function. | |
126 | """ |
|
131 | """ | |
127 | return magic_func.parser.parse_argstring(argstring) |
|
132 | return magic_func.parser.parse_argstring(argstring) | |
128 |
|
133 | |||
129 |
|
134 | |||
130 | def real_name(magic_func): |
|
135 | def real_name(magic_func): | |
131 | """ Find the real name of the magic. |
|
136 | """ Find the real name of the magic. | |
132 | """ |
|
137 | """ | |
133 | magic_name = magic_func.__name__ |
|
138 | magic_name = magic_func.__name__ | |
134 | if magic_name.startswith('magic_'): |
|
139 | if magic_name.startswith('magic_'): | |
135 | magic_name = magic_name[len('magic_'):] |
|
140 | magic_name = magic_name[len('magic_'):] | |
136 | return getattr(magic_func, 'argcmd_name', magic_name) |
|
141 | return getattr(magic_func, 'argcmd_name', magic_name) | |
137 |
|
142 | |||
138 |
|
143 | |||
139 | class ArgDecorator(object): |
|
144 | class ArgDecorator(object): | |
140 | """ Base class for decorators to add ArgumentParser information to a method. |
|
145 | """ Base class for decorators to add ArgumentParser information to a method. | |
141 | """ |
|
146 | """ | |
142 |
|
147 | |||
143 | def __call__(self, func): |
|
148 | def __call__(self, func): | |
144 | if not getattr(func, 'has_arguments', False): |
|
149 | if not getattr(func, 'has_arguments', False): | |
145 | func.has_arguments = True |
|
150 | func.has_arguments = True | |
146 | func.decorators = [] |
|
151 | func.decorators = [] | |
147 | func.decorators.append(self) |
|
152 | func.decorators.append(self) | |
148 | return func |
|
153 | return func | |
149 |
|
154 | |||
150 | def add_to_parser(self, parser, group): |
|
155 | def add_to_parser(self, parser, group): | |
151 | """ Add this object's information to the parser, if necessary. |
|
156 | """ Add this object's information to the parser, if necessary. | |
152 | """ |
|
157 | """ | |
153 | pass |
|
158 | pass | |
154 |
|
159 | |||
155 |
|
160 | |||
156 | class magic_arguments(ArgDecorator): |
|
161 | class magic_arguments(ArgDecorator): | |
157 | """ Mark the magic as having argparse arguments and possibly adjust the |
|
162 | """ Mark the magic as having argparse arguments and possibly adjust the | |
158 | name. |
|
163 | name. | |
159 | """ |
|
164 | """ | |
160 |
|
165 | |||
161 | def __init__(self, name=None): |
|
166 | def __init__(self, name=None): | |
162 | self.name = name |
|
167 | self.name = name | |
163 |
|
168 | |||
164 | def __call__(self, func): |
|
169 | def __call__(self, func): | |
165 | if not getattr(func, 'has_arguments', False): |
|
170 | if not getattr(func, 'has_arguments', False): | |
166 | func.has_arguments = True |
|
171 | func.has_arguments = True | |
167 | func.decorators = [] |
|
172 | func.decorators = [] | |
168 | if self.name is not None: |
|
173 | if self.name is not None: | |
169 | func.argcmd_name = self.name |
|
174 | func.argcmd_name = self.name | |
170 | # This should be the first decorator in the list of decorators, thus the |
|
175 | # This should be the first decorator in the list of decorators, thus the | |
171 | # last to execute. Build the parser. |
|
176 | # last to execute. Build the parser. | |
172 | func.parser = construct_parser(func) |
|
177 | func.parser = construct_parser(func) | |
173 | return func |
|
178 | return func | |
174 |
|
179 | |||
175 |
|
180 | |||
176 | class ArgMethodWrapper(ArgDecorator): |
|
181 | class ArgMethodWrapper(ArgDecorator): | |
177 |
|
182 | |||
178 | """ |
|
183 | """ | |
179 | Base class to define a wrapper for ArgumentParser method. |
|
184 | Base class to define a wrapper for ArgumentParser method. | |
180 |
|
185 | |||
181 | Child class must define either `_method_name` or `add_to_parser`. |
|
186 | Child class must define either `_method_name` or `add_to_parser`. | |
182 |
|
187 | |||
183 | """ |
|
188 | """ | |
184 |
|
189 | |||
185 | _method_name = None |
|
190 | _method_name = None | |
186 |
|
191 | |||
187 | def __init__(self, *args, **kwds): |
|
192 | def __init__(self, *args, **kwds): | |
188 | self.args = args |
|
193 | self.args = args | |
189 | self.kwds = kwds |
|
194 | self.kwds = kwds | |
190 |
|
195 | |||
191 | def add_to_parser(self, parser, group): |
|
196 | def add_to_parser(self, parser, group): | |
192 | """ Add this object's information to the parser. |
|
197 | """ Add this object's information to the parser. | |
193 | """ |
|
198 | """ | |
194 | if group is not None: |
|
199 | if group is not None: | |
195 | parser = group |
|
200 | parser = group | |
196 | getattr(parser, self._method_name)(*self.args, **self.kwds) |
|
201 | getattr(parser, self._method_name)(*self.args, **self.kwds) | |
197 | return None |
|
202 | return None | |
198 |
|
203 | |||
199 |
|
204 | |||
200 | class argument(ArgMethodWrapper): |
|
205 | class argument(ArgMethodWrapper): | |
201 | """ Store arguments and keywords to pass to add_argument(). |
|
206 | """ Store arguments and keywords to pass to add_argument(). | |
202 |
|
207 | |||
203 | Instances also serve to decorate command methods. |
|
208 | Instances also serve to decorate command methods. | |
204 | """ |
|
209 | """ | |
205 | _method_name = 'add_argument' |
|
210 | _method_name = 'add_argument' | |
206 |
|
211 | |||
207 |
|
212 | |||
208 | class defaults(ArgMethodWrapper): |
|
213 | class defaults(ArgMethodWrapper): | |
209 | """ Store arguments and keywords to pass to set_defaults(). |
|
214 | """ Store arguments and keywords to pass to set_defaults(). | |
210 |
|
215 | |||
211 | Instances also serve to decorate command methods. |
|
216 | Instances also serve to decorate command methods. | |
212 | """ |
|
217 | """ | |
213 | _method_name = 'set_defaults' |
|
218 | _method_name = 'set_defaults' | |
214 |
|
219 | |||
215 |
|
220 | |||
216 | class argument_group(ArgMethodWrapper): |
|
221 | class argument_group(ArgMethodWrapper): | |
217 | """ Store arguments and keywords to pass to add_argument_group(). |
|
222 | """ Store arguments and keywords to pass to add_argument_group(). | |
218 |
|
223 | |||
219 | Instances also serve to decorate command methods. |
|
224 | Instances also serve to decorate command methods. | |
220 | """ |
|
225 | """ | |
221 |
|
226 | |||
222 | def add_to_parser(self, parser, group): |
|
227 | def add_to_parser(self, parser, group): | |
223 | """ Add this object's information to the parser. |
|
228 | """ Add this object's information to the parser. | |
224 | """ |
|
229 | """ | |
225 | return parser.add_argument_group(*self.args, **self.kwds) |
|
230 | return parser.add_argument_group(*self.args, **self.kwds) | |
226 |
|
231 | |||
227 |
|
232 | |||
228 | class kwds(ArgDecorator): |
|
233 | class kwds(ArgDecorator): | |
229 | """ Provide other keywords to the sub-parser constructor. |
|
234 | """ Provide other keywords to the sub-parser constructor. | |
230 | """ |
|
235 | """ | |
231 | def __init__(self, **kwds): |
|
236 | def __init__(self, **kwds): | |
232 | self.kwds = kwds |
|
237 | self.kwds = kwds | |
233 |
|
238 | |||
234 | def __call__(self, func): |
|
239 | def __call__(self, func): | |
235 | func = super(kwds, self).__call__(func) |
|
240 | func = super(kwds, self).__call__(func) | |
236 | func.argcmd_kwds = self.kwds |
|
241 | func.argcmd_kwds = self.kwds | |
237 | return func |
|
242 | return func | |
238 |
|
243 | |||
239 |
|
244 | |||
240 | __all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds', |
|
245 | __all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds', | |
241 | 'parse_argstring'] |
|
246 | 'parse_argstring'] |
@@ -1,1242 +1,1247 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """ |
|
2 | """ | |
3 | ultratb.py -- Spice up your tracebacks! |
|
3 | ultratb.py -- Spice up your tracebacks! | |
4 |
|
4 | |||
5 | * ColorTB |
|
5 | * ColorTB | |
6 | I've always found it a bit hard to visually parse tracebacks in Python. The |
|
6 | I've always found it a bit hard to visually parse tracebacks in Python. The | |
7 | ColorTB class is a solution to that problem. It colors the different parts of a |
|
7 | ColorTB class is a solution to that problem. It colors the different parts of a | |
8 | traceback in a manner similar to what you would expect from a syntax-highlighting |
|
8 | traceback in a manner similar to what you would expect from a syntax-highlighting | |
9 | text editor. |
|
9 | text editor. | |
10 |
|
10 | |||
11 | Installation instructions for ColorTB: |
|
11 | Installation instructions for ColorTB: | |
12 | import sys,ultratb |
|
12 | import sys,ultratb | |
13 | sys.excepthook = ultratb.ColorTB() |
|
13 | sys.excepthook = ultratb.ColorTB() | |
14 |
|
14 | |||
15 | * VerboseTB |
|
15 | * VerboseTB | |
16 | I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds |
|
16 | I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds | |
17 | of useful info when a traceback occurs. Ping originally had it spit out HTML |
|
17 | of useful info when a traceback occurs. Ping originally had it spit out HTML | |
18 | and intended it for CGI programmers, but why should they have all the fun? I |
|
18 | and intended it for CGI programmers, but why should they have all the fun? I | |
19 | altered it to spit out colored text to the terminal. It's a bit overwhelming, |
|
19 | altered it to spit out colored text to the terminal. It's a bit overwhelming, | |
20 | but kind of neat, and maybe useful for long-running programs that you believe |
|
20 | but kind of neat, and maybe useful for long-running programs that you believe | |
21 | are bug-free. If a crash *does* occur in that type of program you want details. |
|
21 | are bug-free. If a crash *does* occur in that type of program you want details. | |
22 | Give it a shot--you'll love it or you'll hate it. |
|
22 | Give it a shot--you'll love it or you'll hate it. | |
23 |
|
23 | |||
24 | Note: |
|
24 | Note: | |
25 |
|
25 | |||
26 | The Verbose mode prints the variables currently visible where the exception |
|
26 | The Verbose mode prints the variables currently visible where the exception | |
27 | happened (shortening their strings if too long). This can potentially be |
|
27 | happened (shortening their strings if too long). This can potentially be | |
28 | very slow, if you happen to have a huge data structure whose string |
|
28 | very slow, if you happen to have a huge data structure whose string | |
29 | representation is complex to compute. Your computer may appear to freeze for |
|
29 | representation is complex to compute. Your computer may appear to freeze for | |
30 | a while with cpu usage at 100%. If this occurs, you can cancel the traceback |
|
30 | a while with cpu usage at 100%. If this occurs, you can cancel the traceback | |
31 | with Ctrl-C (maybe hitting it more than once). |
|
31 | with Ctrl-C (maybe hitting it more than once). | |
32 |
|
32 | |||
33 | If you encounter this kind of situation often, you may want to use the |
|
33 | If you encounter this kind of situation often, you may want to use the | |
34 | Verbose_novars mode instead of the regular Verbose, which avoids formatting |
|
34 | Verbose_novars mode instead of the regular Verbose, which avoids formatting | |
35 | variables (but otherwise includes the information and context given by |
|
35 | variables (but otherwise includes the information and context given by | |
36 | Verbose). |
|
36 | Verbose). | |
37 |
|
37 | |||
38 |
|
38 | |||
39 | Installation instructions for ColorTB: |
|
39 | Installation instructions for ColorTB: | |
40 | import sys,ultratb |
|
40 | import sys,ultratb | |
41 | sys.excepthook = ultratb.VerboseTB() |
|
41 | sys.excepthook = ultratb.VerboseTB() | |
42 |
|
42 | |||
43 | Note: Much of the code in this module was lifted verbatim from the standard |
|
43 | Note: Much of the code in this module was lifted verbatim from the standard | |
44 | library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'. |
|
44 | library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'. | |
45 |
|
45 | |||
46 | * Color schemes |
|
46 | * Color schemes | |
47 | The colors are defined in the class TBTools through the use of the |
|
47 | The colors are defined in the class TBTools through the use of the | |
48 | ColorSchemeTable class. Currently the following exist: |
|
48 | ColorSchemeTable class. Currently the following exist: | |
49 |
|
49 | |||
50 | - NoColor: allows all of this module to be used in any terminal (the color |
|
50 | - NoColor: allows all of this module to be used in any terminal (the color | |
51 | escapes are just dummy blank strings). |
|
51 | escapes are just dummy blank strings). | |
52 |
|
52 | |||
53 | - Linux: is meant to look good in a terminal like the Linux console (black |
|
53 | - Linux: is meant to look good in a terminal like the Linux console (black | |
54 | or very dark background). |
|
54 | or very dark background). | |
55 |
|
55 | |||
56 | - LightBG: similar to Linux but swaps dark/light colors to be more readable |
|
56 | - LightBG: similar to Linux but swaps dark/light colors to be more readable | |
57 | in light background terminals. |
|
57 | in light background terminals. | |
58 |
|
58 | |||
59 | You can implement other color schemes easily, the syntax is fairly |
|
59 | You can implement other color schemes easily, the syntax is fairly | |
60 | self-explanatory. Please send back new schemes you develop to the author for |
|
60 | self-explanatory. Please send back new schemes you develop to the author for | |
61 | possible inclusion in future releases. |
|
61 | possible inclusion in future releases. | |
|
62 | ||||
|
63 | Inheritance diagram: | |||
|
64 | ||||
|
65 | .. inheritance-diagram:: IPython.core.ultratb | |||
|
66 | :parts: 3 | |||
62 | """ |
|
67 | """ | |
63 |
|
68 | |||
64 | #***************************************************************************** |
|
69 | #***************************************************************************** | |
65 | # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu> |
|
70 | # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu> | |
66 | # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu> |
|
71 | # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu> | |
67 | # |
|
72 | # | |
68 | # Distributed under the terms of the BSD License. The full license is in |
|
73 | # Distributed under the terms of the BSD License. The full license is in | |
69 | # the file COPYING, distributed as part of this software. |
|
74 | # the file COPYING, distributed as part of this software. | |
70 | #***************************************************************************** |
|
75 | #***************************************************************************** | |
71 |
|
76 | |||
72 | from __future__ import unicode_literals |
|
77 | from __future__ import unicode_literals | |
73 |
|
78 | |||
74 | import inspect |
|
79 | import inspect | |
75 | import keyword |
|
80 | import keyword | |
76 | import linecache |
|
81 | import linecache | |
77 | import os |
|
82 | import os | |
78 | import pydoc |
|
83 | import pydoc | |
79 | import re |
|
84 | import re | |
80 | import sys |
|
85 | import sys | |
81 | import time |
|
86 | import time | |
82 | import tokenize |
|
87 | import tokenize | |
83 | import traceback |
|
88 | import traceback | |
84 | import types |
|
89 | import types | |
85 |
|
90 | |||
86 | try: # Python 2 |
|
91 | try: # Python 2 | |
87 | generate_tokens = tokenize.generate_tokens |
|
92 | generate_tokens = tokenize.generate_tokens | |
88 | except AttributeError: # Python 3 |
|
93 | except AttributeError: # Python 3 | |
89 | generate_tokens = tokenize.tokenize |
|
94 | generate_tokens = tokenize.tokenize | |
90 |
|
95 | |||
91 | # For purposes of monkeypatching inspect to fix a bug in it. |
|
96 | # For purposes of monkeypatching inspect to fix a bug in it. | |
92 | from inspect import getsourcefile, getfile, getmodule,\ |
|
97 | from inspect import getsourcefile, getfile, getmodule,\ | |
93 | ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode |
|
98 | ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode | |
94 |
|
99 | |||
95 | # IPython's own modules |
|
100 | # IPython's own modules | |
96 | # Modified pdb which doesn't damage IPython's readline handling |
|
101 | # Modified pdb which doesn't damage IPython's readline handling | |
97 | from IPython.core import debugger, ipapi |
|
102 | from IPython.core import debugger, ipapi | |
98 | from IPython.core.display_trap import DisplayTrap |
|
103 | from IPython.core.display_trap import DisplayTrap | |
99 | from IPython.core.excolors import exception_colors |
|
104 | from IPython.core.excolors import exception_colors | |
100 | from IPython.utils import PyColorize |
|
105 | from IPython.utils import PyColorize | |
101 | from IPython.utils import io |
|
106 | from IPython.utils import io | |
102 | from IPython.utils import path as util_path |
|
107 | from IPython.utils import path as util_path | |
103 | from IPython.utils import py3compat |
|
108 | from IPython.utils import py3compat | |
104 | from IPython.utils import pyfile |
|
109 | from IPython.utils import pyfile | |
105 | from IPython.utils import ulinecache |
|
110 | from IPython.utils import ulinecache | |
106 | from IPython.utils.data import uniq_stable |
|
111 | from IPython.utils.data import uniq_stable | |
107 | from IPython.utils.openpy import read_py_file |
|
112 | from IPython.utils.openpy import read_py_file | |
108 | from IPython.utils.warn import info, error |
|
113 | from IPython.utils.warn import info, error | |
109 |
|
114 | |||
110 | # Globals |
|
115 | # Globals | |
111 | # amount of space to put line numbers before verbose tracebacks |
|
116 | # amount of space to put line numbers before verbose tracebacks | |
112 | INDENT_SIZE = 8 |
|
117 | INDENT_SIZE = 8 | |
113 |
|
118 | |||
114 | # Default color scheme. This is used, for example, by the traceback |
|
119 | # Default color scheme. This is used, for example, by the traceback | |
115 | # formatter. When running in an actual IPython instance, the user's rc.colors |
|
120 | # formatter. When running in an actual IPython instance, the user's rc.colors | |
116 | # value is used, but havinga module global makes this functionality available |
|
121 | # value is used, but havinga module global makes this functionality available | |
117 | # to users of ultratb who are NOT running inside ipython. |
|
122 | # to users of ultratb who are NOT running inside ipython. | |
118 | DEFAULT_SCHEME = 'NoColor' |
|
123 | DEFAULT_SCHEME = 'NoColor' | |
119 |
|
124 | |||
120 | #--------------------------------------------------------------------------- |
|
125 | #--------------------------------------------------------------------------- | |
121 | # Code begins |
|
126 | # Code begins | |
122 |
|
127 | |||
123 | # Utility functions |
|
128 | # Utility functions | |
124 | def inspect_error(): |
|
129 | def inspect_error(): | |
125 | """Print a message about internal inspect errors. |
|
130 | """Print a message about internal inspect errors. | |
126 |
|
131 | |||
127 | These are unfortunately quite common.""" |
|
132 | These are unfortunately quite common.""" | |
128 |
|
133 | |||
129 | error('Internal Python error in the inspect module.\n' |
|
134 | error('Internal Python error in the inspect module.\n' | |
130 | 'Below is the traceback from this internal error.\n') |
|
135 | 'Below is the traceback from this internal error.\n') | |
131 |
|
136 | |||
132 | # This function is a monkeypatch we apply to the Python inspect module. We have |
|
137 | # This function is a monkeypatch we apply to the Python inspect module. We have | |
133 | # now found when it's needed (see discussion on issue gh-1456), and we have a |
|
138 | # now found when it's needed (see discussion on issue gh-1456), and we have a | |
134 | # test case (IPython.core.tests.test_ultratb.ChangedPyFileTest) that fails if |
|
139 | # test case (IPython.core.tests.test_ultratb.ChangedPyFileTest) that fails if | |
135 | # the monkeypatch is not applied. TK, Aug 2012. |
|
140 | # the monkeypatch is not applied. TK, Aug 2012. | |
136 | def findsource(object): |
|
141 | def findsource(object): | |
137 | """Return the entire source file and starting line number for an object. |
|
142 | """Return the entire source file and starting line number for an object. | |
138 |
|
143 | |||
139 | The argument may be a module, class, method, function, traceback, frame, |
|
144 | The argument may be a module, class, method, function, traceback, frame, | |
140 | or code object. The source code is returned as a list of all the lines |
|
145 | or code object. The source code is returned as a list of all the lines | |
141 | in the file and the line number indexes a line in that list. An IOError |
|
146 | in the file and the line number indexes a line in that list. An IOError | |
142 | is raised if the source code cannot be retrieved. |
|
147 | is raised if the source code cannot be retrieved. | |
143 |
|
148 | |||
144 | FIXED version with which we monkeypatch the stdlib to work around a bug.""" |
|
149 | FIXED version with which we monkeypatch the stdlib to work around a bug.""" | |
145 |
|
150 | |||
146 | file = getsourcefile(object) or getfile(object) |
|
151 | file = getsourcefile(object) or getfile(object) | |
147 | # If the object is a frame, then trying to get the globals dict from its |
|
152 | # If the object is a frame, then trying to get the globals dict from its | |
148 | # module won't work. Instead, the frame object itself has the globals |
|
153 | # module won't work. Instead, the frame object itself has the globals | |
149 | # dictionary. |
|
154 | # dictionary. | |
150 | globals_dict = None |
|
155 | globals_dict = None | |
151 | if inspect.isframe(object): |
|
156 | if inspect.isframe(object): | |
152 | # XXX: can this ever be false? |
|
157 | # XXX: can this ever be false? | |
153 | globals_dict = object.f_globals |
|
158 | globals_dict = object.f_globals | |
154 | else: |
|
159 | else: | |
155 | module = getmodule(object, file) |
|
160 | module = getmodule(object, file) | |
156 | if module: |
|
161 | if module: | |
157 | globals_dict = module.__dict__ |
|
162 | globals_dict = module.__dict__ | |
158 | lines = linecache.getlines(file, globals_dict) |
|
163 | lines = linecache.getlines(file, globals_dict) | |
159 | if not lines: |
|
164 | if not lines: | |
160 | raise IOError('could not get source code') |
|
165 | raise IOError('could not get source code') | |
161 |
|
166 | |||
162 | if ismodule(object): |
|
167 | if ismodule(object): | |
163 | return lines, 0 |
|
168 | return lines, 0 | |
164 |
|
169 | |||
165 | if isclass(object): |
|
170 | if isclass(object): | |
166 | name = object.__name__ |
|
171 | name = object.__name__ | |
167 | pat = re.compile(r'^(\s*)class\s*' + name + r'\b') |
|
172 | pat = re.compile(r'^(\s*)class\s*' + name + r'\b') | |
168 | # make some effort to find the best matching class definition: |
|
173 | # make some effort to find the best matching class definition: | |
169 | # use the one with the least indentation, which is the one |
|
174 | # use the one with the least indentation, which is the one | |
170 | # that's most probably not inside a function definition. |
|
175 | # that's most probably not inside a function definition. | |
171 | candidates = [] |
|
176 | candidates = [] | |
172 | for i in range(len(lines)): |
|
177 | for i in range(len(lines)): | |
173 | match = pat.match(lines[i]) |
|
178 | match = pat.match(lines[i]) | |
174 | if match: |
|
179 | if match: | |
175 | # if it's at toplevel, it's already the best one |
|
180 | # if it's at toplevel, it's already the best one | |
176 | if lines[i][0] == 'c': |
|
181 | if lines[i][0] == 'c': | |
177 | return lines, i |
|
182 | return lines, i | |
178 | # else add whitespace to candidate list |
|
183 | # else add whitespace to candidate list | |
179 | candidates.append((match.group(1), i)) |
|
184 | candidates.append((match.group(1), i)) | |
180 | if candidates: |
|
185 | if candidates: | |
181 | # this will sort by whitespace, and by line number, |
|
186 | # this will sort by whitespace, and by line number, | |
182 | # less whitespace first |
|
187 | # less whitespace first | |
183 | candidates.sort() |
|
188 | candidates.sort() | |
184 | return lines, candidates[0][1] |
|
189 | return lines, candidates[0][1] | |
185 | else: |
|
190 | else: | |
186 | raise IOError('could not find class definition') |
|
191 | raise IOError('could not find class definition') | |
187 |
|
192 | |||
188 | if ismethod(object): |
|
193 | if ismethod(object): | |
189 | object = object.im_func |
|
194 | object = object.im_func | |
190 | if isfunction(object): |
|
195 | if isfunction(object): | |
191 | object = object.func_code |
|
196 | object = object.func_code | |
192 | if istraceback(object): |
|
197 | if istraceback(object): | |
193 | object = object.tb_frame |
|
198 | object = object.tb_frame | |
194 | if isframe(object): |
|
199 | if isframe(object): | |
195 | object = object.f_code |
|
200 | object = object.f_code | |
196 | if iscode(object): |
|
201 | if iscode(object): | |
197 | if not hasattr(object, 'co_firstlineno'): |
|
202 | if not hasattr(object, 'co_firstlineno'): | |
198 | raise IOError('could not find function definition') |
|
203 | raise IOError('could not find function definition') | |
199 | pat = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)') |
|
204 | pat = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)') | |
200 | pmatch = pat.match |
|
205 | pmatch = pat.match | |
201 | # fperez - fix: sometimes, co_firstlineno can give a number larger than |
|
206 | # fperez - fix: sometimes, co_firstlineno can give a number larger than | |
202 | # the length of lines, which causes an error. Safeguard against that. |
|
207 | # the length of lines, which causes an error. Safeguard against that. | |
203 | lnum = min(object.co_firstlineno,len(lines))-1 |
|
208 | lnum = min(object.co_firstlineno,len(lines))-1 | |
204 | while lnum > 0: |
|
209 | while lnum > 0: | |
205 | if pmatch(lines[lnum]): break |
|
210 | if pmatch(lines[lnum]): break | |
206 | lnum -= 1 |
|
211 | lnum -= 1 | |
207 |
|
212 | |||
208 | return lines, lnum |
|
213 | return lines, lnum | |
209 | raise IOError('could not find code object') |
|
214 | raise IOError('could not find code object') | |
210 |
|
215 | |||
211 | # Monkeypatch inspect to apply our bugfix. This code only works with Python >= 2.5 |
|
216 | # Monkeypatch inspect to apply our bugfix. This code only works with Python >= 2.5 | |
212 | inspect.findsource = findsource |
|
217 | inspect.findsource = findsource | |
213 |
|
218 | |||
214 | def fix_frame_records_filenames(records): |
|
219 | def fix_frame_records_filenames(records): | |
215 | """Try to fix the filenames in each record from inspect.getinnerframes(). |
|
220 | """Try to fix the filenames in each record from inspect.getinnerframes(). | |
216 |
|
221 | |||
217 | Particularly, modules loaded from within zip files have useless filenames |
|
222 | Particularly, modules loaded from within zip files have useless filenames | |
218 | attached to their code object, and inspect.getinnerframes() just uses it. |
|
223 | attached to their code object, and inspect.getinnerframes() just uses it. | |
219 | """ |
|
224 | """ | |
220 | fixed_records = [] |
|
225 | fixed_records = [] | |
221 | for frame, filename, line_no, func_name, lines, index in records: |
|
226 | for frame, filename, line_no, func_name, lines, index in records: | |
222 | # Look inside the frame's globals dictionary for __file__, which should |
|
227 | # Look inside the frame's globals dictionary for __file__, which should | |
223 | # be better. |
|
228 | # be better. | |
224 | better_fn = frame.f_globals.get('__file__', None) |
|
229 | better_fn = frame.f_globals.get('__file__', None) | |
225 | if isinstance(better_fn, str): |
|
230 | if isinstance(better_fn, str): | |
226 | # Check the type just in case someone did something weird with |
|
231 | # Check the type just in case someone did something weird with | |
227 | # __file__. It might also be None if the error occurred during |
|
232 | # __file__. It might also be None if the error occurred during | |
228 | # import. |
|
233 | # import. | |
229 | filename = better_fn |
|
234 | filename = better_fn | |
230 | fixed_records.append((frame, filename, line_no, func_name, lines, index)) |
|
235 | fixed_records.append((frame, filename, line_no, func_name, lines, index)) | |
231 | return fixed_records |
|
236 | return fixed_records | |
232 |
|
237 | |||
233 |
|
238 | |||
234 | def _fixed_getinnerframes(etb, context=1,tb_offset=0): |
|
239 | def _fixed_getinnerframes(etb, context=1,tb_offset=0): | |
235 | LNUM_POS, LINES_POS, INDEX_POS = 2, 4, 5 |
|
240 | LNUM_POS, LINES_POS, INDEX_POS = 2, 4, 5 | |
236 |
|
241 | |||
237 | records = fix_frame_records_filenames(inspect.getinnerframes(etb, context)) |
|
242 | records = fix_frame_records_filenames(inspect.getinnerframes(etb, context)) | |
238 |
|
243 | |||
239 | # If the error is at the console, don't build any context, since it would |
|
244 | # If the error is at the console, don't build any context, since it would | |
240 | # otherwise produce 5 blank lines printed out (there is no file at the |
|
245 | # otherwise produce 5 blank lines printed out (there is no file at the | |
241 | # console) |
|
246 | # console) | |
242 | rec_check = records[tb_offset:] |
|
247 | rec_check = records[tb_offset:] | |
243 | try: |
|
248 | try: | |
244 | rname = rec_check[0][1] |
|
249 | rname = rec_check[0][1] | |
245 | if rname == '<ipython console>' or rname.endswith('<string>'): |
|
250 | if rname == '<ipython console>' or rname.endswith('<string>'): | |
246 | return rec_check |
|
251 | return rec_check | |
247 | except IndexError: |
|
252 | except IndexError: | |
248 | pass |
|
253 | pass | |
249 |
|
254 | |||
250 | aux = traceback.extract_tb(etb) |
|
255 | aux = traceback.extract_tb(etb) | |
251 | assert len(records) == len(aux) |
|
256 | assert len(records) == len(aux) | |
252 | for i, (file, lnum, _, _) in zip(range(len(records)), aux): |
|
257 | for i, (file, lnum, _, _) in zip(range(len(records)), aux): | |
253 | maybeStart = lnum-1 - context//2 |
|
258 | maybeStart = lnum-1 - context//2 | |
254 | start = max(maybeStart, 0) |
|
259 | start = max(maybeStart, 0) | |
255 | end = start + context |
|
260 | end = start + context | |
256 | lines = ulinecache.getlines(file)[start:end] |
|
261 | lines = ulinecache.getlines(file)[start:end] | |
257 | buf = list(records[i]) |
|
262 | buf = list(records[i]) | |
258 | buf[LNUM_POS] = lnum |
|
263 | buf[LNUM_POS] = lnum | |
259 | buf[INDEX_POS] = lnum - 1 - start |
|
264 | buf[INDEX_POS] = lnum - 1 - start | |
260 | buf[LINES_POS] = lines |
|
265 | buf[LINES_POS] = lines | |
261 | records[i] = tuple(buf) |
|
266 | records[i] = tuple(buf) | |
262 | return records[tb_offset:] |
|
267 | return records[tb_offset:] | |
263 |
|
268 | |||
264 | # Helper function -- largely belongs to VerboseTB, but we need the same |
|
269 | # Helper function -- largely belongs to VerboseTB, but we need the same | |
265 | # functionality to produce a pseudo verbose TB for SyntaxErrors, so that they |
|
270 | # functionality to produce a pseudo verbose TB for SyntaxErrors, so that they | |
266 | # can be recognized properly by ipython.el's py-traceback-line-re |
|
271 | # can be recognized properly by ipython.el's py-traceback-line-re | |
267 | # (SyntaxErrors have to be treated specially because they have no traceback) |
|
272 | # (SyntaxErrors have to be treated specially because they have no traceback) | |
268 |
|
273 | |||
269 | _parser = PyColorize.Parser() |
|
274 | _parser = PyColorize.Parser() | |
270 |
|
275 | |||
271 | def _format_traceback_lines(lnum, index, lines, Colors, lvals=None,scheme=None): |
|
276 | def _format_traceback_lines(lnum, index, lines, Colors, lvals=None,scheme=None): | |
272 | numbers_width = INDENT_SIZE - 1 |
|
277 | numbers_width = INDENT_SIZE - 1 | |
273 | res = [] |
|
278 | res = [] | |
274 | i = lnum - index |
|
279 | i = lnum - index | |
275 |
|
280 | |||
276 | # This lets us get fully syntax-highlighted tracebacks. |
|
281 | # This lets us get fully syntax-highlighted tracebacks. | |
277 | if scheme is None: |
|
282 | if scheme is None: | |
278 | ipinst = ipapi.get() |
|
283 | ipinst = ipapi.get() | |
279 | if ipinst is not None: |
|
284 | if ipinst is not None: | |
280 | scheme = ipinst.colors |
|
285 | scheme = ipinst.colors | |
281 | else: |
|
286 | else: | |
282 | scheme = DEFAULT_SCHEME |
|
287 | scheme = DEFAULT_SCHEME | |
283 |
|
288 | |||
284 | _line_format = _parser.format2 |
|
289 | _line_format = _parser.format2 | |
285 |
|
290 | |||
286 | for line in lines: |
|
291 | for line in lines: | |
287 | line = py3compat.cast_unicode(line) |
|
292 | line = py3compat.cast_unicode(line) | |
288 |
|
293 | |||
289 | new_line, err = _line_format(line, 'str', scheme) |
|
294 | new_line, err = _line_format(line, 'str', scheme) | |
290 | if not err: line = new_line |
|
295 | if not err: line = new_line | |
291 |
|
296 | |||
292 | if i == lnum: |
|
297 | if i == lnum: | |
293 | # This is the line with the error |
|
298 | # This is the line with the error | |
294 | pad = numbers_width - len(str(i)) |
|
299 | pad = numbers_width - len(str(i)) | |
295 | if pad >= 3: |
|
300 | if pad >= 3: | |
296 | marker = '-'*(pad-3) + '-> ' |
|
301 | marker = '-'*(pad-3) + '-> ' | |
297 | elif pad == 2: |
|
302 | elif pad == 2: | |
298 | marker = '> ' |
|
303 | marker = '> ' | |
299 | elif pad == 1: |
|
304 | elif pad == 1: | |
300 | marker = '>' |
|
305 | marker = '>' | |
301 | else: |
|
306 | else: | |
302 | marker = '' |
|
307 | marker = '' | |
303 | num = marker + str(i) |
|
308 | num = marker + str(i) | |
304 | line = '%s%s%s %s%s' %(Colors.linenoEm, num, |
|
309 | line = '%s%s%s %s%s' %(Colors.linenoEm, num, | |
305 | Colors.line, line, Colors.Normal) |
|
310 | Colors.line, line, Colors.Normal) | |
306 | else: |
|
311 | else: | |
307 | num = '%*s' % (numbers_width,i) |
|
312 | num = '%*s' % (numbers_width,i) | |
308 | line = '%s%s%s %s' %(Colors.lineno, num, |
|
313 | line = '%s%s%s %s' %(Colors.lineno, num, | |
309 | Colors.Normal, line) |
|
314 | Colors.Normal, line) | |
310 |
|
315 | |||
311 | res.append(line) |
|
316 | res.append(line) | |
312 | if lvals and i == lnum: |
|
317 | if lvals and i == lnum: | |
313 | res.append(lvals + '\n') |
|
318 | res.append(lvals + '\n') | |
314 | i = i + 1 |
|
319 | i = i + 1 | |
315 | return res |
|
320 | return res | |
316 |
|
321 | |||
317 |
|
322 | |||
318 | #--------------------------------------------------------------------------- |
|
323 | #--------------------------------------------------------------------------- | |
319 | # Module classes |
|
324 | # Module classes | |
320 | class TBTools(object): |
|
325 | class TBTools(object): | |
321 | """Basic tools used by all traceback printer classes.""" |
|
326 | """Basic tools used by all traceback printer classes.""" | |
322 |
|
327 | |||
323 | # Number of frames to skip when reporting tracebacks |
|
328 | # Number of frames to skip when reporting tracebacks | |
324 | tb_offset = 0 |
|
329 | tb_offset = 0 | |
325 |
|
330 | |||
326 | def __init__(self, color_scheme='NoColor', call_pdb=False, ostream=None): |
|
331 | def __init__(self, color_scheme='NoColor', call_pdb=False, ostream=None): | |
327 | # Whether to call the interactive pdb debugger after printing |
|
332 | # Whether to call the interactive pdb debugger after printing | |
328 | # tracebacks or not |
|
333 | # tracebacks or not | |
329 | self.call_pdb = call_pdb |
|
334 | self.call_pdb = call_pdb | |
330 |
|
335 | |||
331 | # Output stream to write to. Note that we store the original value in |
|
336 | # Output stream to write to. Note that we store the original value in | |
332 | # a private attribute and then make the public ostream a property, so |
|
337 | # a private attribute and then make the public ostream a property, so | |
333 | # that we can delay accessing io.stdout until runtime. The way |
|
338 | # that we can delay accessing io.stdout until runtime. The way | |
334 | # things are written now, the io.stdout object is dynamically managed |
|
339 | # things are written now, the io.stdout object is dynamically managed | |
335 | # so a reference to it should NEVER be stored statically. This |
|
340 | # so a reference to it should NEVER be stored statically. This | |
336 | # property approach confines this detail to a single location, and all |
|
341 | # property approach confines this detail to a single location, and all | |
337 | # subclasses can simply access self.ostream for writing. |
|
342 | # subclasses can simply access self.ostream for writing. | |
338 | self._ostream = ostream |
|
343 | self._ostream = ostream | |
339 |
|
344 | |||
340 | # Create color table |
|
345 | # Create color table | |
341 | self.color_scheme_table = exception_colors() |
|
346 | self.color_scheme_table = exception_colors() | |
342 |
|
347 | |||
343 | self.set_colors(color_scheme) |
|
348 | self.set_colors(color_scheme) | |
344 | self.old_scheme = color_scheme # save initial value for toggles |
|
349 | self.old_scheme = color_scheme # save initial value for toggles | |
345 |
|
350 | |||
346 | if call_pdb: |
|
351 | if call_pdb: | |
347 | self.pdb = debugger.Pdb(self.color_scheme_table.active_scheme_name) |
|
352 | self.pdb = debugger.Pdb(self.color_scheme_table.active_scheme_name) | |
348 | else: |
|
353 | else: | |
349 | self.pdb = None |
|
354 | self.pdb = None | |
350 |
|
355 | |||
351 | def _get_ostream(self): |
|
356 | def _get_ostream(self): | |
352 | """Output stream that exceptions are written to. |
|
357 | """Output stream that exceptions are written to. | |
353 |
|
358 | |||
354 | Valid values are: |
|
359 | Valid values are: | |
355 |
|
360 | |||
356 | - None: the default, which means that IPython will dynamically resolve |
|
361 | - None: the default, which means that IPython will dynamically resolve | |
357 | to io.stdout. This ensures compatibility with most tools, including |
|
362 | to io.stdout. This ensures compatibility with most tools, including | |
358 | Windows (where plain stdout doesn't recognize ANSI escapes). |
|
363 | Windows (where plain stdout doesn't recognize ANSI escapes). | |
359 |
|
364 | |||
360 | - Any object with 'write' and 'flush' attributes. |
|
365 | - Any object with 'write' and 'flush' attributes. | |
361 | """ |
|
366 | """ | |
362 | return io.stdout if self._ostream is None else self._ostream |
|
367 | return io.stdout if self._ostream is None else self._ostream | |
363 |
|
368 | |||
364 | def _set_ostream(self, val): |
|
369 | def _set_ostream(self, val): | |
365 | assert val is None or (hasattr(val, 'write') and hasattr(val, 'flush')) |
|
370 | assert val is None or (hasattr(val, 'write') and hasattr(val, 'flush')) | |
366 | self._ostream = val |
|
371 | self._ostream = val | |
367 |
|
372 | |||
368 | ostream = property(_get_ostream, _set_ostream) |
|
373 | ostream = property(_get_ostream, _set_ostream) | |
369 |
|
374 | |||
370 | def set_colors(self,*args,**kw): |
|
375 | def set_colors(self,*args,**kw): | |
371 | """Shorthand access to the color table scheme selector method.""" |
|
376 | """Shorthand access to the color table scheme selector method.""" | |
372 |
|
377 | |||
373 | # Set own color table |
|
378 | # Set own color table | |
374 | self.color_scheme_table.set_active_scheme(*args,**kw) |
|
379 | self.color_scheme_table.set_active_scheme(*args,**kw) | |
375 | # for convenience, set Colors to the active scheme |
|
380 | # for convenience, set Colors to the active scheme | |
376 | self.Colors = self.color_scheme_table.active_colors |
|
381 | self.Colors = self.color_scheme_table.active_colors | |
377 | # Also set colors of debugger |
|
382 | # Also set colors of debugger | |
378 | if hasattr(self,'pdb') and self.pdb is not None: |
|
383 | if hasattr(self,'pdb') and self.pdb is not None: | |
379 | self.pdb.set_colors(*args,**kw) |
|
384 | self.pdb.set_colors(*args,**kw) | |
380 |
|
385 | |||
381 | def color_toggle(self): |
|
386 | def color_toggle(self): | |
382 | """Toggle between the currently active color scheme and NoColor.""" |
|
387 | """Toggle between the currently active color scheme and NoColor.""" | |
383 |
|
388 | |||
384 | if self.color_scheme_table.active_scheme_name == 'NoColor': |
|
389 | if self.color_scheme_table.active_scheme_name == 'NoColor': | |
385 | self.color_scheme_table.set_active_scheme(self.old_scheme) |
|
390 | self.color_scheme_table.set_active_scheme(self.old_scheme) | |
386 | self.Colors = self.color_scheme_table.active_colors |
|
391 | self.Colors = self.color_scheme_table.active_colors | |
387 | else: |
|
392 | else: | |
388 | self.old_scheme = self.color_scheme_table.active_scheme_name |
|
393 | self.old_scheme = self.color_scheme_table.active_scheme_name | |
389 | self.color_scheme_table.set_active_scheme('NoColor') |
|
394 | self.color_scheme_table.set_active_scheme('NoColor') | |
390 | self.Colors = self.color_scheme_table.active_colors |
|
395 | self.Colors = self.color_scheme_table.active_colors | |
391 |
|
396 | |||
392 | def stb2text(self, stb): |
|
397 | def stb2text(self, stb): | |
393 | """Convert a structured traceback (a list) to a string.""" |
|
398 | """Convert a structured traceback (a list) to a string.""" | |
394 | return '\n'.join(stb) |
|
399 | return '\n'.join(stb) | |
395 |
|
400 | |||
396 | def text(self, etype, value, tb, tb_offset=None, context=5): |
|
401 | def text(self, etype, value, tb, tb_offset=None, context=5): | |
397 | """Return formatted traceback. |
|
402 | """Return formatted traceback. | |
398 |
|
403 | |||
399 | Subclasses may override this if they add extra arguments. |
|
404 | Subclasses may override this if they add extra arguments. | |
400 | """ |
|
405 | """ | |
401 | tb_list = self.structured_traceback(etype, value, tb, |
|
406 | tb_list = self.structured_traceback(etype, value, tb, | |
402 | tb_offset, context) |
|
407 | tb_offset, context) | |
403 | return self.stb2text(tb_list) |
|
408 | return self.stb2text(tb_list) | |
404 |
|
409 | |||
405 | def structured_traceback(self, etype, evalue, tb, tb_offset=None, |
|
410 | def structured_traceback(self, etype, evalue, tb, tb_offset=None, | |
406 | context=5, mode=None): |
|
411 | context=5, mode=None): | |
407 | """Return a list of traceback frames. |
|
412 | """Return a list of traceback frames. | |
408 |
|
413 | |||
409 | Must be implemented by each class. |
|
414 | Must be implemented by each class. | |
410 | """ |
|
415 | """ | |
411 | raise NotImplementedError() |
|
416 | raise NotImplementedError() | |
412 |
|
417 | |||
413 |
|
418 | |||
414 | #--------------------------------------------------------------------------- |
|
419 | #--------------------------------------------------------------------------- | |
415 | class ListTB(TBTools): |
|
420 | class ListTB(TBTools): | |
416 | """Print traceback information from a traceback list, with optional color. |
|
421 | """Print traceback information from a traceback list, with optional color. | |
417 |
|
422 | |||
418 | Calling: requires 3 arguments: |
|
423 | Calling: requires 3 arguments: | |
419 | (etype, evalue, elist) |
|
424 | (etype, evalue, elist) | |
420 | as would be obtained by: |
|
425 | as would be obtained by: | |
421 | etype, evalue, tb = sys.exc_info() |
|
426 | etype, evalue, tb = sys.exc_info() | |
422 | if tb: |
|
427 | if tb: | |
423 | elist = traceback.extract_tb(tb) |
|
428 | elist = traceback.extract_tb(tb) | |
424 | else: |
|
429 | else: | |
425 | elist = None |
|
430 | elist = None | |
426 |
|
431 | |||
427 | It can thus be used by programs which need to process the traceback before |
|
432 | It can thus be used by programs which need to process the traceback before | |
428 | printing (such as console replacements based on the code module from the |
|
433 | printing (such as console replacements based on the code module from the | |
429 | standard library). |
|
434 | standard library). | |
430 |
|
435 | |||
431 | Because they are meant to be called without a full traceback (only a |
|
436 | Because they are meant to be called without a full traceback (only a | |
432 | list), instances of this class can't call the interactive pdb debugger.""" |
|
437 | list), instances of this class can't call the interactive pdb debugger.""" | |
433 |
|
438 | |||
434 | def __init__(self,color_scheme = 'NoColor', call_pdb=False, ostream=None): |
|
439 | def __init__(self,color_scheme = 'NoColor', call_pdb=False, ostream=None): | |
435 | TBTools.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb, |
|
440 | TBTools.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb, | |
436 | ostream=ostream) |
|
441 | ostream=ostream) | |
437 |
|
442 | |||
438 | def __call__(self, etype, value, elist): |
|
443 | def __call__(self, etype, value, elist): | |
439 | self.ostream.flush() |
|
444 | self.ostream.flush() | |
440 | self.ostream.write(self.text(etype, value, elist)) |
|
445 | self.ostream.write(self.text(etype, value, elist)) | |
441 | self.ostream.write('\n') |
|
446 | self.ostream.write('\n') | |
442 |
|
447 | |||
443 | def structured_traceback(self, etype, value, elist, tb_offset=None, |
|
448 | def structured_traceback(self, etype, value, elist, tb_offset=None, | |
444 | context=5): |
|
449 | context=5): | |
445 | """Return a color formatted string with the traceback info. |
|
450 | """Return a color formatted string with the traceback info. | |
446 |
|
451 | |||
447 | Parameters |
|
452 | Parameters | |
448 | ---------- |
|
453 | ---------- | |
449 | etype : exception type |
|
454 | etype : exception type | |
450 | Type of the exception raised. |
|
455 | Type of the exception raised. | |
451 |
|
456 | |||
452 | value : object |
|
457 | value : object | |
453 | Data stored in the exception |
|
458 | Data stored in the exception | |
454 |
|
459 | |||
455 | elist : list |
|
460 | elist : list | |
456 | List of frames, see class docstring for details. |
|
461 | List of frames, see class docstring for details. | |
457 |
|
462 | |||
458 | tb_offset : int, optional |
|
463 | tb_offset : int, optional | |
459 | Number of frames in the traceback to skip. If not given, the |
|
464 | Number of frames in the traceback to skip. If not given, the | |
460 | instance value is used (set in constructor). |
|
465 | instance value is used (set in constructor). | |
461 |
|
466 | |||
462 | context : int, optional |
|
467 | context : int, optional | |
463 | Number of lines of context information to print. |
|
468 | Number of lines of context information to print. | |
464 |
|
469 | |||
465 | Returns |
|
470 | Returns | |
466 | ------- |
|
471 | ------- | |
467 | String with formatted exception. |
|
472 | String with formatted exception. | |
468 | """ |
|
473 | """ | |
469 | tb_offset = self.tb_offset if tb_offset is None else tb_offset |
|
474 | tb_offset = self.tb_offset if tb_offset is None else tb_offset | |
470 | Colors = self.Colors |
|
475 | Colors = self.Colors | |
471 | out_list = [] |
|
476 | out_list = [] | |
472 | if elist: |
|
477 | if elist: | |
473 |
|
478 | |||
474 | if tb_offset and len(elist) > tb_offset: |
|
479 | if tb_offset and len(elist) > tb_offset: | |
475 | elist = elist[tb_offset:] |
|
480 | elist = elist[tb_offset:] | |
476 |
|
481 | |||
477 | out_list.append('Traceback %s(most recent call last)%s:' % |
|
482 | out_list.append('Traceback %s(most recent call last)%s:' % | |
478 | (Colors.normalEm, Colors.Normal) + '\n') |
|
483 | (Colors.normalEm, Colors.Normal) + '\n') | |
479 | out_list.extend(self._format_list(elist)) |
|
484 | out_list.extend(self._format_list(elist)) | |
480 | # The exception info should be a single entry in the list. |
|
485 | # The exception info should be a single entry in the list. | |
481 | lines = ''.join(self._format_exception_only(etype, value)) |
|
486 | lines = ''.join(self._format_exception_only(etype, value)) | |
482 | out_list.append(lines) |
|
487 | out_list.append(lines) | |
483 |
|
488 | |||
484 | # Note: this code originally read: |
|
489 | # Note: this code originally read: | |
485 |
|
490 | |||
486 | ## for line in lines[:-1]: |
|
491 | ## for line in lines[:-1]: | |
487 | ## out_list.append(" "+line) |
|
492 | ## out_list.append(" "+line) | |
488 | ## out_list.append(lines[-1]) |
|
493 | ## out_list.append(lines[-1]) | |
489 |
|
494 | |||
490 | # This means it was indenting everything but the last line by a little |
|
495 | # This means it was indenting everything but the last line by a little | |
491 | # bit. I've disabled this for now, but if we see ugliness somewhre we |
|
496 | # bit. I've disabled this for now, but if we see ugliness somewhre we | |
492 | # can restore it. |
|
497 | # can restore it. | |
493 |
|
498 | |||
494 | return out_list |
|
499 | return out_list | |
495 |
|
500 | |||
496 | def _format_list(self, extracted_list): |
|
501 | def _format_list(self, extracted_list): | |
497 | """Format a list of traceback entry tuples for printing. |
|
502 | """Format a list of traceback entry tuples for printing. | |
498 |
|
503 | |||
499 | Given a list of tuples as returned by extract_tb() or |
|
504 | Given a list of tuples as returned by extract_tb() or | |
500 | extract_stack(), return a list of strings ready for printing. |
|
505 | extract_stack(), return a list of strings ready for printing. | |
501 | Each string in the resulting list corresponds to the item with the |
|
506 | Each string in the resulting list corresponds to the item with the | |
502 | same index in the argument list. Each string ends in a newline; |
|
507 | same index in the argument list. Each string ends in a newline; | |
503 | the strings may contain internal newlines as well, for those items |
|
508 | the strings may contain internal newlines as well, for those items | |
504 | whose source text line is not None. |
|
509 | whose source text line is not None. | |
505 |
|
510 | |||
506 | Lifted almost verbatim from traceback.py |
|
511 | Lifted almost verbatim from traceback.py | |
507 | """ |
|
512 | """ | |
508 |
|
513 | |||
509 | Colors = self.Colors |
|
514 | Colors = self.Colors | |
510 | list = [] |
|
515 | list = [] | |
511 | for filename, lineno, name, line in extracted_list[:-1]: |
|
516 | for filename, lineno, name, line in extracted_list[:-1]: | |
512 | item = ' File %s"%s"%s, line %s%d%s, in %s%s%s\n' % \ |
|
517 | item = ' File %s"%s"%s, line %s%d%s, in %s%s%s\n' % \ | |
513 | (Colors.filename, filename, Colors.Normal, |
|
518 | (Colors.filename, filename, Colors.Normal, | |
514 | Colors.lineno, lineno, Colors.Normal, |
|
519 | Colors.lineno, lineno, Colors.Normal, | |
515 | Colors.name, name, Colors.Normal) |
|
520 | Colors.name, name, Colors.Normal) | |
516 | if line: |
|
521 | if line: | |
517 | item += ' %s\n' % line.strip() |
|
522 | item += ' %s\n' % line.strip() | |
518 | list.append(item) |
|
523 | list.append(item) | |
519 | # Emphasize the last entry |
|
524 | # Emphasize the last entry | |
520 | filename, lineno, name, line = extracted_list[-1] |
|
525 | filename, lineno, name, line = extracted_list[-1] | |
521 | item = '%s File %s"%s"%s, line %s%d%s, in %s%s%s%s\n' % \ |
|
526 | item = '%s File %s"%s"%s, line %s%d%s, in %s%s%s%s\n' % \ | |
522 | (Colors.normalEm, |
|
527 | (Colors.normalEm, | |
523 | Colors.filenameEm, filename, Colors.normalEm, |
|
528 | Colors.filenameEm, filename, Colors.normalEm, | |
524 | Colors.linenoEm, lineno, Colors.normalEm, |
|
529 | Colors.linenoEm, lineno, Colors.normalEm, | |
525 | Colors.nameEm, name, Colors.normalEm, |
|
530 | Colors.nameEm, name, Colors.normalEm, | |
526 | Colors.Normal) |
|
531 | Colors.Normal) | |
527 | if line: |
|
532 | if line: | |
528 | item += '%s %s%s\n' % (Colors.line, line.strip(), |
|
533 | item += '%s %s%s\n' % (Colors.line, line.strip(), | |
529 | Colors.Normal) |
|
534 | Colors.Normal) | |
530 | list.append(item) |
|
535 | list.append(item) | |
531 | #from pprint import pformat; print 'LISTTB', pformat(list) # dbg |
|
536 | #from pprint import pformat; print 'LISTTB', pformat(list) # dbg | |
532 | return list |
|
537 | return list | |
533 |
|
538 | |||
534 | def _format_exception_only(self, etype, value): |
|
539 | def _format_exception_only(self, etype, value): | |
535 | """Format the exception part of a traceback. |
|
540 | """Format the exception part of a traceback. | |
536 |
|
541 | |||
537 | The arguments are the exception type and value such as given by |
|
542 | The arguments are the exception type and value such as given by | |
538 | sys.exc_info()[:2]. The return value is a list of strings, each ending |
|
543 | sys.exc_info()[:2]. The return value is a list of strings, each ending | |
539 | in a newline. Normally, the list contains a single string; however, |
|
544 | in a newline. Normally, the list contains a single string; however, | |
540 | for SyntaxError exceptions, it contains several lines that (when |
|
545 | for SyntaxError exceptions, it contains several lines that (when | |
541 | printed) display detailed information about where the syntax error |
|
546 | printed) display detailed information about where the syntax error | |
542 | occurred. The message indicating which exception occurred is the |
|
547 | occurred. The message indicating which exception occurred is the | |
543 | always last string in the list. |
|
548 | always last string in the list. | |
544 |
|
549 | |||
545 | Also lifted nearly verbatim from traceback.py |
|
550 | Also lifted nearly verbatim from traceback.py | |
546 | """ |
|
551 | """ | |
547 | have_filedata = False |
|
552 | have_filedata = False | |
548 | Colors = self.Colors |
|
553 | Colors = self.Colors | |
549 | list = [] |
|
554 | list = [] | |
550 | stype = Colors.excName + etype.__name__ + Colors.Normal |
|
555 | stype = Colors.excName + etype.__name__ + Colors.Normal | |
551 | if value is None: |
|
556 | if value is None: | |
552 | # Not sure if this can still happen in Python 2.6 and above |
|
557 | # Not sure if this can still happen in Python 2.6 and above | |
553 | list.append( py3compat.cast_unicode(stype) + '\n') |
|
558 | list.append( py3compat.cast_unicode(stype) + '\n') | |
554 | else: |
|
559 | else: | |
555 | if issubclass(etype, SyntaxError): |
|
560 | if issubclass(etype, SyntaxError): | |
556 | have_filedata = True |
|
561 | have_filedata = True | |
557 | #print 'filename is',filename # dbg |
|
562 | #print 'filename is',filename # dbg | |
558 | if not value.filename: value.filename = "<string>" |
|
563 | if not value.filename: value.filename = "<string>" | |
559 | if value.lineno: |
|
564 | if value.lineno: | |
560 | lineno = value.lineno |
|
565 | lineno = value.lineno | |
561 | textline = ulinecache.getline(value.filename, value.lineno) |
|
566 | textline = ulinecache.getline(value.filename, value.lineno) | |
562 | else: |
|
567 | else: | |
563 | lineno = 'unknown' |
|
568 | lineno = 'unknown' | |
564 | textline = '' |
|
569 | textline = '' | |
565 | list.append('%s File %s"%s"%s, line %s%s%s\n' % \ |
|
570 | list.append('%s File %s"%s"%s, line %s%s%s\n' % \ | |
566 | (Colors.normalEm, |
|
571 | (Colors.normalEm, | |
567 | Colors.filenameEm, py3compat.cast_unicode(value.filename), Colors.normalEm, |
|
572 | Colors.filenameEm, py3compat.cast_unicode(value.filename), Colors.normalEm, | |
568 | Colors.linenoEm, lineno, Colors.Normal )) |
|
573 | Colors.linenoEm, lineno, Colors.Normal )) | |
569 | if textline == '': |
|
574 | if textline == '': | |
570 | textline = py3compat.cast_unicode(value.text, "utf-8") |
|
575 | textline = py3compat.cast_unicode(value.text, "utf-8") | |
571 |
|
576 | |||
572 | if textline is not None: |
|
577 | if textline is not None: | |
573 | i = 0 |
|
578 | i = 0 | |
574 | while i < len(textline) and textline[i].isspace(): |
|
579 | while i < len(textline) and textline[i].isspace(): | |
575 | i += 1 |
|
580 | i += 1 | |
576 | list.append('%s %s%s\n' % (Colors.line, |
|
581 | list.append('%s %s%s\n' % (Colors.line, | |
577 | textline.strip(), |
|
582 | textline.strip(), | |
578 | Colors.Normal)) |
|
583 | Colors.Normal)) | |
579 | if value.offset is not None: |
|
584 | if value.offset is not None: | |
580 | s = ' ' |
|
585 | s = ' ' | |
581 | for c in textline[i:value.offset-1]: |
|
586 | for c in textline[i:value.offset-1]: | |
582 | if c.isspace(): |
|
587 | if c.isspace(): | |
583 | s += c |
|
588 | s += c | |
584 | else: |
|
589 | else: | |
585 | s += ' ' |
|
590 | s += ' ' | |
586 | list.append('%s%s^%s\n' % (Colors.caret, s, |
|
591 | list.append('%s%s^%s\n' % (Colors.caret, s, | |
587 | Colors.Normal) ) |
|
592 | Colors.Normal) ) | |
588 |
|
593 | |||
589 | try: |
|
594 | try: | |
590 | s = value.msg |
|
595 | s = value.msg | |
591 | except Exception: |
|
596 | except Exception: | |
592 | s = self._some_str(value) |
|
597 | s = self._some_str(value) | |
593 | if s: |
|
598 | if s: | |
594 | list.append('%s%s:%s %s\n' % (str(stype), Colors.excName, |
|
599 | list.append('%s%s:%s %s\n' % (str(stype), Colors.excName, | |
595 | Colors.Normal, s)) |
|
600 | Colors.Normal, s)) | |
596 | else: |
|
601 | else: | |
597 | list.append('%s\n' % str(stype)) |
|
602 | list.append('%s\n' % str(stype)) | |
598 |
|
603 | |||
599 | # sync with user hooks |
|
604 | # sync with user hooks | |
600 | if have_filedata: |
|
605 | if have_filedata: | |
601 | ipinst = ipapi.get() |
|
606 | ipinst = ipapi.get() | |
602 | if ipinst is not None: |
|
607 | if ipinst is not None: | |
603 | ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0) |
|
608 | ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0) | |
604 |
|
609 | |||
605 | return list |
|
610 | return list | |
606 |
|
611 | |||
607 | def get_exception_only(self, etype, value): |
|
612 | def get_exception_only(self, etype, value): | |
608 | """Only print the exception type and message, without a traceback. |
|
613 | """Only print the exception type and message, without a traceback. | |
609 |
|
614 | |||
610 | Parameters |
|
615 | Parameters | |
611 | ---------- |
|
616 | ---------- | |
612 | etype : exception type |
|
617 | etype : exception type | |
613 | value : exception value |
|
618 | value : exception value | |
614 | """ |
|
619 | """ | |
615 | return ListTB.structured_traceback(self, etype, value, []) |
|
620 | return ListTB.structured_traceback(self, etype, value, []) | |
616 |
|
621 | |||
617 |
|
622 | |||
618 | def show_exception_only(self, etype, evalue): |
|
623 | def show_exception_only(self, etype, evalue): | |
619 | """Only print the exception type and message, without a traceback. |
|
624 | """Only print the exception type and message, without a traceback. | |
620 |
|
625 | |||
621 | Parameters |
|
626 | Parameters | |
622 | ---------- |
|
627 | ---------- | |
623 | etype : exception type |
|
628 | etype : exception type | |
624 | value : exception value |
|
629 | value : exception value | |
625 | """ |
|
630 | """ | |
626 | # This method needs to use __call__ from *this* class, not the one from |
|
631 | # This method needs to use __call__ from *this* class, not the one from | |
627 | # a subclass whose signature or behavior may be different |
|
632 | # a subclass whose signature or behavior may be different | |
628 | ostream = self.ostream |
|
633 | ostream = self.ostream | |
629 | ostream.flush() |
|
634 | ostream.flush() | |
630 | ostream.write('\n'.join(self.get_exception_only(etype, evalue))) |
|
635 | ostream.write('\n'.join(self.get_exception_only(etype, evalue))) | |
631 | ostream.flush() |
|
636 | ostream.flush() | |
632 |
|
637 | |||
633 | def _some_str(self, value): |
|
638 | def _some_str(self, value): | |
634 | # Lifted from traceback.py |
|
639 | # Lifted from traceback.py | |
635 | try: |
|
640 | try: | |
636 | return str(value) |
|
641 | return str(value) | |
637 | except: |
|
642 | except: | |
638 | return '<unprintable %s object>' % type(value).__name__ |
|
643 | return '<unprintable %s object>' % type(value).__name__ | |
639 |
|
644 | |||
640 | #---------------------------------------------------------------------------- |
|
645 | #---------------------------------------------------------------------------- | |
641 | class VerboseTB(TBTools): |
|
646 | class VerboseTB(TBTools): | |
642 | """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead |
|
647 | """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead | |
643 | of HTML. Requires inspect and pydoc. Crazy, man. |
|
648 | of HTML. Requires inspect and pydoc. Crazy, man. | |
644 |
|
649 | |||
645 | Modified version which optionally strips the topmost entries from the |
|
650 | Modified version which optionally strips the topmost entries from the | |
646 | traceback, to be used with alternate interpreters (because their own code |
|
651 | traceback, to be used with alternate interpreters (because their own code | |
647 | would appear in the traceback).""" |
|
652 | would appear in the traceback).""" | |
648 |
|
653 | |||
649 | def __init__(self,color_scheme = 'Linux', call_pdb=False, ostream=None, |
|
654 | def __init__(self,color_scheme = 'Linux', call_pdb=False, ostream=None, | |
650 | tb_offset=0, long_header=False, include_vars=True, |
|
655 | tb_offset=0, long_header=False, include_vars=True, | |
651 | check_cache=None): |
|
656 | check_cache=None): | |
652 | """Specify traceback offset, headers and color scheme. |
|
657 | """Specify traceback offset, headers and color scheme. | |
653 |
|
658 | |||
654 | Define how many frames to drop from the tracebacks. Calling it with |
|
659 | Define how many frames to drop from the tracebacks. Calling it with | |
655 | tb_offset=1 allows use of this handler in interpreters which will have |
|
660 | tb_offset=1 allows use of this handler in interpreters which will have | |
656 | their own code at the top of the traceback (VerboseTB will first |
|
661 | their own code at the top of the traceback (VerboseTB will first | |
657 | remove that frame before printing the traceback info).""" |
|
662 | remove that frame before printing the traceback info).""" | |
658 | TBTools.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb, |
|
663 | TBTools.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb, | |
659 | ostream=ostream) |
|
664 | ostream=ostream) | |
660 | self.tb_offset = tb_offset |
|
665 | self.tb_offset = tb_offset | |
661 | self.long_header = long_header |
|
666 | self.long_header = long_header | |
662 | self.include_vars = include_vars |
|
667 | self.include_vars = include_vars | |
663 | # By default we use linecache.checkcache, but the user can provide a |
|
668 | # By default we use linecache.checkcache, but the user can provide a | |
664 | # different check_cache implementation. This is used by the IPython |
|
669 | # different check_cache implementation. This is used by the IPython | |
665 | # kernel to provide tracebacks for interactive code that is cached, |
|
670 | # kernel to provide tracebacks for interactive code that is cached, | |
666 | # by a compiler instance that flushes the linecache but preserves its |
|
671 | # by a compiler instance that flushes the linecache but preserves its | |
667 | # own code cache. |
|
672 | # own code cache. | |
668 | if check_cache is None: |
|
673 | if check_cache is None: | |
669 | check_cache = linecache.checkcache |
|
674 | check_cache = linecache.checkcache | |
670 | self.check_cache = check_cache |
|
675 | self.check_cache = check_cache | |
671 |
|
676 | |||
672 | def structured_traceback(self, etype, evalue, etb, tb_offset=None, |
|
677 | def structured_traceback(self, etype, evalue, etb, tb_offset=None, | |
673 | context=5): |
|
678 | context=5): | |
674 | """Return a nice text document describing the traceback.""" |
|
679 | """Return a nice text document describing the traceback.""" | |
675 |
|
680 | |||
676 | tb_offset = self.tb_offset if tb_offset is None else tb_offset |
|
681 | tb_offset = self.tb_offset if tb_offset is None else tb_offset | |
677 |
|
682 | |||
678 | # some locals |
|
683 | # some locals | |
679 | try: |
|
684 | try: | |
680 | etype = etype.__name__ |
|
685 | etype = etype.__name__ | |
681 | except AttributeError: |
|
686 | except AttributeError: | |
682 | pass |
|
687 | pass | |
683 | Colors = self.Colors # just a shorthand + quicker name lookup |
|
688 | Colors = self.Colors # just a shorthand + quicker name lookup | |
684 | ColorsNormal = Colors.Normal # used a lot |
|
689 | ColorsNormal = Colors.Normal # used a lot | |
685 | col_scheme = self.color_scheme_table.active_scheme_name |
|
690 | col_scheme = self.color_scheme_table.active_scheme_name | |
686 | indent = ' '*INDENT_SIZE |
|
691 | indent = ' '*INDENT_SIZE | |
687 | em_normal = '%s\n%s%s' % (Colors.valEm, indent,ColorsNormal) |
|
692 | em_normal = '%s\n%s%s' % (Colors.valEm, indent,ColorsNormal) | |
688 | undefined = '%sundefined%s' % (Colors.em, ColorsNormal) |
|
693 | undefined = '%sundefined%s' % (Colors.em, ColorsNormal) | |
689 | exc = '%s%s%s' % (Colors.excName,etype,ColorsNormal) |
|
694 | exc = '%s%s%s' % (Colors.excName,etype,ColorsNormal) | |
690 |
|
695 | |||
691 | # some internal-use functions |
|
696 | # some internal-use functions | |
692 | def text_repr(value): |
|
697 | def text_repr(value): | |
693 | """Hopefully pretty robust repr equivalent.""" |
|
698 | """Hopefully pretty robust repr equivalent.""" | |
694 | # this is pretty horrible but should always return *something* |
|
699 | # this is pretty horrible but should always return *something* | |
695 | try: |
|
700 | try: | |
696 | return pydoc.text.repr(value) |
|
701 | return pydoc.text.repr(value) | |
697 | except KeyboardInterrupt: |
|
702 | except KeyboardInterrupt: | |
698 | raise |
|
703 | raise | |
699 | except: |
|
704 | except: | |
700 | try: |
|
705 | try: | |
701 | return repr(value) |
|
706 | return repr(value) | |
702 | except KeyboardInterrupt: |
|
707 | except KeyboardInterrupt: | |
703 | raise |
|
708 | raise | |
704 | except: |
|
709 | except: | |
705 | try: |
|
710 | try: | |
706 | # all still in an except block so we catch |
|
711 | # all still in an except block so we catch | |
707 | # getattr raising |
|
712 | # getattr raising | |
708 | name = getattr(value, '__name__', None) |
|
713 | name = getattr(value, '__name__', None) | |
709 | if name: |
|
714 | if name: | |
710 | # ick, recursion |
|
715 | # ick, recursion | |
711 | return text_repr(name) |
|
716 | return text_repr(name) | |
712 | klass = getattr(value, '__class__', None) |
|
717 | klass = getattr(value, '__class__', None) | |
713 | if klass: |
|
718 | if klass: | |
714 | return '%s instance' % text_repr(klass) |
|
719 | return '%s instance' % text_repr(klass) | |
715 | except KeyboardInterrupt: |
|
720 | except KeyboardInterrupt: | |
716 | raise |
|
721 | raise | |
717 | except: |
|
722 | except: | |
718 | return 'UNRECOVERABLE REPR FAILURE' |
|
723 | return 'UNRECOVERABLE REPR FAILURE' | |
719 | def eqrepr(value, repr=text_repr): return '=%s' % repr(value) |
|
724 | def eqrepr(value, repr=text_repr): return '=%s' % repr(value) | |
720 | def nullrepr(value, repr=text_repr): return '' |
|
725 | def nullrepr(value, repr=text_repr): return '' | |
721 |
|
726 | |||
722 | # meat of the code begins |
|
727 | # meat of the code begins | |
723 | try: |
|
728 | try: | |
724 | etype = etype.__name__ |
|
729 | etype = etype.__name__ | |
725 | except AttributeError: |
|
730 | except AttributeError: | |
726 | pass |
|
731 | pass | |
727 |
|
732 | |||
728 | if self.long_header: |
|
733 | if self.long_header: | |
729 | # Header with the exception type, python version, and date |
|
734 | # Header with the exception type, python version, and date | |
730 | pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable |
|
735 | pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable | |
731 | date = time.ctime(time.time()) |
|
736 | date = time.ctime(time.time()) | |
732 |
|
737 | |||
733 | head = '%s%s%s\n%s%s%s\n%s' % (Colors.topline, '-'*75, ColorsNormal, |
|
738 | head = '%s%s%s\n%s%s%s\n%s' % (Colors.topline, '-'*75, ColorsNormal, | |
734 | exc, ' '*(75-len(str(etype))-len(pyver)), |
|
739 | exc, ' '*(75-len(str(etype))-len(pyver)), | |
735 | pyver, date.rjust(75) ) |
|
740 | pyver, date.rjust(75) ) | |
736 | head += "\nA problem occured executing Python code. Here is the sequence of function"\ |
|
741 | head += "\nA problem occured executing Python code. Here is the sequence of function"\ | |
737 | "\ncalls leading up to the error, with the most recent (innermost) call last." |
|
742 | "\ncalls leading up to the error, with the most recent (innermost) call last." | |
738 | else: |
|
743 | else: | |
739 | # Simplified header |
|
744 | # Simplified header | |
740 | head = '%s%s%s\n%s%s' % (Colors.topline, '-'*75, ColorsNormal,exc, |
|
745 | head = '%s%s%s\n%s%s' % (Colors.topline, '-'*75, ColorsNormal,exc, | |
741 | 'Traceback (most recent call last)'.\ |
|
746 | 'Traceback (most recent call last)'.\ | |
742 | rjust(75 - len(str(etype)) ) ) |
|
747 | rjust(75 - len(str(etype)) ) ) | |
743 | frames = [] |
|
748 | frames = [] | |
744 | # Flush cache before calling inspect. This helps alleviate some of the |
|
749 | # Flush cache before calling inspect. This helps alleviate some of the | |
745 | # problems with python 2.3's inspect.py. |
|
750 | # problems with python 2.3's inspect.py. | |
746 | ##self.check_cache() |
|
751 | ##self.check_cache() | |
747 | # Drop topmost frames if requested |
|
752 | # Drop topmost frames if requested | |
748 | try: |
|
753 | try: | |
749 | # Try the default getinnerframes and Alex's: Alex's fixes some |
|
754 | # Try the default getinnerframes and Alex's: Alex's fixes some | |
750 | # problems, but it generates empty tracebacks for console errors |
|
755 | # problems, but it generates empty tracebacks for console errors | |
751 | # (5 blanks lines) where none should be returned. |
|
756 | # (5 blanks lines) where none should be returned. | |
752 | #records = inspect.getinnerframes(etb, context)[tb_offset:] |
|
757 | #records = inspect.getinnerframes(etb, context)[tb_offset:] | |
753 | #print 'python records:', records # dbg |
|
758 | #print 'python records:', records # dbg | |
754 | records = _fixed_getinnerframes(etb, context, tb_offset) |
|
759 | records = _fixed_getinnerframes(etb, context, tb_offset) | |
755 | #print 'alex records:', records # dbg |
|
760 | #print 'alex records:', records # dbg | |
756 | except: |
|
761 | except: | |
757 |
|
762 | |||
758 | # FIXME: I've been getting many crash reports from python 2.3 |
|
763 | # FIXME: I've been getting many crash reports from python 2.3 | |
759 | # users, traceable to inspect.py. If I can find a small test-case |
|
764 | # users, traceable to inspect.py. If I can find a small test-case | |
760 | # to reproduce this, I should either write a better workaround or |
|
765 | # to reproduce this, I should either write a better workaround or | |
761 | # file a bug report against inspect (if that's the real problem). |
|
766 | # file a bug report against inspect (if that's the real problem). | |
762 | # So far, I haven't been able to find an isolated example to |
|
767 | # So far, I haven't been able to find an isolated example to | |
763 | # reproduce the problem. |
|
768 | # reproduce the problem. | |
764 | inspect_error() |
|
769 | inspect_error() | |
765 | traceback.print_exc(file=self.ostream) |
|
770 | traceback.print_exc(file=self.ostream) | |
766 | info('\nUnfortunately, your original traceback can not be constructed.\n') |
|
771 | info('\nUnfortunately, your original traceback can not be constructed.\n') | |
767 | return '' |
|
772 | return '' | |
768 |
|
773 | |||
769 | # build some color string templates outside these nested loops |
|
774 | # build some color string templates outside these nested loops | |
770 | tpl_link = '%s%%s%s' % (Colors.filenameEm,ColorsNormal) |
|
775 | tpl_link = '%s%%s%s' % (Colors.filenameEm,ColorsNormal) | |
771 | tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm, |
|
776 | tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm, | |
772 | ColorsNormal) |
|
777 | ColorsNormal) | |
773 | tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \ |
|
778 | tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \ | |
774 | (Colors.vName, Colors.valEm, ColorsNormal) |
|
779 | (Colors.vName, Colors.valEm, ColorsNormal) | |
775 | tpl_local_var = '%s%%s%s' % (Colors.vName, ColorsNormal) |
|
780 | tpl_local_var = '%s%%s%s' % (Colors.vName, ColorsNormal) | |
776 | tpl_global_var = '%sglobal%s %s%%s%s' % (Colors.em, ColorsNormal, |
|
781 | tpl_global_var = '%sglobal%s %s%%s%s' % (Colors.em, ColorsNormal, | |
777 | Colors.vName, ColorsNormal) |
|
782 | Colors.vName, ColorsNormal) | |
778 | tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal) |
|
783 | tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal) | |
779 | tpl_line = '%s%%s%s %%s' % (Colors.lineno, ColorsNormal) |
|
784 | tpl_line = '%s%%s%s %%s' % (Colors.lineno, ColorsNormal) | |
780 | tpl_line_em = '%s%%s%s %%s%s' % (Colors.linenoEm,Colors.line, |
|
785 | tpl_line_em = '%s%%s%s %%s%s' % (Colors.linenoEm,Colors.line, | |
781 | ColorsNormal) |
|
786 | ColorsNormal) | |
782 |
|
787 | |||
783 | # now, loop over all records printing context and info |
|
788 | # now, loop over all records printing context and info | |
784 | abspath = os.path.abspath |
|
789 | abspath = os.path.abspath | |
785 | for frame, file, lnum, func, lines, index in records: |
|
790 | for frame, file, lnum, func, lines, index in records: | |
786 | #print '*** record:',file,lnum,func,lines,index # dbg |
|
791 | #print '*** record:',file,lnum,func,lines,index # dbg | |
787 | if not file: |
|
792 | if not file: | |
788 | file = '?' |
|
793 | file = '?' | |
789 | elif not(file.startswith(str("<")) and file.endswith(str(">"))): |
|
794 | elif not(file.startswith(str("<")) and file.endswith(str(">"))): | |
790 | # Guess that filenames like <string> aren't real filenames, so |
|
795 | # Guess that filenames like <string> aren't real filenames, so | |
791 | # don't call abspath on them. |
|
796 | # don't call abspath on them. | |
792 | try: |
|
797 | try: | |
793 | file = abspath(file) |
|
798 | file = abspath(file) | |
794 | except OSError: |
|
799 | except OSError: | |
795 | # Not sure if this can still happen: abspath now works with |
|
800 | # Not sure if this can still happen: abspath now works with | |
796 | # file names like <string> |
|
801 | # file names like <string> | |
797 | pass |
|
802 | pass | |
798 | file = py3compat.cast_unicode(file, util_path.fs_encoding) |
|
803 | file = py3compat.cast_unicode(file, util_path.fs_encoding) | |
799 | link = tpl_link % file |
|
804 | link = tpl_link % file | |
800 | args, varargs, varkw, locals = inspect.getargvalues(frame) |
|
805 | args, varargs, varkw, locals = inspect.getargvalues(frame) | |
801 |
|
806 | |||
802 | if func == '?': |
|
807 | if func == '?': | |
803 | call = '' |
|
808 | call = '' | |
804 | else: |
|
809 | else: | |
805 | # Decide whether to include variable details or not |
|
810 | # Decide whether to include variable details or not | |
806 | var_repr = self.include_vars and eqrepr or nullrepr |
|
811 | var_repr = self.include_vars and eqrepr or nullrepr | |
807 | try: |
|
812 | try: | |
808 | call = tpl_call % (func,inspect.formatargvalues(args, |
|
813 | call = tpl_call % (func,inspect.formatargvalues(args, | |
809 | varargs, varkw, |
|
814 | varargs, varkw, | |
810 | locals,formatvalue=var_repr)) |
|
815 | locals,formatvalue=var_repr)) | |
811 | except KeyError: |
|
816 | except KeyError: | |
812 | # This happens in situations like errors inside generator |
|
817 | # This happens in situations like errors inside generator | |
813 | # expressions, where local variables are listed in the |
|
818 | # expressions, where local variables are listed in the | |
814 | # line, but can't be extracted from the frame. I'm not |
|
819 | # line, but can't be extracted from the frame. I'm not | |
815 | # 100% sure this isn't actually a bug in inspect itself, |
|
820 | # 100% sure this isn't actually a bug in inspect itself, | |
816 | # but since there's no info for us to compute with, the |
|
821 | # but since there's no info for us to compute with, the | |
817 | # best we can do is report the failure and move on. Here |
|
822 | # best we can do is report the failure and move on. Here | |
818 | # we must *not* call any traceback construction again, |
|
823 | # we must *not* call any traceback construction again, | |
819 | # because that would mess up use of %debug later on. So we |
|
824 | # because that would mess up use of %debug later on. So we | |
820 | # simply report the failure and move on. The only |
|
825 | # simply report the failure and move on. The only | |
821 | # limitation will be that this frame won't have locals |
|
826 | # limitation will be that this frame won't have locals | |
822 | # listed in the call signature. Quite subtle problem... |
|
827 | # listed in the call signature. Quite subtle problem... | |
823 | # I can't think of a good way to validate this in a unit |
|
828 | # I can't think of a good way to validate this in a unit | |
824 | # test, but running a script consisting of: |
|
829 | # test, but running a script consisting of: | |
825 | # dict( (k,v.strip()) for (k,v) in range(10) ) |
|
830 | # dict( (k,v.strip()) for (k,v) in range(10) ) | |
826 | # will illustrate the error, if this exception catch is |
|
831 | # will illustrate the error, if this exception catch is | |
827 | # disabled. |
|
832 | # disabled. | |
828 | call = tpl_call_fail % func |
|
833 | call = tpl_call_fail % func | |
829 |
|
834 | |||
830 | # Don't attempt to tokenize binary files. |
|
835 | # Don't attempt to tokenize binary files. | |
831 | if file.endswith(('.so', '.pyd', '.dll')): |
|
836 | if file.endswith(('.so', '.pyd', '.dll')): | |
832 | frames.append('%s %s\n' % (link,call)) |
|
837 | frames.append('%s %s\n' % (link,call)) | |
833 | continue |
|
838 | continue | |
834 | elif file.endswith(('.pyc','.pyo')): |
|
839 | elif file.endswith(('.pyc','.pyo')): | |
835 | # Look up the corresponding source file. |
|
840 | # Look up the corresponding source file. | |
836 | file = pyfile.source_from_cache(file) |
|
841 | file = pyfile.source_from_cache(file) | |
837 |
|
842 | |||
838 | def linereader(file=file, lnum=[lnum], getline=ulinecache.getline): |
|
843 | def linereader(file=file, lnum=[lnum], getline=ulinecache.getline): | |
839 | line = getline(file, lnum[0]) |
|
844 | line = getline(file, lnum[0]) | |
840 | lnum[0] += 1 |
|
845 | lnum[0] += 1 | |
841 | return line |
|
846 | return line | |
842 |
|
847 | |||
843 | # Build the list of names on this line of code where the exception |
|
848 | # Build the list of names on this line of code where the exception | |
844 | # occurred. |
|
849 | # occurred. | |
845 | try: |
|
850 | try: | |
846 | names = [] |
|
851 | names = [] | |
847 | name_cont = False |
|
852 | name_cont = False | |
848 |
|
853 | |||
849 | for token_type, token, start, end, line in generate_tokens(linereader): |
|
854 | for token_type, token, start, end, line in generate_tokens(linereader): | |
850 | # build composite names |
|
855 | # build composite names | |
851 | if token_type == tokenize.NAME and token not in keyword.kwlist: |
|
856 | if token_type == tokenize.NAME and token not in keyword.kwlist: | |
852 | if name_cont: |
|
857 | if name_cont: | |
853 | # Continuation of a dotted name |
|
858 | # Continuation of a dotted name | |
854 | try: |
|
859 | try: | |
855 | names[-1].append(token) |
|
860 | names[-1].append(token) | |
856 | except IndexError: |
|
861 | except IndexError: | |
857 | names.append([token]) |
|
862 | names.append([token]) | |
858 | name_cont = False |
|
863 | name_cont = False | |
859 | else: |
|
864 | else: | |
860 | # Regular new names. We append everything, the caller |
|
865 | # Regular new names. We append everything, the caller | |
861 | # will be responsible for pruning the list later. It's |
|
866 | # will be responsible for pruning the list later. It's | |
862 | # very tricky to try to prune as we go, b/c composite |
|
867 | # very tricky to try to prune as we go, b/c composite | |
863 | # names can fool us. The pruning at the end is easy |
|
868 | # names can fool us. The pruning at the end is easy | |
864 | # to do (or the caller can print a list with repeated |
|
869 | # to do (or the caller can print a list with repeated | |
865 | # names if so desired. |
|
870 | # names if so desired. | |
866 | names.append([token]) |
|
871 | names.append([token]) | |
867 | elif token == '.': |
|
872 | elif token == '.': | |
868 | name_cont = True |
|
873 | name_cont = True | |
869 | elif token_type == tokenize.NEWLINE: |
|
874 | elif token_type == tokenize.NEWLINE: | |
870 | break |
|
875 | break | |
871 |
|
876 | |||
872 | except (IndexError, UnicodeDecodeError): |
|
877 | except (IndexError, UnicodeDecodeError): | |
873 | # signals exit of tokenizer |
|
878 | # signals exit of tokenizer | |
874 | pass |
|
879 | pass | |
875 | except tokenize.TokenError as msg: |
|
880 | except tokenize.TokenError as msg: | |
876 | _m = ("An unexpected error occurred while tokenizing input\n" |
|
881 | _m = ("An unexpected error occurred while tokenizing input\n" | |
877 | "The following traceback may be corrupted or invalid\n" |
|
882 | "The following traceback may be corrupted or invalid\n" | |
878 | "The error message is: %s\n" % msg) |
|
883 | "The error message is: %s\n" % msg) | |
879 | error(_m) |
|
884 | error(_m) | |
880 |
|
885 | |||
881 | # Join composite names (e.g. "dict.fromkeys") |
|
886 | # Join composite names (e.g. "dict.fromkeys") | |
882 | names = ['.'.join(n) for n in names] |
|
887 | names = ['.'.join(n) for n in names] | |
883 | # prune names list of duplicates, but keep the right order |
|
888 | # prune names list of duplicates, but keep the right order | |
884 | unique_names = uniq_stable(names) |
|
889 | unique_names = uniq_stable(names) | |
885 |
|
890 | |||
886 | # Start loop over vars |
|
891 | # Start loop over vars | |
887 | lvals = [] |
|
892 | lvals = [] | |
888 | if self.include_vars: |
|
893 | if self.include_vars: | |
889 | for name_full in unique_names: |
|
894 | for name_full in unique_names: | |
890 | name_base = name_full.split('.',1)[0] |
|
895 | name_base = name_full.split('.',1)[0] | |
891 | if name_base in frame.f_code.co_varnames: |
|
896 | if name_base in frame.f_code.co_varnames: | |
892 | if name_base in locals: |
|
897 | if name_base in locals: | |
893 | try: |
|
898 | try: | |
894 | value = repr(eval(name_full,locals)) |
|
899 | value = repr(eval(name_full,locals)) | |
895 | except: |
|
900 | except: | |
896 | value = undefined |
|
901 | value = undefined | |
897 | else: |
|
902 | else: | |
898 | value = undefined |
|
903 | value = undefined | |
899 | name = tpl_local_var % name_full |
|
904 | name = tpl_local_var % name_full | |
900 | else: |
|
905 | else: | |
901 | if name_base in frame.f_globals: |
|
906 | if name_base in frame.f_globals: | |
902 | try: |
|
907 | try: | |
903 | value = repr(eval(name_full,frame.f_globals)) |
|
908 | value = repr(eval(name_full,frame.f_globals)) | |
904 | except: |
|
909 | except: | |
905 | value = undefined |
|
910 | value = undefined | |
906 | else: |
|
911 | else: | |
907 | value = undefined |
|
912 | value = undefined | |
908 | name = tpl_global_var % name_full |
|
913 | name = tpl_global_var % name_full | |
909 | lvals.append(tpl_name_val % (name,value)) |
|
914 | lvals.append(tpl_name_val % (name,value)) | |
910 | if lvals: |
|
915 | if lvals: | |
911 | lvals = '%s%s' % (indent,em_normal.join(lvals)) |
|
916 | lvals = '%s%s' % (indent,em_normal.join(lvals)) | |
912 | else: |
|
917 | else: | |
913 | lvals = '' |
|
918 | lvals = '' | |
914 |
|
919 | |||
915 | level = '%s %s\n' % (link,call) |
|
920 | level = '%s %s\n' % (link,call) | |
916 |
|
921 | |||
917 | if index is None: |
|
922 | if index is None: | |
918 | frames.append(level) |
|
923 | frames.append(level) | |
919 | else: |
|
924 | else: | |
920 | frames.append('%s%s' % (level,''.join( |
|
925 | frames.append('%s%s' % (level,''.join( | |
921 | _format_traceback_lines(lnum,index,lines,Colors,lvals, |
|
926 | _format_traceback_lines(lnum,index,lines,Colors,lvals, | |
922 | col_scheme)))) |
|
927 | col_scheme)))) | |
923 |
|
928 | |||
924 | # Get (safely) a string form of the exception info |
|
929 | # Get (safely) a string form of the exception info | |
925 | try: |
|
930 | try: | |
926 | etype_str,evalue_str = map(str,(etype,evalue)) |
|
931 | etype_str,evalue_str = map(str,(etype,evalue)) | |
927 | except: |
|
932 | except: | |
928 | # User exception is improperly defined. |
|
933 | # User exception is improperly defined. | |
929 | etype,evalue = str,sys.exc_info()[:2] |
|
934 | etype,evalue = str,sys.exc_info()[:2] | |
930 | etype_str,evalue_str = map(str,(etype,evalue)) |
|
935 | etype_str,evalue_str = map(str,(etype,evalue)) | |
931 | # ... and format it |
|
936 | # ... and format it | |
932 | exception = ['%s%s%s: %s' % (Colors.excName, etype_str, |
|
937 | exception = ['%s%s%s: %s' % (Colors.excName, etype_str, | |
933 | ColorsNormal, py3compat.cast_unicode(evalue_str))] |
|
938 | ColorsNormal, py3compat.cast_unicode(evalue_str))] | |
934 | if (not py3compat.PY3) and type(evalue) is types.InstanceType: |
|
939 | if (not py3compat.PY3) and type(evalue) is types.InstanceType: | |
935 | try: |
|
940 | try: | |
936 | names = [w for w in dir(evalue) if isinstance(w, basestring)] |
|
941 | names = [w for w in dir(evalue) if isinstance(w, basestring)] | |
937 | except: |
|
942 | except: | |
938 | # Every now and then, an object with funny inernals blows up |
|
943 | # Every now and then, an object with funny inernals blows up | |
939 | # when dir() is called on it. We do the best we can to report |
|
944 | # when dir() is called on it. We do the best we can to report | |
940 | # the problem and continue |
|
945 | # the problem and continue | |
941 | _m = '%sException reporting error (object with broken dir())%s:' |
|
946 | _m = '%sException reporting error (object with broken dir())%s:' | |
942 | exception.append(_m % (Colors.excName,ColorsNormal)) |
|
947 | exception.append(_m % (Colors.excName,ColorsNormal)) | |
943 | etype_str,evalue_str = map(str,sys.exc_info()[:2]) |
|
948 | etype_str,evalue_str = map(str,sys.exc_info()[:2]) | |
944 | exception.append('%s%s%s: %s' % (Colors.excName,etype_str, |
|
949 | exception.append('%s%s%s: %s' % (Colors.excName,etype_str, | |
945 | ColorsNormal, py3compat.cast_unicode(evalue_str))) |
|
950 | ColorsNormal, py3compat.cast_unicode(evalue_str))) | |
946 | names = [] |
|
951 | names = [] | |
947 | for name in names: |
|
952 | for name in names: | |
948 | value = text_repr(getattr(evalue, name)) |
|
953 | value = text_repr(getattr(evalue, name)) | |
949 | exception.append('\n%s%s = %s' % (indent, name, value)) |
|
954 | exception.append('\n%s%s = %s' % (indent, name, value)) | |
950 |
|
955 | |||
951 | # vds: >> |
|
956 | # vds: >> | |
952 | if records: |
|
957 | if records: | |
953 | filepath, lnum = records[-1][1:3] |
|
958 | filepath, lnum = records[-1][1:3] | |
954 | #print "file:", str(file), "linenb", str(lnum) # dbg |
|
959 | #print "file:", str(file), "linenb", str(lnum) # dbg | |
955 | filepath = os.path.abspath(filepath) |
|
960 | filepath = os.path.abspath(filepath) | |
956 | ipinst = ipapi.get() |
|
961 | ipinst = ipapi.get() | |
957 | if ipinst is not None: |
|
962 | if ipinst is not None: | |
958 | ipinst.hooks.synchronize_with_editor(filepath, lnum, 0) |
|
963 | ipinst.hooks.synchronize_with_editor(filepath, lnum, 0) | |
959 | # vds: << |
|
964 | # vds: << | |
960 |
|
965 | |||
961 | # return all our info assembled as a single string |
|
966 | # return all our info assembled as a single string | |
962 | # return '%s\n\n%s\n%s' % (head,'\n'.join(frames),''.join(exception[0]) ) |
|
967 | # return '%s\n\n%s\n%s' % (head,'\n'.join(frames),''.join(exception[0]) ) | |
963 | return [head] + frames + [''.join(exception[0])] |
|
968 | return [head] + frames + [''.join(exception[0])] | |
964 |
|
969 | |||
965 | def debugger(self,force=False): |
|
970 | def debugger(self,force=False): | |
966 | """Call up the pdb debugger if desired, always clean up the tb |
|
971 | """Call up the pdb debugger if desired, always clean up the tb | |
967 | reference. |
|
972 | reference. | |
968 |
|
973 | |||
969 | Keywords: |
|
974 | Keywords: | |
970 |
|
975 | |||
971 | - force(False): by default, this routine checks the instance call_pdb |
|
976 | - force(False): by default, this routine checks the instance call_pdb | |
972 | flag and does not actually invoke the debugger if the flag is false. |
|
977 | flag and does not actually invoke the debugger if the flag is false. | |
973 | The 'force' option forces the debugger to activate even if the flag |
|
978 | The 'force' option forces the debugger to activate even if the flag | |
974 | is false. |
|
979 | is false. | |
975 |
|
980 | |||
976 | If the call_pdb flag is set, the pdb interactive debugger is |
|
981 | If the call_pdb flag is set, the pdb interactive debugger is | |
977 | invoked. In all cases, the self.tb reference to the current traceback |
|
982 | invoked. In all cases, the self.tb reference to the current traceback | |
978 | is deleted to prevent lingering references which hamper memory |
|
983 | is deleted to prevent lingering references which hamper memory | |
979 | management. |
|
984 | management. | |
980 |
|
985 | |||
981 | Note that each call to pdb() does an 'import readline', so if your app |
|
986 | Note that each call to pdb() does an 'import readline', so if your app | |
982 | requires a special setup for the readline completers, you'll have to |
|
987 | requires a special setup for the readline completers, you'll have to | |
983 | fix that by hand after invoking the exception handler.""" |
|
988 | fix that by hand after invoking the exception handler.""" | |
984 |
|
989 | |||
985 | if force or self.call_pdb: |
|
990 | if force or self.call_pdb: | |
986 | if self.pdb is None: |
|
991 | if self.pdb is None: | |
987 | self.pdb = debugger.Pdb( |
|
992 | self.pdb = debugger.Pdb( | |
988 | self.color_scheme_table.active_scheme_name) |
|
993 | self.color_scheme_table.active_scheme_name) | |
989 | # the system displayhook may have changed, restore the original |
|
994 | # the system displayhook may have changed, restore the original | |
990 | # for pdb |
|
995 | # for pdb | |
991 | display_trap = DisplayTrap(hook=sys.__displayhook__) |
|
996 | display_trap = DisplayTrap(hook=sys.__displayhook__) | |
992 | with display_trap: |
|
997 | with display_trap: | |
993 | self.pdb.reset() |
|
998 | self.pdb.reset() | |
994 | # Find the right frame so we don't pop up inside ipython itself |
|
999 | # Find the right frame so we don't pop up inside ipython itself | |
995 | if hasattr(self,'tb') and self.tb is not None: |
|
1000 | if hasattr(self,'tb') and self.tb is not None: | |
996 | etb = self.tb |
|
1001 | etb = self.tb | |
997 | else: |
|
1002 | else: | |
998 | etb = self.tb = sys.last_traceback |
|
1003 | etb = self.tb = sys.last_traceback | |
999 | while self.tb is not None and self.tb.tb_next is not None: |
|
1004 | while self.tb is not None and self.tb.tb_next is not None: | |
1000 | self.tb = self.tb.tb_next |
|
1005 | self.tb = self.tb.tb_next | |
1001 | if etb and etb.tb_next: |
|
1006 | if etb and etb.tb_next: | |
1002 | etb = etb.tb_next |
|
1007 | etb = etb.tb_next | |
1003 | self.pdb.botframe = etb.tb_frame |
|
1008 | self.pdb.botframe = etb.tb_frame | |
1004 | self.pdb.interaction(self.tb.tb_frame, self.tb) |
|
1009 | self.pdb.interaction(self.tb.tb_frame, self.tb) | |
1005 |
|
1010 | |||
1006 | if hasattr(self,'tb'): |
|
1011 | if hasattr(self,'tb'): | |
1007 | del self.tb |
|
1012 | del self.tb | |
1008 |
|
1013 | |||
1009 | def handler(self, info=None): |
|
1014 | def handler(self, info=None): | |
1010 | (etype, evalue, etb) = info or sys.exc_info() |
|
1015 | (etype, evalue, etb) = info or sys.exc_info() | |
1011 | self.tb = etb |
|
1016 | self.tb = etb | |
1012 | ostream = self.ostream |
|
1017 | ostream = self.ostream | |
1013 | ostream.flush() |
|
1018 | ostream.flush() | |
1014 | ostream.write(self.text(etype, evalue, etb)) |
|
1019 | ostream.write(self.text(etype, evalue, etb)) | |
1015 | ostream.write('\n') |
|
1020 | ostream.write('\n') | |
1016 | ostream.flush() |
|
1021 | ostream.flush() | |
1017 |
|
1022 | |||
1018 | # Changed so an instance can just be called as VerboseTB_inst() and print |
|
1023 | # Changed so an instance can just be called as VerboseTB_inst() and print | |
1019 | # out the right info on its own. |
|
1024 | # out the right info on its own. | |
1020 | def __call__(self, etype=None, evalue=None, etb=None): |
|
1025 | def __call__(self, etype=None, evalue=None, etb=None): | |
1021 | """This hook can replace sys.excepthook (for Python 2.1 or higher).""" |
|
1026 | """This hook can replace sys.excepthook (for Python 2.1 or higher).""" | |
1022 | if etb is None: |
|
1027 | if etb is None: | |
1023 | self.handler() |
|
1028 | self.handler() | |
1024 | else: |
|
1029 | else: | |
1025 | self.handler((etype, evalue, etb)) |
|
1030 | self.handler((etype, evalue, etb)) | |
1026 | try: |
|
1031 | try: | |
1027 | self.debugger() |
|
1032 | self.debugger() | |
1028 | except KeyboardInterrupt: |
|
1033 | except KeyboardInterrupt: | |
1029 | print "\nKeyboardInterrupt" |
|
1034 | print "\nKeyboardInterrupt" | |
1030 |
|
1035 | |||
1031 | #---------------------------------------------------------------------------- |
|
1036 | #---------------------------------------------------------------------------- | |
1032 | class FormattedTB(VerboseTB, ListTB): |
|
1037 | class FormattedTB(VerboseTB, ListTB): | |
1033 | """Subclass ListTB but allow calling with a traceback. |
|
1038 | """Subclass ListTB but allow calling with a traceback. | |
1034 |
|
1039 | |||
1035 | It can thus be used as a sys.excepthook for Python > 2.1. |
|
1040 | It can thus be used as a sys.excepthook for Python > 2.1. | |
1036 |
|
1041 | |||
1037 | Also adds 'Context' and 'Verbose' modes, not available in ListTB. |
|
1042 | Also adds 'Context' and 'Verbose' modes, not available in ListTB. | |
1038 |
|
1043 | |||
1039 | Allows a tb_offset to be specified. This is useful for situations where |
|
1044 | Allows a tb_offset to be specified. This is useful for situations where | |
1040 | one needs to remove a number of topmost frames from the traceback (such as |
|
1045 | one needs to remove a number of topmost frames from the traceback (such as | |
1041 | occurs with python programs that themselves execute other python code, |
|
1046 | occurs with python programs that themselves execute other python code, | |
1042 | like Python shells). """ |
|
1047 | like Python shells). """ | |
1043 |
|
1048 | |||
1044 | def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False, |
|
1049 | def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False, | |
1045 | ostream=None, |
|
1050 | ostream=None, | |
1046 | tb_offset=0, long_header=False, include_vars=False, |
|
1051 | tb_offset=0, long_header=False, include_vars=False, | |
1047 | check_cache=None): |
|
1052 | check_cache=None): | |
1048 |
|
1053 | |||
1049 | # NEVER change the order of this list. Put new modes at the end: |
|
1054 | # NEVER change the order of this list. Put new modes at the end: | |
1050 | self.valid_modes = ['Plain','Context','Verbose'] |
|
1055 | self.valid_modes = ['Plain','Context','Verbose'] | |
1051 | self.verbose_modes = self.valid_modes[1:3] |
|
1056 | self.verbose_modes = self.valid_modes[1:3] | |
1052 |
|
1057 | |||
1053 | VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb, |
|
1058 | VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb, | |
1054 | ostream=ostream, tb_offset=tb_offset, |
|
1059 | ostream=ostream, tb_offset=tb_offset, | |
1055 | long_header=long_header, include_vars=include_vars, |
|
1060 | long_header=long_header, include_vars=include_vars, | |
1056 | check_cache=check_cache) |
|
1061 | check_cache=check_cache) | |
1057 |
|
1062 | |||
1058 | # Different types of tracebacks are joined with different separators to |
|
1063 | # Different types of tracebacks are joined with different separators to | |
1059 | # form a single string. They are taken from this dict |
|
1064 | # form a single string. They are taken from this dict | |
1060 | self._join_chars = dict(Plain='', Context='\n', Verbose='\n') |
|
1065 | self._join_chars = dict(Plain='', Context='\n', Verbose='\n') | |
1061 | # set_mode also sets the tb_join_char attribute |
|
1066 | # set_mode also sets the tb_join_char attribute | |
1062 | self.set_mode(mode) |
|
1067 | self.set_mode(mode) | |
1063 |
|
1068 | |||
1064 | def _extract_tb(self,tb): |
|
1069 | def _extract_tb(self,tb): | |
1065 | if tb: |
|
1070 | if tb: | |
1066 | return traceback.extract_tb(tb) |
|
1071 | return traceback.extract_tb(tb) | |
1067 | else: |
|
1072 | else: | |
1068 | return None |
|
1073 | return None | |
1069 |
|
1074 | |||
1070 | def structured_traceback(self, etype, value, tb, tb_offset=None, context=5): |
|
1075 | def structured_traceback(self, etype, value, tb, tb_offset=None, context=5): | |
1071 | tb_offset = self.tb_offset if tb_offset is None else tb_offset |
|
1076 | tb_offset = self.tb_offset if tb_offset is None else tb_offset | |
1072 | mode = self.mode |
|
1077 | mode = self.mode | |
1073 | if mode in self.verbose_modes: |
|
1078 | if mode in self.verbose_modes: | |
1074 | # Verbose modes need a full traceback |
|
1079 | # Verbose modes need a full traceback | |
1075 | return VerboseTB.structured_traceback( |
|
1080 | return VerboseTB.structured_traceback( | |
1076 | self, etype, value, tb, tb_offset, context |
|
1081 | self, etype, value, tb, tb_offset, context | |
1077 | ) |
|
1082 | ) | |
1078 | else: |
|
1083 | else: | |
1079 | # We must check the source cache because otherwise we can print |
|
1084 | # We must check the source cache because otherwise we can print | |
1080 | # out-of-date source code. |
|
1085 | # out-of-date source code. | |
1081 | self.check_cache() |
|
1086 | self.check_cache() | |
1082 | # Now we can extract and format the exception |
|
1087 | # Now we can extract and format the exception | |
1083 | elist = self._extract_tb(tb) |
|
1088 | elist = self._extract_tb(tb) | |
1084 | return ListTB.structured_traceback( |
|
1089 | return ListTB.structured_traceback( | |
1085 | self, etype, value, elist, tb_offset, context |
|
1090 | self, etype, value, elist, tb_offset, context | |
1086 | ) |
|
1091 | ) | |
1087 |
|
1092 | |||
1088 | def stb2text(self, stb): |
|
1093 | def stb2text(self, stb): | |
1089 | """Convert a structured traceback (a list) to a string.""" |
|
1094 | """Convert a structured traceback (a list) to a string.""" | |
1090 | return self.tb_join_char.join(stb) |
|
1095 | return self.tb_join_char.join(stb) | |
1091 |
|
1096 | |||
1092 |
|
1097 | |||
1093 | def set_mode(self,mode=None): |
|
1098 | def set_mode(self,mode=None): | |
1094 | """Switch to the desired mode. |
|
1099 | """Switch to the desired mode. | |
1095 |
|
1100 | |||
1096 | If mode is not specified, cycles through the available modes.""" |
|
1101 | If mode is not specified, cycles through the available modes.""" | |
1097 |
|
1102 | |||
1098 | if not mode: |
|
1103 | if not mode: | |
1099 | new_idx = ( self.valid_modes.index(self.mode) + 1 ) % \ |
|
1104 | new_idx = ( self.valid_modes.index(self.mode) + 1 ) % \ | |
1100 | len(self.valid_modes) |
|
1105 | len(self.valid_modes) | |
1101 | self.mode = self.valid_modes[new_idx] |
|
1106 | self.mode = self.valid_modes[new_idx] | |
1102 | elif mode not in self.valid_modes: |
|
1107 | elif mode not in self.valid_modes: | |
1103 | raise ValueError('Unrecognized mode in FormattedTB: <'+mode+'>\n' |
|
1108 | raise ValueError('Unrecognized mode in FormattedTB: <'+mode+'>\n' | |
1104 | 'Valid modes: '+str(self.valid_modes)) |
|
1109 | 'Valid modes: '+str(self.valid_modes)) | |
1105 | else: |
|
1110 | else: | |
1106 | self.mode = mode |
|
1111 | self.mode = mode | |
1107 | # include variable details only in 'Verbose' mode |
|
1112 | # include variable details only in 'Verbose' mode | |
1108 | self.include_vars = (self.mode == self.valid_modes[2]) |
|
1113 | self.include_vars = (self.mode == self.valid_modes[2]) | |
1109 | # Set the join character for generating text tracebacks |
|
1114 | # Set the join character for generating text tracebacks | |
1110 | self.tb_join_char = self._join_chars[self.mode] |
|
1115 | self.tb_join_char = self._join_chars[self.mode] | |
1111 |
|
1116 | |||
1112 | # some convenient shorcuts |
|
1117 | # some convenient shorcuts | |
1113 | def plain(self): |
|
1118 | def plain(self): | |
1114 | self.set_mode(self.valid_modes[0]) |
|
1119 | self.set_mode(self.valid_modes[0]) | |
1115 |
|
1120 | |||
1116 | def context(self): |
|
1121 | def context(self): | |
1117 | self.set_mode(self.valid_modes[1]) |
|
1122 | self.set_mode(self.valid_modes[1]) | |
1118 |
|
1123 | |||
1119 | def verbose(self): |
|
1124 | def verbose(self): | |
1120 | self.set_mode(self.valid_modes[2]) |
|
1125 | self.set_mode(self.valid_modes[2]) | |
1121 |
|
1126 | |||
1122 | #---------------------------------------------------------------------------- |
|
1127 | #---------------------------------------------------------------------------- | |
1123 | class AutoFormattedTB(FormattedTB): |
|
1128 | class AutoFormattedTB(FormattedTB): | |
1124 | """A traceback printer which can be called on the fly. |
|
1129 | """A traceback printer which can be called on the fly. | |
1125 |
|
1130 | |||
1126 | It will find out about exceptions by itself. |
|
1131 | It will find out about exceptions by itself. | |
1127 |
|
1132 | |||
1128 | A brief example: |
|
1133 | A brief example: | |
1129 |
|
1134 | |||
1130 | AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux') |
|
1135 | AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux') | |
1131 | try: |
|
1136 | try: | |
1132 | ... |
|
1137 | ... | |
1133 | except: |
|
1138 | except: | |
1134 | AutoTB() # or AutoTB(out=logfile) where logfile is an open file object |
|
1139 | AutoTB() # or AutoTB(out=logfile) where logfile is an open file object | |
1135 | """ |
|
1140 | """ | |
1136 |
|
1141 | |||
1137 | def __call__(self,etype=None,evalue=None,etb=None, |
|
1142 | def __call__(self,etype=None,evalue=None,etb=None, | |
1138 | out=None,tb_offset=None): |
|
1143 | out=None,tb_offset=None): | |
1139 | """Print out a formatted exception traceback. |
|
1144 | """Print out a formatted exception traceback. | |
1140 |
|
1145 | |||
1141 | Optional arguments: |
|
1146 | Optional arguments: | |
1142 | - out: an open file-like object to direct output to. |
|
1147 | - out: an open file-like object to direct output to. | |
1143 |
|
1148 | |||
1144 | - tb_offset: the number of frames to skip over in the stack, on a |
|
1149 | - tb_offset: the number of frames to skip over in the stack, on a | |
1145 | per-call basis (this overrides temporarily the instance's tb_offset |
|
1150 | per-call basis (this overrides temporarily the instance's tb_offset | |
1146 | given at initialization time. """ |
|
1151 | given at initialization time. """ | |
1147 |
|
1152 | |||
1148 |
|
1153 | |||
1149 | if out is None: |
|
1154 | if out is None: | |
1150 | out = self.ostream |
|
1155 | out = self.ostream | |
1151 | out.flush() |
|
1156 | out.flush() | |
1152 | out.write(self.text(etype, evalue, etb, tb_offset)) |
|
1157 | out.write(self.text(etype, evalue, etb, tb_offset)) | |
1153 | out.write('\n') |
|
1158 | out.write('\n') | |
1154 | out.flush() |
|
1159 | out.flush() | |
1155 | # FIXME: we should remove the auto pdb behavior from here and leave |
|
1160 | # FIXME: we should remove the auto pdb behavior from here and leave | |
1156 | # that to the clients. |
|
1161 | # that to the clients. | |
1157 | try: |
|
1162 | try: | |
1158 | self.debugger() |
|
1163 | self.debugger() | |
1159 | except KeyboardInterrupt: |
|
1164 | except KeyboardInterrupt: | |
1160 | print "\nKeyboardInterrupt" |
|
1165 | print "\nKeyboardInterrupt" | |
1161 |
|
1166 | |||
1162 | def structured_traceback(self, etype=None, value=None, tb=None, |
|
1167 | def structured_traceback(self, etype=None, value=None, tb=None, | |
1163 | tb_offset=None, context=5): |
|
1168 | tb_offset=None, context=5): | |
1164 | if etype is None: |
|
1169 | if etype is None: | |
1165 | etype,value,tb = sys.exc_info() |
|
1170 | etype,value,tb = sys.exc_info() | |
1166 | self.tb = tb |
|
1171 | self.tb = tb | |
1167 | return FormattedTB.structured_traceback( |
|
1172 | return FormattedTB.structured_traceback( | |
1168 | self, etype, value, tb, tb_offset, context) |
|
1173 | self, etype, value, tb, tb_offset, context) | |
1169 |
|
1174 | |||
1170 | #--------------------------------------------------------------------------- |
|
1175 | #--------------------------------------------------------------------------- | |
1171 |
|
1176 | |||
1172 | # A simple class to preserve Nathan's original functionality. |
|
1177 | # A simple class to preserve Nathan's original functionality. | |
1173 | class ColorTB(FormattedTB): |
|
1178 | class ColorTB(FormattedTB): | |
1174 | """Shorthand to initialize a FormattedTB in Linux colors mode.""" |
|
1179 | """Shorthand to initialize a FormattedTB in Linux colors mode.""" | |
1175 | def __init__(self,color_scheme='Linux',call_pdb=0): |
|
1180 | def __init__(self,color_scheme='Linux',call_pdb=0): | |
1176 | FormattedTB.__init__(self,color_scheme=color_scheme, |
|
1181 | FormattedTB.__init__(self,color_scheme=color_scheme, | |
1177 | call_pdb=call_pdb) |
|
1182 | call_pdb=call_pdb) | |
1178 |
|
1183 | |||
1179 |
|
1184 | |||
1180 | class SyntaxTB(ListTB): |
|
1185 | class SyntaxTB(ListTB): | |
1181 | """Extension which holds some state: the last exception value""" |
|
1186 | """Extension which holds some state: the last exception value""" | |
1182 |
|
1187 | |||
1183 | def __init__(self,color_scheme = 'NoColor'): |
|
1188 | def __init__(self,color_scheme = 'NoColor'): | |
1184 | ListTB.__init__(self,color_scheme) |
|
1189 | ListTB.__init__(self,color_scheme) | |
1185 | self.last_syntax_error = None |
|
1190 | self.last_syntax_error = None | |
1186 |
|
1191 | |||
1187 | def __call__(self, etype, value, elist): |
|
1192 | def __call__(self, etype, value, elist): | |
1188 | self.last_syntax_error = value |
|
1193 | self.last_syntax_error = value | |
1189 | ListTB.__call__(self,etype,value,elist) |
|
1194 | ListTB.__call__(self,etype,value,elist) | |
1190 |
|
1195 | |||
1191 | def clear_err_state(self): |
|
1196 | def clear_err_state(self): | |
1192 | """Return the current error state and clear it""" |
|
1197 | """Return the current error state and clear it""" | |
1193 | e = self.last_syntax_error |
|
1198 | e = self.last_syntax_error | |
1194 | self.last_syntax_error = None |
|
1199 | self.last_syntax_error = None | |
1195 | return e |
|
1200 | return e | |
1196 |
|
1201 | |||
1197 | def stb2text(self, stb): |
|
1202 | def stb2text(self, stb): | |
1198 | """Convert a structured traceback (a list) to a string.""" |
|
1203 | """Convert a structured traceback (a list) to a string.""" | |
1199 | return ''.join(stb) |
|
1204 | return ''.join(stb) | |
1200 |
|
1205 | |||
1201 |
|
1206 | |||
1202 | #---------------------------------------------------------------------------- |
|
1207 | #---------------------------------------------------------------------------- | |
1203 | # module testing (minimal) |
|
1208 | # module testing (minimal) | |
1204 | if __name__ == "__main__": |
|
1209 | if __name__ == "__main__": | |
1205 | def spam(c, d_e): |
|
1210 | def spam(c, d_e): | |
1206 | (d, e) = d_e |
|
1211 | (d, e) = d_e | |
1207 | x = c + d |
|
1212 | x = c + d | |
1208 | y = c * d |
|
1213 | y = c * d | |
1209 | foo(x, y) |
|
1214 | foo(x, y) | |
1210 |
|
1215 | |||
1211 | def foo(a, b, bar=1): |
|
1216 | def foo(a, b, bar=1): | |
1212 | eggs(a, b + bar) |
|
1217 | eggs(a, b + bar) | |
1213 |
|
1218 | |||
1214 | def eggs(f, g, z=globals()): |
|
1219 | def eggs(f, g, z=globals()): | |
1215 | h = f + g |
|
1220 | h = f + g | |
1216 | i = f - g |
|
1221 | i = f - g | |
1217 | return h / i |
|
1222 | return h / i | |
1218 |
|
1223 | |||
1219 | print '' |
|
1224 | print '' | |
1220 | print '*** Before ***' |
|
1225 | print '*** Before ***' | |
1221 | try: |
|
1226 | try: | |
1222 | print spam(1, (2, 3)) |
|
1227 | print spam(1, (2, 3)) | |
1223 | except: |
|
1228 | except: | |
1224 | traceback.print_exc() |
|
1229 | traceback.print_exc() | |
1225 | print '' |
|
1230 | print '' | |
1226 |
|
1231 | |||
1227 | handler = ColorTB() |
|
1232 | handler = ColorTB() | |
1228 | print '*** ColorTB ***' |
|
1233 | print '*** ColorTB ***' | |
1229 | try: |
|
1234 | try: | |
1230 | print spam(1, (2, 3)) |
|
1235 | print spam(1, (2, 3)) | |
1231 | except: |
|
1236 | except: | |
1232 | handler(*sys.exc_info()) |
|
1237 | handler(*sys.exc_info()) | |
1233 | print '' |
|
1238 | print '' | |
1234 |
|
1239 | |||
1235 | handler = VerboseTB() |
|
1240 | handler = VerboseTB() | |
1236 | print '*** VerboseTB ***' |
|
1241 | print '*** VerboseTB ***' | |
1237 | try: |
|
1242 | try: | |
1238 | print spam(1, (2, 3)) |
|
1243 | print spam(1, (2, 3)) | |
1239 | except: |
|
1244 | except: | |
1240 | handler(*sys.exc_info()) |
|
1245 | handler(*sys.exc_info()) | |
1241 | print '' |
|
1246 | print '' | |
1242 |
|
1247 |
@@ -1,578 +1,583 b'' | |||||
1 | from __future__ import unicode_literals |
|
|||
2 |
|
||||
3 |
|
||||
4 |
|
|
1 | """Module for interactive demos using IPython. | |
5 |
|
2 | |||
6 | This module implements a few classes for running Python scripts interactively |
|
3 | This module implements a few classes for running Python scripts interactively | |
7 | in IPython for demonstrations. With very simple markup (a few tags in |
|
4 | in IPython for demonstrations. With very simple markup (a few tags in | |
8 | comments), you can control points where the script stops executing and returns |
|
5 | comments), you can control points where the script stops executing and returns | |
9 | control to IPython. |
|
6 | control to IPython. | |
10 |
|
7 | |||
11 |
|
8 | |||
12 | Provided classes |
|
9 | Provided classes | |
13 | ================ |
|
10 | ---------------- | |
14 |
|
11 | |||
15 | The classes are (see their docstrings for further details): |
|
12 | The classes are (see their docstrings for further details): | |
16 |
|
13 | |||
17 | - Demo: pure python demos |
|
14 | - Demo: pure python demos | |
18 |
|
15 | |||
19 | - IPythonDemo: demos with input to be processed by IPython as if it had been |
|
16 | - IPythonDemo: demos with input to be processed by IPython as if it had been | |
20 | typed interactively (so magics work, as well as any other special syntax you |
|
17 | typed interactively (so magics work, as well as any other special syntax you | |
21 | may have added via input prefilters). |
|
18 | may have added via input prefilters). | |
22 |
|
19 | |||
23 | - LineDemo: single-line version of the Demo class. These demos are executed |
|
20 | - LineDemo: single-line version of the Demo class. These demos are executed | |
24 | one line at a time, and require no markup. |
|
21 | one line at a time, and require no markup. | |
25 |
|
22 | |||
26 | - IPythonLineDemo: IPython version of the LineDemo class (the demo is |
|
23 | - IPythonLineDemo: IPython version of the LineDemo class (the demo is | |
27 | executed a line at a time, but processed via IPython). |
|
24 | executed a line at a time, but processed via IPython). | |
28 |
|
25 | |||
29 | - ClearMixin: mixin to make Demo classes with less visual clutter. It |
|
26 | - ClearMixin: mixin to make Demo classes with less visual clutter. It | |
30 | declares an empty marquee and a pre_cmd that clears the screen before each |
|
27 | declares an empty marquee and a pre_cmd that clears the screen before each | |
31 | block (see Subclassing below). |
|
28 | block (see Subclassing below). | |
32 |
|
29 | |||
33 | - ClearDemo, ClearIPDemo: mixin-enabled versions of the Demo and IPythonDemo |
|
30 | - ClearDemo, ClearIPDemo: mixin-enabled versions of the Demo and IPythonDemo | |
34 | classes. |
|
31 | classes. | |
35 |
|
32 | |||
|
33 | Inheritance diagram: | |||
|
34 | ||||
|
35 | .. inheritance-diagram:: IPython.lib.demo | |||
|
36 | :parts: 3 | |||
36 |
|
37 | |||
37 | Subclassing |
|
38 | Subclassing | |
38 | =========== |
|
39 | ----------- | |
39 |
|
40 | |||
40 | The classes here all include a few methods meant to make customization by |
|
41 | The classes here all include a few methods meant to make customization by | |
41 | subclassing more convenient. Their docstrings below have some more details: |
|
42 | subclassing more convenient. Their docstrings below have some more details: | |
42 |
|
43 | |||
43 | - marquee(): generates a marquee to provide visible on-screen markers at each |
|
44 | - marquee(): generates a marquee to provide visible on-screen markers at each | |
44 | block start and end. |
|
45 | block start and end. | |
45 |
|
46 | |||
46 | - pre_cmd(): run right before the execution of each block. |
|
47 | - pre_cmd(): run right before the execution of each block. | |
47 |
|
48 | |||
48 | - post_cmd(): run right after the execution of each block. If the block |
|
49 | - post_cmd(): run right after the execution of each block. If the block | |
49 | raises an exception, this is NOT called. |
|
50 | raises an exception, this is NOT called. | |
50 |
|
51 | |||
51 |
|
52 | |||
52 | Operation |
|
53 | Operation | |
53 | ========= |
|
54 | --------- | |
54 |
|
55 | |||
55 | The file is run in its own empty namespace (though you can pass it a string of |
|
56 | The file is run in its own empty namespace (though you can pass it a string of | |
56 | arguments as if in a command line environment, and it will see those as |
|
57 | arguments as if in a command line environment, and it will see those as | |
57 | sys.argv). But at each stop, the global IPython namespace is updated with the |
|
58 | sys.argv). But at each stop, the global IPython namespace is updated with the | |
58 | current internal demo namespace, so you can work interactively with the data |
|
59 | current internal demo namespace, so you can work interactively with the data | |
59 | accumulated so far. |
|
60 | accumulated so far. | |
60 |
|
61 | |||
61 | By default, each block of code is printed (with syntax highlighting) before |
|
62 | By default, each block of code is printed (with syntax highlighting) before | |
62 | executing it and you have to confirm execution. This is intended to show the |
|
63 | executing it and you have to confirm execution. This is intended to show the | |
63 | code to an audience first so you can discuss it, and only proceed with |
|
64 | code to an audience first so you can discuss it, and only proceed with | |
64 | execution once you agree. There are a few tags which allow you to modify this |
|
65 | execution once you agree. There are a few tags which allow you to modify this | |
65 | behavior. |
|
66 | behavior. | |
66 |
|
67 | |||
67 | The supported tags are: |
|
68 | The supported tags are: | |
68 |
|
69 | |||
69 | # <demo> stop |
|
70 | # <demo> stop | |
70 |
|
71 | |||
71 | Defines block boundaries, the points where IPython stops execution of the |
|
72 | Defines block boundaries, the points where IPython stops execution of the | |
72 | file and returns to the interactive prompt. |
|
73 | file and returns to the interactive prompt. | |
73 |
|
74 | |||
74 | You can optionally mark the stop tag with extra dashes before and after the |
|
75 | You can optionally mark the stop tag with extra dashes before and after the | |
75 | word 'stop', to help visually distinguish the blocks in a text editor: |
|
76 | word 'stop', to help visually distinguish the blocks in a text editor: | |
76 |
|
77 | |||
77 | # <demo> --- stop --- |
|
78 | # <demo> --- stop --- | |
78 |
|
79 | |||
79 |
|
80 | |||
80 | # <demo> silent |
|
81 | # <demo> silent | |
81 |
|
82 | |||
82 | Make a block execute silently (and hence automatically). Typically used in |
|
83 | Make a block execute silently (and hence automatically). Typically used in | |
83 | cases where you have some boilerplate or initialization code which you need |
|
84 | cases where you have some boilerplate or initialization code which you need | |
84 | executed but do not want to be seen in the demo. |
|
85 | executed but do not want to be seen in the demo. | |
85 |
|
86 | |||
86 | # <demo> auto |
|
87 | # <demo> auto | |
87 |
|
88 | |||
88 | Make a block execute automatically, but still being printed. Useful for |
|
89 | Make a block execute automatically, but still being printed. Useful for | |
89 | simple code which does not warrant discussion, since it avoids the extra |
|
90 | simple code which does not warrant discussion, since it avoids the extra | |
90 | manual confirmation. |
|
91 | manual confirmation. | |
91 |
|
92 | |||
92 | # <demo> auto_all |
|
93 | # <demo> auto_all | |
93 |
|
94 | |||
94 | This tag can _only_ be in the first block, and if given it overrides the |
|
95 | This tag can _only_ be in the first block, and if given it overrides the | |
95 | individual auto tags to make the whole demo fully automatic (no block asks |
|
96 | individual auto tags to make the whole demo fully automatic (no block asks | |
96 | for confirmation). It can also be given at creation time (or the attribute |
|
97 | for confirmation). It can also be given at creation time (or the attribute | |
97 | set later) to override what's in the file. |
|
98 | set later) to override what's in the file. | |
98 |
|
99 | |||
99 | While _any_ python file can be run as a Demo instance, if there are no stop |
|
100 | While _any_ python file can be run as a Demo instance, if there are no stop | |
100 | tags the whole file will run in a single block (no different that calling |
|
101 | tags the whole file will run in a single block (no different that calling | |
101 | first %pycat and then %run). The minimal markup to make this useful is to |
|
102 | first %pycat and then %run). The minimal markup to make this useful is to | |
102 | place a set of stop tags; the other tags are only there to let you fine-tune |
|
103 | place a set of stop tags; the other tags are only there to let you fine-tune | |
103 | the execution. |
|
104 | the execution. | |
104 |
|
105 | |||
105 | This is probably best explained with the simple example file below. You can |
|
106 | This is probably best explained with the simple example file below. You can | |
106 | copy this into a file named ex_demo.py, and try running it via: |
|
107 | copy this into a file named ex_demo.py, and try running it via: | |
107 |
|
108 | |||
108 | from IPython.demo import Demo |
|
109 | from IPython.demo import Demo | |
109 | d = Demo('ex_demo.py') |
|
110 | d = Demo('ex_demo.py') | |
110 | d() <--- Call the d object (omit the parens if you have autocall set to 2). |
|
111 | d() <--- Call the d object (omit the parens if you have autocall set to 2). | |
111 |
|
112 | |||
112 | Each time you call the demo object, it runs the next block. The demo object |
|
113 | Each time you call the demo object, it runs the next block. The demo object | |
113 | has a few useful methods for navigation, like again(), edit(), jump(), seek() |
|
114 | has a few useful methods for navigation, like again(), edit(), jump(), seek() | |
114 | and back(). It can be reset for a new run via reset() or reloaded from disk |
|
115 | and back(). It can be reset for a new run via reset() or reloaded from disk | |
115 | (in case you've edited the source) via reload(). See their docstrings below. |
|
116 | (in case you've edited the source) via reload(). See their docstrings below. | |
116 |
|
117 | |||
117 | Note: To make this simpler to explore, a file called "demo-exercizer.py" has |
|
118 | Note: To make this simpler to explore, a file called "demo-exercizer.py" has | |
118 | been added to the "docs/examples/core" directory. Just cd to this directory in |
|
119 | been added to the "docs/examples/core" directory. Just cd to this directory in | |
119 | an IPython session, and type:: |
|
120 | an IPython session, and type:: | |
120 |
|
121 | |||
121 | %run demo-exercizer.py |
|
122 | %run demo-exercizer.py | |
122 |
|
123 | |||
123 | and then follow the directions. |
|
124 | and then follow the directions. | |
124 |
|
125 | |||
125 | Example |
|
126 | Example | |
126 | ======= |
|
127 | ------- | |
127 |
|
128 | |||
128 | The following is a very simple example of a valid demo file. |
|
129 | The following is a very simple example of a valid demo file. | |
129 |
|
130 | |||
130 | #################### EXAMPLE DEMO <ex_demo.py> ############################### |
|
131 | :: | |
131 | '''A simple interactive demo to illustrate the use of IPython's Demo class.''' |
|
|||
132 |
|
132 | |||
133 | print 'Hello, welcome to an interactive IPython demo.' |
|
133 | #################### EXAMPLE DEMO <ex_demo.py> ############################### | |
|
134 | '''A simple interactive demo to illustrate the use of IPython's Demo class.''' | |||
134 |
|
135 | |||
135 | # The mark below defines a block boundary, which is a point where IPython will |
|
136 | print 'Hello, welcome to an interactive IPython demo.' | |
136 | # stop execution and return to the interactive prompt. The dashes are actually |
|
|||
137 | # optional and used only as a visual aid to clearly separate blocks while |
|
|||
138 | # editing the demo code. |
|
|||
139 | # <demo> stop |
|
|||
140 |
|
137 | |||
141 | x = 1 |
|
138 | # The mark below defines a block boundary, which is a point where IPython will | |
142 | y = 2 |
|
139 | # stop execution and return to the interactive prompt. The dashes are actually | |
|
140 | # optional and used only as a visual aid to clearly separate blocks while | |||
|
141 | # editing the demo code. | |||
|
142 | # <demo> stop | |||
143 |
|
143 | |||
144 | # <demo> stop |
|
144 | x = 1 | |
|
145 | y = 2 | |||
145 |
|
146 | |||
146 | # the mark below makes this block as silent |
|
147 | # <demo> stop | |
147 | # <demo> silent |
|
|||
148 |
|
148 | |||
149 | print 'This is a silent block, which gets executed but not printed.' |
|
149 | # the mark below makes this block as silent | |
|
150 | # <demo> silent | |||
150 |
|
151 | |||
151 | # <demo> stop |
|
152 | print 'This is a silent block, which gets executed but not printed.' | |
152 | # <demo> auto |
|
|||
153 | print 'This is an automatic block.' |
|
|||
154 | print 'It is executed without asking for confirmation, but printed.' |
|
|||
155 | z = x+y |
|
|||
156 |
|
153 | |||
157 | print 'z=',x |
|
154 | # <demo> stop | |
|
155 | # <demo> auto | |||
|
156 | print 'This is an automatic block.' | |||
|
157 | print 'It is executed without asking for confirmation, but printed.' | |||
|
158 | z = x+y | |||
158 |
|
159 | |||
159 | # <demo> stop |
|
160 | print 'z=',x | |
160 | # This is just another normal block. |
|
|||
161 | print 'z is now:', z |
|
|||
162 |
|
161 | |||
163 | print 'bye!' |
|
162 | # <demo> stop | |
164 | ################### END EXAMPLE DEMO <ex_demo.py> ############################ |
|
163 | # This is just another normal block. | |
|
164 | print 'z is now:', z | |||
|
165 | ||||
|
166 | print 'bye!' | |||
|
167 | ################### END EXAMPLE DEMO <ex_demo.py> ############################ | |||
165 | """ |
|
168 | """ | |
166 |
|
169 | |||
|
170 | from __future__ import unicode_literals | |||
|
171 | ||||
167 | #***************************************************************************** |
|
172 | #***************************************************************************** | |
168 | # Copyright (C) 2005-2006 Fernando Perez. <Fernando.Perez@colorado.edu> |
|
173 | # Copyright (C) 2005-2006 Fernando Perez. <Fernando.Perez@colorado.edu> | |
169 | # |
|
174 | # | |
170 | # Distributed under the terms of the BSD License. The full license is in |
|
175 | # Distributed under the terms of the BSD License. The full license is in | |
171 | # the file COPYING, distributed as part of this software. |
|
176 | # the file COPYING, distributed as part of this software. | |
172 | # |
|
177 | # | |
173 | #***************************************************************************** |
|
178 | #***************************************************************************** | |
174 | from __future__ import print_function |
|
179 | from __future__ import print_function | |
175 |
|
180 | |||
176 | import os |
|
181 | import os | |
177 | import re |
|
182 | import re | |
178 | import shlex |
|
183 | import shlex | |
179 | import sys |
|
184 | import sys | |
180 |
|
185 | |||
181 | from IPython.utils.PyColorize import Parser |
|
186 | from IPython.utils.PyColorize import Parser | |
182 | from IPython.utils import io |
|
187 | from IPython.utils import io | |
183 | from IPython.utils.io import file_read, file_readlines |
|
188 | from IPython.utils.io import file_read, file_readlines | |
184 | from IPython.utils.text import marquee |
|
189 | from IPython.utils.text import marquee | |
185 | from IPython.utils import openpy |
|
190 | from IPython.utils import openpy | |
186 | __all__ = ['Demo','IPythonDemo','LineDemo','IPythonLineDemo','DemoError'] |
|
191 | __all__ = ['Demo','IPythonDemo','LineDemo','IPythonLineDemo','DemoError'] | |
187 |
|
192 | |||
188 | class DemoError(Exception): pass |
|
193 | class DemoError(Exception): pass | |
189 |
|
194 | |||
190 | def re_mark(mark): |
|
195 | def re_mark(mark): | |
191 | return re.compile(r'^\s*#\s+<demo>\s+%s\s*$' % mark,re.MULTILINE) |
|
196 | return re.compile(r'^\s*#\s+<demo>\s+%s\s*$' % mark,re.MULTILINE) | |
192 |
|
197 | |||
193 | class Demo(object): |
|
198 | class Demo(object): | |
194 |
|
199 | |||
195 | re_stop = re_mark('-*\s?stop\s?-*') |
|
200 | re_stop = re_mark('-*\s?stop\s?-*') | |
196 | re_silent = re_mark('silent') |
|
201 | re_silent = re_mark('silent') | |
197 | re_auto = re_mark('auto') |
|
202 | re_auto = re_mark('auto') | |
198 | re_auto_all = re_mark('auto_all') |
|
203 | re_auto_all = re_mark('auto_all') | |
199 |
|
204 | |||
200 | def __init__(self,src,title='',arg_str='',auto_all=None): |
|
205 | def __init__(self,src,title='',arg_str='',auto_all=None): | |
201 | """Make a new demo object. To run the demo, simply call the object. |
|
206 | """Make a new demo object. To run the demo, simply call the object. | |
202 |
|
207 | |||
203 | See the module docstring for full details and an example (you can use |
|
208 | See the module docstring for full details and an example (you can use | |
204 | IPython.Demo? in IPython to see it). |
|
209 | IPython.Demo? in IPython to see it). | |
205 |
|
210 | |||
206 | Inputs: |
|
211 | Inputs: | |
207 |
|
212 | |||
208 | - src is either a file, or file-like object, or a |
|
213 | - src is either a file, or file-like object, or a | |
209 | string that can be resolved to a filename. |
|
214 | string that can be resolved to a filename. | |
210 |
|
215 | |||
211 | Optional inputs: |
|
216 | Optional inputs: | |
212 |
|
217 | |||
213 | - title: a string to use as the demo name. Of most use when the demo |
|
218 | - title: a string to use as the demo name. Of most use when the demo | |
214 | you are making comes from an object that has no filename, or if you |
|
219 | you are making comes from an object that has no filename, or if you | |
215 | want an alternate denotation distinct from the filename. |
|
220 | want an alternate denotation distinct from the filename. | |
216 |
|
221 | |||
217 | - arg_str(''): a string of arguments, internally converted to a list |
|
222 | - arg_str(''): a string of arguments, internally converted to a list | |
218 | just like sys.argv, so the demo script can see a similar |
|
223 | just like sys.argv, so the demo script can see a similar | |
219 | environment. |
|
224 | environment. | |
220 |
|
225 | |||
221 | - auto_all(None): global flag to run all blocks automatically without |
|
226 | - auto_all(None): global flag to run all blocks automatically without | |
222 | confirmation. This attribute overrides the block-level tags and |
|
227 | confirmation. This attribute overrides the block-level tags and | |
223 | applies to the whole demo. It is an attribute of the object, and |
|
228 | applies to the whole demo. It is an attribute of the object, and | |
224 | can be changed at runtime simply by reassigning it to a boolean |
|
229 | can be changed at runtime simply by reassigning it to a boolean | |
225 | value. |
|
230 | value. | |
226 | """ |
|
231 | """ | |
227 | if hasattr(src, "read"): |
|
232 | if hasattr(src, "read"): | |
228 | # It seems to be a file or a file-like object |
|
233 | # It seems to be a file or a file-like object | |
229 | self.fname = "from a file-like object" |
|
234 | self.fname = "from a file-like object" | |
230 | if title == '': |
|
235 | if title == '': | |
231 | self.title = "from a file-like object" |
|
236 | self.title = "from a file-like object" | |
232 | else: |
|
237 | else: | |
233 | self.title = title |
|
238 | self.title = title | |
234 | else: |
|
239 | else: | |
235 | # Assume it's a string or something that can be converted to one |
|
240 | # Assume it's a string or something that can be converted to one | |
236 | self.fname = src |
|
241 | self.fname = src | |
237 | if title == '': |
|
242 | if title == '': | |
238 | (filepath, filename) = os.path.split(src) |
|
243 | (filepath, filename) = os.path.split(src) | |
239 | self.title = filename |
|
244 | self.title = filename | |
240 | else: |
|
245 | else: | |
241 | self.title = title |
|
246 | self.title = title | |
242 | self.sys_argv = [src] + shlex.split(arg_str) |
|
247 | self.sys_argv = [src] + shlex.split(arg_str) | |
243 | self.auto_all = auto_all |
|
248 | self.auto_all = auto_all | |
244 | self.src = src |
|
249 | self.src = src | |
245 |
|
250 | |||
246 | # get a few things from ipython. While it's a bit ugly design-wise, |
|
251 | # get a few things from ipython. While it's a bit ugly design-wise, | |
247 | # it ensures that things like color scheme and the like are always in |
|
252 | # it ensures that things like color scheme and the like are always in | |
248 | # sync with the ipython mode being used. This class is only meant to |
|
253 | # sync with the ipython mode being used. This class is only meant to | |
249 | # be used inside ipython anyways, so it's OK. |
|
254 | # be used inside ipython anyways, so it's OK. | |
250 | ip = get_ipython() # this is in builtins whenever IPython is running |
|
255 | ip = get_ipython() # this is in builtins whenever IPython is running | |
251 | self.ip_ns = ip.user_ns |
|
256 | self.ip_ns = ip.user_ns | |
252 | self.ip_colorize = ip.pycolorize |
|
257 | self.ip_colorize = ip.pycolorize | |
253 | self.ip_showtb = ip.showtraceback |
|
258 | self.ip_showtb = ip.showtraceback | |
254 | self.ip_run_cell = ip.run_cell |
|
259 | self.ip_run_cell = ip.run_cell | |
255 | self.shell = ip |
|
260 | self.shell = ip | |
256 |
|
261 | |||
257 | # load user data and initialize data structures |
|
262 | # load user data and initialize data structures | |
258 | self.reload() |
|
263 | self.reload() | |
259 |
|
264 | |||
260 | def fload(self): |
|
265 | def fload(self): | |
261 | """Load file object.""" |
|
266 | """Load file object.""" | |
262 | # read data and parse into blocks |
|
267 | # read data and parse into blocks | |
263 | if hasattr(self, 'fobj') and self.fobj is not None: |
|
268 | if hasattr(self, 'fobj') and self.fobj is not None: | |
264 | self.fobj.close() |
|
269 | self.fobj.close() | |
265 | if hasattr(self.src, "read"): |
|
270 | if hasattr(self.src, "read"): | |
266 | # It seems to be a file or a file-like object |
|
271 | # It seems to be a file or a file-like object | |
267 | self.fobj = self.src |
|
272 | self.fobj = self.src | |
268 | else: |
|
273 | else: | |
269 | # Assume it's a string or something that can be converted to one |
|
274 | # Assume it's a string or something that can be converted to one | |
270 | self.fobj = openpy.open(self.fname) |
|
275 | self.fobj = openpy.open(self.fname) | |
271 |
|
276 | |||
272 | def reload(self): |
|
277 | def reload(self): | |
273 | """Reload source from disk and initialize state.""" |
|
278 | """Reload source from disk and initialize state.""" | |
274 | self.fload() |
|
279 | self.fload() | |
275 |
|
280 | |||
276 | self.src = "".join(openpy.strip_encoding_cookie(self.fobj)) |
|
281 | self.src = "".join(openpy.strip_encoding_cookie(self.fobj)) | |
277 | src_b = [b.strip() for b in self.re_stop.split(self.src) if b] |
|
282 | src_b = [b.strip() for b in self.re_stop.split(self.src) if b] | |
278 | self._silent = [bool(self.re_silent.findall(b)) for b in src_b] |
|
283 | self._silent = [bool(self.re_silent.findall(b)) for b in src_b] | |
279 | self._auto = [bool(self.re_auto.findall(b)) for b in src_b] |
|
284 | self._auto = [bool(self.re_auto.findall(b)) for b in src_b] | |
280 |
|
285 | |||
281 | # if auto_all is not given (def. None), we read it from the file |
|
286 | # if auto_all is not given (def. None), we read it from the file | |
282 | if self.auto_all is None: |
|
287 | if self.auto_all is None: | |
283 | self.auto_all = bool(self.re_auto_all.findall(src_b[0])) |
|
288 | self.auto_all = bool(self.re_auto_all.findall(src_b[0])) | |
284 | else: |
|
289 | else: | |
285 | self.auto_all = bool(self.auto_all) |
|
290 | self.auto_all = bool(self.auto_all) | |
286 |
|
291 | |||
287 | # Clean the sources from all markup so it doesn't get displayed when |
|
292 | # Clean the sources from all markup so it doesn't get displayed when | |
288 | # running the demo |
|
293 | # running the demo | |
289 | src_blocks = [] |
|
294 | src_blocks = [] | |
290 | auto_strip = lambda s: self.re_auto.sub('',s) |
|
295 | auto_strip = lambda s: self.re_auto.sub('',s) | |
291 | for i,b in enumerate(src_b): |
|
296 | for i,b in enumerate(src_b): | |
292 | if self._auto[i]: |
|
297 | if self._auto[i]: | |
293 | src_blocks.append(auto_strip(b)) |
|
298 | src_blocks.append(auto_strip(b)) | |
294 | else: |
|
299 | else: | |
295 | src_blocks.append(b) |
|
300 | src_blocks.append(b) | |
296 | # remove the auto_all marker |
|
301 | # remove the auto_all marker | |
297 | src_blocks[0] = self.re_auto_all.sub('',src_blocks[0]) |
|
302 | src_blocks[0] = self.re_auto_all.sub('',src_blocks[0]) | |
298 |
|
303 | |||
299 | self.nblocks = len(src_blocks) |
|
304 | self.nblocks = len(src_blocks) | |
300 | self.src_blocks = src_blocks |
|
305 | self.src_blocks = src_blocks | |
301 |
|
306 | |||
302 | # also build syntax-highlighted source |
|
307 | # also build syntax-highlighted source | |
303 | self.src_blocks_colored = map(self.ip_colorize,self.src_blocks) |
|
308 | self.src_blocks_colored = map(self.ip_colorize,self.src_blocks) | |
304 |
|
309 | |||
305 | # ensure clean namespace and seek offset |
|
310 | # ensure clean namespace and seek offset | |
306 | self.reset() |
|
311 | self.reset() | |
307 |
|
312 | |||
308 | def reset(self): |
|
313 | def reset(self): | |
309 | """Reset the namespace and seek pointer to restart the demo""" |
|
314 | """Reset the namespace and seek pointer to restart the demo""" | |
310 | self.user_ns = {} |
|
315 | self.user_ns = {} | |
311 | self.finished = False |
|
316 | self.finished = False | |
312 | self.block_index = 0 |
|
317 | self.block_index = 0 | |
313 |
|
318 | |||
314 | def _validate_index(self,index): |
|
319 | def _validate_index(self,index): | |
315 | if index<0 or index>=self.nblocks: |
|
320 | if index<0 or index>=self.nblocks: | |
316 | raise ValueError('invalid block index %s' % index) |
|
321 | raise ValueError('invalid block index %s' % index) | |
317 |
|
322 | |||
318 | def _get_index(self,index): |
|
323 | def _get_index(self,index): | |
319 | """Get the current block index, validating and checking status. |
|
324 | """Get the current block index, validating and checking status. | |
320 |
|
325 | |||
321 | Returns None if the demo is finished""" |
|
326 | Returns None if the demo is finished""" | |
322 |
|
327 | |||
323 | if index is None: |
|
328 | if index is None: | |
324 | if self.finished: |
|
329 | if self.finished: | |
325 | print('Demo finished. Use <demo_name>.reset() if you want to rerun it.', file=io.stdout) |
|
330 | print('Demo finished. Use <demo_name>.reset() if you want to rerun it.', file=io.stdout) | |
326 | return None |
|
331 | return None | |
327 | index = self.block_index |
|
332 | index = self.block_index | |
328 | else: |
|
333 | else: | |
329 | self._validate_index(index) |
|
334 | self._validate_index(index) | |
330 | return index |
|
335 | return index | |
331 |
|
336 | |||
332 | def seek(self,index): |
|
337 | def seek(self,index): | |
333 | """Move the current seek pointer to the given block. |
|
338 | """Move the current seek pointer to the given block. | |
334 |
|
339 | |||
335 | You can use negative indices to seek from the end, with identical |
|
340 | You can use negative indices to seek from the end, with identical | |
336 | semantics to those of Python lists.""" |
|
341 | semantics to those of Python lists.""" | |
337 | if index<0: |
|
342 | if index<0: | |
338 | index = self.nblocks + index |
|
343 | index = self.nblocks + index | |
339 | self._validate_index(index) |
|
344 | self._validate_index(index) | |
340 | self.block_index = index |
|
345 | self.block_index = index | |
341 | self.finished = False |
|
346 | self.finished = False | |
342 |
|
347 | |||
343 | def back(self,num=1): |
|
348 | def back(self,num=1): | |
344 | """Move the seek pointer back num blocks (default is 1).""" |
|
349 | """Move the seek pointer back num blocks (default is 1).""" | |
345 | self.seek(self.block_index-num) |
|
350 | self.seek(self.block_index-num) | |
346 |
|
351 | |||
347 | def jump(self,num=1): |
|
352 | def jump(self,num=1): | |
348 | """Jump a given number of blocks relative to the current one. |
|
353 | """Jump a given number of blocks relative to the current one. | |
349 |
|
354 | |||
350 | The offset can be positive or negative, defaults to 1.""" |
|
355 | The offset can be positive or negative, defaults to 1.""" | |
351 | self.seek(self.block_index+num) |
|
356 | self.seek(self.block_index+num) | |
352 |
|
357 | |||
353 | def again(self): |
|
358 | def again(self): | |
354 | """Move the seek pointer back one block and re-execute.""" |
|
359 | """Move the seek pointer back one block and re-execute.""" | |
355 | self.back(1) |
|
360 | self.back(1) | |
356 | self() |
|
361 | self() | |
357 |
|
362 | |||
358 | def edit(self,index=None): |
|
363 | def edit(self,index=None): | |
359 | """Edit a block. |
|
364 | """Edit a block. | |
360 |
|
365 | |||
361 | If no number is given, use the last block executed. |
|
366 | If no number is given, use the last block executed. | |
362 |
|
367 | |||
363 | This edits the in-memory copy of the demo, it does NOT modify the |
|
368 | This edits the in-memory copy of the demo, it does NOT modify the | |
364 | original source file. If you want to do that, simply open the file in |
|
369 | original source file. If you want to do that, simply open the file in | |
365 | an editor and use reload() when you make changes to the file. This |
|
370 | an editor and use reload() when you make changes to the file. This | |
366 | method is meant to let you change a block during a demonstration for |
|
371 | method is meant to let you change a block during a demonstration for | |
367 | explanatory purposes, without damaging your original script.""" |
|
372 | explanatory purposes, without damaging your original script.""" | |
368 |
|
373 | |||
369 | index = self._get_index(index) |
|
374 | index = self._get_index(index) | |
370 | if index is None: |
|
375 | if index is None: | |
371 | return |
|
376 | return | |
372 | # decrease the index by one (unless we're at the very beginning), so |
|
377 | # decrease the index by one (unless we're at the very beginning), so | |
373 | # that the default demo.edit() call opens up the sblock we've last run |
|
378 | # that the default demo.edit() call opens up the sblock we've last run | |
374 | if index>0: |
|
379 | if index>0: | |
375 | index -= 1 |
|
380 | index -= 1 | |
376 |
|
381 | |||
377 | filename = self.shell.mktempfile(self.src_blocks[index]) |
|
382 | filename = self.shell.mktempfile(self.src_blocks[index]) | |
378 | self.shell.hooks.editor(filename,1) |
|
383 | self.shell.hooks.editor(filename,1) | |
379 | new_block = file_read(filename) |
|
384 | new_block = file_read(filename) | |
380 | # update the source and colored block |
|
385 | # update the source and colored block | |
381 | self.src_blocks[index] = new_block |
|
386 | self.src_blocks[index] = new_block | |
382 | self.src_blocks_colored[index] = self.ip_colorize(new_block) |
|
387 | self.src_blocks_colored[index] = self.ip_colorize(new_block) | |
383 | self.block_index = index |
|
388 | self.block_index = index | |
384 | # call to run with the newly edited index |
|
389 | # call to run with the newly edited index | |
385 | self() |
|
390 | self() | |
386 |
|
391 | |||
387 | def show(self,index=None): |
|
392 | def show(self,index=None): | |
388 | """Show a single block on screen""" |
|
393 | """Show a single block on screen""" | |
389 |
|
394 | |||
390 | index = self._get_index(index) |
|
395 | index = self._get_index(index) | |
391 | if index is None: |
|
396 | if index is None: | |
392 | return |
|
397 | return | |
393 |
|
398 | |||
394 | print(self.marquee('<%s> block # %s (%s remaining)' % |
|
399 | print(self.marquee('<%s> block # %s (%s remaining)' % | |
395 | (self.title,index,self.nblocks-index-1)), file=io.stdout) |
|
400 | (self.title,index,self.nblocks-index-1)), file=io.stdout) | |
396 | print((self.src_blocks_colored[index]), file=io.stdout) |
|
401 | print((self.src_blocks_colored[index]), file=io.stdout) | |
397 | sys.stdout.flush() |
|
402 | sys.stdout.flush() | |
398 |
|
403 | |||
399 | def show_all(self): |
|
404 | def show_all(self): | |
400 | """Show entire demo on screen, block by block""" |
|
405 | """Show entire demo on screen, block by block""" | |
401 |
|
406 | |||
402 | fname = self.title |
|
407 | fname = self.title | |
403 | title = self.title |
|
408 | title = self.title | |
404 | nblocks = self.nblocks |
|
409 | nblocks = self.nblocks | |
405 | silent = self._silent |
|
410 | silent = self._silent | |
406 | marquee = self.marquee |
|
411 | marquee = self.marquee | |
407 | for index,block in enumerate(self.src_blocks_colored): |
|
412 | for index,block in enumerate(self.src_blocks_colored): | |
408 | if silent[index]: |
|
413 | if silent[index]: | |
409 | print(marquee('<%s> SILENT block # %s (%s remaining)' % |
|
414 | print(marquee('<%s> SILENT block # %s (%s remaining)' % | |
410 | (title,index,nblocks-index-1)), file=io.stdout) |
|
415 | (title,index,nblocks-index-1)), file=io.stdout) | |
411 | else: |
|
416 | else: | |
412 | print(marquee('<%s> block # %s (%s remaining)' % |
|
417 | print(marquee('<%s> block # %s (%s remaining)' % | |
413 | (title,index,nblocks-index-1)), file=io.stdout) |
|
418 | (title,index,nblocks-index-1)), file=io.stdout) | |
414 | print(block, end=' ', file=io.stdout) |
|
419 | print(block, end=' ', file=io.stdout) | |
415 | sys.stdout.flush() |
|
420 | sys.stdout.flush() | |
416 |
|
421 | |||
417 | def run_cell(self,source): |
|
422 | def run_cell(self,source): | |
418 | """Execute a string with one or more lines of code""" |
|
423 | """Execute a string with one or more lines of code""" | |
419 |
|
424 | |||
420 | exec source in self.user_ns |
|
425 | exec source in self.user_ns | |
421 |
|
426 | |||
422 | def __call__(self,index=None): |
|
427 | def __call__(self,index=None): | |
423 | """run a block of the demo. |
|
428 | """run a block of the demo. | |
424 |
|
429 | |||
425 | If index is given, it should be an integer >=1 and <= nblocks. This |
|
430 | If index is given, it should be an integer >=1 and <= nblocks. This | |
426 | means that the calling convention is one off from typical Python |
|
431 | means that the calling convention is one off from typical Python | |
427 | lists. The reason for the inconsistency is that the demo always |
|
432 | lists. The reason for the inconsistency is that the demo always | |
428 | prints 'Block n/N, and N is the total, so it would be very odd to use |
|
433 | prints 'Block n/N, and N is the total, so it would be very odd to use | |
429 | zero-indexing here.""" |
|
434 | zero-indexing here.""" | |
430 |
|
435 | |||
431 | index = self._get_index(index) |
|
436 | index = self._get_index(index) | |
432 | if index is None: |
|
437 | if index is None: | |
433 | return |
|
438 | return | |
434 | try: |
|
439 | try: | |
435 | marquee = self.marquee |
|
440 | marquee = self.marquee | |
436 | next_block = self.src_blocks[index] |
|
441 | next_block = self.src_blocks[index] | |
437 | self.block_index += 1 |
|
442 | self.block_index += 1 | |
438 | if self._silent[index]: |
|
443 | if self._silent[index]: | |
439 | print(marquee('Executing silent block # %s (%s remaining)' % |
|
444 | print(marquee('Executing silent block # %s (%s remaining)' % | |
440 | (index,self.nblocks-index-1)), file=io.stdout) |
|
445 | (index,self.nblocks-index-1)), file=io.stdout) | |
441 | else: |
|
446 | else: | |
442 | self.pre_cmd() |
|
447 | self.pre_cmd() | |
443 | self.show(index) |
|
448 | self.show(index) | |
444 | if self.auto_all or self._auto[index]: |
|
449 | if self.auto_all or self._auto[index]: | |
445 | print(marquee('output:'), file=io.stdout) |
|
450 | print(marquee('output:'), file=io.stdout) | |
446 | else: |
|
451 | else: | |
447 | print(marquee('Press <q> to quit, <Enter> to execute...'), end=' ', file=io.stdout) |
|
452 | print(marquee('Press <q> to quit, <Enter> to execute...'), end=' ', file=io.stdout) | |
448 | ans = raw_input().strip() |
|
453 | ans = raw_input().strip() | |
449 | if ans: |
|
454 | if ans: | |
450 | print(marquee('Block NOT executed'), file=io.stdout) |
|
455 | print(marquee('Block NOT executed'), file=io.stdout) | |
451 | return |
|
456 | return | |
452 | try: |
|
457 | try: | |
453 | save_argv = sys.argv |
|
458 | save_argv = sys.argv | |
454 | sys.argv = self.sys_argv |
|
459 | sys.argv = self.sys_argv | |
455 | self.run_cell(next_block) |
|
460 | self.run_cell(next_block) | |
456 | self.post_cmd() |
|
461 | self.post_cmd() | |
457 | finally: |
|
462 | finally: | |
458 | sys.argv = save_argv |
|
463 | sys.argv = save_argv | |
459 |
|
464 | |||
460 | except: |
|
465 | except: | |
461 | self.ip_showtb(filename=self.fname) |
|
466 | self.ip_showtb(filename=self.fname) | |
462 | else: |
|
467 | else: | |
463 | self.ip_ns.update(self.user_ns) |
|
468 | self.ip_ns.update(self.user_ns) | |
464 |
|
469 | |||
465 | if self.block_index == self.nblocks: |
|
470 | if self.block_index == self.nblocks: | |
466 | mq1 = self.marquee('END OF DEMO') |
|
471 | mq1 = self.marquee('END OF DEMO') | |
467 | if mq1: |
|
472 | if mq1: | |
468 | # avoid spurious print >>io.stdout,s if empty marquees are used |
|
473 | # avoid spurious print >>io.stdout,s if empty marquees are used | |
469 | print(file=io.stdout) |
|
474 | print(file=io.stdout) | |
470 | print(mq1, file=io.stdout) |
|
475 | print(mq1, file=io.stdout) | |
471 | print(self.marquee('Use <demo_name>.reset() if you want to rerun it.'), file=io.stdout) |
|
476 | print(self.marquee('Use <demo_name>.reset() if you want to rerun it.'), file=io.stdout) | |
472 | self.finished = True |
|
477 | self.finished = True | |
473 |
|
478 | |||
474 | # These methods are meant to be overridden by subclasses who may wish to |
|
479 | # These methods are meant to be overridden by subclasses who may wish to | |
475 | # customize the behavior of of their demos. |
|
480 | # customize the behavior of of their demos. | |
476 | def marquee(self,txt='',width=78,mark='*'): |
|
481 | def marquee(self,txt='',width=78,mark='*'): | |
477 | """Return the input string centered in a 'marquee'.""" |
|
482 | """Return the input string centered in a 'marquee'.""" | |
478 | return marquee(txt,width,mark) |
|
483 | return marquee(txt,width,mark) | |
479 |
|
484 | |||
480 | def pre_cmd(self): |
|
485 | def pre_cmd(self): | |
481 | """Method called before executing each block.""" |
|
486 | """Method called before executing each block.""" | |
482 | pass |
|
487 | pass | |
483 |
|
488 | |||
484 | def post_cmd(self): |
|
489 | def post_cmd(self): | |
485 | """Method called after executing each block.""" |
|
490 | """Method called after executing each block.""" | |
486 | pass |
|
491 | pass | |
487 |
|
492 | |||
488 |
|
493 | |||
489 | class IPythonDemo(Demo): |
|
494 | class IPythonDemo(Demo): | |
490 | """Class for interactive demos with IPython's input processing applied. |
|
495 | """Class for interactive demos with IPython's input processing applied. | |
491 |
|
496 | |||
492 | This subclasses Demo, but instead of executing each block by the Python |
|
497 | This subclasses Demo, but instead of executing each block by the Python | |
493 | interpreter (via exec), it actually calls IPython on it, so that any input |
|
498 | interpreter (via exec), it actually calls IPython on it, so that any input | |
494 | filters which may be in place are applied to the input block. |
|
499 | filters which may be in place are applied to the input block. | |
495 |
|
500 | |||
496 | If you have an interactive environment which exposes special input |
|
501 | If you have an interactive environment which exposes special input | |
497 | processing, you can use this class instead to write demo scripts which |
|
502 | processing, you can use this class instead to write demo scripts which | |
498 | operate exactly as if you had typed them interactively. The default Demo |
|
503 | operate exactly as if you had typed them interactively. The default Demo | |
499 | class requires the input to be valid, pure Python code. |
|
504 | class requires the input to be valid, pure Python code. | |
500 | """ |
|
505 | """ | |
501 |
|
506 | |||
502 | def run_cell(self,source): |
|
507 | def run_cell(self,source): | |
503 | """Execute a string with one or more lines of code""" |
|
508 | """Execute a string with one or more lines of code""" | |
504 |
|
509 | |||
505 | self.shell.run_cell(source) |
|
510 | self.shell.run_cell(source) | |
506 |
|
511 | |||
507 | class LineDemo(Demo): |
|
512 | class LineDemo(Demo): | |
508 | """Demo where each line is executed as a separate block. |
|
513 | """Demo where each line is executed as a separate block. | |
509 |
|
514 | |||
510 | The input script should be valid Python code. |
|
515 | The input script should be valid Python code. | |
511 |
|
516 | |||
512 | This class doesn't require any markup at all, and it's meant for simple |
|
517 | This class doesn't require any markup at all, and it's meant for simple | |
513 | scripts (with no nesting or any kind of indentation) which consist of |
|
518 | scripts (with no nesting or any kind of indentation) which consist of | |
514 | multiple lines of input to be executed, one at a time, as if they had been |
|
519 | multiple lines of input to be executed, one at a time, as if they had been | |
515 | typed in the interactive prompt. |
|
520 | typed in the interactive prompt. | |
516 |
|
521 | |||
517 | Note: the input can not have *any* indentation, which means that only |
|
522 | Note: the input can not have *any* indentation, which means that only | |
518 | single-lines of input are accepted, not even function definitions are |
|
523 | single-lines of input are accepted, not even function definitions are | |
519 | valid.""" |
|
524 | valid.""" | |
520 |
|
525 | |||
521 | def reload(self): |
|
526 | def reload(self): | |
522 | """Reload source from disk and initialize state.""" |
|
527 | """Reload source from disk and initialize state.""" | |
523 | # read data and parse into blocks |
|
528 | # read data and parse into blocks | |
524 | self.fload() |
|
529 | self.fload() | |
525 | lines = self.fobj.readlines() |
|
530 | lines = self.fobj.readlines() | |
526 | src_b = [l for l in lines if l.strip()] |
|
531 | src_b = [l for l in lines if l.strip()] | |
527 | nblocks = len(src_b) |
|
532 | nblocks = len(src_b) | |
528 | self.src = ''.join(lines) |
|
533 | self.src = ''.join(lines) | |
529 | self._silent = [False]*nblocks |
|
534 | self._silent = [False]*nblocks | |
530 | self._auto = [True]*nblocks |
|
535 | self._auto = [True]*nblocks | |
531 | self.auto_all = True |
|
536 | self.auto_all = True | |
532 | self.nblocks = nblocks |
|
537 | self.nblocks = nblocks | |
533 | self.src_blocks = src_b |
|
538 | self.src_blocks = src_b | |
534 |
|
539 | |||
535 | # also build syntax-highlighted source |
|
540 | # also build syntax-highlighted source | |
536 | self.src_blocks_colored = map(self.ip_colorize,self.src_blocks) |
|
541 | self.src_blocks_colored = map(self.ip_colorize,self.src_blocks) | |
537 |
|
542 | |||
538 | # ensure clean namespace and seek offset |
|
543 | # ensure clean namespace and seek offset | |
539 | self.reset() |
|
544 | self.reset() | |
540 |
|
545 | |||
541 |
|
546 | |||
542 | class IPythonLineDemo(IPythonDemo,LineDemo): |
|
547 | class IPythonLineDemo(IPythonDemo,LineDemo): | |
543 | """Variant of the LineDemo class whose input is processed by IPython.""" |
|
548 | """Variant of the LineDemo class whose input is processed by IPython.""" | |
544 | pass |
|
549 | pass | |
545 |
|
550 | |||
546 |
|
551 | |||
547 | class ClearMixin(object): |
|
552 | class ClearMixin(object): | |
548 | """Use this mixin to make Demo classes with less visual clutter. |
|
553 | """Use this mixin to make Demo classes with less visual clutter. | |
549 |
|
554 | |||
550 | Demos using this mixin will clear the screen before every block and use |
|
555 | Demos using this mixin will clear the screen before every block and use | |
551 | blank marquees. |
|
556 | blank marquees. | |
552 |
|
557 | |||
553 | Note that in order for the methods defined here to actually override those |
|
558 | Note that in order for the methods defined here to actually override those | |
554 | of the classes it's mixed with, it must go /first/ in the inheritance |
|
559 | of the classes it's mixed with, it must go /first/ in the inheritance | |
555 | tree. For example: |
|
560 | tree. For example: | |
556 |
|
561 | |||
557 | class ClearIPDemo(ClearMixin,IPythonDemo): pass |
|
562 | class ClearIPDemo(ClearMixin,IPythonDemo): pass | |
558 |
|
563 | |||
559 | will provide an IPythonDemo class with the mixin's features. |
|
564 | will provide an IPythonDemo class with the mixin's features. | |
560 | """ |
|
565 | """ | |
561 |
|
566 | |||
562 | def marquee(self,txt='',width=78,mark='*'): |
|
567 | def marquee(self,txt='',width=78,mark='*'): | |
563 | """Blank marquee that returns '' no matter what the input.""" |
|
568 | """Blank marquee that returns '' no matter what the input.""" | |
564 | return '' |
|
569 | return '' | |
565 |
|
570 | |||
566 | def pre_cmd(self): |
|
571 | def pre_cmd(self): | |
567 | """Method called before executing each block. |
|
572 | """Method called before executing each block. | |
568 |
|
573 | |||
569 | This one simply clears the screen.""" |
|
574 | This one simply clears the screen.""" | |
570 | from IPython.utils.terminal import term_clear |
|
575 | from IPython.utils.terminal import term_clear | |
571 | term_clear() |
|
576 | term_clear() | |
572 |
|
577 | |||
573 | class ClearDemo(ClearMixin,Demo): |
|
578 | class ClearDemo(ClearMixin,Demo): | |
574 | pass |
|
579 | pass | |
575 |
|
580 | |||
576 |
|
581 | |||
577 | class ClearIPDemo(ClearMixin,IPythonDemo): |
|
582 | class ClearIPDemo(ClearMixin,IPythonDemo): | |
578 | pass |
|
583 | pass |
@@ -1,443 +1,441 b'' | |||||
1 | #!/usr/bin/env python |
|
1 | #!/usr/bin/env python | |
2 | """Module for interactively running scripts. |
|
2 | """Module for interactively running scripts. | |
3 |
|
3 | |||
4 | This module implements classes for interactively running scripts written for |
|
4 | This module implements classes for interactively running scripts written for | |
5 | any system with a prompt which can be matched by a regexp suitable for |
|
5 | any system with a prompt which can be matched by a regexp suitable for | |
6 | pexpect. It can be used to run as if they had been typed up interactively, an |
|
6 | pexpect. It can be used to run as if they had been typed up interactively, an | |
7 | arbitrary series of commands for the target system. |
|
7 | arbitrary series of commands for the target system. | |
8 |
|
8 | |||
9 | The module includes classes ready for IPython (with the default prompts), |
|
9 | The module includes classes ready for IPython (with the default prompts), | |
10 | plain Python and SAGE, but making a new one is trivial. To see how to use it, |
|
10 | plain Python and SAGE, but making a new one is trivial. To see how to use it, | |
11 | simply run the module as a script: |
|
11 | simply run the module as a script: | |
12 |
|
12 | |||
13 | ./irunner.py --help |
|
13 | ./irunner.py --help | |
14 |
|
14 | |||
15 |
|
15 | |||
16 | This is an extension of Ken Schutte <kschutte-AT-csail.mit.edu>'s script |
|
16 | This is an extension of Ken Schutte <kschutte-AT-csail.mit.edu>'s script | |
17 | contributed on the ipython-user list: |
|
17 | contributed on the ipython-user list: | |
18 |
|
18 | |||
19 | http://mail.scipy.org/pipermail/ipython-user/2006-May/003539.html |
|
19 | http://mail.scipy.org/pipermail/ipython-user/2006-May/003539.html | |
20 |
|
20 | |||
21 |
|
21 | Notes | ||
22 | NOTES: |
|
22 | ----- | |
23 |
|
23 | |||
24 | - This module requires pexpect, available in most linux distros, or which can |
|
24 | - This module requires pexpect, available in most linux distros, or which can | |
25 | be downloaded from |
|
25 | be downloaded from http://pexpect.sourceforge.net | |
26 |
|
||||
27 | http://pexpect.sourceforge.net |
|
|||
28 |
|
26 | |||
29 | - Because pexpect only works under Unix or Windows-Cygwin, this has the same |
|
27 | - Because pexpect only works under Unix or Windows-Cygwin, this has the same | |
30 | limitations. This means that it will NOT work under native windows Python. |
|
28 | limitations. This means that it will NOT work under native windows Python. | |
31 | """ |
|
29 | """ | |
32 | from __future__ import print_function |
|
30 | from __future__ import print_function | |
33 |
|
31 | |||
34 | # Stdlib imports |
|
32 | # Stdlib imports | |
35 | import optparse |
|
33 | import optparse | |
36 | import os |
|
34 | import os | |
37 | import sys |
|
35 | import sys | |
38 |
|
36 | |||
39 | # Third-party modules: we carry a copy of pexpect to reduce the need for |
|
37 | # Third-party modules: we carry a copy of pexpect to reduce the need for | |
40 | # external dependencies, but our import checks for a system version first. |
|
38 | # external dependencies, but our import checks for a system version first. | |
41 | from IPython.external import pexpect |
|
39 | from IPython.external import pexpect | |
42 | from IPython.utils import py3compat |
|
40 | from IPython.utils import py3compat | |
43 |
|
41 | |||
44 | # Global usage strings, to avoid indentation issues when typing it below. |
|
42 | # Global usage strings, to avoid indentation issues when typing it below. | |
45 | USAGE = """ |
|
43 | USAGE = """ | |
46 | Interactive script runner, type: %s |
|
44 | Interactive script runner, type: %s | |
47 |
|
45 | |||
48 | runner [opts] script_name |
|
46 | runner [opts] script_name | |
49 | """ |
|
47 | """ | |
50 |
|
48 | |||
51 | def pexpect_monkeypatch(): |
|
49 | def pexpect_monkeypatch(): | |
52 | """Patch pexpect to prevent unhandled exceptions at VM teardown. |
|
50 | """Patch pexpect to prevent unhandled exceptions at VM teardown. | |
53 |
|
51 | |||
54 | Calling this function will monkeypatch the pexpect.spawn class and modify |
|
52 | Calling this function will monkeypatch the pexpect.spawn class and modify | |
55 | its __del__ method to make it more robust in the face of failures that can |
|
53 | its __del__ method to make it more robust in the face of failures that can | |
56 | occur if it is called when the Python VM is shutting down. |
|
54 | occur if it is called when the Python VM is shutting down. | |
57 |
|
55 | |||
58 | Since Python may fire __del__ methods arbitrarily late, it's possible for |
|
56 | Since Python may fire __del__ methods arbitrarily late, it's possible for | |
59 | them to execute during the teardown of the Python VM itself. At this |
|
57 | them to execute during the teardown of the Python VM itself. At this | |
60 | point, various builtin modules have been reset to None. Thus, the call to |
|
58 | point, various builtin modules have been reset to None. Thus, the call to | |
61 | self.close() will trigger an exception because it tries to call os.close(), |
|
59 | self.close() will trigger an exception because it tries to call os.close(), | |
62 | and os is now None. |
|
60 | and os is now None. | |
63 | """ |
|
61 | """ | |
64 |
|
62 | |||
65 | if pexpect.__version__[:3] >= '2.2': |
|
63 | if pexpect.__version__[:3] >= '2.2': | |
66 | # No need to patch, fix is already the upstream version. |
|
64 | # No need to patch, fix is already the upstream version. | |
67 | return |
|
65 | return | |
68 |
|
66 | |||
69 | def __del__(self): |
|
67 | def __del__(self): | |
70 | """This makes sure that no system resources are left open. |
|
68 | """This makes sure that no system resources are left open. | |
71 | Python only garbage collects Python objects. OS file descriptors |
|
69 | Python only garbage collects Python objects. OS file descriptors | |
72 | are not Python objects, so they must be handled explicitly. |
|
70 | are not Python objects, so they must be handled explicitly. | |
73 | If the child file descriptor was opened outside of this class |
|
71 | If the child file descriptor was opened outside of this class | |
74 | (passed to the constructor) then this does not close it. |
|
72 | (passed to the constructor) then this does not close it. | |
75 | """ |
|
73 | """ | |
76 | if not self.closed: |
|
74 | if not self.closed: | |
77 | try: |
|
75 | try: | |
78 | self.close() |
|
76 | self.close() | |
79 | except AttributeError: |
|
77 | except AttributeError: | |
80 | pass |
|
78 | pass | |
81 |
|
79 | |||
82 | pexpect.spawn.__del__ = __del__ |
|
80 | pexpect.spawn.__del__ = __del__ | |
83 |
|
81 | |||
84 | pexpect_monkeypatch() |
|
82 | pexpect_monkeypatch() | |
85 |
|
83 | |||
86 | # The generic runner class |
|
84 | # The generic runner class | |
87 | class InteractiveRunner(object): |
|
85 | class InteractiveRunner(object): | |
88 | """Class to run a sequence of commands through an interactive program.""" |
|
86 | """Class to run a sequence of commands through an interactive program.""" | |
89 |
|
87 | |||
90 | def __init__(self,program,prompts,args=None,out=sys.stdout,echo=True): |
|
88 | def __init__(self,program,prompts,args=None,out=sys.stdout,echo=True): | |
91 | """Construct a runner. |
|
89 | """Construct a runner. | |
92 |
|
90 | |||
93 | Inputs: |
|
91 | Inputs: | |
94 |
|
92 | |||
95 | - program: command to execute the given program. |
|
93 | - program: command to execute the given program. | |
96 |
|
94 | |||
97 | - prompts: a list of patterns to match as valid prompts, in the |
|
95 | - prompts: a list of patterns to match as valid prompts, in the | |
98 | format used by pexpect. This basically means that it can be either |
|
96 | format used by pexpect. This basically means that it can be either | |
99 | a string (to be compiled as a regular expression) or a list of such |
|
97 | a string (to be compiled as a regular expression) or a list of such | |
100 | (it must be a true list, as pexpect does type checks). |
|
98 | (it must be a true list, as pexpect does type checks). | |
101 |
|
99 | |||
102 | If more than one prompt is given, the first is treated as the main |
|
100 | If more than one prompt is given, the first is treated as the main | |
103 | program prompt and the others as 'continuation' prompts, like |
|
101 | program prompt and the others as 'continuation' prompts, like | |
104 | python's. This means that blank lines in the input source are |
|
102 | python's. This means that blank lines in the input source are | |
105 | ommitted when the first prompt is matched, but are NOT ommitted when |
|
103 | ommitted when the first prompt is matched, but are NOT ommitted when | |
106 | the continuation one matches, since this is how python signals the |
|
104 | the continuation one matches, since this is how python signals the | |
107 | end of multiline input interactively. |
|
105 | end of multiline input interactively. | |
108 |
|
106 | |||
109 | Optional inputs: |
|
107 | Optional inputs: | |
110 |
|
108 | |||
111 | - args(None): optional list of strings to pass as arguments to the |
|
109 | - args(None): optional list of strings to pass as arguments to the | |
112 | child program. |
|
110 | child program. | |
113 |
|
111 | |||
114 | - out(sys.stdout): if given, an output stream to be used when writing |
|
112 | - out(sys.stdout): if given, an output stream to be used when writing | |
115 | output. The only requirement is that it must have a .write() method. |
|
113 | output. The only requirement is that it must have a .write() method. | |
116 |
|
114 | |||
117 | Public members not parameterized in the constructor: |
|
115 | Public members not parameterized in the constructor: | |
118 |
|
116 | |||
119 | - delaybeforesend(0): Newer versions of pexpect have a delay before |
|
117 | - delaybeforesend(0): Newer versions of pexpect have a delay before | |
120 | sending each new input. For our purposes here, it's typically best |
|
118 | sending each new input. For our purposes here, it's typically best | |
121 | to just set this to zero, but if you encounter reliability problems |
|
119 | to just set this to zero, but if you encounter reliability problems | |
122 | or want an interactive run to pause briefly at each prompt, just |
|
120 | or want an interactive run to pause briefly at each prompt, just | |
123 | increase this value (it is measured in seconds). Note that this |
|
121 | increase this value (it is measured in seconds). Note that this | |
124 | variable is not honored at all by older versions of pexpect. |
|
122 | variable is not honored at all by older versions of pexpect. | |
125 | """ |
|
123 | """ | |
126 |
|
124 | |||
127 | self.program = program |
|
125 | self.program = program | |
128 | self.prompts = prompts |
|
126 | self.prompts = prompts | |
129 | if args is None: args = [] |
|
127 | if args is None: args = [] | |
130 | self.args = args |
|
128 | self.args = args | |
131 | self.out = out |
|
129 | self.out = out | |
132 | self.echo = echo |
|
130 | self.echo = echo | |
133 | # Other public members which we don't make as parameters, but which |
|
131 | # Other public members which we don't make as parameters, but which | |
134 | # users may occasionally want to tweak |
|
132 | # users may occasionally want to tweak | |
135 | self.delaybeforesend = 0 |
|
133 | self.delaybeforesend = 0 | |
136 |
|
134 | |||
137 | # Create child process and hold on to it so we don't have to re-create |
|
135 | # Create child process and hold on to it so we don't have to re-create | |
138 | # for every single execution call |
|
136 | # for every single execution call | |
139 | c = self.child = pexpect.spawn(self.program,self.args,timeout=None) |
|
137 | c = self.child = pexpect.spawn(self.program,self.args,timeout=None) | |
140 | c.delaybeforesend = self.delaybeforesend |
|
138 | c.delaybeforesend = self.delaybeforesend | |
141 | # pexpect hard-codes the terminal size as (24,80) (rows,columns). |
|
139 | # pexpect hard-codes the terminal size as (24,80) (rows,columns). | |
142 | # This causes problems because any line longer than 80 characters gets |
|
140 | # This causes problems because any line longer than 80 characters gets | |
143 | # completely overwrapped on the printed outptut (even though |
|
141 | # completely overwrapped on the printed outptut (even though | |
144 | # internally the code runs fine). We reset this to 99 rows X 200 |
|
142 | # internally the code runs fine). We reset this to 99 rows X 200 | |
145 | # columns (arbitrarily chosen), which should avoid problems in all |
|
143 | # columns (arbitrarily chosen), which should avoid problems in all | |
146 | # reasonable cases. |
|
144 | # reasonable cases. | |
147 | c.setwinsize(99,200) |
|
145 | c.setwinsize(99,200) | |
148 |
|
146 | |||
149 | def close(self): |
|
147 | def close(self): | |
150 | """close child process""" |
|
148 | """close child process""" | |
151 |
|
149 | |||
152 | self.child.close() |
|
150 | self.child.close() | |
153 |
|
151 | |||
154 | def run_file(self,fname,interact=False,get_output=False): |
|
152 | def run_file(self,fname,interact=False,get_output=False): | |
155 | """Run the given file interactively. |
|
153 | """Run the given file interactively. | |
156 |
|
154 | |||
157 | Inputs: |
|
155 | Inputs: | |
158 |
|
156 | |||
159 | -fname: name of the file to execute. |
|
157 | -fname: name of the file to execute. | |
160 |
|
158 | |||
161 | See the run_source docstring for the meaning of the optional |
|
159 | See the run_source docstring for the meaning of the optional | |
162 | arguments.""" |
|
160 | arguments.""" | |
163 |
|
161 | |||
164 | fobj = open(fname,'r') |
|
162 | fobj = open(fname,'r') | |
165 | try: |
|
163 | try: | |
166 | out = self.run_source(fobj,interact,get_output) |
|
164 | out = self.run_source(fobj,interact,get_output) | |
167 | finally: |
|
165 | finally: | |
168 | fobj.close() |
|
166 | fobj.close() | |
169 | if get_output: |
|
167 | if get_output: | |
170 | return out |
|
168 | return out | |
171 |
|
169 | |||
172 | def run_source(self,source,interact=False,get_output=False): |
|
170 | def run_source(self,source,interact=False,get_output=False): | |
173 | """Run the given source code interactively. |
|
171 | """Run the given source code interactively. | |
174 |
|
172 | |||
175 | Inputs: |
|
173 | Inputs: | |
176 |
|
174 | |||
177 | - source: a string of code to be executed, or an open file object we |
|
175 | - source: a string of code to be executed, or an open file object we | |
178 | can iterate over. |
|
176 | can iterate over. | |
179 |
|
177 | |||
180 | Optional inputs: |
|
178 | Optional inputs: | |
181 |
|
179 | |||
182 | - interact(False): if true, start to interact with the running |
|
180 | - interact(False): if true, start to interact with the running | |
183 | program at the end of the script. Otherwise, just exit. |
|
181 | program at the end of the script. Otherwise, just exit. | |
184 |
|
182 | |||
185 | - get_output(False): if true, capture the output of the child process |
|
183 | - get_output(False): if true, capture the output of the child process | |
186 | (filtering the input commands out) and return it as a string. |
|
184 | (filtering the input commands out) and return it as a string. | |
187 |
|
185 | |||
188 | Returns: |
|
186 | Returns: | |
189 | A string containing the process output, but only if requested. |
|
187 | A string containing the process output, but only if requested. | |
190 | """ |
|
188 | """ | |
191 |
|
189 | |||
192 | # if the source is a string, chop it up in lines so we can iterate |
|
190 | # if the source is a string, chop it up in lines so we can iterate | |
193 | # over it just as if it were an open file. |
|
191 | # over it just as if it were an open file. | |
194 | if isinstance(source, basestring): |
|
192 | if isinstance(source, basestring): | |
195 | source = source.splitlines(True) |
|
193 | source = source.splitlines(True) | |
196 |
|
194 | |||
197 | if self.echo: |
|
195 | if self.echo: | |
198 | # normalize all strings we write to use the native OS line |
|
196 | # normalize all strings we write to use the native OS line | |
199 | # separators. |
|
197 | # separators. | |
200 | linesep = os.linesep |
|
198 | linesep = os.linesep | |
201 | stdwrite = self.out.write |
|
199 | stdwrite = self.out.write | |
202 | write = lambda s: stdwrite(s.replace('\r\n',linesep)) |
|
200 | write = lambda s: stdwrite(s.replace('\r\n',linesep)) | |
203 | else: |
|
201 | else: | |
204 | # Quiet mode, all writes are no-ops |
|
202 | # Quiet mode, all writes are no-ops | |
205 | write = lambda s: None |
|
203 | write = lambda s: None | |
206 |
|
204 | |||
207 | c = self.child |
|
205 | c = self.child | |
208 | prompts = c.compile_pattern_list(self.prompts) |
|
206 | prompts = c.compile_pattern_list(self.prompts) | |
209 | prompt_idx = c.expect_list(prompts) |
|
207 | prompt_idx = c.expect_list(prompts) | |
210 |
|
208 | |||
211 | # Flag whether the script ends normally or not, to know whether we can |
|
209 | # Flag whether the script ends normally or not, to know whether we can | |
212 | # do anything further with the underlying process. |
|
210 | # do anything further with the underlying process. | |
213 | end_normal = True |
|
211 | end_normal = True | |
214 |
|
212 | |||
215 | # If the output was requested, store it in a list for return at the end |
|
213 | # If the output was requested, store it in a list for return at the end | |
216 | if get_output: |
|
214 | if get_output: | |
217 | output = [] |
|
215 | output = [] | |
218 | store_output = output.append |
|
216 | store_output = output.append | |
219 |
|
217 | |||
220 | for cmd in source: |
|
218 | for cmd in source: | |
221 | # skip blank lines for all matches to the 'main' prompt, while the |
|
219 | # skip blank lines for all matches to the 'main' prompt, while the | |
222 | # secondary prompts do not |
|
220 | # secondary prompts do not | |
223 | if prompt_idx==0 and \ |
|
221 | if prompt_idx==0 and \ | |
224 | (cmd.isspace() or cmd.lstrip().startswith('#')): |
|
222 | (cmd.isspace() or cmd.lstrip().startswith('#')): | |
225 | write(cmd) |
|
223 | write(cmd) | |
226 | continue |
|
224 | continue | |
227 |
|
225 | |||
228 | # write('AFTER: '+c.after) # dbg |
|
226 | # write('AFTER: '+c.after) # dbg | |
229 | write(c.after) |
|
227 | write(c.after) | |
230 | c.send(cmd) |
|
228 | c.send(cmd) | |
231 | try: |
|
229 | try: | |
232 | prompt_idx = c.expect_list(prompts) |
|
230 | prompt_idx = c.expect_list(prompts) | |
233 | except pexpect.EOF: |
|
231 | except pexpect.EOF: | |
234 | # this will happen if the child dies unexpectedly |
|
232 | # this will happen if the child dies unexpectedly | |
235 | write(c.before) |
|
233 | write(c.before) | |
236 | end_normal = False |
|
234 | end_normal = False | |
237 | break |
|
235 | break | |
238 |
|
236 | |||
239 | write(c.before) |
|
237 | write(c.before) | |
240 |
|
238 | |||
241 | # With an echoing process, the output we get in c.before contains |
|
239 | # With an echoing process, the output we get in c.before contains | |
242 | # the command sent, a newline, and then the actual process output |
|
240 | # the command sent, a newline, and then the actual process output | |
243 | if get_output: |
|
241 | if get_output: | |
244 | store_output(c.before[len(cmd+'\n'):]) |
|
242 | store_output(c.before[len(cmd+'\n'):]) | |
245 | #write('CMD: <<%s>>' % cmd) # dbg |
|
243 | #write('CMD: <<%s>>' % cmd) # dbg | |
246 | #write('OUTPUT: <<%s>>' % output[-1]) # dbg |
|
244 | #write('OUTPUT: <<%s>>' % output[-1]) # dbg | |
247 |
|
245 | |||
248 | self.out.flush() |
|
246 | self.out.flush() | |
249 | if end_normal: |
|
247 | if end_normal: | |
250 | if interact: |
|
248 | if interact: | |
251 | c.send('\n') |
|
249 | c.send('\n') | |
252 | print('<< Starting interactive mode >>', end=' ') |
|
250 | print('<< Starting interactive mode >>', end=' ') | |
253 | try: |
|
251 | try: | |
254 | c.interact() |
|
252 | c.interact() | |
255 | except OSError: |
|
253 | except OSError: | |
256 | # This is what fires when the child stops. Simply print a |
|
254 | # This is what fires when the child stops. Simply print a | |
257 | # newline so the system prompt is aligned. The extra |
|
255 | # newline so the system prompt is aligned. The extra | |
258 | # space is there to make sure it gets printed, otherwise |
|
256 | # space is there to make sure it gets printed, otherwise | |
259 | # OS buffering sometimes just suppresses it. |
|
257 | # OS buffering sometimes just suppresses it. | |
260 | write(' \n') |
|
258 | write(' \n') | |
261 | self.out.flush() |
|
259 | self.out.flush() | |
262 | else: |
|
260 | else: | |
263 | if interact: |
|
261 | if interact: | |
264 | e="Further interaction is not possible: child process is dead." |
|
262 | e="Further interaction is not possible: child process is dead." | |
265 | print(e, file=sys.stderr) |
|
263 | print(e, file=sys.stderr) | |
266 |
|
264 | |||
267 | # Leave the child ready for more input later on, otherwise select just |
|
265 | # Leave the child ready for more input later on, otherwise select just | |
268 | # hangs on the second invocation. |
|
266 | # hangs on the second invocation. | |
269 | if c.isalive(): |
|
267 | if c.isalive(): | |
270 | c.send('\n') |
|
268 | c.send('\n') | |
271 |
|
269 | |||
272 | # Return any requested output |
|
270 | # Return any requested output | |
273 | if get_output: |
|
271 | if get_output: | |
274 | return ''.join(output) |
|
272 | return ''.join(output) | |
275 |
|
273 | |||
276 | def main(self,argv=None): |
|
274 | def main(self,argv=None): | |
277 | """Run as a command-line script.""" |
|
275 | """Run as a command-line script.""" | |
278 |
|
276 | |||
279 | parser = optparse.OptionParser(usage=USAGE % self.__class__.__name__) |
|
277 | parser = optparse.OptionParser(usage=USAGE % self.__class__.__name__) | |
280 | newopt = parser.add_option |
|
278 | newopt = parser.add_option | |
281 | newopt('-i','--interact',action='store_true',default=False, |
|
279 | newopt('-i','--interact',action='store_true',default=False, | |
282 | help='Interact with the program after the script is run.') |
|
280 | help='Interact with the program after the script is run.') | |
283 |
|
281 | |||
284 | opts,args = parser.parse_args(argv) |
|
282 | opts,args = parser.parse_args(argv) | |
285 |
|
283 | |||
286 | if len(args) != 1: |
|
284 | if len(args) != 1: | |
287 | print("You must supply exactly one file to run.", file=sys.stderr) |
|
285 | print("You must supply exactly one file to run.", file=sys.stderr) | |
288 | sys.exit(1) |
|
286 | sys.exit(1) | |
289 |
|
287 | |||
290 | self.run_file(args[0],opts.interact) |
|
288 | self.run_file(args[0],opts.interact) | |
291 |
|
289 | |||
292 | _ipython_cmd = "ipython3" if py3compat.PY3 else "ipython" |
|
290 | _ipython_cmd = "ipython3" if py3compat.PY3 else "ipython" | |
293 |
|
291 | |||
294 | # Specific runners for particular programs |
|
292 | # Specific runners for particular programs | |
295 | class IPythonRunner(InteractiveRunner): |
|
293 | class IPythonRunner(InteractiveRunner): | |
296 | """Interactive IPython runner. |
|
294 | """Interactive IPython runner. | |
297 |
|
295 | |||
298 | This initalizes IPython in 'nocolor' mode for simplicity. This lets us |
|
296 | This initalizes IPython in 'nocolor' mode for simplicity. This lets us | |
299 | avoid having to write a regexp that matches ANSI sequences, though pexpect |
|
297 | avoid having to write a regexp that matches ANSI sequences, though pexpect | |
300 | does support them. If anyone contributes patches for ANSI color support, |
|
298 | does support them. If anyone contributes patches for ANSI color support, | |
301 | they will be welcome. |
|
299 | they will be welcome. | |
302 |
|
300 | |||
303 | It also sets the prompts manually, since the prompt regexps for |
|
301 | It also sets the prompts manually, since the prompt regexps for | |
304 | pexpect need to be matched to the actual prompts, so user-customized |
|
302 | pexpect need to be matched to the actual prompts, so user-customized | |
305 | prompts would break this. |
|
303 | prompts would break this. | |
306 | """ |
|
304 | """ | |
307 |
|
305 | |||
308 | def __init__(self,program = _ipython_cmd, args=None, out=sys.stdout, echo=True): |
|
306 | def __init__(self,program = _ipython_cmd, args=None, out=sys.stdout, echo=True): | |
309 | """New runner, optionally passing the ipython command to use.""" |
|
307 | """New runner, optionally passing the ipython command to use.""" | |
310 | args0 = ['--colors=NoColor', |
|
308 | args0 = ['--colors=NoColor', | |
311 | '--no-term-title', |
|
309 | '--no-term-title', | |
312 | '--no-autoindent', |
|
310 | '--no-autoindent', | |
313 | # '--quick' is important, to prevent loading default config: |
|
311 | # '--quick' is important, to prevent loading default config: | |
314 | '--quick'] |
|
312 | '--quick'] | |
315 | if args is None: args = args0 |
|
313 | if args is None: args = args0 | |
316 | else: args = args0 + args |
|
314 | else: args = args0 + args | |
317 | prompts = [r'In \[\d+\]: ',r' \.*: '] |
|
315 | prompts = [r'In \[\d+\]: ',r' \.*: '] | |
318 | InteractiveRunner.__init__(self,program,prompts,args,out,echo) |
|
316 | InteractiveRunner.__init__(self,program,prompts,args,out,echo) | |
319 |
|
317 | |||
320 |
|
318 | |||
321 | class PythonRunner(InteractiveRunner): |
|
319 | class PythonRunner(InteractiveRunner): | |
322 | """Interactive Python runner.""" |
|
320 | """Interactive Python runner.""" | |
323 |
|
321 | |||
324 | def __init__(self,program=sys.executable, args=None, out=sys.stdout, echo=True): |
|
322 | def __init__(self,program=sys.executable, args=None, out=sys.stdout, echo=True): | |
325 | """New runner, optionally passing the python command to use.""" |
|
323 | """New runner, optionally passing the python command to use.""" | |
326 |
|
324 | |||
327 | prompts = [r'>>> ',r'\.\.\. '] |
|
325 | prompts = [r'>>> ',r'\.\.\. '] | |
328 | InteractiveRunner.__init__(self,program,prompts,args,out,echo) |
|
326 | InteractiveRunner.__init__(self,program,prompts,args,out,echo) | |
329 |
|
327 | |||
330 |
|
328 | |||
331 | class SAGERunner(InteractiveRunner): |
|
329 | class SAGERunner(InteractiveRunner): | |
332 | """Interactive SAGE runner. |
|
330 | """Interactive SAGE runner. | |
333 |
|
331 | |||
334 | WARNING: this runner only works if you manually adjust your SAGE |
|
332 | WARNING: this runner only works if you manually adjust your SAGE | |
335 | configuration so that the 'color' option in the configuration file is set to |
|
333 | configuration so that the 'color' option in the configuration file is set to | |
336 | 'NoColor', because currently the prompt matching regexp does not identify |
|
334 | 'NoColor', because currently the prompt matching regexp does not identify | |
337 | color sequences.""" |
|
335 | color sequences.""" | |
338 |
|
336 | |||
339 | def __init__(self,program='sage',args=None,out=sys.stdout,echo=True): |
|
337 | def __init__(self,program='sage',args=None,out=sys.stdout,echo=True): | |
340 | """New runner, optionally passing the sage command to use.""" |
|
338 | """New runner, optionally passing the sage command to use.""" | |
341 |
|
339 | |||
342 | prompts = ['sage: ',r'\s*\.\.\. '] |
|
340 | prompts = ['sage: ',r'\s*\.\.\. '] | |
343 | InteractiveRunner.__init__(self,program,prompts,args,out,echo) |
|
341 | InteractiveRunner.__init__(self,program,prompts,args,out,echo) | |
344 |
|
342 | |||
345 |
|
343 | |||
346 | class RunnerFactory(object): |
|
344 | class RunnerFactory(object): | |
347 | """Code runner factory. |
|
345 | """Code runner factory. | |
348 |
|
346 | |||
349 | This class provides an IPython code runner, but enforces that only one |
|
347 | This class provides an IPython code runner, but enforces that only one | |
350 | runner is ever instantiated. The runner is created based on the extension |
|
348 | runner is ever instantiated. The runner is created based on the extension | |
351 | of the first file to run, and it raises an exception if a runner is later |
|
349 | of the first file to run, and it raises an exception if a runner is later | |
352 | requested for a different extension type. |
|
350 | requested for a different extension type. | |
353 |
|
351 | |||
354 | This ensures that we don't generate example files for doctest with a mix of |
|
352 | This ensures that we don't generate example files for doctest with a mix of | |
355 | python and ipython syntax. |
|
353 | python and ipython syntax. | |
356 | """ |
|
354 | """ | |
357 |
|
355 | |||
358 | def __init__(self,out=sys.stdout): |
|
356 | def __init__(self,out=sys.stdout): | |
359 | """Instantiate a code runner.""" |
|
357 | """Instantiate a code runner.""" | |
360 |
|
358 | |||
361 | self.out = out |
|
359 | self.out = out | |
362 | self.runner = None |
|
360 | self.runner = None | |
363 | self.runnerClass = None |
|
361 | self.runnerClass = None | |
364 |
|
362 | |||
365 | def _makeRunner(self,runnerClass): |
|
363 | def _makeRunner(self,runnerClass): | |
366 | self.runnerClass = runnerClass |
|
364 | self.runnerClass = runnerClass | |
367 | self.runner = runnerClass(out=self.out) |
|
365 | self.runner = runnerClass(out=self.out) | |
368 | return self.runner |
|
366 | return self.runner | |
369 |
|
367 | |||
370 | def __call__(self,fname): |
|
368 | def __call__(self,fname): | |
371 | """Return a runner for the given filename.""" |
|
369 | """Return a runner for the given filename.""" | |
372 |
|
370 | |||
373 | if fname.endswith('.py'): |
|
371 | if fname.endswith('.py'): | |
374 | runnerClass = PythonRunner |
|
372 | runnerClass = PythonRunner | |
375 | elif fname.endswith('.ipy'): |
|
373 | elif fname.endswith('.ipy'): | |
376 | runnerClass = IPythonRunner |
|
374 | runnerClass = IPythonRunner | |
377 | else: |
|
375 | else: | |
378 | raise ValueError('Unknown file type for Runner: %r' % fname) |
|
376 | raise ValueError('Unknown file type for Runner: %r' % fname) | |
379 |
|
377 | |||
380 | if self.runner is None: |
|
378 | if self.runner is None: | |
381 | return self._makeRunner(runnerClass) |
|
379 | return self._makeRunner(runnerClass) | |
382 | else: |
|
380 | else: | |
383 | if runnerClass==self.runnerClass: |
|
381 | if runnerClass==self.runnerClass: | |
384 | return self.runner |
|
382 | return self.runner | |
385 | else: |
|
383 | else: | |
386 | e='A runner of type %r can not run file %r' % \ |
|
384 | e='A runner of type %r can not run file %r' % \ | |
387 | (self.runnerClass,fname) |
|
385 | (self.runnerClass,fname) | |
388 | raise ValueError(e) |
|
386 | raise ValueError(e) | |
389 |
|
387 | |||
390 |
|
388 | |||
391 | # Global usage string, to avoid indentation issues if typed in a function def. |
|
389 | # Global usage string, to avoid indentation issues if typed in a function def. | |
392 | MAIN_USAGE = """ |
|
390 | MAIN_USAGE = """ | |
393 | %prog [options] file_to_run |
|
391 | %prog [options] file_to_run | |
394 |
|
392 | |||
395 | This is an interface to the various interactive runners available in this |
|
393 | This is an interface to the various interactive runners available in this | |
396 | module. If you want to pass specific options to one of the runners, you need |
|
394 | module. If you want to pass specific options to one of the runners, you need | |
397 | to first terminate the main options with a '--', and then provide the runner's |
|
395 | to first terminate the main options with a '--', and then provide the runner's | |
398 | options. For example: |
|
396 | options. For example: | |
399 |
|
397 | |||
400 | irunner.py --python -- --help |
|
398 | irunner.py --python -- --help | |
401 |
|
399 | |||
402 | will pass --help to the python runner. Similarly, |
|
400 | will pass --help to the python runner. Similarly, | |
403 |
|
401 | |||
404 | irunner.py --ipython -- --interact script.ipy |
|
402 | irunner.py --ipython -- --interact script.ipy | |
405 |
|
403 | |||
406 | will run the script.ipy file under the IPython runner, and then will start to |
|
404 | will run the script.ipy file under the IPython runner, and then will start to | |
407 | interact with IPython at the end of the script (instead of exiting). |
|
405 | interact with IPython at the end of the script (instead of exiting). | |
408 |
|
406 | |||
409 | The already implemented runners are listed below; adding one for a new program |
|
407 | The already implemented runners are listed below; adding one for a new program | |
410 | is a trivial task, see the source for examples. |
|
408 | is a trivial task, see the source for examples. | |
411 | """ |
|
409 | """ | |
412 |
|
410 | |||
413 | def main(): |
|
411 | def main(): | |
414 | """Run as a command-line script.""" |
|
412 | """Run as a command-line script.""" | |
415 |
|
413 | |||
416 | parser = optparse.OptionParser(usage=MAIN_USAGE) |
|
414 | parser = optparse.OptionParser(usage=MAIN_USAGE) | |
417 | newopt = parser.add_option |
|
415 | newopt = parser.add_option | |
418 | newopt('--ipython',action='store_const',dest='mode',const='ipython', |
|
416 | newopt('--ipython',action='store_const',dest='mode',const='ipython', | |
419 | help='IPython interactive runner (default).') |
|
417 | help='IPython interactive runner (default).') | |
420 | newopt('--python',action='store_const',dest='mode',const='python', |
|
418 | newopt('--python',action='store_const',dest='mode',const='python', | |
421 | help='Python interactive runner.') |
|
419 | help='Python interactive runner.') | |
422 | newopt('--sage',action='store_const',dest='mode',const='sage', |
|
420 | newopt('--sage',action='store_const',dest='mode',const='sage', | |
423 | help='SAGE interactive runner.') |
|
421 | help='SAGE interactive runner.') | |
424 |
|
422 | |||
425 | opts,args = parser.parse_args() |
|
423 | opts,args = parser.parse_args() | |
426 | runners = dict(ipython=IPythonRunner, |
|
424 | runners = dict(ipython=IPythonRunner, | |
427 | python=PythonRunner, |
|
425 | python=PythonRunner, | |
428 | sage=SAGERunner) |
|
426 | sage=SAGERunner) | |
429 |
|
427 | |||
430 | try: |
|
428 | try: | |
431 | ext = os.path.splitext(args[0])[-1] |
|
429 | ext = os.path.splitext(args[0])[-1] | |
432 | except IndexError: |
|
430 | except IndexError: | |
433 | ext = '' |
|
431 | ext = '' | |
434 | modes = {'.ipy':'ipython', |
|
432 | modes = {'.ipy':'ipython', | |
435 | '.py':'python', |
|
433 | '.py':'python', | |
436 | '.sage':'sage'} |
|
434 | '.sage':'sage'} | |
437 | mode = modes.get(ext,"ipython") |
|
435 | mode = modes.get(ext,"ipython") | |
438 | if opts.mode: |
|
436 | if opts.mode: | |
439 | mode = opts.mode |
|
437 | mode = opts.mode | |
440 | runners[mode]().main(args) |
|
438 | runners[mode]().main(args) | |
441 |
|
439 | |||
442 | if __name__ == '__main__': |
|
440 | if __name__ == '__main__': | |
443 | main() |
|
441 | main() |
@@ -1,734 +1,736 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """ |
|
2 | """ | |
3 | pretty |
|
3 | Python advanced pretty printer. This pretty printer is intended to | |
4 | ~~ |
|
4 | replace the old `pprint` python module which does not allow developers | |
|
5 | to provide their own pretty print callbacks. | |||
5 |
|
6 | |||
6 | Python advanced pretty printer. This pretty printer is intended to |
|
7 | This module is based on ruby's `prettyprint.rb` library by `Tanaka Akira`. | |
7 | replace the old `pprint` python module which does not allow developers |
|
|||
8 | to provide their own pretty print callbacks. |
|
|||
9 |
|
8 | |||
10 | This module is based on ruby's `prettyprint.rb` library by `Tanaka Akira`. |
|
|||
11 |
|
9 | |||
|
10 | Example Usage | |||
|
11 | ------------- | |||
12 |
|
12 | |||
13 | Example Usage |
|
13 | To directly print the representation of an object use `pprint`:: | |
14 | ============= |
|
|||
15 |
|
14 | |||
16 | To directly print the representation of an object use `pprint`:: |
|
15 | from pretty import pprint | |
|
16 | pprint(complex_object) | |||
17 |
|
17 | |||
18 | from pretty import pprint |
|
18 | To get a string of the output use `pretty`:: | |
19 | pprint(complex_object) |
|
|||
20 |
|
19 | |||
21 | To get a string of the output use `pretty`:: |
|
20 | from pretty import pretty | |
|
21 | string = pretty(complex_object) | |||
22 |
|
22 | |||
23 | from pretty import pretty |
|
|||
24 | string = pretty(complex_object) |
|
|||
25 |
|
23 | |||
|
24 | Extending | |||
|
25 | --------- | |||
26 |
|
26 | |||
27 | Extending |
|
27 | The pretty library allows developers to add pretty printing rules for their | |
28 | ========= |
|
28 | own objects. This process is straightforward. All you have to do is to | |
|
29 | add a `_repr_pretty_` method to your object and call the methods on the | |||
|
30 | pretty printer passed:: | |||
29 |
|
31 | |||
30 | The pretty library allows developers to add pretty printing rules for their |
|
32 | class MyObject(object): | |
31 | own objects. This process is straightforward. All you have to do is to |
|
|||
32 | add a `_repr_pretty_` method to your object and call the methods on the |
|
|||
33 | pretty printer passed:: |
|
|||
34 |
|
33 | |||
35 | class MyObject(object): |
|
34 | def _repr_pretty_(self, p, cycle): | |
36 |
|
35 | ... | ||
37 | def _repr_pretty_(self, p, cycle): |
|
|||
38 | ... |
|
|||
39 |
|
36 | |||
40 |
|
|
37 | Depending on the python version you want to support you have two | |
41 |
|
|
38 | possibilities. The following list shows the python 2.5 version and the | |
42 |
|
|
39 | compatibility one. | |
43 |
|
40 | |||
44 |
|
41 | |||
45 |
|
|
42 | Here the example implementation of a `_repr_pretty_` method for a list | |
46 |
|
|
43 | subclass for python 2.5 and higher (python 2.5 requires the with statement | |
47 |
|
|
44 | __future__ import):: | |
48 |
|
45 | |||
49 |
|
|
46 | class MyList(list): | |
50 |
|
47 | |||
51 |
|
|
48 | def _repr_pretty_(self, p, cycle): | |
52 |
|
|
49 | if cycle: | |
53 |
|
|
50 | p.text('MyList(...)') | |
54 |
|
|
51 | else: | |
55 |
|
|
52 | with p.group(8, 'MyList([', '])'): | |
56 | for idx, item in enumerate(self): |
|
|||
57 | if idx: |
|
|||
58 | p.text(',') |
|
|||
59 | p.breakable() |
|
|||
60 | p.pretty(item) |
|
|||
61 |
|
||||
62 | The `cycle` parameter is `True` if pretty detected a cycle. You *have* to |
|
|||
63 | react to that or the result is an infinite loop. `p.text()` just adds |
|
|||
64 | non breaking text to the output, `p.breakable()` either adds a whitespace |
|
|||
65 | or breaks here. If you pass it an argument it's used instead of the |
|
|||
66 | default space. `p.pretty` prettyprints another object using the pretty print |
|
|||
67 | method. |
|
|||
68 |
|
||||
69 | The first parameter to the `group` function specifies the extra indentation |
|
|||
70 | of the next line. In this example the next item will either be not |
|
|||
71 | breaked (if the items are short enough) or aligned with the right edge of |
|
|||
72 | the opening bracked of `MyList`. |
|
|||
73 |
|
||||
74 | If you want to support python 2.4 and lower you can use this code:: |
|
|||
75 |
|
||||
76 | class MyList(list): |
|
|||
77 |
|
||||
78 | def _repr_pretty_(self, p, cycle): |
|
|||
79 | if cycle: |
|
|||
80 | p.text('MyList(...)') |
|
|||
81 | else: |
|
|||
82 | p.begin_group(8, 'MyList([') |
|
|||
83 | for idx, item in enumerate(self): |
|
53 | for idx, item in enumerate(self): | |
84 | if idx: |
|
54 | if idx: | |
85 | p.text(',') |
|
55 | p.text(',') | |
86 | p.breakable() |
|
56 | p.breakable() | |
87 | p.pretty(item) |
|
57 | p.pretty(item) | |
88 | p.end_group(8, '])') |
|
|||
89 |
|
58 | |||
90 | If you just want to indent something you can use the group function |
|
59 | The `cycle` parameter is `True` if pretty detected a cycle. You *have* to | |
91 | without open / close parameters. Under python 2.5 you can also use this |
|
60 | react to that or the result is an infinite loop. `p.text()` just adds | |
92 | code:: |
|
61 | non breaking text to the output, `p.breakable()` either adds a whitespace | |
|
62 | or breaks here. If you pass it an argument it's used instead of the | |||
|
63 | default space. `p.pretty` prettyprints another object using the pretty print | |||
|
64 | method. | |||
93 |
|
65 | |||
94 | with p.indent(2): |
|
66 | The first parameter to the `group` function specifies the extra indentation | |
95 | ... |
|
67 | of the next line. In this example the next item will either be not | |
|
68 | breaked (if the items are short enough) or aligned with the right edge of | |||
|
69 | the opening bracked of `MyList`. | |||
|
70 | ||||
|
71 | If you want to support python 2.4 and lower you can use this code:: | |||
|
72 | ||||
|
73 | class MyList(list): | |||
|
74 | ||||
|
75 | def _repr_pretty_(self, p, cycle): | |||
|
76 | if cycle: | |||
|
77 | p.text('MyList(...)') | |||
|
78 | else: | |||
|
79 | p.begin_group(8, 'MyList([') | |||
|
80 | for idx, item in enumerate(self): | |||
|
81 | if idx: | |||
|
82 | p.text(',') | |||
|
83 | p.breakable() | |||
|
84 | p.pretty(item) | |||
|
85 | p.end_group(8, '])') | |||
|
86 | ||||
|
87 | If you just want to indent something you can use the group function | |||
|
88 | without open / close parameters. Under python 2.5 you can also use this | |||
|
89 | code:: | |||
|
90 | ||||
|
91 | with p.indent(2): | |||
|
92 | ... | |||
|
93 | ||||
|
94 | Or under python2.4 you might want to modify ``p.indentation`` by hand but | |||
|
95 | this is rather ugly. | |||
|
96 | ||||
|
97 | Inheritance diagram: | |||
96 |
|
98 | |||
97 | Or under python2.4 you might want to modify ``p.indentation`` by hand but |
|
99 | .. inheritance-diagram:: IPython.lib.pretty | |
98 | this is rather ugly. |
|
100 | :parts: 3 | |
99 |
|
101 | |||
100 |
|
|
102 | :copyright: 2007 by Armin Ronacher. | |
101 |
|
|
103 | Portions (c) 2009 by Robert Kern. | |
102 |
|
|
104 | :license: BSD License. | |
103 | """ |
|
105 | """ | |
104 | from __future__ import with_statement |
|
106 | from __future__ import with_statement | |
105 | from contextlib import contextmanager |
|
107 | from contextlib import contextmanager | |
106 | import sys |
|
108 | import sys | |
107 | import types |
|
109 | import types | |
108 | import re |
|
110 | import re | |
109 | import datetime |
|
111 | import datetime | |
110 | from StringIO import StringIO |
|
112 | from StringIO import StringIO | |
111 | from collections import deque |
|
113 | from collections import deque | |
112 |
|
114 | |||
113 |
|
115 | |||
114 | __all__ = ['pretty', 'pprint', 'PrettyPrinter', 'RepresentationPrinter', |
|
116 | __all__ = ['pretty', 'pprint', 'PrettyPrinter', 'RepresentationPrinter', | |
115 | 'for_type', 'for_type_by_name'] |
|
117 | 'for_type', 'for_type_by_name'] | |
116 |
|
118 | |||
117 |
|
119 | |||
118 | _re_pattern_type = type(re.compile('')) |
|
120 | _re_pattern_type = type(re.compile('')) | |
119 |
|
121 | |||
120 |
|
122 | |||
121 | def pretty(obj, verbose=False, max_width=79, newline='\n'): |
|
123 | def pretty(obj, verbose=False, max_width=79, newline='\n'): | |
122 | """ |
|
124 | """ | |
123 | Pretty print the object's representation. |
|
125 | Pretty print the object's representation. | |
124 | """ |
|
126 | """ | |
125 | stream = StringIO() |
|
127 | stream = StringIO() | |
126 | printer = RepresentationPrinter(stream, verbose, max_width, newline) |
|
128 | printer = RepresentationPrinter(stream, verbose, max_width, newline) | |
127 | printer.pretty(obj) |
|
129 | printer.pretty(obj) | |
128 | printer.flush() |
|
130 | printer.flush() | |
129 | return stream.getvalue() |
|
131 | return stream.getvalue() | |
130 |
|
132 | |||
131 |
|
133 | |||
132 | def pprint(obj, verbose=False, max_width=79, newline='\n'): |
|
134 | def pprint(obj, verbose=False, max_width=79, newline='\n'): | |
133 | """ |
|
135 | """ | |
134 | Like `pretty` but print to stdout. |
|
136 | Like `pretty` but print to stdout. | |
135 | """ |
|
137 | """ | |
136 | printer = RepresentationPrinter(sys.stdout, verbose, max_width, newline) |
|
138 | printer = RepresentationPrinter(sys.stdout, verbose, max_width, newline) | |
137 | printer.pretty(obj) |
|
139 | printer.pretty(obj) | |
138 | printer.flush() |
|
140 | printer.flush() | |
139 | sys.stdout.write(newline) |
|
141 | sys.stdout.write(newline) | |
140 | sys.stdout.flush() |
|
142 | sys.stdout.flush() | |
141 |
|
143 | |||
142 | class _PrettyPrinterBase(object): |
|
144 | class _PrettyPrinterBase(object): | |
143 |
|
145 | |||
144 | @contextmanager |
|
146 | @contextmanager | |
145 | def indent(self, indent): |
|
147 | def indent(self, indent): | |
146 | """with statement support for indenting/dedenting.""" |
|
148 | """with statement support for indenting/dedenting.""" | |
147 | self.indentation += indent |
|
149 | self.indentation += indent | |
148 | try: |
|
150 | try: | |
149 | yield |
|
151 | yield | |
150 | finally: |
|
152 | finally: | |
151 | self.indentation -= indent |
|
153 | self.indentation -= indent | |
152 |
|
154 | |||
153 | @contextmanager |
|
155 | @contextmanager | |
154 | def group(self, indent=0, open='', close=''): |
|
156 | def group(self, indent=0, open='', close=''): | |
155 | """like begin_group / end_group but for the with statement.""" |
|
157 | """like begin_group / end_group but for the with statement.""" | |
156 | self.begin_group(indent, open) |
|
158 | self.begin_group(indent, open) | |
157 | try: |
|
159 | try: | |
158 | yield |
|
160 | yield | |
159 | finally: |
|
161 | finally: | |
160 | self.end_group(indent, close) |
|
162 | self.end_group(indent, close) | |
161 |
|
163 | |||
162 | class PrettyPrinter(_PrettyPrinterBase): |
|
164 | class PrettyPrinter(_PrettyPrinterBase): | |
163 | """ |
|
165 | """ | |
164 | Baseclass for the `RepresentationPrinter` prettyprinter that is used to |
|
166 | Baseclass for the `RepresentationPrinter` prettyprinter that is used to | |
165 | generate pretty reprs of objects. Contrary to the `RepresentationPrinter` |
|
167 | generate pretty reprs of objects. Contrary to the `RepresentationPrinter` | |
166 | this printer knows nothing about the default pprinters or the `_repr_pretty_` |
|
168 | this printer knows nothing about the default pprinters or the `_repr_pretty_` | |
167 | callback method. |
|
169 | callback method. | |
168 | """ |
|
170 | """ | |
169 |
|
171 | |||
170 | def __init__(self, output, max_width=79, newline='\n'): |
|
172 | def __init__(self, output, max_width=79, newline='\n'): | |
171 | self.output = output |
|
173 | self.output = output | |
172 | self.max_width = max_width |
|
174 | self.max_width = max_width | |
173 | self.newline = newline |
|
175 | self.newline = newline | |
174 | self.output_width = 0 |
|
176 | self.output_width = 0 | |
175 | self.buffer_width = 0 |
|
177 | self.buffer_width = 0 | |
176 | self.buffer = deque() |
|
178 | self.buffer = deque() | |
177 |
|
179 | |||
178 | root_group = Group(0) |
|
180 | root_group = Group(0) | |
179 | self.group_stack = [root_group] |
|
181 | self.group_stack = [root_group] | |
180 | self.group_queue = GroupQueue(root_group) |
|
182 | self.group_queue = GroupQueue(root_group) | |
181 | self.indentation = 0 |
|
183 | self.indentation = 0 | |
182 |
|
184 | |||
183 | def _break_outer_groups(self): |
|
185 | def _break_outer_groups(self): | |
184 | while self.max_width < self.output_width + self.buffer_width: |
|
186 | while self.max_width < self.output_width + self.buffer_width: | |
185 | group = self.group_queue.deq() |
|
187 | group = self.group_queue.deq() | |
186 | if not group: |
|
188 | if not group: | |
187 | return |
|
189 | return | |
188 | while group.breakables: |
|
190 | while group.breakables: | |
189 | x = self.buffer.popleft() |
|
191 | x = self.buffer.popleft() | |
190 | self.output_width = x.output(self.output, self.output_width) |
|
192 | self.output_width = x.output(self.output, self.output_width) | |
191 | self.buffer_width -= x.width |
|
193 | self.buffer_width -= x.width | |
192 | while self.buffer and isinstance(self.buffer[0], Text): |
|
194 | while self.buffer and isinstance(self.buffer[0], Text): | |
193 | x = self.buffer.popleft() |
|
195 | x = self.buffer.popleft() | |
194 | self.output_width = x.output(self.output, self.output_width) |
|
196 | self.output_width = x.output(self.output, self.output_width) | |
195 | self.buffer_width -= x.width |
|
197 | self.buffer_width -= x.width | |
196 |
|
198 | |||
197 | def text(self, obj): |
|
199 | def text(self, obj): | |
198 | """Add literal text to the output.""" |
|
200 | """Add literal text to the output.""" | |
199 | width = len(obj) |
|
201 | width = len(obj) | |
200 | if self.buffer: |
|
202 | if self.buffer: | |
201 | text = self.buffer[-1] |
|
203 | text = self.buffer[-1] | |
202 | if not isinstance(text, Text): |
|
204 | if not isinstance(text, Text): | |
203 | text = Text() |
|
205 | text = Text() | |
204 | self.buffer.append(text) |
|
206 | self.buffer.append(text) | |
205 | text.add(obj, width) |
|
207 | text.add(obj, width) | |
206 | self.buffer_width += width |
|
208 | self.buffer_width += width | |
207 | self._break_outer_groups() |
|
209 | self._break_outer_groups() | |
208 | else: |
|
210 | else: | |
209 | self.output.write(obj) |
|
211 | self.output.write(obj) | |
210 | self.output_width += width |
|
212 | self.output_width += width | |
211 |
|
213 | |||
212 | def breakable(self, sep=' '): |
|
214 | def breakable(self, sep=' '): | |
213 | """ |
|
215 | """ | |
214 | Add a breakable separator to the output. This does not mean that it |
|
216 | Add a breakable separator to the output. This does not mean that it | |
215 | will automatically break here. If no breaking on this position takes |
|
217 | will automatically break here. If no breaking on this position takes | |
216 | place the `sep` is inserted which default to one space. |
|
218 | place the `sep` is inserted which default to one space. | |
217 | """ |
|
219 | """ | |
218 | width = len(sep) |
|
220 | width = len(sep) | |
219 | group = self.group_stack[-1] |
|
221 | group = self.group_stack[-1] | |
220 | if group.want_break: |
|
222 | if group.want_break: | |
221 | self.flush() |
|
223 | self.flush() | |
222 | self.output.write(self.newline) |
|
224 | self.output.write(self.newline) | |
223 | self.output.write(' ' * self.indentation) |
|
225 | self.output.write(' ' * self.indentation) | |
224 | self.output_width = self.indentation |
|
226 | self.output_width = self.indentation | |
225 | self.buffer_width = 0 |
|
227 | self.buffer_width = 0 | |
226 | else: |
|
228 | else: | |
227 | self.buffer.append(Breakable(sep, width, self)) |
|
229 | self.buffer.append(Breakable(sep, width, self)) | |
228 | self.buffer_width += width |
|
230 | self.buffer_width += width | |
229 | self._break_outer_groups() |
|
231 | self._break_outer_groups() | |
230 |
|
232 | |||
231 |
|
233 | |||
232 | def begin_group(self, indent=0, open=''): |
|
234 | def begin_group(self, indent=0, open=''): | |
233 | """ |
|
235 | """ | |
234 | Begin a group. If you want support for python < 2.5 which doesn't has |
|
236 | Begin a group. If you want support for python < 2.5 which doesn't has | |
235 | the with statement this is the preferred way: |
|
237 | the with statement this is the preferred way: | |
236 |
|
238 | |||
237 | p.begin_group(1, '{') |
|
239 | p.begin_group(1, '{') | |
238 | ... |
|
240 | ... | |
239 | p.end_group(1, '}') |
|
241 | p.end_group(1, '}') | |
240 |
|
242 | |||
241 | The python 2.5 expression would be this: |
|
243 | The python 2.5 expression would be this: | |
242 |
|
244 | |||
243 | with p.group(1, '{', '}'): |
|
245 | with p.group(1, '{', '}'): | |
244 | ... |
|
246 | ... | |
245 |
|
247 | |||
246 | The first parameter specifies the indentation for the next line (usually |
|
248 | The first parameter specifies the indentation for the next line (usually | |
247 | the width of the opening text), the second the opening text. All |
|
249 | the width of the opening text), the second the opening text. All | |
248 | parameters are optional. |
|
250 | parameters are optional. | |
249 | """ |
|
251 | """ | |
250 | if open: |
|
252 | if open: | |
251 | self.text(open) |
|
253 | self.text(open) | |
252 | group = Group(self.group_stack[-1].depth + 1) |
|
254 | group = Group(self.group_stack[-1].depth + 1) | |
253 | self.group_stack.append(group) |
|
255 | self.group_stack.append(group) | |
254 | self.group_queue.enq(group) |
|
256 | self.group_queue.enq(group) | |
255 | self.indentation += indent |
|
257 | self.indentation += indent | |
256 |
|
258 | |||
257 | def end_group(self, dedent=0, close=''): |
|
259 | def end_group(self, dedent=0, close=''): | |
258 | """End a group. See `begin_group` for more details.""" |
|
260 | """End a group. See `begin_group` for more details.""" | |
259 | self.indentation -= dedent |
|
261 | self.indentation -= dedent | |
260 | group = self.group_stack.pop() |
|
262 | group = self.group_stack.pop() | |
261 | if not group.breakables: |
|
263 | if not group.breakables: | |
262 | self.group_queue.remove(group) |
|
264 | self.group_queue.remove(group) | |
263 | if close: |
|
265 | if close: | |
264 | self.text(close) |
|
266 | self.text(close) | |
265 |
|
267 | |||
266 | def flush(self): |
|
268 | def flush(self): | |
267 | """Flush data that is left in the buffer.""" |
|
269 | """Flush data that is left in the buffer.""" | |
268 | for data in self.buffer: |
|
270 | for data in self.buffer: | |
269 | self.output_width += data.output(self.output, self.output_width) |
|
271 | self.output_width += data.output(self.output, self.output_width) | |
270 | self.buffer.clear() |
|
272 | self.buffer.clear() | |
271 | self.buffer_width = 0 |
|
273 | self.buffer_width = 0 | |
272 |
|
274 | |||
273 |
|
275 | |||
274 | def _get_mro(obj_class): |
|
276 | def _get_mro(obj_class): | |
275 | """ Get a reasonable method resolution order of a class and its superclasses |
|
277 | """ Get a reasonable method resolution order of a class and its superclasses | |
276 | for both old-style and new-style classes. |
|
278 | for both old-style and new-style classes. | |
277 | """ |
|
279 | """ | |
278 | if not hasattr(obj_class, '__mro__'): |
|
280 | if not hasattr(obj_class, '__mro__'): | |
279 | # Old-style class. Mix in object to make a fake new-style class. |
|
281 | # Old-style class. Mix in object to make a fake new-style class. | |
280 | try: |
|
282 | try: | |
281 | obj_class = type(obj_class.__name__, (obj_class, object), {}) |
|
283 | obj_class = type(obj_class.__name__, (obj_class, object), {}) | |
282 | except TypeError: |
|
284 | except TypeError: | |
283 | # Old-style extension type that does not descend from object. |
|
285 | # Old-style extension type that does not descend from object. | |
284 | # FIXME: try to construct a more thorough MRO. |
|
286 | # FIXME: try to construct a more thorough MRO. | |
285 | mro = [obj_class] |
|
287 | mro = [obj_class] | |
286 | else: |
|
288 | else: | |
287 | mro = obj_class.__mro__[1:-1] |
|
289 | mro = obj_class.__mro__[1:-1] | |
288 | else: |
|
290 | else: | |
289 | mro = obj_class.__mro__ |
|
291 | mro = obj_class.__mro__ | |
290 | return mro |
|
292 | return mro | |
291 |
|
293 | |||
292 |
|
294 | |||
293 | class RepresentationPrinter(PrettyPrinter): |
|
295 | class RepresentationPrinter(PrettyPrinter): | |
294 | """ |
|
296 | """ | |
295 | Special pretty printer that has a `pretty` method that calls the pretty |
|
297 | Special pretty printer that has a `pretty` method that calls the pretty | |
296 | printer for a python object. |
|
298 | printer for a python object. | |
297 |
|
299 | |||
298 | This class stores processing data on `self` so you must *never* use |
|
300 | This class stores processing data on `self` so you must *never* use | |
299 | this class in a threaded environment. Always lock it or reinstanciate |
|
301 | this class in a threaded environment. Always lock it or reinstanciate | |
300 | it. |
|
302 | it. | |
301 |
|
303 | |||
302 | Instances also have a verbose flag callbacks can access to control their |
|
304 | Instances also have a verbose flag callbacks can access to control their | |
303 | output. For example the default instance repr prints all attributes and |
|
305 | output. For example the default instance repr prints all attributes and | |
304 | methods that are not prefixed by an underscore if the printer is in |
|
306 | methods that are not prefixed by an underscore if the printer is in | |
305 | verbose mode. |
|
307 | verbose mode. | |
306 | """ |
|
308 | """ | |
307 |
|
309 | |||
308 | def __init__(self, output, verbose=False, max_width=79, newline='\n', |
|
310 | def __init__(self, output, verbose=False, max_width=79, newline='\n', | |
309 | singleton_pprinters=None, type_pprinters=None, deferred_pprinters=None): |
|
311 | singleton_pprinters=None, type_pprinters=None, deferred_pprinters=None): | |
310 |
|
312 | |||
311 | PrettyPrinter.__init__(self, output, max_width, newline) |
|
313 | PrettyPrinter.__init__(self, output, max_width, newline) | |
312 | self.verbose = verbose |
|
314 | self.verbose = verbose | |
313 | self.stack = [] |
|
315 | self.stack = [] | |
314 | if singleton_pprinters is None: |
|
316 | if singleton_pprinters is None: | |
315 | singleton_pprinters = _singleton_pprinters.copy() |
|
317 | singleton_pprinters = _singleton_pprinters.copy() | |
316 | self.singleton_pprinters = singleton_pprinters |
|
318 | self.singleton_pprinters = singleton_pprinters | |
317 | if type_pprinters is None: |
|
319 | if type_pprinters is None: | |
318 | type_pprinters = _type_pprinters.copy() |
|
320 | type_pprinters = _type_pprinters.copy() | |
319 | self.type_pprinters = type_pprinters |
|
321 | self.type_pprinters = type_pprinters | |
320 | if deferred_pprinters is None: |
|
322 | if deferred_pprinters is None: | |
321 | deferred_pprinters = _deferred_type_pprinters.copy() |
|
323 | deferred_pprinters = _deferred_type_pprinters.copy() | |
322 | self.deferred_pprinters = deferred_pprinters |
|
324 | self.deferred_pprinters = deferred_pprinters | |
323 |
|
325 | |||
324 | def pretty(self, obj): |
|
326 | def pretty(self, obj): | |
325 | """Pretty print the given object.""" |
|
327 | """Pretty print the given object.""" | |
326 | obj_id = id(obj) |
|
328 | obj_id = id(obj) | |
327 | cycle = obj_id in self.stack |
|
329 | cycle = obj_id in self.stack | |
328 | self.stack.append(obj_id) |
|
330 | self.stack.append(obj_id) | |
329 | self.begin_group() |
|
331 | self.begin_group() | |
330 | try: |
|
332 | try: | |
331 | obj_class = getattr(obj, '__class__', None) or type(obj) |
|
333 | obj_class = getattr(obj, '__class__', None) or type(obj) | |
332 | # First try to find registered singleton printers for the type. |
|
334 | # First try to find registered singleton printers for the type. | |
333 | try: |
|
335 | try: | |
334 | printer = self.singleton_pprinters[obj_id] |
|
336 | printer = self.singleton_pprinters[obj_id] | |
335 | except (TypeError, KeyError): |
|
337 | except (TypeError, KeyError): | |
336 | pass |
|
338 | pass | |
337 | else: |
|
339 | else: | |
338 | return printer(obj, self, cycle) |
|
340 | return printer(obj, self, cycle) | |
339 | # Next walk the mro and check for either: |
|
341 | # Next walk the mro and check for either: | |
340 | # 1) a registered printer |
|
342 | # 1) a registered printer | |
341 | # 2) a _repr_pretty_ method |
|
343 | # 2) a _repr_pretty_ method | |
342 | for cls in _get_mro(obj_class): |
|
344 | for cls in _get_mro(obj_class): | |
343 | if cls in self.type_pprinters: |
|
345 | if cls in self.type_pprinters: | |
344 | # printer registered in self.type_pprinters |
|
346 | # printer registered in self.type_pprinters | |
345 | return self.type_pprinters[cls](obj, self, cycle) |
|
347 | return self.type_pprinters[cls](obj, self, cycle) | |
346 | else: |
|
348 | else: | |
347 | # deferred printer |
|
349 | # deferred printer | |
348 | printer = self._in_deferred_types(cls) |
|
350 | printer = self._in_deferred_types(cls) | |
349 | if printer is not None: |
|
351 | if printer is not None: | |
350 | return printer(obj, self, cycle) |
|
352 | return printer(obj, self, cycle) | |
351 | else: |
|
353 | else: | |
352 | # Finally look for special method names. |
|
354 | # Finally look for special method names. | |
353 | # Some objects automatically create any requested |
|
355 | # Some objects automatically create any requested | |
354 | # attribute. Try to ignore most of them by checking for |
|
356 | # attribute. Try to ignore most of them by checking for | |
355 | # callability. |
|
357 | # callability. | |
356 | if '_repr_pretty_' in cls.__dict__: |
|
358 | if '_repr_pretty_' in cls.__dict__: | |
357 | meth = cls._repr_pretty_ |
|
359 | meth = cls._repr_pretty_ | |
358 | if callable(meth): |
|
360 | if callable(meth): | |
359 | return meth(obj, self, cycle) |
|
361 | return meth(obj, self, cycle) | |
360 | return _default_pprint(obj, self, cycle) |
|
362 | return _default_pprint(obj, self, cycle) | |
361 | finally: |
|
363 | finally: | |
362 | self.end_group() |
|
364 | self.end_group() | |
363 | self.stack.pop() |
|
365 | self.stack.pop() | |
364 |
|
366 | |||
365 | def _in_deferred_types(self, cls): |
|
367 | def _in_deferred_types(self, cls): | |
366 | """ |
|
368 | """ | |
367 | Check if the given class is specified in the deferred type registry. |
|
369 | Check if the given class is specified in the deferred type registry. | |
368 |
|
370 | |||
369 | Returns the printer from the registry if it exists, and None if the |
|
371 | Returns the printer from the registry if it exists, and None if the | |
370 | class is not in the registry. Successful matches will be moved to the |
|
372 | class is not in the registry. Successful matches will be moved to the | |
371 | regular type registry for future use. |
|
373 | regular type registry for future use. | |
372 | """ |
|
374 | """ | |
373 | mod = getattr(cls, '__module__', None) |
|
375 | mod = getattr(cls, '__module__', None) | |
374 | name = getattr(cls, '__name__', None) |
|
376 | name = getattr(cls, '__name__', None) | |
375 | key = (mod, name) |
|
377 | key = (mod, name) | |
376 | printer = None |
|
378 | printer = None | |
377 | if key in self.deferred_pprinters: |
|
379 | if key in self.deferred_pprinters: | |
378 | # Move the printer over to the regular registry. |
|
380 | # Move the printer over to the regular registry. | |
379 | printer = self.deferred_pprinters.pop(key) |
|
381 | printer = self.deferred_pprinters.pop(key) | |
380 | self.type_pprinters[cls] = printer |
|
382 | self.type_pprinters[cls] = printer | |
381 | return printer |
|
383 | return printer | |
382 |
|
384 | |||
383 |
|
385 | |||
384 | class Printable(object): |
|
386 | class Printable(object): | |
385 |
|
387 | |||
386 | def output(self, stream, output_width): |
|
388 | def output(self, stream, output_width): | |
387 | return output_width |
|
389 | return output_width | |
388 |
|
390 | |||
389 |
|
391 | |||
390 | class Text(Printable): |
|
392 | class Text(Printable): | |
391 |
|
393 | |||
392 | def __init__(self): |
|
394 | def __init__(self): | |
393 | self.objs = [] |
|
395 | self.objs = [] | |
394 | self.width = 0 |
|
396 | self.width = 0 | |
395 |
|
397 | |||
396 | def output(self, stream, output_width): |
|
398 | def output(self, stream, output_width): | |
397 | for obj in self.objs: |
|
399 | for obj in self.objs: | |
398 | stream.write(obj) |
|
400 | stream.write(obj) | |
399 | return output_width + self.width |
|
401 | return output_width + self.width | |
400 |
|
402 | |||
401 | def add(self, obj, width): |
|
403 | def add(self, obj, width): | |
402 | self.objs.append(obj) |
|
404 | self.objs.append(obj) | |
403 | self.width += width |
|
405 | self.width += width | |
404 |
|
406 | |||
405 |
|
407 | |||
406 | class Breakable(Printable): |
|
408 | class Breakable(Printable): | |
407 |
|
409 | |||
408 | def __init__(self, seq, width, pretty): |
|
410 | def __init__(self, seq, width, pretty): | |
409 | self.obj = seq |
|
411 | self.obj = seq | |
410 | self.width = width |
|
412 | self.width = width | |
411 | self.pretty = pretty |
|
413 | self.pretty = pretty | |
412 | self.indentation = pretty.indentation |
|
414 | self.indentation = pretty.indentation | |
413 | self.group = pretty.group_stack[-1] |
|
415 | self.group = pretty.group_stack[-1] | |
414 | self.group.breakables.append(self) |
|
416 | self.group.breakables.append(self) | |
415 |
|
417 | |||
416 | def output(self, stream, output_width): |
|
418 | def output(self, stream, output_width): | |
417 | self.group.breakables.popleft() |
|
419 | self.group.breakables.popleft() | |
418 | if self.group.want_break: |
|
420 | if self.group.want_break: | |
419 | stream.write(self.pretty.newline) |
|
421 | stream.write(self.pretty.newline) | |
420 | stream.write(' ' * self.indentation) |
|
422 | stream.write(' ' * self.indentation) | |
421 | return self.indentation |
|
423 | return self.indentation | |
422 | if not self.group.breakables: |
|
424 | if not self.group.breakables: | |
423 | self.pretty.group_queue.remove(self.group) |
|
425 | self.pretty.group_queue.remove(self.group) | |
424 | stream.write(self.obj) |
|
426 | stream.write(self.obj) | |
425 | return output_width + self.width |
|
427 | return output_width + self.width | |
426 |
|
428 | |||
427 |
|
429 | |||
428 | class Group(Printable): |
|
430 | class Group(Printable): | |
429 |
|
431 | |||
430 | def __init__(self, depth): |
|
432 | def __init__(self, depth): | |
431 | self.depth = depth |
|
433 | self.depth = depth | |
432 | self.breakables = deque() |
|
434 | self.breakables = deque() | |
433 | self.want_break = False |
|
435 | self.want_break = False | |
434 |
|
436 | |||
435 |
|
437 | |||
436 | class GroupQueue(object): |
|
438 | class GroupQueue(object): | |
437 |
|
439 | |||
438 | def __init__(self, *groups): |
|
440 | def __init__(self, *groups): | |
439 | self.queue = [] |
|
441 | self.queue = [] | |
440 | for group in groups: |
|
442 | for group in groups: | |
441 | self.enq(group) |
|
443 | self.enq(group) | |
442 |
|
444 | |||
443 | def enq(self, group): |
|
445 | def enq(self, group): | |
444 | depth = group.depth |
|
446 | depth = group.depth | |
445 | while depth > len(self.queue) - 1: |
|
447 | while depth > len(self.queue) - 1: | |
446 | self.queue.append([]) |
|
448 | self.queue.append([]) | |
447 | self.queue[depth].append(group) |
|
449 | self.queue[depth].append(group) | |
448 |
|
450 | |||
449 | def deq(self): |
|
451 | def deq(self): | |
450 | for stack in self.queue: |
|
452 | for stack in self.queue: | |
451 | for idx, group in enumerate(reversed(stack)): |
|
453 | for idx, group in enumerate(reversed(stack)): | |
452 | if group.breakables: |
|
454 | if group.breakables: | |
453 | del stack[idx] |
|
455 | del stack[idx] | |
454 | group.want_break = True |
|
456 | group.want_break = True | |
455 | return group |
|
457 | return group | |
456 | for group in stack: |
|
458 | for group in stack: | |
457 | group.want_break = True |
|
459 | group.want_break = True | |
458 | del stack[:] |
|
460 | del stack[:] | |
459 |
|
461 | |||
460 | def remove(self, group): |
|
462 | def remove(self, group): | |
461 | try: |
|
463 | try: | |
462 | self.queue[group.depth].remove(group) |
|
464 | self.queue[group.depth].remove(group) | |
463 | except ValueError: |
|
465 | except ValueError: | |
464 | pass |
|
466 | pass | |
465 |
|
467 | |||
466 | try: |
|
468 | try: | |
467 | _baseclass_reprs = (object.__repr__, types.InstanceType.__repr__) |
|
469 | _baseclass_reprs = (object.__repr__, types.InstanceType.__repr__) | |
468 | except AttributeError: # Python 3 |
|
470 | except AttributeError: # Python 3 | |
469 | _baseclass_reprs = (object.__repr__,) |
|
471 | _baseclass_reprs = (object.__repr__,) | |
470 |
|
472 | |||
471 |
|
473 | |||
472 | def _default_pprint(obj, p, cycle): |
|
474 | def _default_pprint(obj, p, cycle): | |
473 | """ |
|
475 | """ | |
474 | The default print function. Used if an object does not provide one and |
|
476 | The default print function. Used if an object does not provide one and | |
475 | it's none of the builtin objects. |
|
477 | it's none of the builtin objects. | |
476 | """ |
|
478 | """ | |
477 | klass = getattr(obj, '__class__', None) or type(obj) |
|
479 | klass = getattr(obj, '__class__', None) or type(obj) | |
478 | if getattr(klass, '__repr__', None) not in _baseclass_reprs: |
|
480 | if getattr(klass, '__repr__', None) not in _baseclass_reprs: | |
479 | # A user-provided repr. |
|
481 | # A user-provided repr. | |
480 | p.text(repr(obj)) |
|
482 | p.text(repr(obj)) | |
481 | return |
|
483 | return | |
482 | p.begin_group(1, '<') |
|
484 | p.begin_group(1, '<') | |
483 | p.pretty(klass) |
|
485 | p.pretty(klass) | |
484 | p.text(' at 0x%x' % id(obj)) |
|
486 | p.text(' at 0x%x' % id(obj)) | |
485 | if cycle: |
|
487 | if cycle: | |
486 | p.text(' ...') |
|
488 | p.text(' ...') | |
487 | elif p.verbose: |
|
489 | elif p.verbose: | |
488 | first = True |
|
490 | first = True | |
489 | for key in dir(obj): |
|
491 | for key in dir(obj): | |
490 | if not key.startswith('_'): |
|
492 | if not key.startswith('_'): | |
491 | try: |
|
493 | try: | |
492 | value = getattr(obj, key) |
|
494 | value = getattr(obj, key) | |
493 | except AttributeError: |
|
495 | except AttributeError: | |
494 | continue |
|
496 | continue | |
495 | if isinstance(value, types.MethodType): |
|
497 | if isinstance(value, types.MethodType): | |
496 | continue |
|
498 | continue | |
497 | if not first: |
|
499 | if not first: | |
498 | p.text(',') |
|
500 | p.text(',') | |
499 | p.breakable() |
|
501 | p.breakable() | |
500 | p.text(key) |
|
502 | p.text(key) | |
501 | p.text('=') |
|
503 | p.text('=') | |
502 | step = len(key) + 1 |
|
504 | step = len(key) + 1 | |
503 | p.indentation += step |
|
505 | p.indentation += step | |
504 | p.pretty(value) |
|
506 | p.pretty(value) | |
505 | p.indentation -= step |
|
507 | p.indentation -= step | |
506 | first = False |
|
508 | first = False | |
507 | p.end_group(1, '>') |
|
509 | p.end_group(1, '>') | |
508 |
|
510 | |||
509 |
|
511 | |||
510 | def _seq_pprinter_factory(start, end, basetype): |
|
512 | def _seq_pprinter_factory(start, end, basetype): | |
511 | """ |
|
513 | """ | |
512 | Factory that returns a pprint function useful for sequences. Used by |
|
514 | Factory that returns a pprint function useful for sequences. Used by | |
513 | the default pprint for tuples, dicts, lists, sets and frozensets. |
|
515 | the default pprint for tuples, dicts, lists, sets and frozensets. | |
514 | """ |
|
516 | """ | |
515 | def inner(obj, p, cycle): |
|
517 | def inner(obj, p, cycle): | |
516 | typ = type(obj) |
|
518 | typ = type(obj) | |
517 | if basetype is not None and typ is not basetype and typ.__repr__ != basetype.__repr__: |
|
519 | if basetype is not None and typ is not basetype and typ.__repr__ != basetype.__repr__: | |
518 | # If the subclass provides its own repr, use it instead. |
|
520 | # If the subclass provides its own repr, use it instead. | |
519 | return p.text(typ.__repr__(obj)) |
|
521 | return p.text(typ.__repr__(obj)) | |
520 |
|
522 | |||
521 | if cycle: |
|
523 | if cycle: | |
522 | return p.text(start + '...' + end) |
|
524 | return p.text(start + '...' + end) | |
523 | step = len(start) |
|
525 | step = len(start) | |
524 | p.begin_group(step, start) |
|
526 | p.begin_group(step, start) | |
525 | for idx, x in enumerate(obj): |
|
527 | for idx, x in enumerate(obj): | |
526 | if idx: |
|
528 | if idx: | |
527 | p.text(',') |
|
529 | p.text(',') | |
528 | p.breakable() |
|
530 | p.breakable() | |
529 | p.pretty(x) |
|
531 | p.pretty(x) | |
530 | if len(obj) == 1 and type(obj) is tuple: |
|
532 | if len(obj) == 1 and type(obj) is tuple: | |
531 | # Special case for 1-item tuples. |
|
533 | # Special case for 1-item tuples. | |
532 | p.text(',') |
|
534 | p.text(',') | |
533 | p.end_group(step, end) |
|
535 | p.end_group(step, end) | |
534 | return inner |
|
536 | return inner | |
535 |
|
537 | |||
536 |
|
538 | |||
537 | def _dict_pprinter_factory(start, end, basetype=None): |
|
539 | def _dict_pprinter_factory(start, end, basetype=None): | |
538 | """ |
|
540 | """ | |
539 | Factory that returns a pprint function used by the default pprint of |
|
541 | Factory that returns a pprint function used by the default pprint of | |
540 | dicts and dict proxies. |
|
542 | dicts and dict proxies. | |
541 | """ |
|
543 | """ | |
542 | def inner(obj, p, cycle): |
|
544 | def inner(obj, p, cycle): | |
543 | typ = type(obj) |
|
545 | typ = type(obj) | |
544 | if basetype is not None and typ is not basetype and typ.__repr__ != basetype.__repr__: |
|
546 | if basetype is not None and typ is not basetype and typ.__repr__ != basetype.__repr__: | |
545 | # If the subclass provides its own repr, use it instead. |
|
547 | # If the subclass provides its own repr, use it instead. | |
546 | return p.text(typ.__repr__(obj)) |
|
548 | return p.text(typ.__repr__(obj)) | |
547 |
|
549 | |||
548 | if cycle: |
|
550 | if cycle: | |
549 | return p.text('{...}') |
|
551 | return p.text('{...}') | |
550 | p.begin_group(1, start) |
|
552 | p.begin_group(1, start) | |
551 | keys = obj.keys() |
|
553 | keys = obj.keys() | |
552 | try: |
|
554 | try: | |
553 | keys.sort() |
|
555 | keys.sort() | |
554 | except Exception as e: |
|
556 | except Exception as e: | |
555 | # Sometimes the keys don't sort. |
|
557 | # Sometimes the keys don't sort. | |
556 | pass |
|
558 | pass | |
557 | for idx, key in enumerate(keys): |
|
559 | for idx, key in enumerate(keys): | |
558 | if idx: |
|
560 | if idx: | |
559 | p.text(',') |
|
561 | p.text(',') | |
560 | p.breakable() |
|
562 | p.breakable() | |
561 | p.pretty(key) |
|
563 | p.pretty(key) | |
562 | p.text(': ') |
|
564 | p.text(': ') | |
563 | p.pretty(obj[key]) |
|
565 | p.pretty(obj[key]) | |
564 | p.end_group(1, end) |
|
566 | p.end_group(1, end) | |
565 | return inner |
|
567 | return inner | |
566 |
|
568 | |||
567 |
|
569 | |||
568 | def _super_pprint(obj, p, cycle): |
|
570 | def _super_pprint(obj, p, cycle): | |
569 | """The pprint for the super type.""" |
|
571 | """The pprint for the super type.""" | |
570 | p.begin_group(8, '<super: ') |
|
572 | p.begin_group(8, '<super: ') | |
571 | p.pretty(obj.__self_class__) |
|
573 | p.pretty(obj.__self_class__) | |
572 | p.text(',') |
|
574 | p.text(',') | |
573 | p.breakable() |
|
575 | p.breakable() | |
574 | p.pretty(obj.__self__) |
|
576 | p.pretty(obj.__self__) | |
575 | p.end_group(8, '>') |
|
577 | p.end_group(8, '>') | |
576 |
|
578 | |||
577 |
|
579 | |||
578 | def _re_pattern_pprint(obj, p, cycle): |
|
580 | def _re_pattern_pprint(obj, p, cycle): | |
579 | """The pprint function for regular expression patterns.""" |
|
581 | """The pprint function for regular expression patterns.""" | |
580 | p.text('re.compile(') |
|
582 | p.text('re.compile(') | |
581 | pattern = repr(obj.pattern) |
|
583 | pattern = repr(obj.pattern) | |
582 | if pattern[:1] in 'uU': |
|
584 | if pattern[:1] in 'uU': | |
583 | pattern = pattern[1:] |
|
585 | pattern = pattern[1:] | |
584 | prefix = 'ur' |
|
586 | prefix = 'ur' | |
585 | else: |
|
587 | else: | |
586 | prefix = 'r' |
|
588 | prefix = 'r' | |
587 | pattern = prefix + pattern.replace('\\\\', '\\') |
|
589 | pattern = prefix + pattern.replace('\\\\', '\\') | |
588 | p.text(pattern) |
|
590 | p.text(pattern) | |
589 | if obj.flags: |
|
591 | if obj.flags: | |
590 | p.text(',') |
|
592 | p.text(',') | |
591 | p.breakable() |
|
593 | p.breakable() | |
592 | done_one = False |
|
594 | done_one = False | |
593 | for flag in ('TEMPLATE', 'IGNORECASE', 'LOCALE', 'MULTILINE', 'DOTALL', |
|
595 | for flag in ('TEMPLATE', 'IGNORECASE', 'LOCALE', 'MULTILINE', 'DOTALL', | |
594 | 'UNICODE', 'VERBOSE', 'DEBUG'): |
|
596 | 'UNICODE', 'VERBOSE', 'DEBUG'): | |
595 | if obj.flags & getattr(re, flag): |
|
597 | if obj.flags & getattr(re, flag): | |
596 | if done_one: |
|
598 | if done_one: | |
597 | p.text('|') |
|
599 | p.text('|') | |
598 | p.text('re.' + flag) |
|
600 | p.text('re.' + flag) | |
599 | done_one = True |
|
601 | done_one = True | |
600 | p.text(')') |
|
602 | p.text(')') | |
601 |
|
603 | |||
602 |
|
604 | |||
603 | def _type_pprint(obj, p, cycle): |
|
605 | def _type_pprint(obj, p, cycle): | |
604 | """The pprint for classes and types.""" |
|
606 | """The pprint for classes and types.""" | |
605 | if obj.__module__ in ('__builtin__', 'exceptions'): |
|
607 | if obj.__module__ in ('__builtin__', 'exceptions'): | |
606 | name = obj.__name__ |
|
608 | name = obj.__name__ | |
607 | else: |
|
609 | else: | |
608 | name = obj.__module__ + '.' + obj.__name__ |
|
610 | name = obj.__module__ + '.' + obj.__name__ | |
609 | p.text(name) |
|
611 | p.text(name) | |
610 |
|
612 | |||
611 |
|
613 | |||
612 | def _repr_pprint(obj, p, cycle): |
|
614 | def _repr_pprint(obj, p, cycle): | |
613 | """A pprint that just redirects to the normal repr function.""" |
|
615 | """A pprint that just redirects to the normal repr function.""" | |
614 | p.text(repr(obj)) |
|
616 | p.text(repr(obj)) | |
615 |
|
617 | |||
616 |
|
618 | |||
617 | def _function_pprint(obj, p, cycle): |
|
619 | def _function_pprint(obj, p, cycle): | |
618 | """Base pprint for all functions and builtin functions.""" |
|
620 | """Base pprint for all functions and builtin functions.""" | |
619 | if obj.__module__ in ('__builtin__', 'exceptions') or not obj.__module__: |
|
621 | if obj.__module__ in ('__builtin__', 'exceptions') or not obj.__module__: | |
620 | name = obj.__name__ |
|
622 | name = obj.__name__ | |
621 | else: |
|
623 | else: | |
622 | name = obj.__module__ + '.' + obj.__name__ |
|
624 | name = obj.__module__ + '.' + obj.__name__ | |
623 | p.text('<function %s>' % name) |
|
625 | p.text('<function %s>' % name) | |
624 |
|
626 | |||
625 |
|
627 | |||
626 | def _exception_pprint(obj, p, cycle): |
|
628 | def _exception_pprint(obj, p, cycle): | |
627 | """Base pprint for all exceptions.""" |
|
629 | """Base pprint for all exceptions.""" | |
628 | if obj.__class__.__module__ in ('exceptions', 'builtins'): |
|
630 | if obj.__class__.__module__ in ('exceptions', 'builtins'): | |
629 | name = obj.__class__.__name__ |
|
631 | name = obj.__class__.__name__ | |
630 | else: |
|
632 | else: | |
631 | name = '%s.%s' % ( |
|
633 | name = '%s.%s' % ( | |
632 | obj.__class__.__module__, |
|
634 | obj.__class__.__module__, | |
633 | obj.__class__.__name__ |
|
635 | obj.__class__.__name__ | |
634 | ) |
|
636 | ) | |
635 | step = len(name) + 1 |
|
637 | step = len(name) + 1 | |
636 | p.begin_group(step, name + '(') |
|
638 | p.begin_group(step, name + '(') | |
637 | for idx, arg in enumerate(getattr(obj, 'args', ())): |
|
639 | for idx, arg in enumerate(getattr(obj, 'args', ())): | |
638 | if idx: |
|
640 | if idx: | |
639 | p.text(',') |
|
641 | p.text(',') | |
640 | p.breakable() |
|
642 | p.breakable() | |
641 | p.pretty(arg) |
|
643 | p.pretty(arg) | |
642 | p.end_group(step, ')') |
|
644 | p.end_group(step, ')') | |
643 |
|
645 | |||
644 |
|
646 | |||
645 | #: the exception base |
|
647 | #: the exception base | |
646 | try: |
|
648 | try: | |
647 | _exception_base = BaseException |
|
649 | _exception_base = BaseException | |
648 | except NameError: |
|
650 | except NameError: | |
649 | _exception_base = Exception |
|
651 | _exception_base = Exception | |
650 |
|
652 | |||
651 |
|
653 | |||
652 | #: printers for builtin types |
|
654 | #: printers for builtin types | |
653 | _type_pprinters = { |
|
655 | _type_pprinters = { | |
654 | int: _repr_pprint, |
|
656 | int: _repr_pprint, | |
655 | long: _repr_pprint, |
|
657 | long: _repr_pprint, | |
656 | float: _repr_pprint, |
|
658 | float: _repr_pprint, | |
657 | str: _repr_pprint, |
|
659 | str: _repr_pprint, | |
658 | unicode: _repr_pprint, |
|
660 | unicode: _repr_pprint, | |
659 | tuple: _seq_pprinter_factory('(', ')', tuple), |
|
661 | tuple: _seq_pprinter_factory('(', ')', tuple), | |
660 | list: _seq_pprinter_factory('[', ']', list), |
|
662 | list: _seq_pprinter_factory('[', ']', list), | |
661 | dict: _dict_pprinter_factory('{', '}', dict), |
|
663 | dict: _dict_pprinter_factory('{', '}', dict), | |
662 |
|
664 | |||
663 | set: _seq_pprinter_factory('set([', '])', set), |
|
665 | set: _seq_pprinter_factory('set([', '])', set), | |
664 | frozenset: _seq_pprinter_factory('frozenset([', '])', frozenset), |
|
666 | frozenset: _seq_pprinter_factory('frozenset([', '])', frozenset), | |
665 | super: _super_pprint, |
|
667 | super: _super_pprint, | |
666 | _re_pattern_type: _re_pattern_pprint, |
|
668 | _re_pattern_type: _re_pattern_pprint, | |
667 | type: _type_pprint, |
|
669 | type: _type_pprint, | |
668 | types.FunctionType: _function_pprint, |
|
670 | types.FunctionType: _function_pprint, | |
669 | types.BuiltinFunctionType: _function_pprint, |
|
671 | types.BuiltinFunctionType: _function_pprint, | |
670 | types.SliceType: _repr_pprint, |
|
672 | types.SliceType: _repr_pprint, | |
671 | types.MethodType: _repr_pprint, |
|
673 | types.MethodType: _repr_pprint, | |
672 |
|
674 | |||
673 | datetime.datetime: _repr_pprint, |
|
675 | datetime.datetime: _repr_pprint, | |
674 | datetime.timedelta: _repr_pprint, |
|
676 | datetime.timedelta: _repr_pprint, | |
675 | _exception_base: _exception_pprint |
|
677 | _exception_base: _exception_pprint | |
676 | } |
|
678 | } | |
677 |
|
679 | |||
678 | try: |
|
680 | try: | |
679 | _type_pprinters[types.DictProxyType] = _dict_pprinter_factory('<dictproxy {', '}>') |
|
681 | _type_pprinters[types.DictProxyType] = _dict_pprinter_factory('<dictproxy {', '}>') | |
680 | _type_pprinters[types.ClassType] = _type_pprint |
|
682 | _type_pprinters[types.ClassType] = _type_pprint | |
681 | except AttributeError: # Python 3 |
|
683 | except AttributeError: # Python 3 | |
682 | pass |
|
684 | pass | |
683 |
|
685 | |||
684 | try: |
|
686 | try: | |
685 | _type_pprinters[xrange] = _repr_pprint |
|
687 | _type_pprinters[xrange] = _repr_pprint | |
686 | except NameError: |
|
688 | except NameError: | |
687 | _type_pprinters[range] = _repr_pprint |
|
689 | _type_pprinters[range] = _repr_pprint | |
688 |
|
690 | |||
689 | #: printers for types specified by name |
|
691 | #: printers for types specified by name | |
690 | _deferred_type_pprinters = { |
|
692 | _deferred_type_pprinters = { | |
691 | } |
|
693 | } | |
692 |
|
694 | |||
693 | def for_type(typ, func): |
|
695 | def for_type(typ, func): | |
694 | """ |
|
696 | """ | |
695 | Add a pretty printer for a given type. |
|
697 | Add a pretty printer for a given type. | |
696 | """ |
|
698 | """ | |
697 | oldfunc = _type_pprinters.get(typ, None) |
|
699 | oldfunc = _type_pprinters.get(typ, None) | |
698 | if func is not None: |
|
700 | if func is not None: | |
699 | # To support easy restoration of old pprinters, we need to ignore Nones. |
|
701 | # To support easy restoration of old pprinters, we need to ignore Nones. | |
700 | _type_pprinters[typ] = func |
|
702 | _type_pprinters[typ] = func | |
701 | return oldfunc |
|
703 | return oldfunc | |
702 |
|
704 | |||
703 | def for_type_by_name(type_module, type_name, func): |
|
705 | def for_type_by_name(type_module, type_name, func): | |
704 | """ |
|
706 | """ | |
705 | Add a pretty printer for a type specified by the module and name of a type |
|
707 | Add a pretty printer for a type specified by the module and name of a type | |
706 | rather than the type object itself. |
|
708 | rather than the type object itself. | |
707 | """ |
|
709 | """ | |
708 | key = (type_module, type_name) |
|
710 | key = (type_module, type_name) | |
709 | oldfunc = _deferred_type_pprinters.get(key, None) |
|
711 | oldfunc = _deferred_type_pprinters.get(key, None) | |
710 | if func is not None: |
|
712 | if func is not None: | |
711 | # To support easy restoration of old pprinters, we need to ignore Nones. |
|
713 | # To support easy restoration of old pprinters, we need to ignore Nones. | |
712 | _deferred_type_pprinters[key] = func |
|
714 | _deferred_type_pprinters[key] = func | |
713 | return oldfunc |
|
715 | return oldfunc | |
714 |
|
716 | |||
715 |
|
717 | |||
716 | #: printers for the default singletons |
|
718 | #: printers for the default singletons | |
717 | _singleton_pprinters = dict.fromkeys(map(id, [None, True, False, Ellipsis, |
|
719 | _singleton_pprinters = dict.fromkeys(map(id, [None, True, False, Ellipsis, | |
718 | NotImplemented]), _repr_pprint) |
|
720 | NotImplemented]), _repr_pprint) | |
719 |
|
721 | |||
720 |
|
722 | |||
721 | if __name__ == '__main__': |
|
723 | if __name__ == '__main__': | |
722 | from random import randrange |
|
724 | from random import randrange | |
723 | class Foo(object): |
|
725 | class Foo(object): | |
724 | def __init__(self): |
|
726 | def __init__(self): | |
725 | self.foo = 1 |
|
727 | self.foo = 1 | |
726 | self.bar = re.compile(r'\s+') |
|
728 | self.bar = re.compile(r'\s+') | |
727 | self.blub = dict.fromkeys(range(30), randrange(1, 40)) |
|
729 | self.blub = dict.fromkeys(range(30), randrange(1, 40)) | |
728 | self.hehe = 23424.234234 |
|
730 | self.hehe = 23424.234234 | |
729 | self.list = ["blub", "blah", self] |
|
731 | self.list = ["blub", "blah", self] | |
730 |
|
732 | |||
731 | def get_foo(self): |
|
733 | def get_foo(self): | |
732 | print "foo" |
|
734 | print "foo" | |
733 |
|
735 | |||
734 | pprint(Foo(), verbose=True) |
|
736 | pprint(Foo(), verbose=True) |
@@ -1,341 +1,346 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 |
|
2 | |||
3 | """Classes and functions for kernel related errors and exceptions. |
|
3 | """Classes and functions for kernel related errors and exceptions. | |
4 |
|
4 | |||
|
5 | Inheritance diagram: | |||
|
6 | ||||
|
7 | .. inheritance-diagram:: IPython.parallel.error | |||
|
8 | :parts: 3 | |||
|
9 | ||||
5 | Authors: |
|
10 | Authors: | |
6 |
|
11 | |||
7 | * Brian Granger |
|
12 | * Brian Granger | |
8 | * Min RK |
|
13 | * Min RK | |
9 | """ |
|
14 | """ | |
10 | from __future__ import print_function |
|
15 | from __future__ import print_function | |
11 |
|
16 | |||
12 | import sys |
|
17 | import sys | |
13 | import traceback |
|
18 | import traceback | |
14 |
|
19 | |||
15 | __docformat__ = "restructuredtext en" |
|
20 | __docformat__ = "restructuredtext en" | |
16 |
|
21 | |||
17 | # Tell nose to skip this module |
|
22 | # Tell nose to skip this module | |
18 | __test__ = {} |
|
23 | __test__ = {} | |
19 |
|
24 | |||
20 | #------------------------------------------------------------------------------- |
|
25 | #------------------------------------------------------------------------------- | |
21 | # Copyright (C) 2008-2011 The IPython Development Team |
|
26 | # Copyright (C) 2008-2011 The IPython Development Team | |
22 | # |
|
27 | # | |
23 | # Distributed under the terms of the BSD License. The full license is in |
|
28 | # Distributed under the terms of the BSD License. The full license is in | |
24 | # the file COPYING, distributed as part of this software. |
|
29 | # the file COPYING, distributed as part of this software. | |
25 | #------------------------------------------------------------------------------- |
|
30 | #------------------------------------------------------------------------------- | |
26 |
|
31 | |||
27 | #------------------------------------------------------------------------------- |
|
32 | #------------------------------------------------------------------------------- | |
28 | # Error classes |
|
33 | # Error classes | |
29 | #------------------------------------------------------------------------------- |
|
34 | #------------------------------------------------------------------------------- | |
30 | class IPythonError(Exception): |
|
35 | class IPythonError(Exception): | |
31 | """Base exception that all of our exceptions inherit from. |
|
36 | """Base exception that all of our exceptions inherit from. | |
32 |
|
37 | |||
33 | This can be raised by code that doesn't have any more specific |
|
38 | This can be raised by code that doesn't have any more specific | |
34 | information.""" |
|
39 | information.""" | |
35 |
|
40 | |||
36 | pass |
|
41 | pass | |
37 |
|
42 | |||
38 | # Exceptions associated with the controller objects |
|
43 | # Exceptions associated with the controller objects | |
39 | class ControllerError(IPythonError): pass |
|
44 | class ControllerError(IPythonError): pass | |
40 |
|
45 | |||
41 | class ControllerCreationError(ControllerError): pass |
|
46 | class ControllerCreationError(ControllerError): pass | |
42 |
|
47 | |||
43 |
|
48 | |||
44 | # Exceptions associated with the Engines |
|
49 | # Exceptions associated with the Engines | |
45 | class EngineError(IPythonError): pass |
|
50 | class EngineError(IPythonError): pass | |
46 |
|
51 | |||
47 | class EngineCreationError(EngineError): pass |
|
52 | class EngineCreationError(EngineError): pass | |
48 |
|
53 | |||
49 | class KernelError(IPythonError): |
|
54 | class KernelError(IPythonError): | |
50 | pass |
|
55 | pass | |
51 |
|
56 | |||
52 | class NotDefined(KernelError): |
|
57 | class NotDefined(KernelError): | |
53 | def __init__(self, name): |
|
58 | def __init__(self, name): | |
54 | self.name = name |
|
59 | self.name = name | |
55 | self.args = (name,) |
|
60 | self.args = (name,) | |
56 |
|
61 | |||
57 | def __repr__(self): |
|
62 | def __repr__(self): | |
58 | return '<NotDefined: %s>' % self.name |
|
63 | return '<NotDefined: %s>' % self.name | |
59 |
|
64 | |||
60 | __str__ = __repr__ |
|
65 | __str__ = __repr__ | |
61 |
|
66 | |||
62 |
|
67 | |||
63 | class QueueCleared(KernelError): |
|
68 | class QueueCleared(KernelError): | |
64 | pass |
|
69 | pass | |
65 |
|
70 | |||
66 |
|
71 | |||
67 | class IdInUse(KernelError): |
|
72 | class IdInUse(KernelError): | |
68 | pass |
|
73 | pass | |
69 |
|
74 | |||
70 |
|
75 | |||
71 | class ProtocolError(KernelError): |
|
76 | class ProtocolError(KernelError): | |
72 | pass |
|
77 | pass | |
73 |
|
78 | |||
74 |
|
79 | |||
75 | class ConnectionError(KernelError): |
|
80 | class ConnectionError(KernelError): | |
76 | pass |
|
81 | pass | |
77 |
|
82 | |||
78 |
|
83 | |||
79 | class InvalidEngineID(KernelError): |
|
84 | class InvalidEngineID(KernelError): | |
80 | pass |
|
85 | pass | |
81 |
|
86 | |||
82 |
|
87 | |||
83 | class NoEnginesRegistered(KernelError): |
|
88 | class NoEnginesRegistered(KernelError): | |
84 | pass |
|
89 | pass | |
85 |
|
90 | |||
86 |
|
91 | |||
87 | class InvalidClientID(KernelError): |
|
92 | class InvalidClientID(KernelError): | |
88 | pass |
|
93 | pass | |
89 |
|
94 | |||
90 |
|
95 | |||
91 | class InvalidDeferredID(KernelError): |
|
96 | class InvalidDeferredID(KernelError): | |
92 | pass |
|
97 | pass | |
93 |
|
98 | |||
94 |
|
99 | |||
95 | class SerializationError(KernelError): |
|
100 | class SerializationError(KernelError): | |
96 | pass |
|
101 | pass | |
97 |
|
102 | |||
98 |
|
103 | |||
99 | class MessageSizeError(KernelError): |
|
104 | class MessageSizeError(KernelError): | |
100 | pass |
|
105 | pass | |
101 |
|
106 | |||
102 |
|
107 | |||
103 | class PBMessageSizeError(MessageSizeError): |
|
108 | class PBMessageSizeError(MessageSizeError): | |
104 | pass |
|
109 | pass | |
105 |
|
110 | |||
106 |
|
111 | |||
107 | class ResultNotCompleted(KernelError): |
|
112 | class ResultNotCompleted(KernelError): | |
108 | pass |
|
113 | pass | |
109 |
|
114 | |||
110 |
|
115 | |||
111 | class ResultAlreadyRetrieved(KernelError): |
|
116 | class ResultAlreadyRetrieved(KernelError): | |
112 | pass |
|
117 | pass | |
113 |
|
118 | |||
114 | class ClientError(KernelError): |
|
119 | class ClientError(KernelError): | |
115 | pass |
|
120 | pass | |
116 |
|
121 | |||
117 |
|
122 | |||
118 | class TaskAborted(KernelError): |
|
123 | class TaskAborted(KernelError): | |
119 | pass |
|
124 | pass | |
120 |
|
125 | |||
121 |
|
126 | |||
122 | class TaskTimeout(KernelError): |
|
127 | class TaskTimeout(KernelError): | |
123 | pass |
|
128 | pass | |
124 |
|
129 | |||
125 |
|
130 | |||
126 | class NotAPendingResult(KernelError): |
|
131 | class NotAPendingResult(KernelError): | |
127 | pass |
|
132 | pass | |
128 |
|
133 | |||
129 |
|
134 | |||
130 | class UnpickleableException(KernelError): |
|
135 | class UnpickleableException(KernelError): | |
131 | pass |
|
136 | pass | |
132 |
|
137 | |||
133 |
|
138 | |||
134 | class AbortedPendingDeferredError(KernelError): |
|
139 | class AbortedPendingDeferredError(KernelError): | |
135 | pass |
|
140 | pass | |
136 |
|
141 | |||
137 |
|
142 | |||
138 | class InvalidProperty(KernelError): |
|
143 | class InvalidProperty(KernelError): | |
139 | pass |
|
144 | pass | |
140 |
|
145 | |||
141 |
|
146 | |||
142 | class MissingBlockArgument(KernelError): |
|
147 | class MissingBlockArgument(KernelError): | |
143 | pass |
|
148 | pass | |
144 |
|
149 | |||
145 |
|
150 | |||
146 | class StopLocalExecution(KernelError): |
|
151 | class StopLocalExecution(KernelError): | |
147 | pass |
|
152 | pass | |
148 |
|
153 | |||
149 |
|
154 | |||
150 | class SecurityError(KernelError): |
|
155 | class SecurityError(KernelError): | |
151 | pass |
|
156 | pass | |
152 |
|
157 | |||
153 |
|
158 | |||
154 | class FileTimeoutError(KernelError): |
|
159 | class FileTimeoutError(KernelError): | |
155 | pass |
|
160 | pass | |
156 |
|
161 | |||
157 | class TimeoutError(KernelError): |
|
162 | class TimeoutError(KernelError): | |
158 | pass |
|
163 | pass | |
159 |
|
164 | |||
160 | class UnmetDependency(KernelError): |
|
165 | class UnmetDependency(KernelError): | |
161 | pass |
|
166 | pass | |
162 |
|
167 | |||
163 | class ImpossibleDependency(UnmetDependency): |
|
168 | class ImpossibleDependency(UnmetDependency): | |
164 | pass |
|
169 | pass | |
165 |
|
170 | |||
166 | class DependencyTimeout(ImpossibleDependency): |
|
171 | class DependencyTimeout(ImpossibleDependency): | |
167 | pass |
|
172 | pass | |
168 |
|
173 | |||
169 | class InvalidDependency(ImpossibleDependency): |
|
174 | class InvalidDependency(ImpossibleDependency): | |
170 | pass |
|
175 | pass | |
171 |
|
176 | |||
172 | class RemoteError(KernelError): |
|
177 | class RemoteError(KernelError): | |
173 | """Error raised elsewhere""" |
|
178 | """Error raised elsewhere""" | |
174 | ename=None |
|
179 | ename=None | |
175 | evalue=None |
|
180 | evalue=None | |
176 | traceback=None |
|
181 | traceback=None | |
177 | engine_info=None |
|
182 | engine_info=None | |
178 |
|
183 | |||
179 | def __init__(self, ename, evalue, traceback, engine_info=None): |
|
184 | def __init__(self, ename, evalue, traceback, engine_info=None): | |
180 | self.ename=ename |
|
185 | self.ename=ename | |
181 | self.evalue=evalue |
|
186 | self.evalue=evalue | |
182 | self.traceback=traceback |
|
187 | self.traceback=traceback | |
183 | self.engine_info=engine_info or {} |
|
188 | self.engine_info=engine_info or {} | |
184 | self.args=(ename, evalue) |
|
189 | self.args=(ename, evalue) | |
185 |
|
190 | |||
186 | def __repr__(self): |
|
191 | def __repr__(self): | |
187 | engineid = self.engine_info.get('engine_id', ' ') |
|
192 | engineid = self.engine_info.get('engine_id', ' ') | |
188 | return "<Remote[%s]:%s(%s)>"%(engineid, self.ename, self.evalue) |
|
193 | return "<Remote[%s]:%s(%s)>"%(engineid, self.ename, self.evalue) | |
189 |
|
194 | |||
190 | def __str__(self): |
|
195 | def __str__(self): | |
191 | return "%s(%s)" % (self.ename, self.evalue) |
|
196 | return "%s(%s)" % (self.ename, self.evalue) | |
192 |
|
197 | |||
193 | def render_traceback(self): |
|
198 | def render_traceback(self): | |
194 | """render traceback to a list of lines""" |
|
199 | """render traceback to a list of lines""" | |
195 | return (self.traceback or "No traceback available").splitlines() |
|
200 | return (self.traceback or "No traceback available").splitlines() | |
196 |
|
201 | |||
197 | def _render_traceback_(self): |
|
202 | def _render_traceback_(self): | |
198 | """Special method for custom tracebacks within IPython. |
|
203 | """Special method for custom tracebacks within IPython. | |
199 |
|
204 | |||
200 | This will be called by IPython instead of displaying the local traceback. |
|
205 | This will be called by IPython instead of displaying the local traceback. | |
201 |
|
206 | |||
202 | It should return a traceback rendered as a list of lines. |
|
207 | It should return a traceback rendered as a list of lines. | |
203 | """ |
|
208 | """ | |
204 | return self.render_traceback() |
|
209 | return self.render_traceback() | |
205 |
|
210 | |||
206 | def print_traceback(self, excid=None): |
|
211 | def print_traceback(self, excid=None): | |
207 | """print my traceback""" |
|
212 | """print my traceback""" | |
208 | print('\n'.join(self.render_traceback())) |
|
213 | print('\n'.join(self.render_traceback())) | |
209 |
|
214 | |||
210 |
|
215 | |||
211 |
|
216 | |||
212 |
|
217 | |||
213 | class TaskRejectError(KernelError): |
|
218 | class TaskRejectError(KernelError): | |
214 | """Exception to raise when a task should be rejected by an engine. |
|
219 | """Exception to raise when a task should be rejected by an engine. | |
215 |
|
220 | |||
216 | This exception can be used to allow a task running on an engine to test |
|
221 | This exception can be used to allow a task running on an engine to test | |
217 | if the engine (or the user's namespace on the engine) has the needed |
|
222 | if the engine (or the user's namespace on the engine) has the needed | |
218 | task dependencies. If not, the task should raise this exception. For |
|
223 | task dependencies. If not, the task should raise this exception. For | |
219 | the task to be retried on another engine, the task should be created |
|
224 | the task to be retried on another engine, the task should be created | |
220 | with the `retries` argument > 1. |
|
225 | with the `retries` argument > 1. | |
221 |
|
226 | |||
222 | The advantage of this approach over our older properties system is that |
|
227 | The advantage of this approach over our older properties system is that | |
223 | tasks have full access to the user's namespace on the engines and the |
|
228 | tasks have full access to the user's namespace on the engines and the | |
224 | properties don't have to be managed or tested by the controller. |
|
229 | properties don't have to be managed or tested by the controller. | |
225 | """ |
|
230 | """ | |
226 |
|
231 | |||
227 |
|
232 | |||
228 | class CompositeError(RemoteError): |
|
233 | class CompositeError(RemoteError): | |
229 | """Error for representing possibly multiple errors on engines""" |
|
234 | """Error for representing possibly multiple errors on engines""" | |
230 | def __init__(self, message, elist): |
|
235 | def __init__(self, message, elist): | |
231 | Exception.__init__(self, *(message, elist)) |
|
236 | Exception.__init__(self, *(message, elist)) | |
232 | # Don't use pack_exception because it will conflict with the .message |
|
237 | # Don't use pack_exception because it will conflict with the .message | |
233 | # attribute that is being deprecated in 2.6 and beyond. |
|
238 | # attribute that is being deprecated in 2.6 and beyond. | |
234 | self.msg = message |
|
239 | self.msg = message | |
235 | self.elist = elist |
|
240 | self.elist = elist | |
236 | self.args = [ e[0] for e in elist ] |
|
241 | self.args = [ e[0] for e in elist ] | |
237 |
|
242 | |||
238 | def _get_engine_str(self, ei): |
|
243 | def _get_engine_str(self, ei): | |
239 | if not ei: |
|
244 | if not ei: | |
240 | return '[Engine Exception]' |
|
245 | return '[Engine Exception]' | |
241 | else: |
|
246 | else: | |
242 | return '[%s:%s]: ' % (ei['engine_id'], ei['method']) |
|
247 | return '[%s:%s]: ' % (ei['engine_id'], ei['method']) | |
243 |
|
248 | |||
244 | def _get_traceback(self, ev): |
|
249 | def _get_traceback(self, ev): | |
245 | try: |
|
250 | try: | |
246 | tb = ev._ipython_traceback_text |
|
251 | tb = ev._ipython_traceback_text | |
247 | except AttributeError: |
|
252 | except AttributeError: | |
248 | return 'No traceback available' |
|
253 | return 'No traceback available' | |
249 | else: |
|
254 | else: | |
250 | return tb |
|
255 | return tb | |
251 |
|
256 | |||
252 | def __str__(self): |
|
257 | def __str__(self): | |
253 | s = str(self.msg) |
|
258 | s = str(self.msg) | |
254 | for en, ev, etb, ei in self.elist: |
|
259 | for en, ev, etb, ei in self.elist: | |
255 | engine_str = self._get_engine_str(ei) |
|
260 | engine_str = self._get_engine_str(ei) | |
256 | s = s + '\n' + engine_str + en + ': ' + str(ev) |
|
261 | s = s + '\n' + engine_str + en + ': ' + str(ev) | |
257 | return s |
|
262 | return s | |
258 |
|
263 | |||
259 | def __repr__(self): |
|
264 | def __repr__(self): | |
260 | return "CompositeError(%i)"%len(self.elist) |
|
265 | return "CompositeError(%i)"%len(self.elist) | |
261 |
|
266 | |||
262 | def render_traceback(self, excid=None): |
|
267 | def render_traceback(self, excid=None): | |
263 | """render one or all of my tracebacks to a list of lines""" |
|
268 | """render one or all of my tracebacks to a list of lines""" | |
264 | lines = [] |
|
269 | lines = [] | |
265 | if excid is None: |
|
270 | if excid is None: | |
266 | for (en,ev,etb,ei) in self.elist: |
|
271 | for (en,ev,etb,ei) in self.elist: | |
267 | lines.append(self._get_engine_str(ei)) |
|
272 | lines.append(self._get_engine_str(ei)) | |
268 | lines.extend((etb or 'No traceback available').splitlines()) |
|
273 | lines.extend((etb or 'No traceback available').splitlines()) | |
269 | lines.append('') |
|
274 | lines.append('') | |
270 | else: |
|
275 | else: | |
271 | try: |
|
276 | try: | |
272 | en,ev,etb,ei = self.elist[excid] |
|
277 | en,ev,etb,ei = self.elist[excid] | |
273 | except: |
|
278 | except: | |
274 | raise IndexError("an exception with index %i does not exist"%excid) |
|
279 | raise IndexError("an exception with index %i does not exist"%excid) | |
275 | else: |
|
280 | else: | |
276 | lines.append(self._get_engine_str(ei)) |
|
281 | lines.append(self._get_engine_str(ei)) | |
277 | lines.extend((etb or 'No traceback available').splitlines()) |
|
282 | lines.extend((etb or 'No traceback available').splitlines()) | |
278 |
|
283 | |||
279 | return lines |
|
284 | return lines | |
280 |
|
285 | |||
281 | def print_traceback(self, excid=None): |
|
286 | def print_traceback(self, excid=None): | |
282 | print('\n'.join(self.render_traceback(excid))) |
|
287 | print('\n'.join(self.render_traceback(excid))) | |
283 |
|
288 | |||
284 | def raise_exception(self, excid=0): |
|
289 | def raise_exception(self, excid=0): | |
285 | try: |
|
290 | try: | |
286 | en,ev,etb,ei = self.elist[excid] |
|
291 | en,ev,etb,ei = self.elist[excid] | |
287 | except: |
|
292 | except: | |
288 | raise IndexError("an exception with index %i does not exist"%excid) |
|
293 | raise IndexError("an exception with index %i does not exist"%excid) | |
289 | else: |
|
294 | else: | |
290 | raise RemoteError(en, ev, etb, ei) |
|
295 | raise RemoteError(en, ev, etb, ei) | |
291 |
|
296 | |||
292 |
|
297 | |||
293 | def collect_exceptions(rdict_or_list, method='unspecified'): |
|
298 | def collect_exceptions(rdict_or_list, method='unspecified'): | |
294 | """check a result dict for errors, and raise CompositeError if any exist. |
|
299 | """check a result dict for errors, and raise CompositeError if any exist. | |
295 | Passthrough otherwise.""" |
|
300 | Passthrough otherwise.""" | |
296 | elist = [] |
|
301 | elist = [] | |
297 | if isinstance(rdict_or_list, dict): |
|
302 | if isinstance(rdict_or_list, dict): | |
298 | rlist = rdict_or_list.values() |
|
303 | rlist = rdict_or_list.values() | |
299 | else: |
|
304 | else: | |
300 | rlist = rdict_or_list |
|
305 | rlist = rdict_or_list | |
301 | for r in rlist: |
|
306 | for r in rlist: | |
302 | if isinstance(r, RemoteError): |
|
307 | if isinstance(r, RemoteError): | |
303 | en, ev, etb, ei = r.ename, r.evalue, r.traceback, r.engine_info |
|
308 | en, ev, etb, ei = r.ename, r.evalue, r.traceback, r.engine_info | |
304 | # Sometimes we could have CompositeError in our list. Just take |
|
309 | # Sometimes we could have CompositeError in our list. Just take | |
305 | # the errors out of them and put them in our new list. This |
|
310 | # the errors out of them and put them in our new list. This | |
306 | # has the effect of flattening lists of CompositeErrors into one |
|
311 | # has the effect of flattening lists of CompositeErrors into one | |
307 | # CompositeError |
|
312 | # CompositeError | |
308 | if en=='CompositeError': |
|
313 | if en=='CompositeError': | |
309 | for e in ev.elist: |
|
314 | for e in ev.elist: | |
310 | elist.append(e) |
|
315 | elist.append(e) | |
311 | else: |
|
316 | else: | |
312 | elist.append((en, ev, etb, ei)) |
|
317 | elist.append((en, ev, etb, ei)) | |
313 | if len(elist)==0: |
|
318 | if len(elist)==0: | |
314 | return rdict_or_list |
|
319 | return rdict_or_list | |
315 | else: |
|
320 | else: | |
316 | msg = "one or more exceptions from call to method: %s" % (method) |
|
321 | msg = "one or more exceptions from call to method: %s" % (method) | |
317 | # This silliness is needed so the debugger has access to the exception |
|
322 | # This silliness is needed so the debugger has access to the exception | |
318 | # instance (e in this case) |
|
323 | # instance (e in this case) | |
319 | try: |
|
324 | try: | |
320 | raise CompositeError(msg, elist) |
|
325 | raise CompositeError(msg, elist) | |
321 | except CompositeError as e: |
|
326 | except CompositeError as e: | |
322 | raise e |
|
327 | raise e | |
323 |
|
328 | |||
324 | def wrap_exception(engine_info={}): |
|
329 | def wrap_exception(engine_info={}): | |
325 | etype, evalue, tb = sys.exc_info() |
|
330 | etype, evalue, tb = sys.exc_info() | |
326 | stb = traceback.format_exception(etype, evalue, tb) |
|
331 | stb = traceback.format_exception(etype, evalue, tb) | |
327 | exc_content = { |
|
332 | exc_content = { | |
328 | 'status' : 'error', |
|
333 | 'status' : 'error', | |
329 | 'traceback' : stb, |
|
334 | 'traceback' : stb, | |
330 | 'ename' : unicode(etype.__name__), |
|
335 | 'ename' : unicode(etype.__name__), | |
331 | 'evalue' : unicode(evalue), |
|
336 | 'evalue' : unicode(evalue), | |
332 | 'engine_info' : engine_info |
|
337 | 'engine_info' : engine_info | |
333 | } |
|
338 | } | |
334 | return exc_content |
|
339 | return exc_content | |
335 |
|
340 | |||
336 | def unwrap_exception(content): |
|
341 | def unwrap_exception(content): | |
337 | err = RemoteError(content['ename'], content['evalue'], |
|
342 | err = RemoteError(content['ename'], content['evalue'], | |
338 | ''.join(content['traceback']), |
|
343 | ''.join(content['traceback']), | |
339 | content.get('engine_info', {})) |
|
344 | content.get('engine_info', {})) | |
340 | return err |
|
345 | return err | |
341 |
|
346 |
@@ -1,845 +1,850 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """ |
|
2 | """ | |
3 | Utilities for working with strings and text. |
|
3 | Utilities for working with strings and text. | |
|
4 | ||||
|
5 | Inheritance diagram: | |||
|
6 | ||||
|
7 | .. inheritance-diagram:: IPython.utils.text | |||
|
8 | :parts: 3 | |||
4 | """ |
|
9 | """ | |
5 |
|
10 | |||
6 | #----------------------------------------------------------------------------- |
|
11 | #----------------------------------------------------------------------------- | |
7 | # Copyright (C) 2008-2011 The IPython Development Team |
|
12 | # Copyright (C) 2008-2011 The IPython Development Team | |
8 | # |
|
13 | # | |
9 | # Distributed under the terms of the BSD License. The full license is in |
|
14 | # Distributed under the terms of the BSD License. The full license is in | |
10 | # the file COPYING, distributed as part of this software. |
|
15 | # the file COPYING, distributed as part of this software. | |
11 | #----------------------------------------------------------------------------- |
|
16 | #----------------------------------------------------------------------------- | |
12 |
|
17 | |||
13 | #----------------------------------------------------------------------------- |
|
18 | #----------------------------------------------------------------------------- | |
14 | # Imports |
|
19 | # Imports | |
15 | #----------------------------------------------------------------------------- |
|
20 | #----------------------------------------------------------------------------- | |
16 |
|
21 | |||
17 | import __main__ |
|
22 | import __main__ | |
18 |
|
23 | |||
19 | import os |
|
24 | import os | |
20 | import re |
|
25 | import re | |
21 | import shutil |
|
26 | import shutil | |
22 | import sys |
|
27 | import sys | |
23 | import textwrap |
|
28 | import textwrap | |
24 | from string import Formatter |
|
29 | from string import Formatter | |
25 |
|
30 | |||
26 | from IPython.external.path import path |
|
31 | from IPython.external.path import path | |
27 | from IPython.testing.skipdoctest import skip_doctest_py3, skip_doctest |
|
32 | from IPython.testing.skipdoctest import skip_doctest_py3, skip_doctest | |
28 | from IPython.utils import py3compat |
|
33 | from IPython.utils import py3compat | |
29 | from IPython.utils.io import nlprint |
|
34 | from IPython.utils.io import nlprint | |
30 | from IPython.utils.data import flatten |
|
35 | from IPython.utils.data import flatten | |
31 |
|
36 | |||
32 | #----------------------------------------------------------------------------- |
|
37 | #----------------------------------------------------------------------------- | |
33 | # Code |
|
38 | # Code | |
34 | #----------------------------------------------------------------------------- |
|
39 | #----------------------------------------------------------------------------- | |
35 |
|
40 | |||
36 | def unquote_ends(istr): |
|
41 | def unquote_ends(istr): | |
37 | """Remove a single pair of quotes from the endpoints of a string.""" |
|
42 | """Remove a single pair of quotes from the endpoints of a string.""" | |
38 |
|
43 | |||
39 | if not istr: |
|
44 | if not istr: | |
40 | return istr |
|
45 | return istr | |
41 | if (istr[0]=="'" and istr[-1]=="'") or \ |
|
46 | if (istr[0]=="'" and istr[-1]=="'") or \ | |
42 | (istr[0]=='"' and istr[-1]=='"'): |
|
47 | (istr[0]=='"' and istr[-1]=='"'): | |
43 | return istr[1:-1] |
|
48 | return istr[1:-1] | |
44 | else: |
|
49 | else: | |
45 | return istr |
|
50 | return istr | |
46 |
|
51 | |||
47 |
|
52 | |||
48 | class LSString(str): |
|
53 | class LSString(str): | |
49 | """String derivative with a special access attributes. |
|
54 | """String derivative with a special access attributes. | |
50 |
|
55 | |||
51 | These are normal strings, but with the special attributes: |
|
56 | These are normal strings, but with the special attributes: | |
52 |
|
57 | |||
53 | .l (or .list) : value as list (split on newlines). |
|
58 | .l (or .list) : value as list (split on newlines). | |
54 | .n (or .nlstr): original value (the string itself). |
|
59 | .n (or .nlstr): original value (the string itself). | |
55 | .s (or .spstr): value as whitespace-separated string. |
|
60 | .s (or .spstr): value as whitespace-separated string. | |
56 | .p (or .paths): list of path objects |
|
61 | .p (or .paths): list of path objects | |
57 |
|
62 | |||
58 | Any values which require transformations are computed only once and |
|
63 | Any values which require transformations are computed only once and | |
59 | cached. |
|
64 | cached. | |
60 |
|
65 | |||
61 | Such strings are very useful to efficiently interact with the shell, which |
|
66 | Such strings are very useful to efficiently interact with the shell, which | |
62 | typically only understands whitespace-separated options for commands.""" |
|
67 | typically only understands whitespace-separated options for commands.""" | |
63 |
|
68 | |||
64 | def get_list(self): |
|
69 | def get_list(self): | |
65 | try: |
|
70 | try: | |
66 | return self.__list |
|
71 | return self.__list | |
67 | except AttributeError: |
|
72 | except AttributeError: | |
68 | self.__list = self.split('\n') |
|
73 | self.__list = self.split('\n') | |
69 | return self.__list |
|
74 | return self.__list | |
70 |
|
75 | |||
71 | l = list = property(get_list) |
|
76 | l = list = property(get_list) | |
72 |
|
77 | |||
73 | def get_spstr(self): |
|
78 | def get_spstr(self): | |
74 | try: |
|
79 | try: | |
75 | return self.__spstr |
|
80 | return self.__spstr | |
76 | except AttributeError: |
|
81 | except AttributeError: | |
77 | self.__spstr = self.replace('\n',' ') |
|
82 | self.__spstr = self.replace('\n',' ') | |
78 | return self.__spstr |
|
83 | return self.__spstr | |
79 |
|
84 | |||
80 | s = spstr = property(get_spstr) |
|
85 | s = spstr = property(get_spstr) | |
81 |
|
86 | |||
82 | def get_nlstr(self): |
|
87 | def get_nlstr(self): | |
83 | return self |
|
88 | return self | |
84 |
|
89 | |||
85 | n = nlstr = property(get_nlstr) |
|
90 | n = nlstr = property(get_nlstr) | |
86 |
|
91 | |||
87 | def get_paths(self): |
|
92 | def get_paths(self): | |
88 | try: |
|
93 | try: | |
89 | return self.__paths |
|
94 | return self.__paths | |
90 | except AttributeError: |
|
95 | except AttributeError: | |
91 | self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)] |
|
96 | self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)] | |
92 | return self.__paths |
|
97 | return self.__paths | |
93 |
|
98 | |||
94 | p = paths = property(get_paths) |
|
99 | p = paths = property(get_paths) | |
95 |
|
100 | |||
96 | # FIXME: We need to reimplement type specific displayhook and then add this |
|
101 | # FIXME: We need to reimplement type specific displayhook and then add this | |
97 | # back as a custom printer. This should also be moved outside utils into the |
|
102 | # back as a custom printer. This should also be moved outside utils into the | |
98 | # core. |
|
103 | # core. | |
99 |
|
104 | |||
100 | # def print_lsstring(arg): |
|
105 | # def print_lsstring(arg): | |
101 | # """ Prettier (non-repr-like) and more informative printer for LSString """ |
|
106 | # """ Prettier (non-repr-like) and more informative printer for LSString """ | |
102 | # print "LSString (.p, .n, .l, .s available). Value:" |
|
107 | # print "LSString (.p, .n, .l, .s available). Value:" | |
103 | # print arg |
|
108 | # print arg | |
104 | # |
|
109 | # | |
105 | # |
|
110 | # | |
106 | # print_lsstring = result_display.when_type(LSString)(print_lsstring) |
|
111 | # print_lsstring = result_display.when_type(LSString)(print_lsstring) | |
107 |
|
112 | |||
108 |
|
113 | |||
109 | class SList(list): |
|
114 | class SList(list): | |
110 | """List derivative with a special access attributes. |
|
115 | """List derivative with a special access attributes. | |
111 |
|
116 | |||
112 | These are normal lists, but with the special attributes: |
|
117 | These are normal lists, but with the special attributes: | |
113 |
|
118 | |||
114 | .l (or .list) : value as list (the list itself). |
|
119 | .l (or .list) : value as list (the list itself). | |
115 | .n (or .nlstr): value as a string, joined on newlines. |
|
120 | .n (or .nlstr): value as a string, joined on newlines. | |
116 | .s (or .spstr): value as a string, joined on spaces. |
|
121 | .s (or .spstr): value as a string, joined on spaces. | |
117 | .p (or .paths): list of path objects |
|
122 | .p (or .paths): list of path objects | |
118 |
|
123 | |||
119 | Any values which require transformations are computed only once and |
|
124 | Any values which require transformations are computed only once and | |
120 | cached.""" |
|
125 | cached.""" | |
121 |
|
126 | |||
122 | def get_list(self): |
|
127 | def get_list(self): | |
123 | return self |
|
128 | return self | |
124 |
|
129 | |||
125 | l = list = property(get_list) |
|
130 | l = list = property(get_list) | |
126 |
|
131 | |||
127 | def get_spstr(self): |
|
132 | def get_spstr(self): | |
128 | try: |
|
133 | try: | |
129 | return self.__spstr |
|
134 | return self.__spstr | |
130 | except AttributeError: |
|
135 | except AttributeError: | |
131 | self.__spstr = ' '.join(self) |
|
136 | self.__spstr = ' '.join(self) | |
132 | return self.__spstr |
|
137 | return self.__spstr | |
133 |
|
138 | |||
134 | s = spstr = property(get_spstr) |
|
139 | s = spstr = property(get_spstr) | |
135 |
|
140 | |||
136 | def get_nlstr(self): |
|
141 | def get_nlstr(self): | |
137 | try: |
|
142 | try: | |
138 | return self.__nlstr |
|
143 | return self.__nlstr | |
139 | except AttributeError: |
|
144 | except AttributeError: | |
140 | self.__nlstr = '\n'.join(self) |
|
145 | self.__nlstr = '\n'.join(self) | |
141 | return self.__nlstr |
|
146 | return self.__nlstr | |
142 |
|
147 | |||
143 | n = nlstr = property(get_nlstr) |
|
148 | n = nlstr = property(get_nlstr) | |
144 |
|
149 | |||
145 | def get_paths(self): |
|
150 | def get_paths(self): | |
146 | try: |
|
151 | try: | |
147 | return self.__paths |
|
152 | return self.__paths | |
148 | except AttributeError: |
|
153 | except AttributeError: | |
149 | self.__paths = [path(p) for p in self if os.path.exists(p)] |
|
154 | self.__paths = [path(p) for p in self if os.path.exists(p)] | |
150 | return self.__paths |
|
155 | return self.__paths | |
151 |
|
156 | |||
152 | p = paths = property(get_paths) |
|
157 | p = paths = property(get_paths) | |
153 |
|
158 | |||
154 | def grep(self, pattern, prune = False, field = None): |
|
159 | def grep(self, pattern, prune = False, field = None): | |
155 | """ Return all strings matching 'pattern' (a regex or callable) |
|
160 | """ Return all strings matching 'pattern' (a regex or callable) | |
156 |
|
161 | |||
157 | This is case-insensitive. If prune is true, return all items |
|
162 | This is case-insensitive. If prune is true, return all items | |
158 | NOT matching the pattern. |
|
163 | NOT matching the pattern. | |
159 |
|
164 | |||
160 | If field is specified, the match must occur in the specified |
|
165 | If field is specified, the match must occur in the specified | |
161 | whitespace-separated field. |
|
166 | whitespace-separated field. | |
162 |
|
167 | |||
163 | Examples:: |
|
168 | Examples:: | |
164 |
|
169 | |||
165 | a.grep( lambda x: x.startswith('C') ) |
|
170 | a.grep( lambda x: x.startswith('C') ) | |
166 | a.grep('Cha.*log', prune=1) |
|
171 | a.grep('Cha.*log', prune=1) | |
167 | a.grep('chm', field=-1) |
|
172 | a.grep('chm', field=-1) | |
168 | """ |
|
173 | """ | |
169 |
|
174 | |||
170 | def match_target(s): |
|
175 | def match_target(s): | |
171 | if field is None: |
|
176 | if field is None: | |
172 | return s |
|
177 | return s | |
173 | parts = s.split() |
|
178 | parts = s.split() | |
174 | try: |
|
179 | try: | |
175 | tgt = parts[field] |
|
180 | tgt = parts[field] | |
176 | return tgt |
|
181 | return tgt | |
177 | except IndexError: |
|
182 | except IndexError: | |
178 | return "" |
|
183 | return "" | |
179 |
|
184 | |||
180 | if isinstance(pattern, basestring): |
|
185 | if isinstance(pattern, basestring): | |
181 | pred = lambda x : re.search(pattern, x, re.IGNORECASE) |
|
186 | pred = lambda x : re.search(pattern, x, re.IGNORECASE) | |
182 | else: |
|
187 | else: | |
183 | pred = pattern |
|
188 | pred = pattern | |
184 | if not prune: |
|
189 | if not prune: | |
185 | return SList([el for el in self if pred(match_target(el))]) |
|
190 | return SList([el for el in self if pred(match_target(el))]) | |
186 | else: |
|
191 | else: | |
187 | return SList([el for el in self if not pred(match_target(el))]) |
|
192 | return SList([el for el in self if not pred(match_target(el))]) | |
188 |
|
193 | |||
189 | def fields(self, *fields): |
|
194 | def fields(self, *fields): | |
190 | """ Collect whitespace-separated fields from string list |
|
195 | """ Collect whitespace-separated fields from string list | |
191 |
|
196 | |||
192 | Allows quick awk-like usage of string lists. |
|
197 | Allows quick awk-like usage of string lists. | |
193 |
|
198 | |||
194 | Example data (in var a, created by 'a = !ls -l'):: |
|
199 | Example data (in var a, created by 'a = !ls -l'):: | |
195 | -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog |
|
200 | -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog | |
196 | drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython |
|
201 | drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython | |
197 |
|
202 | |||
198 | a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+'] |
|
203 | a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+'] | |
199 | a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+'] |
|
204 | a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+'] | |
200 | (note the joining by space). |
|
205 | (note the joining by space). | |
201 | a.fields(-1) is ['ChangeLog', 'IPython'] |
|
206 | a.fields(-1) is ['ChangeLog', 'IPython'] | |
202 |
|
207 | |||
203 | IndexErrors are ignored. |
|
208 | IndexErrors are ignored. | |
204 |
|
209 | |||
205 | Without args, fields() just split()'s the strings. |
|
210 | Without args, fields() just split()'s the strings. | |
206 | """ |
|
211 | """ | |
207 | if len(fields) == 0: |
|
212 | if len(fields) == 0: | |
208 | return [el.split() for el in self] |
|
213 | return [el.split() for el in self] | |
209 |
|
214 | |||
210 | res = SList() |
|
215 | res = SList() | |
211 | for el in [f.split() for f in self]: |
|
216 | for el in [f.split() for f in self]: | |
212 | lineparts = [] |
|
217 | lineparts = [] | |
213 |
|
218 | |||
214 | for fd in fields: |
|
219 | for fd in fields: | |
215 | try: |
|
220 | try: | |
216 | lineparts.append(el[fd]) |
|
221 | lineparts.append(el[fd]) | |
217 | except IndexError: |
|
222 | except IndexError: | |
218 | pass |
|
223 | pass | |
219 | if lineparts: |
|
224 | if lineparts: | |
220 | res.append(" ".join(lineparts)) |
|
225 | res.append(" ".join(lineparts)) | |
221 |
|
226 | |||
222 | return res |
|
227 | return res | |
223 |
|
228 | |||
224 | def sort(self,field= None, nums = False): |
|
229 | def sort(self,field= None, nums = False): | |
225 | """ sort by specified fields (see fields()) |
|
230 | """ sort by specified fields (see fields()) | |
226 |
|
231 | |||
227 | Example:: |
|
232 | Example:: | |
228 | a.sort(1, nums = True) |
|
233 | a.sort(1, nums = True) | |
229 |
|
234 | |||
230 | Sorts a by second field, in numerical order (so that 21 > 3) |
|
235 | Sorts a by second field, in numerical order (so that 21 > 3) | |
231 |
|
236 | |||
232 | """ |
|
237 | """ | |
233 |
|
238 | |||
234 | #decorate, sort, undecorate |
|
239 | #decorate, sort, undecorate | |
235 | if field is not None: |
|
240 | if field is not None: | |
236 | dsu = [[SList([line]).fields(field), line] for line in self] |
|
241 | dsu = [[SList([line]).fields(field), line] for line in self] | |
237 | else: |
|
242 | else: | |
238 | dsu = [[line, line] for line in self] |
|
243 | dsu = [[line, line] for line in self] | |
239 | if nums: |
|
244 | if nums: | |
240 | for i in range(len(dsu)): |
|
245 | for i in range(len(dsu)): | |
241 | numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()]) |
|
246 | numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()]) | |
242 | try: |
|
247 | try: | |
243 | n = int(numstr) |
|
248 | n = int(numstr) | |
244 | except ValueError: |
|
249 | except ValueError: | |
245 | n = 0; |
|
250 | n = 0; | |
246 | dsu[i][0] = n |
|
251 | dsu[i][0] = n | |
247 |
|
252 | |||
248 |
|
253 | |||
249 | dsu.sort() |
|
254 | dsu.sort() | |
250 | return SList([t[1] for t in dsu]) |
|
255 | return SList([t[1] for t in dsu]) | |
251 |
|
256 | |||
252 |
|
257 | |||
253 | # FIXME: We need to reimplement type specific displayhook and then add this |
|
258 | # FIXME: We need to reimplement type specific displayhook and then add this | |
254 | # back as a custom printer. This should also be moved outside utils into the |
|
259 | # back as a custom printer. This should also be moved outside utils into the | |
255 | # core. |
|
260 | # core. | |
256 |
|
261 | |||
257 | # def print_slist(arg): |
|
262 | # def print_slist(arg): | |
258 | # """ Prettier (non-repr-like) and more informative printer for SList """ |
|
263 | # """ Prettier (non-repr-like) and more informative printer for SList """ | |
259 | # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):" |
|
264 | # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):" | |
260 | # if hasattr(arg, 'hideonce') and arg.hideonce: |
|
265 | # if hasattr(arg, 'hideonce') and arg.hideonce: | |
261 | # arg.hideonce = False |
|
266 | # arg.hideonce = False | |
262 | # return |
|
267 | # return | |
263 | # |
|
268 | # | |
264 | # nlprint(arg) |
|
269 | # nlprint(arg) | |
265 | # |
|
270 | # | |
266 | # print_slist = result_display.when_type(SList)(print_slist) |
|
271 | # print_slist = result_display.when_type(SList)(print_slist) | |
267 |
|
272 | |||
268 |
|
273 | |||
269 | def esc_quotes(strng): |
|
274 | def esc_quotes(strng): | |
270 | """Return the input string with single and double quotes escaped out""" |
|
275 | """Return the input string with single and double quotes escaped out""" | |
271 |
|
276 | |||
272 | return strng.replace('"','\\"').replace("'","\\'") |
|
277 | return strng.replace('"','\\"').replace("'","\\'") | |
273 |
|
278 | |||
274 |
|
279 | |||
275 | def qw(words,flat=0,sep=None,maxsplit=-1): |
|
280 | def qw(words,flat=0,sep=None,maxsplit=-1): | |
276 | """Similar to Perl's qw() operator, but with some more options. |
|
281 | """Similar to Perl's qw() operator, but with some more options. | |
277 |
|
282 | |||
278 | qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit) |
|
283 | qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit) | |
279 |
|
284 | |||
280 | words can also be a list itself, and with flat=1, the output will be |
|
285 | words can also be a list itself, and with flat=1, the output will be | |
281 | recursively flattened. |
|
286 | recursively flattened. | |
282 |
|
287 | |||
283 | Examples: |
|
288 | Examples: | |
284 |
|
289 | |||
285 | >>> qw('1 2') |
|
290 | >>> qw('1 2') | |
286 | ['1', '2'] |
|
291 | ['1', '2'] | |
287 |
|
292 | |||
288 | >>> qw(['a b','1 2',['m n','p q']]) |
|
293 | >>> qw(['a b','1 2',['m n','p q']]) | |
289 | [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]] |
|
294 | [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]] | |
290 |
|
295 | |||
291 | >>> qw(['a b','1 2',['m n','p q']],flat=1) |
|
296 | >>> qw(['a b','1 2',['m n','p q']],flat=1) | |
292 | ['a', 'b', '1', '2', 'm', 'n', 'p', 'q'] |
|
297 | ['a', 'b', '1', '2', 'm', 'n', 'p', 'q'] | |
293 | """ |
|
298 | """ | |
294 |
|
299 | |||
295 | if isinstance(words, basestring): |
|
300 | if isinstance(words, basestring): | |
296 | return [word.strip() for word in words.split(sep,maxsplit) |
|
301 | return [word.strip() for word in words.split(sep,maxsplit) | |
297 | if word and not word.isspace() ] |
|
302 | if word and not word.isspace() ] | |
298 | if flat: |
|
303 | if flat: | |
299 | return flatten(map(qw,words,[1]*len(words))) |
|
304 | return flatten(map(qw,words,[1]*len(words))) | |
300 | return map(qw,words) |
|
305 | return map(qw,words) | |
301 |
|
306 | |||
302 |
|
307 | |||
303 | def qwflat(words,sep=None,maxsplit=-1): |
|
308 | def qwflat(words,sep=None,maxsplit=-1): | |
304 | """Calls qw(words) in flat mode. It's just a convenient shorthand.""" |
|
309 | """Calls qw(words) in flat mode. It's just a convenient shorthand.""" | |
305 | return qw(words,1,sep,maxsplit) |
|
310 | return qw(words,1,sep,maxsplit) | |
306 |
|
311 | |||
307 |
|
312 | |||
308 | def qw_lol(indata): |
|
313 | def qw_lol(indata): | |
309 | """qw_lol('a b') -> [['a','b']], |
|
314 | """qw_lol('a b') -> [['a','b']], | |
310 | otherwise it's just a call to qw(). |
|
315 | otherwise it's just a call to qw(). | |
311 |
|
316 | |||
312 | We need this to make sure the modules_some keys *always* end up as a |
|
317 | We need this to make sure the modules_some keys *always* end up as a | |
313 | list of lists.""" |
|
318 | list of lists.""" | |
314 |
|
319 | |||
315 | if isinstance(indata, basestring): |
|
320 | if isinstance(indata, basestring): | |
316 | return [qw(indata)] |
|
321 | return [qw(indata)] | |
317 | else: |
|
322 | else: | |
318 | return qw(indata) |
|
323 | return qw(indata) | |
319 |
|
324 | |||
320 |
|
325 | |||
321 | def grep(pat,list,case=1): |
|
326 | def grep(pat,list,case=1): | |
322 | """Simple minded grep-like function. |
|
327 | """Simple minded grep-like function. | |
323 | grep(pat,list) returns occurrences of pat in list, None on failure. |
|
328 | grep(pat,list) returns occurrences of pat in list, None on failure. | |
324 |
|
329 | |||
325 | It only does simple string matching, with no support for regexps. Use the |
|
330 | It only does simple string matching, with no support for regexps. Use the | |
326 | option case=0 for case-insensitive matching.""" |
|
331 | option case=0 for case-insensitive matching.""" | |
327 |
|
332 | |||
328 | # This is pretty crude. At least it should implement copying only references |
|
333 | # This is pretty crude. At least it should implement copying only references | |
329 | # to the original data in case it's big. Now it copies the data for output. |
|
334 | # to the original data in case it's big. Now it copies the data for output. | |
330 | out=[] |
|
335 | out=[] | |
331 | if case: |
|
336 | if case: | |
332 | for term in list: |
|
337 | for term in list: | |
333 | if term.find(pat)>-1: out.append(term) |
|
338 | if term.find(pat)>-1: out.append(term) | |
334 | else: |
|
339 | else: | |
335 | lpat=pat.lower() |
|
340 | lpat=pat.lower() | |
336 | for term in list: |
|
341 | for term in list: | |
337 | if term.lower().find(lpat)>-1: out.append(term) |
|
342 | if term.lower().find(lpat)>-1: out.append(term) | |
338 |
|
343 | |||
339 | if len(out): return out |
|
344 | if len(out): return out | |
340 | else: return None |
|
345 | else: return None | |
341 |
|
346 | |||
342 |
|
347 | |||
343 | def dgrep(pat,*opts): |
|
348 | def dgrep(pat,*opts): | |
344 | """Return grep() on dir()+dir(__builtins__). |
|
349 | """Return grep() on dir()+dir(__builtins__). | |
345 |
|
350 | |||
346 | A very common use of grep() when working interactively.""" |
|
351 | A very common use of grep() when working interactively.""" | |
347 |
|
352 | |||
348 | return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts) |
|
353 | return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts) | |
349 |
|
354 | |||
350 |
|
355 | |||
351 | def idgrep(pat): |
|
356 | def idgrep(pat): | |
352 | """Case-insensitive dgrep()""" |
|
357 | """Case-insensitive dgrep()""" | |
353 |
|
358 | |||
354 | return dgrep(pat,0) |
|
359 | return dgrep(pat,0) | |
355 |
|
360 | |||
356 |
|
361 | |||
357 | def igrep(pat,list): |
|
362 | def igrep(pat,list): | |
358 | """Synonym for case-insensitive grep.""" |
|
363 | """Synonym for case-insensitive grep.""" | |
359 |
|
364 | |||
360 | return grep(pat,list,case=0) |
|
365 | return grep(pat,list,case=0) | |
361 |
|
366 | |||
362 |
|
367 | |||
363 | def indent(instr,nspaces=4, ntabs=0, flatten=False): |
|
368 | def indent(instr,nspaces=4, ntabs=0, flatten=False): | |
364 | """Indent a string a given number of spaces or tabstops. |
|
369 | """Indent a string a given number of spaces or tabstops. | |
365 |
|
370 | |||
366 | indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces. |
|
371 | indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces. | |
367 |
|
372 | |||
368 | Parameters |
|
373 | Parameters | |
369 | ---------- |
|
374 | ---------- | |
370 |
|
375 | |||
371 | instr : basestring |
|
376 | instr : basestring | |
372 | The string to be indented. |
|
377 | The string to be indented. | |
373 | nspaces : int (default: 4) |
|
378 | nspaces : int (default: 4) | |
374 | The number of spaces to be indented. |
|
379 | The number of spaces to be indented. | |
375 | ntabs : int (default: 0) |
|
380 | ntabs : int (default: 0) | |
376 | The number of tabs to be indented. |
|
381 | The number of tabs to be indented. | |
377 | flatten : bool (default: False) |
|
382 | flatten : bool (default: False) | |
378 | Whether to scrub existing indentation. If True, all lines will be |
|
383 | Whether to scrub existing indentation. If True, all lines will be | |
379 | aligned to the same indentation. If False, existing indentation will |
|
384 | aligned to the same indentation. If False, existing indentation will | |
380 | be strictly increased. |
|
385 | be strictly increased. | |
381 |
|
386 | |||
382 | Returns |
|
387 | Returns | |
383 | ------- |
|
388 | ------- | |
384 |
|
389 | |||
385 | str|unicode : string indented by ntabs and nspaces. |
|
390 | str|unicode : string indented by ntabs and nspaces. | |
386 |
|
391 | |||
387 | """ |
|
392 | """ | |
388 | if instr is None: |
|
393 | if instr is None: | |
389 | return |
|
394 | return | |
390 | ind = '\t'*ntabs+' '*nspaces |
|
395 | ind = '\t'*ntabs+' '*nspaces | |
391 | if flatten: |
|
396 | if flatten: | |
392 | pat = re.compile(r'^\s*', re.MULTILINE) |
|
397 | pat = re.compile(r'^\s*', re.MULTILINE) | |
393 | else: |
|
398 | else: | |
394 | pat = re.compile(r'^', re.MULTILINE) |
|
399 | pat = re.compile(r'^', re.MULTILINE) | |
395 | outstr = re.sub(pat, ind, instr) |
|
400 | outstr = re.sub(pat, ind, instr) | |
396 | if outstr.endswith(os.linesep+ind): |
|
401 | if outstr.endswith(os.linesep+ind): | |
397 | return outstr[:-len(ind)] |
|
402 | return outstr[:-len(ind)] | |
398 | else: |
|
403 | else: | |
399 | return outstr |
|
404 | return outstr | |
400 |
|
405 | |||
401 | def native_line_ends(filename,backup=1): |
|
406 | def native_line_ends(filename,backup=1): | |
402 | """Convert (in-place) a file to line-ends native to the current OS. |
|
407 | """Convert (in-place) a file to line-ends native to the current OS. | |
403 |
|
408 | |||
404 | If the optional backup argument is given as false, no backup of the |
|
409 | If the optional backup argument is given as false, no backup of the | |
405 | original file is left. """ |
|
410 | original file is left. """ | |
406 |
|
411 | |||
407 | backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'} |
|
412 | backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'} | |
408 |
|
413 | |||
409 | bak_filename = filename + backup_suffixes[os.name] |
|
414 | bak_filename = filename + backup_suffixes[os.name] | |
410 |
|
415 | |||
411 | original = open(filename).read() |
|
416 | original = open(filename).read() | |
412 | shutil.copy2(filename,bak_filename) |
|
417 | shutil.copy2(filename,bak_filename) | |
413 | try: |
|
418 | try: | |
414 | new = open(filename,'wb') |
|
419 | new = open(filename,'wb') | |
415 | new.write(os.linesep.join(original.splitlines())) |
|
420 | new.write(os.linesep.join(original.splitlines())) | |
416 | new.write(os.linesep) # ALWAYS put an eol at the end of the file |
|
421 | new.write(os.linesep) # ALWAYS put an eol at the end of the file | |
417 | new.close() |
|
422 | new.close() | |
418 | except: |
|
423 | except: | |
419 | os.rename(bak_filename,filename) |
|
424 | os.rename(bak_filename,filename) | |
420 | if not backup: |
|
425 | if not backup: | |
421 | try: |
|
426 | try: | |
422 | os.remove(bak_filename) |
|
427 | os.remove(bak_filename) | |
423 | except: |
|
428 | except: | |
424 | pass |
|
429 | pass | |
425 |
|
430 | |||
426 |
|
431 | |||
427 | def list_strings(arg): |
|
432 | def list_strings(arg): | |
428 | """Always return a list of strings, given a string or list of strings |
|
433 | """Always return a list of strings, given a string or list of strings | |
429 | as input. |
|
434 | as input. | |
430 |
|
435 | |||
431 | :Examples: |
|
436 | :Examples: | |
432 |
|
437 | |||
433 | In [7]: list_strings('A single string') |
|
438 | In [7]: list_strings('A single string') | |
434 | Out[7]: ['A single string'] |
|
439 | Out[7]: ['A single string'] | |
435 |
|
440 | |||
436 | In [8]: list_strings(['A single string in a list']) |
|
441 | In [8]: list_strings(['A single string in a list']) | |
437 | Out[8]: ['A single string in a list'] |
|
442 | Out[8]: ['A single string in a list'] | |
438 |
|
443 | |||
439 | In [9]: list_strings(['A','list','of','strings']) |
|
444 | In [9]: list_strings(['A','list','of','strings']) | |
440 | Out[9]: ['A', 'list', 'of', 'strings'] |
|
445 | Out[9]: ['A', 'list', 'of', 'strings'] | |
441 | """ |
|
446 | """ | |
442 |
|
447 | |||
443 | if isinstance(arg,basestring): return [arg] |
|
448 | if isinstance(arg,basestring): return [arg] | |
444 | else: return arg |
|
449 | else: return arg | |
445 |
|
450 | |||
446 |
|
451 | |||
447 | def marquee(txt='',width=78,mark='*'): |
|
452 | def marquee(txt='',width=78,mark='*'): | |
448 | """Return the input string centered in a 'marquee'. |
|
453 | """Return the input string centered in a 'marquee'. | |
449 |
|
454 | |||
450 | :Examples: |
|
455 | :Examples: | |
451 |
|
456 | |||
452 | In [16]: marquee('A test',40) |
|
457 | In [16]: marquee('A test',40) | |
453 | Out[16]: '**************** A test ****************' |
|
458 | Out[16]: '**************** A test ****************' | |
454 |
|
459 | |||
455 | In [17]: marquee('A test',40,'-') |
|
460 | In [17]: marquee('A test',40,'-') | |
456 | Out[17]: '---------------- A test ----------------' |
|
461 | Out[17]: '---------------- A test ----------------' | |
457 |
|
462 | |||
458 | In [18]: marquee('A test',40,' ') |
|
463 | In [18]: marquee('A test',40,' ') | |
459 | Out[18]: ' A test ' |
|
464 | Out[18]: ' A test ' | |
460 |
|
465 | |||
461 | """ |
|
466 | """ | |
462 | if not txt: |
|
467 | if not txt: | |
463 | return (mark*width)[:width] |
|
468 | return (mark*width)[:width] | |
464 | nmark = (width-len(txt)-2)//len(mark)//2 |
|
469 | nmark = (width-len(txt)-2)//len(mark)//2 | |
465 | if nmark < 0: nmark =0 |
|
470 | if nmark < 0: nmark =0 | |
466 | marks = mark*nmark |
|
471 | marks = mark*nmark | |
467 | return '%s %s %s' % (marks,txt,marks) |
|
472 | return '%s %s %s' % (marks,txt,marks) | |
468 |
|
473 | |||
469 |
|
474 | |||
470 | ini_spaces_re = re.compile(r'^(\s+)') |
|
475 | ini_spaces_re = re.compile(r'^(\s+)') | |
471 |
|
476 | |||
472 | def num_ini_spaces(strng): |
|
477 | def num_ini_spaces(strng): | |
473 | """Return the number of initial spaces in a string""" |
|
478 | """Return the number of initial spaces in a string""" | |
474 |
|
479 | |||
475 | ini_spaces = ini_spaces_re.match(strng) |
|
480 | ini_spaces = ini_spaces_re.match(strng) | |
476 | if ini_spaces: |
|
481 | if ini_spaces: | |
477 | return ini_spaces.end() |
|
482 | return ini_spaces.end() | |
478 | else: |
|
483 | else: | |
479 | return 0 |
|
484 | return 0 | |
480 |
|
485 | |||
481 |
|
486 | |||
482 | def format_screen(strng): |
|
487 | def format_screen(strng): | |
483 | """Format a string for screen printing. |
|
488 | """Format a string for screen printing. | |
484 |
|
489 | |||
485 | This removes some latex-type format codes.""" |
|
490 | This removes some latex-type format codes.""" | |
486 | # Paragraph continue |
|
491 | # Paragraph continue | |
487 | par_re = re.compile(r'\\$',re.MULTILINE) |
|
492 | par_re = re.compile(r'\\$',re.MULTILINE) | |
488 | strng = par_re.sub('',strng) |
|
493 | strng = par_re.sub('',strng) | |
489 | return strng |
|
494 | return strng | |
490 |
|
495 | |||
491 |
|
496 | |||
492 | def dedent(text): |
|
497 | def dedent(text): | |
493 | """Equivalent of textwrap.dedent that ignores unindented first line. |
|
498 | """Equivalent of textwrap.dedent that ignores unindented first line. | |
494 |
|
499 | |||
495 | This means it will still dedent strings like: |
|
500 | This means it will still dedent strings like: | |
496 | '''foo |
|
501 | '''foo | |
497 | is a bar |
|
502 | is a bar | |
498 | ''' |
|
503 | ''' | |
499 |
|
504 | |||
500 | For use in wrap_paragraphs. |
|
505 | For use in wrap_paragraphs. | |
501 | """ |
|
506 | """ | |
502 |
|
507 | |||
503 | if text.startswith('\n'): |
|
508 | if text.startswith('\n'): | |
504 | # text starts with blank line, don't ignore the first line |
|
509 | # text starts with blank line, don't ignore the first line | |
505 | return textwrap.dedent(text) |
|
510 | return textwrap.dedent(text) | |
506 |
|
511 | |||
507 | # split first line |
|
512 | # split first line | |
508 | splits = text.split('\n',1) |
|
513 | splits = text.split('\n',1) | |
509 | if len(splits) == 1: |
|
514 | if len(splits) == 1: | |
510 | # only one line |
|
515 | # only one line | |
511 | return textwrap.dedent(text) |
|
516 | return textwrap.dedent(text) | |
512 |
|
517 | |||
513 | first, rest = splits |
|
518 | first, rest = splits | |
514 | # dedent everything but the first line |
|
519 | # dedent everything but the first line | |
515 | rest = textwrap.dedent(rest) |
|
520 | rest = textwrap.dedent(rest) | |
516 | return '\n'.join([first, rest]) |
|
521 | return '\n'.join([first, rest]) | |
517 |
|
522 | |||
518 |
|
523 | |||
519 | def wrap_paragraphs(text, ncols=80): |
|
524 | def wrap_paragraphs(text, ncols=80): | |
520 | """Wrap multiple paragraphs to fit a specified width. |
|
525 | """Wrap multiple paragraphs to fit a specified width. | |
521 |
|
526 | |||
522 | This is equivalent to textwrap.wrap, but with support for multiple |
|
527 | This is equivalent to textwrap.wrap, but with support for multiple | |
523 | paragraphs, as separated by empty lines. |
|
528 | paragraphs, as separated by empty lines. | |
524 |
|
529 | |||
525 | Returns |
|
530 | Returns | |
526 | ------- |
|
531 | ------- | |
527 |
|
532 | |||
528 | list of complete paragraphs, wrapped to fill `ncols` columns. |
|
533 | list of complete paragraphs, wrapped to fill `ncols` columns. | |
529 | """ |
|
534 | """ | |
530 | paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE) |
|
535 | paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE) | |
531 | text = dedent(text).strip() |
|
536 | text = dedent(text).strip() | |
532 | paragraphs = paragraph_re.split(text)[::2] # every other entry is space |
|
537 | paragraphs = paragraph_re.split(text)[::2] # every other entry is space | |
533 | out_ps = [] |
|
538 | out_ps = [] | |
534 | indent_re = re.compile(r'\n\s+', re.MULTILINE) |
|
539 | indent_re = re.compile(r'\n\s+', re.MULTILINE) | |
535 | for p in paragraphs: |
|
540 | for p in paragraphs: | |
536 | # presume indentation that survives dedent is meaningful formatting, |
|
541 | # presume indentation that survives dedent is meaningful formatting, | |
537 | # so don't fill unless text is flush. |
|
542 | # so don't fill unless text is flush. | |
538 | if indent_re.search(p) is None: |
|
543 | if indent_re.search(p) is None: | |
539 | # wrap paragraph |
|
544 | # wrap paragraph | |
540 | p = textwrap.fill(p, ncols) |
|
545 | p = textwrap.fill(p, ncols) | |
541 | out_ps.append(p) |
|
546 | out_ps.append(p) | |
542 | return out_ps |
|
547 | return out_ps | |
543 |
|
548 | |||
544 |
|
549 | |||
545 | def long_substr(data): |
|
550 | def long_substr(data): | |
546 | """Return the longest common substring in a list of strings. |
|
551 | """Return the longest common substring in a list of strings. | |
547 |
|
552 | |||
548 | Credit: http://stackoverflow.com/questions/2892931/longest-common-substring-from-more-than-two-strings-python |
|
553 | Credit: http://stackoverflow.com/questions/2892931/longest-common-substring-from-more-than-two-strings-python | |
549 | """ |
|
554 | """ | |
550 | substr = '' |
|
555 | substr = '' | |
551 | if len(data) > 1 and len(data[0]) > 0: |
|
556 | if len(data) > 1 and len(data[0]) > 0: | |
552 | for i in range(len(data[0])): |
|
557 | for i in range(len(data[0])): | |
553 | for j in range(len(data[0])-i+1): |
|
558 | for j in range(len(data[0])-i+1): | |
554 | if j > len(substr) and all(data[0][i:i+j] in x for x in data): |
|
559 | if j > len(substr) and all(data[0][i:i+j] in x for x in data): | |
555 | substr = data[0][i:i+j] |
|
560 | substr = data[0][i:i+j] | |
556 | elif len(data) == 1: |
|
561 | elif len(data) == 1: | |
557 | substr = data[0] |
|
562 | substr = data[0] | |
558 | return substr |
|
563 | return substr | |
559 |
|
564 | |||
560 |
|
565 | |||
561 | def strip_email_quotes(text): |
|
566 | def strip_email_quotes(text): | |
562 | """Strip leading email quotation characters ('>'). |
|
567 | """Strip leading email quotation characters ('>'). | |
563 |
|
568 | |||
564 | Removes any combination of leading '>' interspersed with whitespace that |
|
569 | Removes any combination of leading '>' interspersed with whitespace that | |
565 | appears *identically* in all lines of the input text. |
|
570 | appears *identically* in all lines of the input text. | |
566 |
|
571 | |||
567 | Parameters |
|
572 | Parameters | |
568 | ---------- |
|
573 | ---------- | |
569 | text : str |
|
574 | text : str | |
570 |
|
575 | |||
571 | Examples |
|
576 | Examples | |
572 | -------- |
|
577 | -------- | |
573 |
|
578 | |||
574 | Simple uses:: |
|
579 | Simple uses:: | |
575 |
|
580 | |||
576 | In [2]: strip_email_quotes('> > text') |
|
581 | In [2]: strip_email_quotes('> > text') | |
577 | Out[2]: 'text' |
|
582 | Out[2]: 'text' | |
578 |
|
583 | |||
579 | In [3]: strip_email_quotes('> > text\\n> > more') |
|
584 | In [3]: strip_email_quotes('> > text\\n> > more') | |
580 | Out[3]: 'text\\nmore' |
|
585 | Out[3]: 'text\\nmore' | |
581 |
|
586 | |||
582 | Note how only the common prefix that appears in all lines is stripped:: |
|
587 | Note how only the common prefix that appears in all lines is stripped:: | |
583 |
|
588 | |||
584 | In [4]: strip_email_quotes('> > text\\n> > more\\n> more...') |
|
589 | In [4]: strip_email_quotes('> > text\\n> > more\\n> more...') | |
585 | Out[4]: '> text\\n> more\\nmore...' |
|
590 | Out[4]: '> text\\n> more\\nmore...' | |
586 |
|
591 | |||
587 | So if any line has no quote marks ('>') , then none are stripped from any |
|
592 | So if any line has no quote marks ('>') , then none are stripped from any | |
588 | of them :: |
|
593 | of them :: | |
589 |
|
594 | |||
590 | In [5]: strip_email_quotes('> > text\\n> > more\\nlast different') |
|
595 | In [5]: strip_email_quotes('> > text\\n> > more\\nlast different') | |
591 | Out[5]: '> > text\\n> > more\\nlast different' |
|
596 | Out[5]: '> > text\\n> > more\\nlast different' | |
592 | """ |
|
597 | """ | |
593 | lines = text.splitlines() |
|
598 | lines = text.splitlines() | |
594 | matches = set() |
|
599 | matches = set() | |
595 | for line in lines: |
|
600 | for line in lines: | |
596 | prefix = re.match(r'^(\s*>[ >]*)', line) |
|
601 | prefix = re.match(r'^(\s*>[ >]*)', line) | |
597 | if prefix: |
|
602 | if prefix: | |
598 | matches.add(prefix.group(1)) |
|
603 | matches.add(prefix.group(1)) | |
599 | else: |
|
604 | else: | |
600 | break |
|
605 | break | |
601 | else: |
|
606 | else: | |
602 | prefix = long_substr(list(matches)) |
|
607 | prefix = long_substr(list(matches)) | |
603 | if prefix: |
|
608 | if prefix: | |
604 | strip = len(prefix) |
|
609 | strip = len(prefix) | |
605 | text = '\n'.join([ ln[strip:] for ln in lines]) |
|
610 | text = '\n'.join([ ln[strip:] for ln in lines]) | |
606 | return text |
|
611 | return text | |
607 |
|
612 | |||
608 |
|
613 | |||
609 | class EvalFormatter(Formatter): |
|
614 | class EvalFormatter(Formatter): | |
610 | """A String Formatter that allows evaluation of simple expressions. |
|
615 | """A String Formatter that allows evaluation of simple expressions. | |
611 |
|
616 | |||
612 | Note that this version interprets a : as specifying a format string (as per |
|
617 | Note that this version interprets a : as specifying a format string (as per | |
613 | standard string formatting), so if slicing is required, you must explicitly |
|
618 | standard string formatting), so if slicing is required, you must explicitly | |
614 | create a slice. |
|
619 | create a slice. | |
615 |
|
620 | |||
616 | This is to be used in templating cases, such as the parallel batch |
|
621 | This is to be used in templating cases, such as the parallel batch | |
617 | script templates, where simple arithmetic on arguments is useful. |
|
622 | script templates, where simple arithmetic on arguments is useful. | |
618 |
|
623 | |||
619 | Examples |
|
624 | Examples | |
620 | -------- |
|
625 | -------- | |
621 |
|
626 | |||
622 | In [1]: f = EvalFormatter() |
|
627 | In [1]: f = EvalFormatter() | |
623 | In [2]: f.format('{n//4}', n=8) |
|
628 | In [2]: f.format('{n//4}', n=8) | |
624 | Out [2]: '2' |
|
629 | Out [2]: '2' | |
625 |
|
630 | |||
626 | In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello") |
|
631 | In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello") | |
627 | Out [3]: 'll' |
|
632 | Out [3]: 'll' | |
628 | """ |
|
633 | """ | |
629 | def get_field(self, name, args, kwargs): |
|
634 | def get_field(self, name, args, kwargs): | |
630 | v = eval(name, kwargs) |
|
635 | v = eval(name, kwargs) | |
631 | return v, name |
|
636 | return v, name | |
632 |
|
637 | |||
633 |
|
638 | |||
634 | @skip_doctest_py3 |
|
639 | @skip_doctest_py3 | |
635 | class FullEvalFormatter(Formatter): |
|
640 | class FullEvalFormatter(Formatter): | |
636 | """A String Formatter that allows evaluation of simple expressions. |
|
641 | """A String Formatter that allows evaluation of simple expressions. | |
637 |
|
642 | |||
638 | Any time a format key is not found in the kwargs, |
|
643 | Any time a format key is not found in the kwargs, | |
639 | it will be tried as an expression in the kwargs namespace. |
|
644 | it will be tried as an expression in the kwargs namespace. | |
640 |
|
645 | |||
641 | Note that this version allows slicing using [1:2], so you cannot specify |
|
646 | Note that this version allows slicing using [1:2], so you cannot specify | |
642 | a format string. Use :class:`EvalFormatter` to permit format strings. |
|
647 | a format string. Use :class:`EvalFormatter` to permit format strings. | |
643 |
|
648 | |||
644 | Examples |
|
649 | Examples | |
645 | -------- |
|
650 | -------- | |
646 |
|
651 | |||
647 | In [1]: f = FullEvalFormatter() |
|
652 | In [1]: f = FullEvalFormatter() | |
648 | In [2]: f.format('{n//4}', n=8) |
|
653 | In [2]: f.format('{n//4}', n=8) | |
649 | Out[2]: u'2' |
|
654 | Out[2]: u'2' | |
650 |
|
655 | |||
651 | In [3]: f.format('{list(range(5))[2:4]}') |
|
656 | In [3]: f.format('{list(range(5))[2:4]}') | |
652 | Out[3]: u'[2, 3]' |
|
657 | Out[3]: u'[2, 3]' | |
653 |
|
658 | |||
654 | In [4]: f.format('{3*2}') |
|
659 | In [4]: f.format('{3*2}') | |
655 | Out[4]: u'6' |
|
660 | Out[4]: u'6' | |
656 | """ |
|
661 | """ | |
657 | # copied from Formatter._vformat with minor changes to allow eval |
|
662 | # copied from Formatter._vformat with minor changes to allow eval | |
658 | # and replace the format_spec code with slicing |
|
663 | # and replace the format_spec code with slicing | |
659 | def _vformat(self, format_string, args, kwargs, used_args, recursion_depth): |
|
664 | def _vformat(self, format_string, args, kwargs, used_args, recursion_depth): | |
660 | if recursion_depth < 0: |
|
665 | if recursion_depth < 0: | |
661 | raise ValueError('Max string recursion exceeded') |
|
666 | raise ValueError('Max string recursion exceeded') | |
662 | result = [] |
|
667 | result = [] | |
663 | for literal_text, field_name, format_spec, conversion in \ |
|
668 | for literal_text, field_name, format_spec, conversion in \ | |
664 | self.parse(format_string): |
|
669 | self.parse(format_string): | |
665 |
|
670 | |||
666 | # output the literal text |
|
671 | # output the literal text | |
667 | if literal_text: |
|
672 | if literal_text: | |
668 | result.append(literal_text) |
|
673 | result.append(literal_text) | |
669 |
|
674 | |||
670 | # if there's a field, output it |
|
675 | # if there's a field, output it | |
671 | if field_name is not None: |
|
676 | if field_name is not None: | |
672 | # this is some markup, find the object and do |
|
677 | # this is some markup, find the object and do | |
673 | # the formatting |
|
678 | # the formatting | |
674 |
|
679 | |||
675 | if format_spec: |
|
680 | if format_spec: | |
676 | # override format spec, to allow slicing: |
|
681 | # override format spec, to allow slicing: | |
677 | field_name = ':'.join([field_name, format_spec]) |
|
682 | field_name = ':'.join([field_name, format_spec]) | |
678 |
|
683 | |||
679 | # eval the contents of the field for the object |
|
684 | # eval the contents of the field for the object | |
680 | # to be formatted |
|
685 | # to be formatted | |
681 | obj = eval(field_name, kwargs) |
|
686 | obj = eval(field_name, kwargs) | |
682 |
|
687 | |||
683 | # do any conversion on the resulting object |
|
688 | # do any conversion on the resulting object | |
684 | obj = self.convert_field(obj, conversion) |
|
689 | obj = self.convert_field(obj, conversion) | |
685 |
|
690 | |||
686 | # format the object and append to the result |
|
691 | # format the object and append to the result | |
687 | result.append(self.format_field(obj, '')) |
|
692 | result.append(self.format_field(obj, '')) | |
688 |
|
693 | |||
689 | return u''.join(py3compat.cast_unicode(s) for s in result) |
|
694 | return u''.join(py3compat.cast_unicode(s) for s in result) | |
690 |
|
695 | |||
691 |
|
696 | |||
692 | @skip_doctest_py3 |
|
697 | @skip_doctest_py3 | |
693 | class DollarFormatter(FullEvalFormatter): |
|
698 | class DollarFormatter(FullEvalFormatter): | |
694 | """Formatter allowing Itpl style $foo replacement, for names and attribute |
|
699 | """Formatter allowing Itpl style $foo replacement, for names and attribute | |
695 | access only. Standard {foo} replacement also works, and allows full |
|
700 | access only. Standard {foo} replacement also works, and allows full | |
696 | evaluation of its arguments. |
|
701 | evaluation of its arguments. | |
697 |
|
702 | |||
698 | Examples |
|
703 | Examples | |
699 | -------- |
|
704 | -------- | |
700 | In [1]: f = DollarFormatter() |
|
705 | In [1]: f = DollarFormatter() | |
701 | In [2]: f.format('{n//4}', n=8) |
|
706 | In [2]: f.format('{n//4}', n=8) | |
702 | Out[2]: u'2' |
|
707 | Out[2]: u'2' | |
703 |
|
708 | |||
704 | In [3]: f.format('23 * 76 is $result', result=23*76) |
|
709 | In [3]: f.format('23 * 76 is $result', result=23*76) | |
705 | Out[3]: u'23 * 76 is 1748' |
|
710 | Out[3]: u'23 * 76 is 1748' | |
706 |
|
711 | |||
707 | In [4]: f.format('$a or {b}', a=1, b=2) |
|
712 | In [4]: f.format('$a or {b}', a=1, b=2) | |
708 | Out[4]: u'1 or 2' |
|
713 | Out[4]: u'1 or 2' | |
709 | """ |
|
714 | """ | |
710 | _dollar_pattern = re.compile("(.*?)\$(\$?[\w\.]+)") |
|
715 | _dollar_pattern = re.compile("(.*?)\$(\$?[\w\.]+)") | |
711 | def parse(self, fmt_string): |
|
716 | def parse(self, fmt_string): | |
712 | for literal_txt, field_name, format_spec, conversion \ |
|
717 | for literal_txt, field_name, format_spec, conversion \ | |
713 | in Formatter.parse(self, fmt_string): |
|
718 | in Formatter.parse(self, fmt_string): | |
714 |
|
719 | |||
715 | # Find $foo patterns in the literal text. |
|
720 | # Find $foo patterns in the literal text. | |
716 | continue_from = 0 |
|
721 | continue_from = 0 | |
717 | txt = "" |
|
722 | txt = "" | |
718 | for m in self._dollar_pattern.finditer(literal_txt): |
|
723 | for m in self._dollar_pattern.finditer(literal_txt): | |
719 | new_txt, new_field = m.group(1,2) |
|
724 | new_txt, new_field = m.group(1,2) | |
720 | # $$foo --> $foo |
|
725 | # $$foo --> $foo | |
721 | if new_field.startswith("$"): |
|
726 | if new_field.startswith("$"): | |
722 | txt += new_txt + new_field |
|
727 | txt += new_txt + new_field | |
723 | else: |
|
728 | else: | |
724 | yield (txt + new_txt, new_field, "", None) |
|
729 | yield (txt + new_txt, new_field, "", None) | |
725 | txt = "" |
|
730 | txt = "" | |
726 | continue_from = m.end() |
|
731 | continue_from = m.end() | |
727 |
|
732 | |||
728 | # Re-yield the {foo} style pattern |
|
733 | # Re-yield the {foo} style pattern | |
729 | yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion) |
|
734 | yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion) | |
730 |
|
735 | |||
731 | #----------------------------------------------------------------------------- |
|
736 | #----------------------------------------------------------------------------- | |
732 | # Utils to columnize a list of string |
|
737 | # Utils to columnize a list of string | |
733 | #----------------------------------------------------------------------------- |
|
738 | #----------------------------------------------------------------------------- | |
734 |
|
739 | |||
735 | def _chunks(l, n): |
|
740 | def _chunks(l, n): | |
736 | """Yield successive n-sized chunks from l.""" |
|
741 | """Yield successive n-sized chunks from l.""" | |
737 | for i in xrange(0, len(l), n): |
|
742 | for i in xrange(0, len(l), n): | |
738 | yield l[i:i+n] |
|
743 | yield l[i:i+n] | |
739 |
|
744 | |||
740 |
|
745 | |||
741 | def _find_optimal(rlist , separator_size=2 , displaywidth=80): |
|
746 | def _find_optimal(rlist , separator_size=2 , displaywidth=80): | |
742 | """Calculate optimal info to columnize a list of string""" |
|
747 | """Calculate optimal info to columnize a list of string""" | |
743 | for nrow in range(1, len(rlist)+1) : |
|
748 | for nrow in range(1, len(rlist)+1) : | |
744 | chk = map(max,_chunks(rlist, nrow)) |
|
749 | chk = map(max,_chunks(rlist, nrow)) | |
745 | sumlength = sum(chk) |
|
750 | sumlength = sum(chk) | |
746 | ncols = len(chk) |
|
751 | ncols = len(chk) | |
747 | if sumlength+separator_size*(ncols-1) <= displaywidth : |
|
752 | if sumlength+separator_size*(ncols-1) <= displaywidth : | |
748 | break; |
|
753 | break; | |
749 | return {'columns_numbers' : ncols, |
|
754 | return {'columns_numbers' : ncols, | |
750 | 'optimal_separator_width':(displaywidth - sumlength)/(ncols-1) if (ncols -1) else 0, |
|
755 | 'optimal_separator_width':(displaywidth - sumlength)/(ncols-1) if (ncols -1) else 0, | |
751 | 'rows_numbers' : nrow, |
|
756 | 'rows_numbers' : nrow, | |
752 | 'columns_width' : chk |
|
757 | 'columns_width' : chk | |
753 | } |
|
758 | } | |
754 |
|
759 | |||
755 |
|
760 | |||
756 | def _get_or_default(mylist, i, default=None): |
|
761 | def _get_or_default(mylist, i, default=None): | |
757 | """return list item number, or default if don't exist""" |
|
762 | """return list item number, or default if don't exist""" | |
758 | if i >= len(mylist): |
|
763 | if i >= len(mylist): | |
759 | return default |
|
764 | return default | |
760 | else : |
|
765 | else : | |
761 | return mylist[i] |
|
766 | return mylist[i] | |
762 |
|
767 | |||
763 |
|
768 | |||
764 | @skip_doctest |
|
769 | @skip_doctest | |
765 | def compute_item_matrix(items, empty=None, *args, **kwargs) : |
|
770 | def compute_item_matrix(items, empty=None, *args, **kwargs) : | |
766 | """Returns a nested list, and info to columnize items |
|
771 | """Returns a nested list, and info to columnize items | |
767 |
|
772 | |||
768 | Parameters : |
|
773 | Parameters : | |
769 | ------------ |
|
774 | ------------ | |
770 |
|
775 | |||
771 | items : |
|
776 | items : | |
772 | list of strings to columize |
|
777 | list of strings to columize | |
773 | empty : (default None) |
|
778 | empty : (default None) | |
774 | default value to fill list if needed |
|
779 | default value to fill list if needed | |
775 | separator_size : int (default=2) |
|
780 | separator_size : int (default=2) | |
776 | How much caracters will be used as a separation between each columns. |
|
781 | How much caracters will be used as a separation between each columns. | |
777 | displaywidth : int (default=80) |
|
782 | displaywidth : int (default=80) | |
778 | The width of the area onto wich the columns should enter |
|
783 | The width of the area onto wich the columns should enter | |
779 |
|
784 | |||
780 | Returns : |
|
785 | Returns : | |
781 | --------- |
|
786 | --------- | |
782 |
|
787 | |||
783 | Returns a tuple of (strings_matrix, dict_info) |
|
788 | Returns a tuple of (strings_matrix, dict_info) | |
784 |
|
789 | |||
785 | strings_matrix : |
|
790 | strings_matrix : | |
786 |
|
791 | |||
787 | nested list of string, the outer most list contains as many list as |
|
792 | nested list of string, the outer most list contains as many list as | |
788 | rows, the innermost lists have each as many element as colums. If the |
|
793 | rows, the innermost lists have each as many element as colums. If the | |
789 | total number of elements in `items` does not equal the product of |
|
794 | total number of elements in `items` does not equal the product of | |
790 | rows*columns, the last element of some lists are filled with `None`. |
|
795 | rows*columns, the last element of some lists are filled with `None`. | |
791 |
|
796 | |||
792 | dict_info : |
|
797 | dict_info : | |
793 | some info to make columnize easier: |
|
798 | some info to make columnize easier: | |
794 |
|
799 | |||
795 | columns_numbers : number of columns |
|
800 | columns_numbers : number of columns | |
796 | rows_numbers : number of rows |
|
801 | rows_numbers : number of rows | |
797 | columns_width : list of with of each columns |
|
802 | columns_width : list of with of each columns | |
798 | optimal_separator_width : best separator width between columns |
|
803 | optimal_separator_width : best separator width between columns | |
799 |
|
804 | |||
800 | Exemple : |
|
805 | Exemple : | |
801 | --------- |
|
806 | --------- | |
802 |
|
807 | |||
803 | In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l'] |
|
808 | In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l'] | |
804 | ...: compute_item_matrix(l,displaywidth=12) |
|
809 | ...: compute_item_matrix(l,displaywidth=12) | |
805 | Out[1]: |
|
810 | Out[1]: | |
806 | ([['aaa', 'f', 'k'], |
|
811 | ([['aaa', 'f', 'k'], | |
807 | ['b', 'g', 'l'], |
|
812 | ['b', 'g', 'l'], | |
808 | ['cc', 'h', None], |
|
813 | ['cc', 'h', None], | |
809 | ['d', 'i', None], |
|
814 | ['d', 'i', None], | |
810 | ['eeeee', 'j', None]], |
|
815 | ['eeeee', 'j', None]], | |
811 | {'columns_numbers': 3, |
|
816 | {'columns_numbers': 3, | |
812 | 'columns_width': [5, 1, 1], |
|
817 | 'columns_width': [5, 1, 1], | |
813 | 'optimal_separator_width': 2, |
|
818 | 'optimal_separator_width': 2, | |
814 | 'rows_numbers': 5}) |
|
819 | 'rows_numbers': 5}) | |
815 |
|
820 | |||
816 | """ |
|
821 | """ | |
817 | info = _find_optimal(map(len, items), *args, **kwargs) |
|
822 | info = _find_optimal(map(len, items), *args, **kwargs) | |
818 | nrow, ncol = info['rows_numbers'], info['columns_numbers'] |
|
823 | nrow, ncol = info['rows_numbers'], info['columns_numbers'] | |
819 | return ([[ _get_or_default(items, c*nrow+i, default=empty) for c in range(ncol) ] for i in range(nrow) ], info) |
|
824 | return ([[ _get_or_default(items, c*nrow+i, default=empty) for c in range(ncol) ] for i in range(nrow) ], info) | |
820 |
|
825 | |||
821 |
|
826 | |||
822 | def columnize(items, separator=' ', displaywidth=80): |
|
827 | def columnize(items, separator=' ', displaywidth=80): | |
823 | """ Transform a list of strings into a single string with columns. |
|
828 | """ Transform a list of strings into a single string with columns. | |
824 |
|
829 | |||
825 | Parameters |
|
830 | Parameters | |
826 | ---------- |
|
831 | ---------- | |
827 | items : sequence of strings |
|
832 | items : sequence of strings | |
828 | The strings to process. |
|
833 | The strings to process. | |
829 |
|
834 | |||
830 | separator : str, optional [default is two spaces] |
|
835 | separator : str, optional [default is two spaces] | |
831 | The string that separates columns. |
|
836 | The string that separates columns. | |
832 |
|
837 | |||
833 | displaywidth : int, optional [default is 80] |
|
838 | displaywidth : int, optional [default is 80] | |
834 | Width of the display in number of characters. |
|
839 | Width of the display in number of characters. | |
835 |
|
840 | |||
836 | Returns |
|
841 | Returns | |
837 | ------- |
|
842 | ------- | |
838 | The formatted string. |
|
843 | The formatted string. | |
839 | """ |
|
844 | """ | |
840 | if not items : |
|
845 | if not items : | |
841 | return '\n' |
|
846 | return '\n' | |
842 | matrix, info = compute_item_matrix(items, separator_size=len(separator), displaywidth=displaywidth) |
|
847 | matrix, info = compute_item_matrix(items, separator_size=len(separator), displaywidth=displaywidth) | |
843 | fmatrix = [filter(None, x) for x in matrix] |
|
848 | fmatrix = [filter(None, x) for x in matrix] | |
844 | sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['columns_width'])]) |
|
849 | sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['columns_width'])]) | |
845 | return '\n'.join(map(sjoin, fmatrix))+'\n' |
|
850 | return '\n'.join(map(sjoin, fmatrix))+'\n' |
@@ -1,1434 +1,1439 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """ |
|
2 | """ | |
3 | A lightweight Traits like module. |
|
3 | A lightweight Traits like module. | |
4 |
|
4 | |||
5 | This is designed to provide a lightweight, simple, pure Python version of |
|
5 | This is designed to provide a lightweight, simple, pure Python version of | |
6 | many of the capabilities of enthought.traits. This includes: |
|
6 | many of the capabilities of enthought.traits. This includes: | |
7 |
|
7 | |||
8 | * Validation |
|
8 | * Validation | |
9 | * Type specification with defaults |
|
9 | * Type specification with defaults | |
10 | * Static and dynamic notification |
|
10 | * Static and dynamic notification | |
11 | * Basic predefined types |
|
11 | * Basic predefined types | |
12 | * An API that is similar to enthought.traits |
|
12 | * An API that is similar to enthought.traits | |
13 |
|
13 | |||
14 | We don't support: |
|
14 | We don't support: | |
15 |
|
15 | |||
16 | * Delegation |
|
16 | * Delegation | |
17 | * Automatic GUI generation |
|
17 | * Automatic GUI generation | |
18 | * A full set of trait types. Most importantly, we don't provide container |
|
18 | * A full set of trait types. Most importantly, we don't provide container | |
19 | traits (list, dict, tuple) that can trigger notifications if their |
|
19 | traits (list, dict, tuple) that can trigger notifications if their | |
20 | contents change. |
|
20 | contents change. | |
21 | * API compatibility with enthought.traits |
|
21 | * API compatibility with enthought.traits | |
22 |
|
22 | |||
23 | There are also some important difference in our design: |
|
23 | There are also some important difference in our design: | |
24 |
|
24 | |||
25 | * enthought.traits does not validate default values. We do. |
|
25 | * enthought.traits does not validate default values. We do. | |
26 |
|
26 | |||
27 | We choose to create this module because we need these capabilities, but |
|
27 | We choose to create this module because we need these capabilities, but | |
28 | we need them to be pure Python so they work in all Python implementations, |
|
28 | we need them to be pure Python so they work in all Python implementations, | |
29 | including Jython and IronPython. |
|
29 | including Jython and IronPython. | |
30 |
|
30 | |||
|
31 | Inheritance diagram: | |||
|
32 | ||||
|
33 | .. inheritance-diagram:: IPython.utils.traitlets | |||
|
34 | :parts: 3 | |||
|
35 | ||||
31 | Authors: |
|
36 | Authors: | |
32 |
|
37 | |||
33 | * Brian Granger |
|
38 | * Brian Granger | |
34 | * Enthought, Inc. Some of the code in this file comes from enthought.traits |
|
39 | * Enthought, Inc. Some of the code in this file comes from enthought.traits | |
35 | and is licensed under the BSD license. Also, many of the ideas also come |
|
40 | and is licensed under the BSD license. Also, many of the ideas also come | |
36 | from enthought.traits even though our implementation is very different. |
|
41 | from enthought.traits even though our implementation is very different. | |
37 | """ |
|
42 | """ | |
38 |
|
43 | |||
39 | #----------------------------------------------------------------------------- |
|
44 | #----------------------------------------------------------------------------- | |
40 | # Copyright (C) 2008-2011 The IPython Development Team |
|
45 | # Copyright (C) 2008-2011 The IPython Development Team | |
41 | # |
|
46 | # | |
42 | # Distributed under the terms of the BSD License. The full license is in |
|
47 | # Distributed under the terms of the BSD License. The full license is in | |
43 | # the file COPYING, distributed as part of this software. |
|
48 | # the file COPYING, distributed as part of this software. | |
44 | #----------------------------------------------------------------------------- |
|
49 | #----------------------------------------------------------------------------- | |
45 |
|
50 | |||
46 | #----------------------------------------------------------------------------- |
|
51 | #----------------------------------------------------------------------------- | |
47 | # Imports |
|
52 | # Imports | |
48 | #----------------------------------------------------------------------------- |
|
53 | #----------------------------------------------------------------------------- | |
49 |
|
54 | |||
50 |
|
55 | |||
51 | import inspect |
|
56 | import inspect | |
52 | import re |
|
57 | import re | |
53 | import sys |
|
58 | import sys | |
54 | import types |
|
59 | import types | |
55 | from types import FunctionType |
|
60 | from types import FunctionType | |
56 | try: |
|
61 | try: | |
57 | from types import ClassType, InstanceType |
|
62 | from types import ClassType, InstanceType | |
58 | ClassTypes = (ClassType, type) |
|
63 | ClassTypes = (ClassType, type) | |
59 | except: |
|
64 | except: | |
60 | ClassTypes = (type,) |
|
65 | ClassTypes = (type,) | |
61 |
|
66 | |||
62 | from .importstring import import_item |
|
67 | from .importstring import import_item | |
63 | from IPython.utils import py3compat |
|
68 | from IPython.utils import py3compat | |
64 |
|
69 | |||
65 | SequenceTypes = (list, tuple, set, frozenset) |
|
70 | SequenceTypes = (list, tuple, set, frozenset) | |
66 |
|
71 | |||
67 | #----------------------------------------------------------------------------- |
|
72 | #----------------------------------------------------------------------------- | |
68 | # Basic classes |
|
73 | # Basic classes | |
69 | #----------------------------------------------------------------------------- |
|
74 | #----------------------------------------------------------------------------- | |
70 |
|
75 | |||
71 |
|
76 | |||
72 | class NoDefaultSpecified ( object ): pass |
|
77 | class NoDefaultSpecified ( object ): pass | |
73 | NoDefaultSpecified = NoDefaultSpecified() |
|
78 | NoDefaultSpecified = NoDefaultSpecified() | |
74 |
|
79 | |||
75 |
|
80 | |||
76 | class Undefined ( object ): pass |
|
81 | class Undefined ( object ): pass | |
77 | Undefined = Undefined() |
|
82 | Undefined = Undefined() | |
78 |
|
83 | |||
79 | class TraitError(Exception): |
|
84 | class TraitError(Exception): | |
80 | pass |
|
85 | pass | |
81 |
|
86 | |||
82 | #----------------------------------------------------------------------------- |
|
87 | #----------------------------------------------------------------------------- | |
83 | # Utilities |
|
88 | # Utilities | |
84 | #----------------------------------------------------------------------------- |
|
89 | #----------------------------------------------------------------------------- | |
85 |
|
90 | |||
86 |
|
91 | |||
87 | def class_of ( object ): |
|
92 | def class_of ( object ): | |
88 | """ Returns a string containing the class name of an object with the |
|
93 | """ Returns a string containing the class name of an object with the | |
89 | correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image', |
|
94 | correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image', | |
90 | 'a PlotValue'). |
|
95 | 'a PlotValue'). | |
91 | """ |
|
96 | """ | |
92 | if isinstance( object, basestring ): |
|
97 | if isinstance( object, basestring ): | |
93 | return add_article( object ) |
|
98 | return add_article( object ) | |
94 |
|
99 | |||
95 | return add_article( object.__class__.__name__ ) |
|
100 | return add_article( object.__class__.__name__ ) | |
96 |
|
101 | |||
97 |
|
102 | |||
98 | def add_article ( name ): |
|
103 | def add_article ( name ): | |
99 | """ Returns a string containing the correct indefinite article ('a' or 'an') |
|
104 | """ Returns a string containing the correct indefinite article ('a' or 'an') | |
100 | prefixed to the specified string. |
|
105 | prefixed to the specified string. | |
101 | """ |
|
106 | """ | |
102 | if name[:1].lower() in 'aeiou': |
|
107 | if name[:1].lower() in 'aeiou': | |
103 | return 'an ' + name |
|
108 | return 'an ' + name | |
104 |
|
109 | |||
105 | return 'a ' + name |
|
110 | return 'a ' + name | |
106 |
|
111 | |||
107 |
|
112 | |||
108 | def repr_type(obj): |
|
113 | def repr_type(obj): | |
109 | """ Return a string representation of a value and its type for readable |
|
114 | """ Return a string representation of a value and its type for readable | |
110 | error messages. |
|
115 | error messages. | |
111 | """ |
|
116 | """ | |
112 | the_type = type(obj) |
|
117 | the_type = type(obj) | |
113 | if (not py3compat.PY3) and the_type is InstanceType: |
|
118 | if (not py3compat.PY3) and the_type is InstanceType: | |
114 | # Old-style class. |
|
119 | # Old-style class. | |
115 | the_type = obj.__class__ |
|
120 | the_type = obj.__class__ | |
116 | msg = '%r %r' % (obj, the_type) |
|
121 | msg = '%r %r' % (obj, the_type) | |
117 | return msg |
|
122 | return msg | |
118 |
|
123 | |||
119 |
|
124 | |||
120 | def is_trait(t): |
|
125 | def is_trait(t): | |
121 | """ Returns whether the given value is an instance or subclass of TraitType. |
|
126 | """ Returns whether the given value is an instance or subclass of TraitType. | |
122 | """ |
|
127 | """ | |
123 | return (isinstance(t, TraitType) or |
|
128 | return (isinstance(t, TraitType) or | |
124 | (isinstance(t, type) and issubclass(t, TraitType))) |
|
129 | (isinstance(t, type) and issubclass(t, TraitType))) | |
125 |
|
130 | |||
126 |
|
131 | |||
127 | def parse_notifier_name(name): |
|
132 | def parse_notifier_name(name): | |
128 | """Convert the name argument to a list of names. |
|
133 | """Convert the name argument to a list of names. | |
129 |
|
134 | |||
130 | Examples |
|
135 | Examples | |
131 | -------- |
|
136 | -------- | |
132 |
|
137 | |||
133 | >>> parse_notifier_name('a') |
|
138 | >>> parse_notifier_name('a') | |
134 | ['a'] |
|
139 | ['a'] | |
135 | >>> parse_notifier_name(['a','b']) |
|
140 | >>> parse_notifier_name(['a','b']) | |
136 | ['a', 'b'] |
|
141 | ['a', 'b'] | |
137 | >>> parse_notifier_name(None) |
|
142 | >>> parse_notifier_name(None) | |
138 | ['anytrait'] |
|
143 | ['anytrait'] | |
139 | """ |
|
144 | """ | |
140 | if isinstance(name, str): |
|
145 | if isinstance(name, str): | |
141 | return [name] |
|
146 | return [name] | |
142 | elif name is None: |
|
147 | elif name is None: | |
143 | return ['anytrait'] |
|
148 | return ['anytrait'] | |
144 | elif isinstance(name, (list, tuple)): |
|
149 | elif isinstance(name, (list, tuple)): | |
145 | for n in name: |
|
150 | for n in name: | |
146 | assert isinstance(n, str), "names must be strings" |
|
151 | assert isinstance(n, str), "names must be strings" | |
147 | return name |
|
152 | return name | |
148 |
|
153 | |||
149 |
|
154 | |||
150 | class _SimpleTest: |
|
155 | class _SimpleTest: | |
151 | def __init__ ( self, value ): self.value = value |
|
156 | def __init__ ( self, value ): self.value = value | |
152 | def __call__ ( self, test ): |
|
157 | def __call__ ( self, test ): | |
153 | return test == self.value |
|
158 | return test == self.value | |
154 | def __repr__(self): |
|
159 | def __repr__(self): | |
155 | return "<SimpleTest(%r)" % self.value |
|
160 | return "<SimpleTest(%r)" % self.value | |
156 | def __str__(self): |
|
161 | def __str__(self): | |
157 | return self.__repr__() |
|
162 | return self.__repr__() | |
158 |
|
163 | |||
159 |
|
164 | |||
160 | def getmembers(object, predicate=None): |
|
165 | def getmembers(object, predicate=None): | |
161 | """A safe version of inspect.getmembers that handles missing attributes. |
|
166 | """A safe version of inspect.getmembers that handles missing attributes. | |
162 |
|
167 | |||
163 | This is useful when there are descriptor based attributes that for |
|
168 | This is useful when there are descriptor based attributes that for | |
164 | some reason raise AttributeError even though they exist. This happens |
|
169 | some reason raise AttributeError even though they exist. This happens | |
165 | in zope.inteface with the __provides__ attribute. |
|
170 | in zope.inteface with the __provides__ attribute. | |
166 | """ |
|
171 | """ | |
167 | results = [] |
|
172 | results = [] | |
168 | for key in dir(object): |
|
173 | for key in dir(object): | |
169 | try: |
|
174 | try: | |
170 | value = getattr(object, key) |
|
175 | value = getattr(object, key) | |
171 | except AttributeError: |
|
176 | except AttributeError: | |
172 | pass |
|
177 | pass | |
173 | else: |
|
178 | else: | |
174 | if not predicate or predicate(value): |
|
179 | if not predicate or predicate(value): | |
175 | results.append((key, value)) |
|
180 | results.append((key, value)) | |
176 | results.sort() |
|
181 | results.sort() | |
177 | return results |
|
182 | return results | |
178 |
|
183 | |||
179 |
|
184 | |||
180 | #----------------------------------------------------------------------------- |
|
185 | #----------------------------------------------------------------------------- | |
181 | # Base TraitType for all traits |
|
186 | # Base TraitType for all traits | |
182 | #----------------------------------------------------------------------------- |
|
187 | #----------------------------------------------------------------------------- | |
183 |
|
188 | |||
184 |
|
189 | |||
185 | class TraitType(object): |
|
190 | class TraitType(object): | |
186 | """A base class for all trait descriptors. |
|
191 | """A base class for all trait descriptors. | |
187 |
|
192 | |||
188 | Notes |
|
193 | Notes | |
189 | ----- |
|
194 | ----- | |
190 | Our implementation of traits is based on Python's descriptor |
|
195 | Our implementation of traits is based on Python's descriptor | |
191 | prototol. This class is the base class for all such descriptors. The |
|
196 | prototol. This class is the base class for all such descriptors. The | |
192 | only magic we use is a custom metaclass for the main :class:`HasTraits` |
|
197 | only magic we use is a custom metaclass for the main :class:`HasTraits` | |
193 | class that does the following: |
|
198 | class that does the following: | |
194 |
|
199 | |||
195 | 1. Sets the :attr:`name` attribute of every :class:`TraitType` |
|
200 | 1. Sets the :attr:`name` attribute of every :class:`TraitType` | |
196 | instance in the class dict to the name of the attribute. |
|
201 | instance in the class dict to the name of the attribute. | |
197 | 2. Sets the :attr:`this_class` attribute of every :class:`TraitType` |
|
202 | 2. Sets the :attr:`this_class` attribute of every :class:`TraitType` | |
198 | instance in the class dict to the *class* that declared the trait. |
|
203 | instance in the class dict to the *class* that declared the trait. | |
199 | This is used by the :class:`This` trait to allow subclasses to |
|
204 | This is used by the :class:`This` trait to allow subclasses to | |
200 | accept superclasses for :class:`This` values. |
|
205 | accept superclasses for :class:`This` values. | |
201 | """ |
|
206 | """ | |
202 |
|
207 | |||
203 |
|
208 | |||
204 | metadata = {} |
|
209 | metadata = {} | |
205 | default_value = Undefined |
|
210 | default_value = Undefined | |
206 | info_text = 'any value' |
|
211 | info_text = 'any value' | |
207 |
|
212 | |||
208 | def __init__(self, default_value=NoDefaultSpecified, **metadata): |
|
213 | def __init__(self, default_value=NoDefaultSpecified, **metadata): | |
209 | """Create a TraitType. |
|
214 | """Create a TraitType. | |
210 | """ |
|
215 | """ | |
211 | if default_value is not NoDefaultSpecified: |
|
216 | if default_value is not NoDefaultSpecified: | |
212 | self.default_value = default_value |
|
217 | self.default_value = default_value | |
213 |
|
218 | |||
214 | if len(metadata) > 0: |
|
219 | if len(metadata) > 0: | |
215 | if len(self.metadata) > 0: |
|
220 | if len(self.metadata) > 0: | |
216 | self._metadata = self.metadata.copy() |
|
221 | self._metadata = self.metadata.copy() | |
217 | self._metadata.update(metadata) |
|
222 | self._metadata.update(metadata) | |
218 | else: |
|
223 | else: | |
219 | self._metadata = metadata |
|
224 | self._metadata = metadata | |
220 | else: |
|
225 | else: | |
221 | self._metadata = self.metadata |
|
226 | self._metadata = self.metadata | |
222 |
|
227 | |||
223 | self.init() |
|
228 | self.init() | |
224 |
|
229 | |||
225 | def init(self): |
|
230 | def init(self): | |
226 | pass |
|
231 | pass | |
227 |
|
232 | |||
228 | def get_default_value(self): |
|
233 | def get_default_value(self): | |
229 | """Create a new instance of the default value.""" |
|
234 | """Create a new instance of the default value.""" | |
230 | return self.default_value |
|
235 | return self.default_value | |
231 |
|
236 | |||
232 | def instance_init(self, obj): |
|
237 | def instance_init(self, obj): | |
233 | """This is called by :meth:`HasTraits.__new__` to finish init'ing. |
|
238 | """This is called by :meth:`HasTraits.__new__` to finish init'ing. | |
234 |
|
239 | |||
235 | Some stages of initialization must be delayed until the parent |
|
240 | Some stages of initialization must be delayed until the parent | |
236 | :class:`HasTraits` instance has been created. This method is |
|
241 | :class:`HasTraits` instance has been created. This method is | |
237 | called in :meth:`HasTraits.__new__` after the instance has been |
|
242 | called in :meth:`HasTraits.__new__` after the instance has been | |
238 | created. |
|
243 | created. | |
239 |
|
244 | |||
240 | This method trigger the creation and validation of default values |
|
245 | This method trigger the creation and validation of default values | |
241 | and also things like the resolution of str given class names in |
|
246 | and also things like the resolution of str given class names in | |
242 | :class:`Type` and :class`Instance`. |
|
247 | :class:`Type` and :class`Instance`. | |
243 |
|
248 | |||
244 | Parameters |
|
249 | Parameters | |
245 | ---------- |
|
250 | ---------- | |
246 | obj : :class:`HasTraits` instance |
|
251 | obj : :class:`HasTraits` instance | |
247 | The parent :class:`HasTraits` instance that has just been |
|
252 | The parent :class:`HasTraits` instance that has just been | |
248 | created. |
|
253 | created. | |
249 | """ |
|
254 | """ | |
250 | self.set_default_value(obj) |
|
255 | self.set_default_value(obj) | |
251 |
|
256 | |||
252 | def set_default_value(self, obj): |
|
257 | def set_default_value(self, obj): | |
253 | """Set the default value on a per instance basis. |
|
258 | """Set the default value on a per instance basis. | |
254 |
|
259 | |||
255 | This method is called by :meth:`instance_init` to create and |
|
260 | This method is called by :meth:`instance_init` to create and | |
256 | validate the default value. The creation and validation of |
|
261 | validate the default value. The creation and validation of | |
257 | default values must be delayed until the parent :class:`HasTraits` |
|
262 | default values must be delayed until the parent :class:`HasTraits` | |
258 | class has been instantiated. |
|
263 | class has been instantiated. | |
259 | """ |
|
264 | """ | |
260 | # Check for a deferred initializer defined in the same class as the |
|
265 | # Check for a deferred initializer defined in the same class as the | |
261 | # trait declaration or above. |
|
266 | # trait declaration or above. | |
262 | mro = type(obj).mro() |
|
267 | mro = type(obj).mro() | |
263 | meth_name = '_%s_default' % self.name |
|
268 | meth_name = '_%s_default' % self.name | |
264 | for cls in mro[:mro.index(self.this_class)+1]: |
|
269 | for cls in mro[:mro.index(self.this_class)+1]: | |
265 | if meth_name in cls.__dict__: |
|
270 | if meth_name in cls.__dict__: | |
266 | break |
|
271 | break | |
267 | else: |
|
272 | else: | |
268 | # We didn't find one. Do static initialization. |
|
273 | # We didn't find one. Do static initialization. | |
269 | dv = self.get_default_value() |
|
274 | dv = self.get_default_value() | |
270 | newdv = self._validate(obj, dv) |
|
275 | newdv = self._validate(obj, dv) | |
271 | obj._trait_values[self.name] = newdv |
|
276 | obj._trait_values[self.name] = newdv | |
272 | return |
|
277 | return | |
273 | # Complete the dynamic initialization. |
|
278 | # Complete the dynamic initialization. | |
274 | obj._trait_dyn_inits[self.name] = cls.__dict__[meth_name] |
|
279 | obj._trait_dyn_inits[self.name] = cls.__dict__[meth_name] | |
275 |
|
280 | |||
276 | def __get__(self, obj, cls=None): |
|
281 | def __get__(self, obj, cls=None): | |
277 | """Get the value of the trait by self.name for the instance. |
|
282 | """Get the value of the trait by self.name for the instance. | |
278 |
|
283 | |||
279 | Default values are instantiated when :meth:`HasTraits.__new__` |
|
284 | Default values are instantiated when :meth:`HasTraits.__new__` | |
280 | is called. Thus by the time this method gets called either the |
|
285 | is called. Thus by the time this method gets called either the | |
281 | default value or a user defined value (they called :meth:`__set__`) |
|
286 | default value or a user defined value (they called :meth:`__set__`) | |
282 | is in the :class:`HasTraits` instance. |
|
287 | is in the :class:`HasTraits` instance. | |
283 | """ |
|
288 | """ | |
284 | if obj is None: |
|
289 | if obj is None: | |
285 | return self |
|
290 | return self | |
286 | else: |
|
291 | else: | |
287 | try: |
|
292 | try: | |
288 | value = obj._trait_values[self.name] |
|
293 | value = obj._trait_values[self.name] | |
289 | except KeyError: |
|
294 | except KeyError: | |
290 | # Check for a dynamic initializer. |
|
295 | # Check for a dynamic initializer. | |
291 | if self.name in obj._trait_dyn_inits: |
|
296 | if self.name in obj._trait_dyn_inits: | |
292 | value = obj._trait_dyn_inits[self.name](obj) |
|
297 | value = obj._trait_dyn_inits[self.name](obj) | |
293 | # FIXME: Do we really validate here? |
|
298 | # FIXME: Do we really validate here? | |
294 | value = self._validate(obj, value) |
|
299 | value = self._validate(obj, value) | |
295 | obj._trait_values[self.name] = value |
|
300 | obj._trait_values[self.name] = value | |
296 | return value |
|
301 | return value | |
297 | else: |
|
302 | else: | |
298 | raise TraitError('Unexpected error in TraitType: ' |
|
303 | raise TraitError('Unexpected error in TraitType: ' | |
299 | 'both default value and dynamic initializer are ' |
|
304 | 'both default value and dynamic initializer are ' | |
300 | 'absent.') |
|
305 | 'absent.') | |
301 | except Exception: |
|
306 | except Exception: | |
302 | # HasTraits should call set_default_value to populate |
|
307 | # HasTraits should call set_default_value to populate | |
303 | # this. So this should never be reached. |
|
308 | # this. So this should never be reached. | |
304 | raise TraitError('Unexpected error in TraitType: ' |
|
309 | raise TraitError('Unexpected error in TraitType: ' | |
305 | 'default value not set properly') |
|
310 | 'default value not set properly') | |
306 | else: |
|
311 | else: | |
307 | return value |
|
312 | return value | |
308 |
|
313 | |||
309 | def __set__(self, obj, value): |
|
314 | def __set__(self, obj, value): | |
310 | new_value = self._validate(obj, value) |
|
315 | new_value = self._validate(obj, value) | |
311 | old_value = self.__get__(obj) |
|
316 | old_value = self.__get__(obj) | |
312 | obj._trait_values[self.name] = new_value |
|
317 | obj._trait_values[self.name] = new_value | |
313 | if old_value != new_value: |
|
318 | if old_value != new_value: | |
314 | obj._notify_trait(self.name, old_value, new_value) |
|
319 | obj._notify_trait(self.name, old_value, new_value) | |
315 |
|
320 | |||
316 | def _validate(self, obj, value): |
|
321 | def _validate(self, obj, value): | |
317 | if hasattr(self, 'validate'): |
|
322 | if hasattr(self, 'validate'): | |
318 | return self.validate(obj, value) |
|
323 | return self.validate(obj, value) | |
319 | elif hasattr(self, 'is_valid_for'): |
|
324 | elif hasattr(self, 'is_valid_for'): | |
320 | valid = self.is_valid_for(value) |
|
325 | valid = self.is_valid_for(value) | |
321 | if valid: |
|
326 | if valid: | |
322 | return value |
|
327 | return value | |
323 | else: |
|
328 | else: | |
324 | raise TraitError('invalid value for type: %r' % value) |
|
329 | raise TraitError('invalid value for type: %r' % value) | |
325 | elif hasattr(self, 'value_for'): |
|
330 | elif hasattr(self, 'value_for'): | |
326 | return self.value_for(value) |
|
331 | return self.value_for(value) | |
327 | else: |
|
332 | else: | |
328 | return value |
|
333 | return value | |
329 |
|
334 | |||
330 | def info(self): |
|
335 | def info(self): | |
331 | return self.info_text |
|
336 | return self.info_text | |
332 |
|
337 | |||
333 | def error(self, obj, value): |
|
338 | def error(self, obj, value): | |
334 | if obj is not None: |
|
339 | if obj is not None: | |
335 | e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \ |
|
340 | e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \ | |
336 | % (self.name, class_of(obj), |
|
341 | % (self.name, class_of(obj), | |
337 | self.info(), repr_type(value)) |
|
342 | self.info(), repr_type(value)) | |
338 | else: |
|
343 | else: | |
339 | e = "The '%s' trait must be %s, but a value of %r was specified." \ |
|
344 | e = "The '%s' trait must be %s, but a value of %r was specified." \ | |
340 | % (self.name, self.info(), repr_type(value)) |
|
345 | % (self.name, self.info(), repr_type(value)) | |
341 | raise TraitError(e) |
|
346 | raise TraitError(e) | |
342 |
|
347 | |||
343 | def get_metadata(self, key): |
|
348 | def get_metadata(self, key): | |
344 | return getattr(self, '_metadata', {}).get(key, None) |
|
349 | return getattr(self, '_metadata', {}).get(key, None) | |
345 |
|
350 | |||
346 | def set_metadata(self, key, value): |
|
351 | def set_metadata(self, key, value): | |
347 | getattr(self, '_metadata', {})[key] = value |
|
352 | getattr(self, '_metadata', {})[key] = value | |
348 |
|
353 | |||
349 |
|
354 | |||
350 | #----------------------------------------------------------------------------- |
|
355 | #----------------------------------------------------------------------------- | |
351 | # The HasTraits implementation |
|
356 | # The HasTraits implementation | |
352 | #----------------------------------------------------------------------------- |
|
357 | #----------------------------------------------------------------------------- | |
353 |
|
358 | |||
354 |
|
359 | |||
355 | class MetaHasTraits(type): |
|
360 | class MetaHasTraits(type): | |
356 | """A metaclass for HasTraits. |
|
361 | """A metaclass for HasTraits. | |
357 |
|
362 | |||
358 | This metaclass makes sure that any TraitType class attributes are |
|
363 | This metaclass makes sure that any TraitType class attributes are | |
359 | instantiated and sets their name attribute. |
|
364 | instantiated and sets their name attribute. | |
360 | """ |
|
365 | """ | |
361 |
|
366 | |||
362 | def __new__(mcls, name, bases, classdict): |
|
367 | def __new__(mcls, name, bases, classdict): | |
363 | """Create the HasTraits class. |
|
368 | """Create the HasTraits class. | |
364 |
|
369 | |||
365 | This instantiates all TraitTypes in the class dict and sets their |
|
370 | This instantiates all TraitTypes in the class dict and sets their | |
366 | :attr:`name` attribute. |
|
371 | :attr:`name` attribute. | |
367 | """ |
|
372 | """ | |
368 | # print "MetaHasTraitlets (mcls, name): ", mcls, name |
|
373 | # print "MetaHasTraitlets (mcls, name): ", mcls, name | |
369 | # print "MetaHasTraitlets (bases): ", bases |
|
374 | # print "MetaHasTraitlets (bases): ", bases | |
370 | # print "MetaHasTraitlets (classdict): ", classdict |
|
375 | # print "MetaHasTraitlets (classdict): ", classdict | |
371 | for k,v in classdict.iteritems(): |
|
376 | for k,v in classdict.iteritems(): | |
372 | if isinstance(v, TraitType): |
|
377 | if isinstance(v, TraitType): | |
373 | v.name = k |
|
378 | v.name = k | |
374 | elif inspect.isclass(v): |
|
379 | elif inspect.isclass(v): | |
375 | if issubclass(v, TraitType): |
|
380 | if issubclass(v, TraitType): | |
376 | vinst = v() |
|
381 | vinst = v() | |
377 | vinst.name = k |
|
382 | vinst.name = k | |
378 | classdict[k] = vinst |
|
383 | classdict[k] = vinst | |
379 | return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict) |
|
384 | return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict) | |
380 |
|
385 | |||
381 | def __init__(cls, name, bases, classdict): |
|
386 | def __init__(cls, name, bases, classdict): | |
382 | """Finish initializing the HasTraits class. |
|
387 | """Finish initializing the HasTraits class. | |
383 |
|
388 | |||
384 | This sets the :attr:`this_class` attribute of each TraitType in the |
|
389 | This sets the :attr:`this_class` attribute of each TraitType in the | |
385 | class dict to the newly created class ``cls``. |
|
390 | class dict to the newly created class ``cls``. | |
386 | """ |
|
391 | """ | |
387 | for k, v in classdict.iteritems(): |
|
392 | for k, v in classdict.iteritems(): | |
388 | if isinstance(v, TraitType): |
|
393 | if isinstance(v, TraitType): | |
389 | v.this_class = cls |
|
394 | v.this_class = cls | |
390 | super(MetaHasTraits, cls).__init__(name, bases, classdict) |
|
395 | super(MetaHasTraits, cls).__init__(name, bases, classdict) | |
391 |
|
396 | |||
392 | class HasTraits(object): |
|
397 | class HasTraits(object): | |
393 |
|
398 | |||
394 | __metaclass__ = MetaHasTraits |
|
399 | __metaclass__ = MetaHasTraits | |
395 |
|
400 | |||
396 | def __new__(cls, **kw): |
|
401 | def __new__(cls, **kw): | |
397 | # This is needed because in Python 2.6 object.__new__ only accepts |
|
402 | # This is needed because in Python 2.6 object.__new__ only accepts | |
398 | # the cls argument. |
|
403 | # the cls argument. | |
399 | new_meth = super(HasTraits, cls).__new__ |
|
404 | new_meth = super(HasTraits, cls).__new__ | |
400 | if new_meth is object.__new__: |
|
405 | if new_meth is object.__new__: | |
401 | inst = new_meth(cls) |
|
406 | inst = new_meth(cls) | |
402 | else: |
|
407 | else: | |
403 | inst = new_meth(cls, **kw) |
|
408 | inst = new_meth(cls, **kw) | |
404 | inst._trait_values = {} |
|
409 | inst._trait_values = {} | |
405 | inst._trait_notifiers = {} |
|
410 | inst._trait_notifiers = {} | |
406 | inst._trait_dyn_inits = {} |
|
411 | inst._trait_dyn_inits = {} | |
407 | # Here we tell all the TraitType instances to set their default |
|
412 | # Here we tell all the TraitType instances to set their default | |
408 | # values on the instance. |
|
413 | # values on the instance. | |
409 | for key in dir(cls): |
|
414 | for key in dir(cls): | |
410 | # Some descriptors raise AttributeError like zope.interface's |
|
415 | # Some descriptors raise AttributeError like zope.interface's | |
411 | # __provides__ attributes even though they exist. This causes |
|
416 | # __provides__ attributes even though they exist. This causes | |
412 | # AttributeErrors even though they are listed in dir(cls). |
|
417 | # AttributeErrors even though they are listed in dir(cls). | |
413 | try: |
|
418 | try: | |
414 | value = getattr(cls, key) |
|
419 | value = getattr(cls, key) | |
415 | except AttributeError: |
|
420 | except AttributeError: | |
416 | pass |
|
421 | pass | |
417 | else: |
|
422 | else: | |
418 | if isinstance(value, TraitType): |
|
423 | if isinstance(value, TraitType): | |
419 | value.instance_init(inst) |
|
424 | value.instance_init(inst) | |
420 |
|
425 | |||
421 | return inst |
|
426 | return inst | |
422 |
|
427 | |||
423 | def __init__(self, **kw): |
|
428 | def __init__(self, **kw): | |
424 | # Allow trait values to be set using keyword arguments. |
|
429 | # Allow trait values to be set using keyword arguments. | |
425 | # We need to use setattr for this to trigger validation and |
|
430 | # We need to use setattr for this to trigger validation and | |
426 | # notifications. |
|
431 | # notifications. | |
427 | for key, value in kw.iteritems(): |
|
432 | for key, value in kw.iteritems(): | |
428 | setattr(self, key, value) |
|
433 | setattr(self, key, value) | |
429 |
|
434 | |||
430 | def _notify_trait(self, name, old_value, new_value): |
|
435 | def _notify_trait(self, name, old_value, new_value): | |
431 |
|
436 | |||
432 | # First dynamic ones |
|
437 | # First dynamic ones | |
433 | callables = self._trait_notifiers.get(name,[]) |
|
438 | callables = self._trait_notifiers.get(name,[]) | |
434 | more_callables = self._trait_notifiers.get('anytrait',[]) |
|
439 | more_callables = self._trait_notifiers.get('anytrait',[]) | |
435 | callables.extend(more_callables) |
|
440 | callables.extend(more_callables) | |
436 |
|
441 | |||
437 | # Now static ones |
|
442 | # Now static ones | |
438 | try: |
|
443 | try: | |
439 | cb = getattr(self, '_%s_changed' % name) |
|
444 | cb = getattr(self, '_%s_changed' % name) | |
440 | except: |
|
445 | except: | |
441 | pass |
|
446 | pass | |
442 | else: |
|
447 | else: | |
443 | callables.append(cb) |
|
448 | callables.append(cb) | |
444 |
|
449 | |||
445 | # Call them all now |
|
450 | # Call them all now | |
446 | for c in callables: |
|
451 | for c in callables: | |
447 | # Traits catches and logs errors here. I allow them to raise |
|
452 | # Traits catches and logs errors here. I allow them to raise | |
448 | if callable(c): |
|
453 | if callable(c): | |
449 | argspec = inspect.getargspec(c) |
|
454 | argspec = inspect.getargspec(c) | |
450 | nargs = len(argspec[0]) |
|
455 | nargs = len(argspec[0]) | |
451 | # Bound methods have an additional 'self' argument |
|
456 | # Bound methods have an additional 'self' argument | |
452 | # I don't know how to treat unbound methods, but they |
|
457 | # I don't know how to treat unbound methods, but they | |
453 | # can't really be used for callbacks. |
|
458 | # can't really be used for callbacks. | |
454 | if isinstance(c, types.MethodType): |
|
459 | if isinstance(c, types.MethodType): | |
455 | offset = -1 |
|
460 | offset = -1 | |
456 | else: |
|
461 | else: | |
457 | offset = 0 |
|
462 | offset = 0 | |
458 | if nargs + offset == 0: |
|
463 | if nargs + offset == 0: | |
459 | c() |
|
464 | c() | |
460 | elif nargs + offset == 1: |
|
465 | elif nargs + offset == 1: | |
461 | c(name) |
|
466 | c(name) | |
462 | elif nargs + offset == 2: |
|
467 | elif nargs + offset == 2: | |
463 | c(name, new_value) |
|
468 | c(name, new_value) | |
464 | elif nargs + offset == 3: |
|
469 | elif nargs + offset == 3: | |
465 | c(name, old_value, new_value) |
|
470 | c(name, old_value, new_value) | |
466 | else: |
|
471 | else: | |
467 | raise TraitError('a trait changed callback ' |
|
472 | raise TraitError('a trait changed callback ' | |
468 | 'must have 0-3 arguments.') |
|
473 | 'must have 0-3 arguments.') | |
469 | else: |
|
474 | else: | |
470 | raise TraitError('a trait changed callback ' |
|
475 | raise TraitError('a trait changed callback ' | |
471 | 'must be callable.') |
|
476 | 'must be callable.') | |
472 |
|
477 | |||
473 |
|
478 | |||
474 | def _add_notifiers(self, handler, name): |
|
479 | def _add_notifiers(self, handler, name): | |
475 | if name not in self._trait_notifiers: |
|
480 | if name not in self._trait_notifiers: | |
476 | nlist = [] |
|
481 | nlist = [] | |
477 | self._trait_notifiers[name] = nlist |
|
482 | self._trait_notifiers[name] = nlist | |
478 | else: |
|
483 | else: | |
479 | nlist = self._trait_notifiers[name] |
|
484 | nlist = self._trait_notifiers[name] | |
480 | if handler not in nlist: |
|
485 | if handler not in nlist: | |
481 | nlist.append(handler) |
|
486 | nlist.append(handler) | |
482 |
|
487 | |||
483 | def _remove_notifiers(self, handler, name): |
|
488 | def _remove_notifiers(self, handler, name): | |
484 | if name in self._trait_notifiers: |
|
489 | if name in self._trait_notifiers: | |
485 | nlist = self._trait_notifiers[name] |
|
490 | nlist = self._trait_notifiers[name] | |
486 | try: |
|
491 | try: | |
487 | index = nlist.index(handler) |
|
492 | index = nlist.index(handler) | |
488 | except ValueError: |
|
493 | except ValueError: | |
489 | pass |
|
494 | pass | |
490 | else: |
|
495 | else: | |
491 | del nlist[index] |
|
496 | del nlist[index] | |
492 |
|
497 | |||
493 | def on_trait_change(self, handler, name=None, remove=False): |
|
498 | def on_trait_change(self, handler, name=None, remove=False): | |
494 | """Setup a handler to be called when a trait changes. |
|
499 | """Setup a handler to be called when a trait changes. | |
495 |
|
500 | |||
496 | This is used to setup dynamic notifications of trait changes. |
|
501 | This is used to setup dynamic notifications of trait changes. | |
497 |
|
502 | |||
498 | Static handlers can be created by creating methods on a HasTraits |
|
503 | Static handlers can be created by creating methods on a HasTraits | |
499 | subclass with the naming convention '_[traitname]_changed'. Thus, |
|
504 | subclass with the naming convention '_[traitname]_changed'. Thus, | |
500 | to create static handler for the trait 'a', create the method |
|
505 | to create static handler for the trait 'a', create the method | |
501 | _a_changed(self, name, old, new) (fewer arguments can be used, see |
|
506 | _a_changed(self, name, old, new) (fewer arguments can be used, see | |
502 | below). |
|
507 | below). | |
503 |
|
508 | |||
504 | Parameters |
|
509 | Parameters | |
505 | ---------- |
|
510 | ---------- | |
506 | handler : callable |
|
511 | handler : callable | |
507 | A callable that is called when a trait changes. Its |
|
512 | A callable that is called when a trait changes. Its | |
508 | signature can be handler(), handler(name), handler(name, new) |
|
513 | signature can be handler(), handler(name), handler(name, new) | |
509 | or handler(name, old, new). |
|
514 | or handler(name, old, new). | |
510 | name : list, str, None |
|
515 | name : list, str, None | |
511 | If None, the handler will apply to all traits. If a list |
|
516 | If None, the handler will apply to all traits. If a list | |
512 | of str, handler will apply to all names in the list. If a |
|
517 | of str, handler will apply to all names in the list. If a | |
513 | str, the handler will apply just to that name. |
|
518 | str, the handler will apply just to that name. | |
514 | remove : bool |
|
519 | remove : bool | |
515 | If False (the default), then install the handler. If True |
|
520 | If False (the default), then install the handler. If True | |
516 | then unintall it. |
|
521 | then unintall it. | |
517 | """ |
|
522 | """ | |
518 | if remove: |
|
523 | if remove: | |
519 | names = parse_notifier_name(name) |
|
524 | names = parse_notifier_name(name) | |
520 | for n in names: |
|
525 | for n in names: | |
521 | self._remove_notifiers(handler, n) |
|
526 | self._remove_notifiers(handler, n) | |
522 | else: |
|
527 | else: | |
523 | names = parse_notifier_name(name) |
|
528 | names = parse_notifier_name(name) | |
524 | for n in names: |
|
529 | for n in names: | |
525 | self._add_notifiers(handler, n) |
|
530 | self._add_notifiers(handler, n) | |
526 |
|
531 | |||
527 | @classmethod |
|
532 | @classmethod | |
528 | def class_trait_names(cls, **metadata): |
|
533 | def class_trait_names(cls, **metadata): | |
529 | """Get a list of all the names of this classes traits. |
|
534 | """Get a list of all the names of this classes traits. | |
530 |
|
535 | |||
531 | This method is just like the :meth:`trait_names` method, but is unbound. |
|
536 | This method is just like the :meth:`trait_names` method, but is unbound. | |
532 | """ |
|
537 | """ | |
533 | return cls.class_traits(**metadata).keys() |
|
538 | return cls.class_traits(**metadata).keys() | |
534 |
|
539 | |||
535 | @classmethod |
|
540 | @classmethod | |
536 | def class_traits(cls, **metadata): |
|
541 | def class_traits(cls, **metadata): | |
537 | """Get a list of all the traits of this class. |
|
542 | """Get a list of all the traits of this class. | |
538 |
|
543 | |||
539 | This method is just like the :meth:`traits` method, but is unbound. |
|
544 | This method is just like the :meth:`traits` method, but is unbound. | |
540 |
|
545 | |||
541 | The TraitTypes returned don't know anything about the values |
|
546 | The TraitTypes returned don't know anything about the values | |
542 | that the various HasTrait's instances are holding. |
|
547 | that the various HasTrait's instances are holding. | |
543 |
|
548 | |||
544 | This follows the same algorithm as traits does and does not allow |
|
549 | This follows the same algorithm as traits does and does not allow | |
545 | for any simple way of specifying merely that a metadata name |
|
550 | for any simple way of specifying merely that a metadata name | |
546 | exists, but has any value. This is because get_metadata returns |
|
551 | exists, but has any value. This is because get_metadata returns | |
547 | None if a metadata key doesn't exist. |
|
552 | None if a metadata key doesn't exist. | |
548 | """ |
|
553 | """ | |
549 | traits = dict([memb for memb in getmembers(cls) if \ |
|
554 | traits = dict([memb for memb in getmembers(cls) if \ | |
550 | isinstance(memb[1], TraitType)]) |
|
555 | isinstance(memb[1], TraitType)]) | |
551 |
|
556 | |||
552 | if len(metadata) == 0: |
|
557 | if len(metadata) == 0: | |
553 | return traits |
|
558 | return traits | |
554 |
|
559 | |||
555 | for meta_name, meta_eval in metadata.items(): |
|
560 | for meta_name, meta_eval in metadata.items(): | |
556 | if type(meta_eval) is not FunctionType: |
|
561 | if type(meta_eval) is not FunctionType: | |
557 | metadata[meta_name] = _SimpleTest(meta_eval) |
|
562 | metadata[meta_name] = _SimpleTest(meta_eval) | |
558 |
|
563 | |||
559 | result = {} |
|
564 | result = {} | |
560 | for name, trait in traits.items(): |
|
565 | for name, trait in traits.items(): | |
561 | for meta_name, meta_eval in metadata.items(): |
|
566 | for meta_name, meta_eval in metadata.items(): | |
562 | if not meta_eval(trait.get_metadata(meta_name)): |
|
567 | if not meta_eval(trait.get_metadata(meta_name)): | |
563 | break |
|
568 | break | |
564 | else: |
|
569 | else: | |
565 | result[name] = trait |
|
570 | result[name] = trait | |
566 |
|
571 | |||
567 | return result |
|
572 | return result | |
568 |
|
573 | |||
569 | def trait_names(self, **metadata): |
|
574 | def trait_names(self, **metadata): | |
570 | """Get a list of all the names of this classes traits.""" |
|
575 | """Get a list of all the names of this classes traits.""" | |
571 | return self.traits(**metadata).keys() |
|
576 | return self.traits(**metadata).keys() | |
572 |
|
577 | |||
573 | def traits(self, **metadata): |
|
578 | def traits(self, **metadata): | |
574 | """Get a list of all the traits of this class. |
|
579 | """Get a list of all the traits of this class. | |
575 |
|
580 | |||
576 | The TraitTypes returned don't know anything about the values |
|
581 | The TraitTypes returned don't know anything about the values | |
577 | that the various HasTrait's instances are holding. |
|
582 | that the various HasTrait's instances are holding. | |
578 |
|
583 | |||
579 | This follows the same algorithm as traits does and does not allow |
|
584 | This follows the same algorithm as traits does and does not allow | |
580 | for any simple way of specifying merely that a metadata name |
|
585 | for any simple way of specifying merely that a metadata name | |
581 | exists, but has any value. This is because get_metadata returns |
|
586 | exists, but has any value. This is because get_metadata returns | |
582 | None if a metadata key doesn't exist. |
|
587 | None if a metadata key doesn't exist. | |
583 | """ |
|
588 | """ | |
584 | traits = dict([memb for memb in getmembers(self.__class__) if \ |
|
589 | traits = dict([memb for memb in getmembers(self.__class__) if \ | |
585 | isinstance(memb[1], TraitType)]) |
|
590 | isinstance(memb[1], TraitType)]) | |
586 |
|
591 | |||
587 | if len(metadata) == 0: |
|
592 | if len(metadata) == 0: | |
588 | return traits |
|
593 | return traits | |
589 |
|
594 | |||
590 | for meta_name, meta_eval in metadata.items(): |
|
595 | for meta_name, meta_eval in metadata.items(): | |
591 | if type(meta_eval) is not FunctionType: |
|
596 | if type(meta_eval) is not FunctionType: | |
592 | metadata[meta_name] = _SimpleTest(meta_eval) |
|
597 | metadata[meta_name] = _SimpleTest(meta_eval) | |
593 |
|
598 | |||
594 | result = {} |
|
599 | result = {} | |
595 | for name, trait in traits.items(): |
|
600 | for name, trait in traits.items(): | |
596 | for meta_name, meta_eval in metadata.items(): |
|
601 | for meta_name, meta_eval in metadata.items(): | |
597 | if not meta_eval(trait.get_metadata(meta_name)): |
|
602 | if not meta_eval(trait.get_metadata(meta_name)): | |
598 | break |
|
603 | break | |
599 | else: |
|
604 | else: | |
600 | result[name] = trait |
|
605 | result[name] = trait | |
601 |
|
606 | |||
602 | return result |
|
607 | return result | |
603 |
|
608 | |||
604 | def trait_metadata(self, traitname, key): |
|
609 | def trait_metadata(self, traitname, key): | |
605 | """Get metadata values for trait by key.""" |
|
610 | """Get metadata values for trait by key.""" | |
606 | try: |
|
611 | try: | |
607 | trait = getattr(self.__class__, traitname) |
|
612 | trait = getattr(self.__class__, traitname) | |
608 | except AttributeError: |
|
613 | except AttributeError: | |
609 | raise TraitError("Class %s does not have a trait named %s" % |
|
614 | raise TraitError("Class %s does not have a trait named %s" % | |
610 | (self.__class__.__name__, traitname)) |
|
615 | (self.__class__.__name__, traitname)) | |
611 | else: |
|
616 | else: | |
612 | return trait.get_metadata(key) |
|
617 | return trait.get_metadata(key) | |
613 |
|
618 | |||
614 | #----------------------------------------------------------------------------- |
|
619 | #----------------------------------------------------------------------------- | |
615 | # Actual TraitTypes implementations/subclasses |
|
620 | # Actual TraitTypes implementations/subclasses | |
616 | #----------------------------------------------------------------------------- |
|
621 | #----------------------------------------------------------------------------- | |
617 |
|
622 | |||
618 | #----------------------------------------------------------------------------- |
|
623 | #----------------------------------------------------------------------------- | |
619 | # TraitTypes subclasses for handling classes and instances of classes |
|
624 | # TraitTypes subclasses for handling classes and instances of classes | |
620 | #----------------------------------------------------------------------------- |
|
625 | #----------------------------------------------------------------------------- | |
621 |
|
626 | |||
622 |
|
627 | |||
623 | class ClassBasedTraitType(TraitType): |
|
628 | class ClassBasedTraitType(TraitType): | |
624 | """A trait with error reporting for Type, Instance and This.""" |
|
629 | """A trait with error reporting for Type, Instance and This.""" | |
625 |
|
630 | |||
626 | def error(self, obj, value): |
|
631 | def error(self, obj, value): | |
627 | kind = type(value) |
|
632 | kind = type(value) | |
628 | if (not py3compat.PY3) and kind is InstanceType: |
|
633 | if (not py3compat.PY3) and kind is InstanceType: | |
629 | msg = 'class %s' % value.__class__.__name__ |
|
634 | msg = 'class %s' % value.__class__.__name__ | |
630 | else: |
|
635 | else: | |
631 | msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) ) |
|
636 | msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) ) | |
632 |
|
637 | |||
633 | if obj is not None: |
|
638 | if obj is not None: | |
634 | e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \ |
|
639 | e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \ | |
635 | % (self.name, class_of(obj), |
|
640 | % (self.name, class_of(obj), | |
636 | self.info(), msg) |
|
641 | self.info(), msg) | |
637 | else: |
|
642 | else: | |
638 | e = "The '%s' trait must be %s, but a value of %r was specified." \ |
|
643 | e = "The '%s' trait must be %s, but a value of %r was specified." \ | |
639 | % (self.name, self.info(), msg) |
|
644 | % (self.name, self.info(), msg) | |
640 |
|
645 | |||
641 | raise TraitError(e) |
|
646 | raise TraitError(e) | |
642 |
|
647 | |||
643 |
|
648 | |||
644 | class Type(ClassBasedTraitType): |
|
649 | class Type(ClassBasedTraitType): | |
645 | """A trait whose value must be a subclass of a specified class.""" |
|
650 | """A trait whose value must be a subclass of a specified class.""" | |
646 |
|
651 | |||
647 | def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ): |
|
652 | def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ): | |
648 | """Construct a Type trait |
|
653 | """Construct a Type trait | |
649 |
|
654 | |||
650 | A Type trait specifies that its values must be subclasses of |
|
655 | A Type trait specifies that its values must be subclasses of | |
651 | a particular class. |
|
656 | a particular class. | |
652 |
|
657 | |||
653 | If only ``default_value`` is given, it is used for the ``klass`` as |
|
658 | If only ``default_value`` is given, it is used for the ``klass`` as | |
654 | well. |
|
659 | well. | |
655 |
|
660 | |||
656 | Parameters |
|
661 | Parameters | |
657 | ---------- |
|
662 | ---------- | |
658 | default_value : class, str or None |
|
663 | default_value : class, str or None | |
659 | The default value must be a subclass of klass. If an str, |
|
664 | The default value must be a subclass of klass. If an str, | |
660 | the str must be a fully specified class name, like 'foo.bar.Bah'. |
|
665 | the str must be a fully specified class name, like 'foo.bar.Bah'. | |
661 | The string is resolved into real class, when the parent |
|
666 | The string is resolved into real class, when the parent | |
662 | :class:`HasTraits` class is instantiated. |
|
667 | :class:`HasTraits` class is instantiated. | |
663 | klass : class, str, None |
|
668 | klass : class, str, None | |
664 | Values of this trait must be a subclass of klass. The klass |
|
669 | Values of this trait must be a subclass of klass. The klass | |
665 | may be specified in a string like: 'foo.bar.MyClass'. |
|
670 | may be specified in a string like: 'foo.bar.MyClass'. | |
666 | The string is resolved into real class, when the parent |
|
671 | The string is resolved into real class, when the parent | |
667 | :class:`HasTraits` class is instantiated. |
|
672 | :class:`HasTraits` class is instantiated. | |
668 | allow_none : boolean |
|
673 | allow_none : boolean | |
669 | Indicates whether None is allowed as an assignable value. Even if |
|
674 | Indicates whether None is allowed as an assignable value. Even if | |
670 | ``False``, the default value may be ``None``. |
|
675 | ``False``, the default value may be ``None``. | |
671 | """ |
|
676 | """ | |
672 | if default_value is None: |
|
677 | if default_value is None: | |
673 | if klass is None: |
|
678 | if klass is None: | |
674 | klass = object |
|
679 | klass = object | |
675 | elif klass is None: |
|
680 | elif klass is None: | |
676 | klass = default_value |
|
681 | klass = default_value | |
677 |
|
682 | |||
678 | if not (inspect.isclass(klass) or isinstance(klass, basestring)): |
|
683 | if not (inspect.isclass(klass) or isinstance(klass, basestring)): | |
679 | raise TraitError("A Type trait must specify a class.") |
|
684 | raise TraitError("A Type trait must specify a class.") | |
680 |
|
685 | |||
681 | self.klass = klass |
|
686 | self.klass = klass | |
682 | self._allow_none = allow_none |
|
687 | self._allow_none = allow_none | |
683 |
|
688 | |||
684 | super(Type, self).__init__(default_value, **metadata) |
|
689 | super(Type, self).__init__(default_value, **metadata) | |
685 |
|
690 | |||
686 | def validate(self, obj, value): |
|
691 | def validate(self, obj, value): | |
687 | """Validates that the value is a valid object instance.""" |
|
692 | """Validates that the value is a valid object instance.""" | |
688 | try: |
|
693 | try: | |
689 | if issubclass(value, self.klass): |
|
694 | if issubclass(value, self.klass): | |
690 | return value |
|
695 | return value | |
691 | except: |
|
696 | except: | |
692 | if (value is None) and (self._allow_none): |
|
697 | if (value is None) and (self._allow_none): | |
693 | return value |
|
698 | return value | |
694 |
|
699 | |||
695 | self.error(obj, value) |
|
700 | self.error(obj, value) | |
696 |
|
701 | |||
697 | def info(self): |
|
702 | def info(self): | |
698 | """ Returns a description of the trait.""" |
|
703 | """ Returns a description of the trait.""" | |
699 | if isinstance(self.klass, basestring): |
|
704 | if isinstance(self.klass, basestring): | |
700 | klass = self.klass |
|
705 | klass = self.klass | |
701 | else: |
|
706 | else: | |
702 | klass = self.klass.__name__ |
|
707 | klass = self.klass.__name__ | |
703 | result = 'a subclass of ' + klass |
|
708 | result = 'a subclass of ' + klass | |
704 | if self._allow_none: |
|
709 | if self._allow_none: | |
705 | return result + ' or None' |
|
710 | return result + ' or None' | |
706 | return result |
|
711 | return result | |
707 |
|
712 | |||
708 | def instance_init(self, obj): |
|
713 | def instance_init(self, obj): | |
709 | self._resolve_classes() |
|
714 | self._resolve_classes() | |
710 | super(Type, self).instance_init(obj) |
|
715 | super(Type, self).instance_init(obj) | |
711 |
|
716 | |||
712 | def _resolve_classes(self): |
|
717 | def _resolve_classes(self): | |
713 | if isinstance(self.klass, basestring): |
|
718 | if isinstance(self.klass, basestring): | |
714 | self.klass = import_item(self.klass) |
|
719 | self.klass = import_item(self.klass) | |
715 | if isinstance(self.default_value, basestring): |
|
720 | if isinstance(self.default_value, basestring): | |
716 | self.default_value = import_item(self.default_value) |
|
721 | self.default_value = import_item(self.default_value) | |
717 |
|
722 | |||
718 | def get_default_value(self): |
|
723 | def get_default_value(self): | |
719 | return self.default_value |
|
724 | return self.default_value | |
720 |
|
725 | |||
721 |
|
726 | |||
722 | class DefaultValueGenerator(object): |
|
727 | class DefaultValueGenerator(object): | |
723 | """A class for generating new default value instances.""" |
|
728 | """A class for generating new default value instances.""" | |
724 |
|
729 | |||
725 | def __init__(self, *args, **kw): |
|
730 | def __init__(self, *args, **kw): | |
726 | self.args = args |
|
731 | self.args = args | |
727 | self.kw = kw |
|
732 | self.kw = kw | |
728 |
|
733 | |||
729 | def generate(self, klass): |
|
734 | def generate(self, klass): | |
730 | return klass(*self.args, **self.kw) |
|
735 | return klass(*self.args, **self.kw) | |
731 |
|
736 | |||
732 |
|
737 | |||
733 | class Instance(ClassBasedTraitType): |
|
738 | class Instance(ClassBasedTraitType): | |
734 | """A trait whose value must be an instance of a specified class. |
|
739 | """A trait whose value must be an instance of a specified class. | |
735 |
|
740 | |||
736 | The value can also be an instance of a subclass of the specified class. |
|
741 | The value can also be an instance of a subclass of the specified class. | |
737 | """ |
|
742 | """ | |
738 |
|
743 | |||
739 | def __init__(self, klass=None, args=None, kw=None, |
|
744 | def __init__(self, klass=None, args=None, kw=None, | |
740 | allow_none=True, **metadata ): |
|
745 | allow_none=True, **metadata ): | |
741 | """Construct an Instance trait. |
|
746 | """Construct an Instance trait. | |
742 |
|
747 | |||
743 | This trait allows values that are instances of a particular |
|
748 | This trait allows values that are instances of a particular | |
744 | class or its sublclasses. Our implementation is quite different |
|
749 | class or its sublclasses. Our implementation is quite different | |
745 | from that of enthough.traits as we don't allow instances to be used |
|
750 | from that of enthough.traits as we don't allow instances to be used | |
746 | for klass and we handle the ``args`` and ``kw`` arguments differently. |
|
751 | for klass and we handle the ``args`` and ``kw`` arguments differently. | |
747 |
|
752 | |||
748 | Parameters |
|
753 | Parameters | |
749 | ---------- |
|
754 | ---------- | |
750 | klass : class, str |
|
755 | klass : class, str | |
751 | The class that forms the basis for the trait. Class names |
|
756 | The class that forms the basis for the trait. Class names | |
752 | can also be specified as strings, like 'foo.bar.Bar'. |
|
757 | can also be specified as strings, like 'foo.bar.Bar'. | |
753 | args : tuple |
|
758 | args : tuple | |
754 | Positional arguments for generating the default value. |
|
759 | Positional arguments for generating the default value. | |
755 | kw : dict |
|
760 | kw : dict | |
756 | Keyword arguments for generating the default value. |
|
761 | Keyword arguments for generating the default value. | |
757 | allow_none : bool |
|
762 | allow_none : bool | |
758 | Indicates whether None is allowed as a value. |
|
763 | Indicates whether None is allowed as a value. | |
759 |
|
764 | |||
760 | Default Value |
|
765 | Default Value | |
761 | ------------- |
|
766 | ------------- | |
762 | If both ``args`` and ``kw`` are None, then the default value is None. |
|
767 | If both ``args`` and ``kw`` are None, then the default value is None. | |
763 | If ``args`` is a tuple and ``kw`` is a dict, then the default is |
|
768 | If ``args`` is a tuple and ``kw`` is a dict, then the default is | |
764 | created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is |
|
769 | created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is | |
765 | not (but not both), None is replace by ``()`` or ``{}``. |
|
770 | not (but not both), None is replace by ``()`` or ``{}``. | |
766 | """ |
|
771 | """ | |
767 |
|
772 | |||
768 | self._allow_none = allow_none |
|
773 | self._allow_none = allow_none | |
769 |
|
774 | |||
770 | if (klass is None) or (not (inspect.isclass(klass) or isinstance(klass, basestring))): |
|
775 | if (klass is None) or (not (inspect.isclass(klass) or isinstance(klass, basestring))): | |
771 | raise TraitError('The klass argument must be a class' |
|
776 | raise TraitError('The klass argument must be a class' | |
772 | ' you gave: %r' % klass) |
|
777 | ' you gave: %r' % klass) | |
773 | self.klass = klass |
|
778 | self.klass = klass | |
774 |
|
779 | |||
775 | # self.klass is a class, so handle default_value |
|
780 | # self.klass is a class, so handle default_value | |
776 | if args is None and kw is None: |
|
781 | if args is None and kw is None: | |
777 | default_value = None |
|
782 | default_value = None | |
778 | else: |
|
783 | else: | |
779 | if args is None: |
|
784 | if args is None: | |
780 | # kw is not None |
|
785 | # kw is not None | |
781 | args = () |
|
786 | args = () | |
782 | elif kw is None: |
|
787 | elif kw is None: | |
783 | # args is not None |
|
788 | # args is not None | |
784 | kw = {} |
|
789 | kw = {} | |
785 |
|
790 | |||
786 | if not isinstance(kw, dict): |
|
791 | if not isinstance(kw, dict): | |
787 | raise TraitError("The 'kw' argument must be a dict or None.") |
|
792 | raise TraitError("The 'kw' argument must be a dict or None.") | |
788 | if not isinstance(args, tuple): |
|
793 | if not isinstance(args, tuple): | |
789 | raise TraitError("The 'args' argument must be a tuple or None.") |
|
794 | raise TraitError("The 'args' argument must be a tuple or None.") | |
790 |
|
795 | |||
791 | default_value = DefaultValueGenerator(*args, **kw) |
|
796 | default_value = DefaultValueGenerator(*args, **kw) | |
792 |
|
797 | |||
793 | super(Instance, self).__init__(default_value, **metadata) |
|
798 | super(Instance, self).__init__(default_value, **metadata) | |
794 |
|
799 | |||
795 | def validate(self, obj, value): |
|
800 | def validate(self, obj, value): | |
796 | if value is None: |
|
801 | if value is None: | |
797 | if self._allow_none: |
|
802 | if self._allow_none: | |
798 | return value |
|
803 | return value | |
799 | self.error(obj, value) |
|
804 | self.error(obj, value) | |
800 |
|
805 | |||
801 | if isinstance(value, self.klass): |
|
806 | if isinstance(value, self.klass): | |
802 | return value |
|
807 | return value | |
803 | else: |
|
808 | else: | |
804 | self.error(obj, value) |
|
809 | self.error(obj, value) | |
805 |
|
810 | |||
806 | def info(self): |
|
811 | def info(self): | |
807 | if isinstance(self.klass, basestring): |
|
812 | if isinstance(self.klass, basestring): | |
808 | klass = self.klass |
|
813 | klass = self.klass | |
809 | else: |
|
814 | else: | |
810 | klass = self.klass.__name__ |
|
815 | klass = self.klass.__name__ | |
811 | result = class_of(klass) |
|
816 | result = class_of(klass) | |
812 | if self._allow_none: |
|
817 | if self._allow_none: | |
813 | return result + ' or None' |
|
818 | return result + ' or None' | |
814 |
|
819 | |||
815 | return result |
|
820 | return result | |
816 |
|
821 | |||
817 | def instance_init(self, obj): |
|
822 | def instance_init(self, obj): | |
818 | self._resolve_classes() |
|
823 | self._resolve_classes() | |
819 | super(Instance, self).instance_init(obj) |
|
824 | super(Instance, self).instance_init(obj) | |
820 |
|
825 | |||
821 | def _resolve_classes(self): |
|
826 | def _resolve_classes(self): | |
822 | if isinstance(self.klass, basestring): |
|
827 | if isinstance(self.klass, basestring): | |
823 | self.klass = import_item(self.klass) |
|
828 | self.klass = import_item(self.klass) | |
824 |
|
829 | |||
825 | def get_default_value(self): |
|
830 | def get_default_value(self): | |
826 | """Instantiate a default value instance. |
|
831 | """Instantiate a default value instance. | |
827 |
|
832 | |||
828 | This is called when the containing HasTraits classes' |
|
833 | This is called when the containing HasTraits classes' | |
829 | :meth:`__new__` method is called to ensure that a unique instance |
|
834 | :meth:`__new__` method is called to ensure that a unique instance | |
830 | is created for each HasTraits instance. |
|
835 | is created for each HasTraits instance. | |
831 | """ |
|
836 | """ | |
832 | dv = self.default_value |
|
837 | dv = self.default_value | |
833 | if isinstance(dv, DefaultValueGenerator): |
|
838 | if isinstance(dv, DefaultValueGenerator): | |
834 | return dv.generate(self.klass) |
|
839 | return dv.generate(self.klass) | |
835 | else: |
|
840 | else: | |
836 | return dv |
|
841 | return dv | |
837 |
|
842 | |||
838 |
|
843 | |||
839 | class This(ClassBasedTraitType): |
|
844 | class This(ClassBasedTraitType): | |
840 | """A trait for instances of the class containing this trait. |
|
845 | """A trait for instances of the class containing this trait. | |
841 |
|
846 | |||
842 | Because how how and when class bodies are executed, the ``This`` |
|
847 | Because how how and when class bodies are executed, the ``This`` | |
843 | trait can only have a default value of None. This, and because we |
|
848 | trait can only have a default value of None. This, and because we | |
844 | always validate default values, ``allow_none`` is *always* true. |
|
849 | always validate default values, ``allow_none`` is *always* true. | |
845 | """ |
|
850 | """ | |
846 |
|
851 | |||
847 | info_text = 'an instance of the same type as the receiver or None' |
|
852 | info_text = 'an instance of the same type as the receiver or None' | |
848 |
|
853 | |||
849 | def __init__(self, **metadata): |
|
854 | def __init__(self, **metadata): | |
850 | super(This, self).__init__(None, **metadata) |
|
855 | super(This, self).__init__(None, **metadata) | |
851 |
|
856 | |||
852 | def validate(self, obj, value): |
|
857 | def validate(self, obj, value): | |
853 | # What if value is a superclass of obj.__class__? This is |
|
858 | # What if value is a superclass of obj.__class__? This is | |
854 | # complicated if it was the superclass that defined the This |
|
859 | # complicated if it was the superclass that defined the This | |
855 | # trait. |
|
860 | # trait. | |
856 | if isinstance(value, self.this_class) or (value is None): |
|
861 | if isinstance(value, self.this_class) or (value is None): | |
857 | return value |
|
862 | return value | |
858 | else: |
|
863 | else: | |
859 | self.error(obj, value) |
|
864 | self.error(obj, value) | |
860 |
|
865 | |||
861 |
|
866 | |||
862 | #----------------------------------------------------------------------------- |
|
867 | #----------------------------------------------------------------------------- | |
863 | # Basic TraitTypes implementations/subclasses |
|
868 | # Basic TraitTypes implementations/subclasses | |
864 | #----------------------------------------------------------------------------- |
|
869 | #----------------------------------------------------------------------------- | |
865 |
|
870 | |||
866 |
|
871 | |||
867 | class Any(TraitType): |
|
872 | class Any(TraitType): | |
868 | default_value = None |
|
873 | default_value = None | |
869 | info_text = 'any value' |
|
874 | info_text = 'any value' | |
870 |
|
875 | |||
871 |
|
876 | |||
872 | class Int(TraitType): |
|
877 | class Int(TraitType): | |
873 | """An int trait.""" |
|
878 | """An int trait.""" | |
874 |
|
879 | |||
875 | default_value = 0 |
|
880 | default_value = 0 | |
876 | info_text = 'an int' |
|
881 | info_text = 'an int' | |
877 |
|
882 | |||
878 | def validate(self, obj, value): |
|
883 | def validate(self, obj, value): | |
879 | if isinstance(value, int): |
|
884 | if isinstance(value, int): | |
880 | return value |
|
885 | return value | |
881 | self.error(obj, value) |
|
886 | self.error(obj, value) | |
882 |
|
887 | |||
883 | class CInt(Int): |
|
888 | class CInt(Int): | |
884 | """A casting version of the int trait.""" |
|
889 | """A casting version of the int trait.""" | |
885 |
|
890 | |||
886 | def validate(self, obj, value): |
|
891 | def validate(self, obj, value): | |
887 | try: |
|
892 | try: | |
888 | return int(value) |
|
893 | return int(value) | |
889 | except: |
|
894 | except: | |
890 | self.error(obj, value) |
|
895 | self.error(obj, value) | |
891 |
|
896 | |||
892 | if py3compat.PY3: |
|
897 | if py3compat.PY3: | |
893 | Long, CLong = Int, CInt |
|
898 | Long, CLong = Int, CInt | |
894 | Integer = Int |
|
899 | Integer = Int | |
895 | else: |
|
900 | else: | |
896 | class Long(TraitType): |
|
901 | class Long(TraitType): | |
897 | """A long integer trait.""" |
|
902 | """A long integer trait.""" | |
898 |
|
903 | |||
899 | default_value = 0L |
|
904 | default_value = 0L | |
900 | info_text = 'a long' |
|
905 | info_text = 'a long' | |
901 |
|
906 | |||
902 | def validate(self, obj, value): |
|
907 | def validate(self, obj, value): | |
903 | if isinstance(value, long): |
|
908 | if isinstance(value, long): | |
904 | return value |
|
909 | return value | |
905 | if isinstance(value, int): |
|
910 | if isinstance(value, int): | |
906 | return long(value) |
|
911 | return long(value) | |
907 | self.error(obj, value) |
|
912 | self.error(obj, value) | |
908 |
|
913 | |||
909 |
|
914 | |||
910 | class CLong(Long): |
|
915 | class CLong(Long): | |
911 | """A casting version of the long integer trait.""" |
|
916 | """A casting version of the long integer trait.""" | |
912 |
|
917 | |||
913 | def validate(self, obj, value): |
|
918 | def validate(self, obj, value): | |
914 | try: |
|
919 | try: | |
915 | return long(value) |
|
920 | return long(value) | |
916 | except: |
|
921 | except: | |
917 | self.error(obj, value) |
|
922 | self.error(obj, value) | |
918 |
|
923 | |||
919 | class Integer(TraitType): |
|
924 | class Integer(TraitType): | |
920 | """An integer trait. |
|
925 | """An integer trait. | |
921 |
|
926 | |||
922 | Longs that are unnecessary (<= sys.maxint) are cast to ints.""" |
|
927 | Longs that are unnecessary (<= sys.maxint) are cast to ints.""" | |
923 |
|
928 | |||
924 | default_value = 0 |
|
929 | default_value = 0 | |
925 | info_text = 'an integer' |
|
930 | info_text = 'an integer' | |
926 |
|
931 | |||
927 | def validate(self, obj, value): |
|
932 | def validate(self, obj, value): | |
928 | if isinstance(value, int): |
|
933 | if isinstance(value, int): | |
929 | return value |
|
934 | return value | |
930 | if isinstance(value, long): |
|
935 | if isinstance(value, long): | |
931 | # downcast longs that fit in int: |
|
936 | # downcast longs that fit in int: | |
932 | # note that int(n > sys.maxint) returns a long, so |
|
937 | # note that int(n > sys.maxint) returns a long, so | |
933 | # we don't need a condition on this cast |
|
938 | # we don't need a condition on this cast | |
934 | return int(value) |
|
939 | return int(value) | |
935 | if sys.platform == "cli": |
|
940 | if sys.platform == "cli": | |
936 | from System import Int64 |
|
941 | from System import Int64 | |
937 | if isinstance(value, Int64): |
|
942 | if isinstance(value, Int64): | |
938 | return int(value) |
|
943 | return int(value) | |
939 | self.error(obj, value) |
|
944 | self.error(obj, value) | |
940 |
|
945 | |||
941 |
|
946 | |||
942 | class Float(TraitType): |
|
947 | class Float(TraitType): | |
943 | """A float trait.""" |
|
948 | """A float trait.""" | |
944 |
|
949 | |||
945 | default_value = 0.0 |
|
950 | default_value = 0.0 | |
946 | info_text = 'a float' |
|
951 | info_text = 'a float' | |
947 |
|
952 | |||
948 | def validate(self, obj, value): |
|
953 | def validate(self, obj, value): | |
949 | if isinstance(value, float): |
|
954 | if isinstance(value, float): | |
950 | return value |
|
955 | return value | |
951 | if isinstance(value, int): |
|
956 | if isinstance(value, int): | |
952 | return float(value) |
|
957 | return float(value) | |
953 | self.error(obj, value) |
|
958 | self.error(obj, value) | |
954 |
|
959 | |||
955 |
|
960 | |||
956 | class CFloat(Float): |
|
961 | class CFloat(Float): | |
957 | """A casting version of the float trait.""" |
|
962 | """A casting version of the float trait.""" | |
958 |
|
963 | |||
959 | def validate(self, obj, value): |
|
964 | def validate(self, obj, value): | |
960 | try: |
|
965 | try: | |
961 | return float(value) |
|
966 | return float(value) | |
962 | except: |
|
967 | except: | |
963 | self.error(obj, value) |
|
968 | self.error(obj, value) | |
964 |
|
969 | |||
965 | class Complex(TraitType): |
|
970 | class Complex(TraitType): | |
966 | """A trait for complex numbers.""" |
|
971 | """A trait for complex numbers.""" | |
967 |
|
972 | |||
968 | default_value = 0.0 + 0.0j |
|
973 | default_value = 0.0 + 0.0j | |
969 | info_text = 'a complex number' |
|
974 | info_text = 'a complex number' | |
970 |
|
975 | |||
971 | def validate(self, obj, value): |
|
976 | def validate(self, obj, value): | |
972 | if isinstance(value, complex): |
|
977 | if isinstance(value, complex): | |
973 | return value |
|
978 | return value | |
974 | if isinstance(value, (float, int)): |
|
979 | if isinstance(value, (float, int)): | |
975 | return complex(value) |
|
980 | return complex(value) | |
976 | self.error(obj, value) |
|
981 | self.error(obj, value) | |
977 |
|
982 | |||
978 |
|
983 | |||
979 | class CComplex(Complex): |
|
984 | class CComplex(Complex): | |
980 | """A casting version of the complex number trait.""" |
|
985 | """A casting version of the complex number trait.""" | |
981 |
|
986 | |||
982 | def validate (self, obj, value): |
|
987 | def validate (self, obj, value): | |
983 | try: |
|
988 | try: | |
984 | return complex(value) |
|
989 | return complex(value) | |
985 | except: |
|
990 | except: | |
986 | self.error(obj, value) |
|
991 | self.error(obj, value) | |
987 |
|
992 | |||
988 | # We should always be explicit about whether we're using bytes or unicode, both |
|
993 | # We should always be explicit about whether we're using bytes or unicode, both | |
989 | # for Python 3 conversion and for reliable unicode behaviour on Python 2. So |
|
994 | # for Python 3 conversion and for reliable unicode behaviour on Python 2. So | |
990 | # we don't have a Str type. |
|
995 | # we don't have a Str type. | |
991 | class Bytes(TraitType): |
|
996 | class Bytes(TraitType): | |
992 | """A trait for byte strings.""" |
|
997 | """A trait for byte strings.""" | |
993 |
|
998 | |||
994 | default_value = b'' |
|
999 | default_value = b'' | |
995 | info_text = 'a string' |
|
1000 | info_text = 'a string' | |
996 |
|
1001 | |||
997 | def validate(self, obj, value): |
|
1002 | def validate(self, obj, value): | |
998 | if isinstance(value, bytes): |
|
1003 | if isinstance(value, bytes): | |
999 | return value |
|
1004 | return value | |
1000 | self.error(obj, value) |
|
1005 | self.error(obj, value) | |
1001 |
|
1006 | |||
1002 |
|
1007 | |||
1003 | class CBytes(Bytes): |
|
1008 | class CBytes(Bytes): | |
1004 | """A casting version of the byte string trait.""" |
|
1009 | """A casting version of the byte string trait.""" | |
1005 |
|
1010 | |||
1006 | def validate(self, obj, value): |
|
1011 | def validate(self, obj, value): | |
1007 | try: |
|
1012 | try: | |
1008 | return bytes(value) |
|
1013 | return bytes(value) | |
1009 | except: |
|
1014 | except: | |
1010 | self.error(obj, value) |
|
1015 | self.error(obj, value) | |
1011 |
|
1016 | |||
1012 |
|
1017 | |||
1013 | class Unicode(TraitType): |
|
1018 | class Unicode(TraitType): | |
1014 | """A trait for unicode strings.""" |
|
1019 | """A trait for unicode strings.""" | |
1015 |
|
1020 | |||
1016 | default_value = u'' |
|
1021 | default_value = u'' | |
1017 | info_text = 'a unicode string' |
|
1022 | info_text = 'a unicode string' | |
1018 |
|
1023 | |||
1019 | def validate(self, obj, value): |
|
1024 | def validate(self, obj, value): | |
1020 | if isinstance(value, unicode): |
|
1025 | if isinstance(value, unicode): | |
1021 | return value |
|
1026 | return value | |
1022 | if isinstance(value, bytes): |
|
1027 | if isinstance(value, bytes): | |
1023 | return unicode(value) |
|
1028 | return unicode(value) | |
1024 | self.error(obj, value) |
|
1029 | self.error(obj, value) | |
1025 |
|
1030 | |||
1026 |
|
1031 | |||
1027 | class CUnicode(Unicode): |
|
1032 | class CUnicode(Unicode): | |
1028 | """A casting version of the unicode trait.""" |
|
1033 | """A casting version of the unicode trait.""" | |
1029 |
|
1034 | |||
1030 | def validate(self, obj, value): |
|
1035 | def validate(self, obj, value): | |
1031 | try: |
|
1036 | try: | |
1032 | return unicode(value) |
|
1037 | return unicode(value) | |
1033 | except: |
|
1038 | except: | |
1034 | self.error(obj, value) |
|
1039 | self.error(obj, value) | |
1035 |
|
1040 | |||
1036 |
|
1041 | |||
1037 | class ObjectName(TraitType): |
|
1042 | class ObjectName(TraitType): | |
1038 | """A string holding a valid object name in this version of Python. |
|
1043 | """A string holding a valid object name in this version of Python. | |
1039 |
|
1044 | |||
1040 | This does not check that the name exists in any scope.""" |
|
1045 | This does not check that the name exists in any scope.""" | |
1041 | info_text = "a valid object identifier in Python" |
|
1046 | info_text = "a valid object identifier in Python" | |
1042 |
|
1047 | |||
1043 | if py3compat.PY3: |
|
1048 | if py3compat.PY3: | |
1044 | # Python 3: |
|
1049 | # Python 3: | |
1045 | coerce_str = staticmethod(lambda _,s: s) |
|
1050 | coerce_str = staticmethod(lambda _,s: s) | |
1046 |
|
1051 | |||
1047 | else: |
|
1052 | else: | |
1048 | # Python 2: |
|
1053 | # Python 2: | |
1049 | def coerce_str(self, obj, value): |
|
1054 | def coerce_str(self, obj, value): | |
1050 | "In Python 2, coerce ascii-only unicode to str" |
|
1055 | "In Python 2, coerce ascii-only unicode to str" | |
1051 | if isinstance(value, unicode): |
|
1056 | if isinstance(value, unicode): | |
1052 | try: |
|
1057 | try: | |
1053 | return str(value) |
|
1058 | return str(value) | |
1054 | except UnicodeEncodeError: |
|
1059 | except UnicodeEncodeError: | |
1055 | self.error(obj, value) |
|
1060 | self.error(obj, value) | |
1056 | return value |
|
1061 | return value | |
1057 |
|
1062 | |||
1058 | def validate(self, obj, value): |
|
1063 | def validate(self, obj, value): | |
1059 | value = self.coerce_str(obj, value) |
|
1064 | value = self.coerce_str(obj, value) | |
1060 |
|
1065 | |||
1061 | if isinstance(value, str) and py3compat.isidentifier(value): |
|
1066 | if isinstance(value, str) and py3compat.isidentifier(value): | |
1062 | return value |
|
1067 | return value | |
1063 | self.error(obj, value) |
|
1068 | self.error(obj, value) | |
1064 |
|
1069 | |||
1065 | class DottedObjectName(ObjectName): |
|
1070 | class DottedObjectName(ObjectName): | |
1066 | """A string holding a valid dotted object name in Python, such as A.b3._c""" |
|
1071 | """A string holding a valid dotted object name in Python, such as A.b3._c""" | |
1067 | def validate(self, obj, value): |
|
1072 | def validate(self, obj, value): | |
1068 | value = self.coerce_str(obj, value) |
|
1073 | value = self.coerce_str(obj, value) | |
1069 |
|
1074 | |||
1070 | if isinstance(value, str) and py3compat.isidentifier(value, dotted=True): |
|
1075 | if isinstance(value, str) and py3compat.isidentifier(value, dotted=True): | |
1071 | return value |
|
1076 | return value | |
1072 | self.error(obj, value) |
|
1077 | self.error(obj, value) | |
1073 |
|
1078 | |||
1074 |
|
1079 | |||
1075 | class Bool(TraitType): |
|
1080 | class Bool(TraitType): | |
1076 | """A boolean (True, False) trait.""" |
|
1081 | """A boolean (True, False) trait.""" | |
1077 |
|
1082 | |||
1078 | default_value = False |
|
1083 | default_value = False | |
1079 | info_text = 'a boolean' |
|
1084 | info_text = 'a boolean' | |
1080 |
|
1085 | |||
1081 | def validate(self, obj, value): |
|
1086 | def validate(self, obj, value): | |
1082 | if isinstance(value, bool): |
|
1087 | if isinstance(value, bool): | |
1083 | return value |
|
1088 | return value | |
1084 | self.error(obj, value) |
|
1089 | self.error(obj, value) | |
1085 |
|
1090 | |||
1086 |
|
1091 | |||
1087 | class CBool(Bool): |
|
1092 | class CBool(Bool): | |
1088 | """A casting version of the boolean trait.""" |
|
1093 | """A casting version of the boolean trait.""" | |
1089 |
|
1094 | |||
1090 | def validate(self, obj, value): |
|
1095 | def validate(self, obj, value): | |
1091 | try: |
|
1096 | try: | |
1092 | return bool(value) |
|
1097 | return bool(value) | |
1093 | except: |
|
1098 | except: | |
1094 | self.error(obj, value) |
|
1099 | self.error(obj, value) | |
1095 |
|
1100 | |||
1096 |
|
1101 | |||
1097 | class Enum(TraitType): |
|
1102 | class Enum(TraitType): | |
1098 | """An enum that whose value must be in a given sequence.""" |
|
1103 | """An enum that whose value must be in a given sequence.""" | |
1099 |
|
1104 | |||
1100 | def __init__(self, values, default_value=None, allow_none=True, **metadata): |
|
1105 | def __init__(self, values, default_value=None, allow_none=True, **metadata): | |
1101 | self.values = values |
|
1106 | self.values = values | |
1102 | self._allow_none = allow_none |
|
1107 | self._allow_none = allow_none | |
1103 | super(Enum, self).__init__(default_value, **metadata) |
|
1108 | super(Enum, self).__init__(default_value, **metadata) | |
1104 |
|
1109 | |||
1105 | def validate(self, obj, value): |
|
1110 | def validate(self, obj, value): | |
1106 | if value is None: |
|
1111 | if value is None: | |
1107 | if self._allow_none: |
|
1112 | if self._allow_none: | |
1108 | return value |
|
1113 | return value | |
1109 |
|
1114 | |||
1110 | if value in self.values: |
|
1115 | if value in self.values: | |
1111 | return value |
|
1116 | return value | |
1112 | self.error(obj, value) |
|
1117 | self.error(obj, value) | |
1113 |
|
1118 | |||
1114 | def info(self): |
|
1119 | def info(self): | |
1115 | """ Returns a description of the trait.""" |
|
1120 | """ Returns a description of the trait.""" | |
1116 | result = 'any of ' + repr(self.values) |
|
1121 | result = 'any of ' + repr(self.values) | |
1117 | if self._allow_none: |
|
1122 | if self._allow_none: | |
1118 | return result + ' or None' |
|
1123 | return result + ' or None' | |
1119 | return result |
|
1124 | return result | |
1120 |
|
1125 | |||
1121 | class CaselessStrEnum(Enum): |
|
1126 | class CaselessStrEnum(Enum): | |
1122 | """An enum of strings that are caseless in validate.""" |
|
1127 | """An enum of strings that are caseless in validate.""" | |
1123 |
|
1128 | |||
1124 | def validate(self, obj, value): |
|
1129 | def validate(self, obj, value): | |
1125 | if value is None: |
|
1130 | if value is None: | |
1126 | if self._allow_none: |
|
1131 | if self._allow_none: | |
1127 | return value |
|
1132 | return value | |
1128 |
|
1133 | |||
1129 | if not isinstance(value, basestring): |
|
1134 | if not isinstance(value, basestring): | |
1130 | self.error(obj, value) |
|
1135 | self.error(obj, value) | |
1131 |
|
1136 | |||
1132 | for v in self.values: |
|
1137 | for v in self.values: | |
1133 | if v.lower() == value.lower(): |
|
1138 | if v.lower() == value.lower(): | |
1134 | return v |
|
1139 | return v | |
1135 | self.error(obj, value) |
|
1140 | self.error(obj, value) | |
1136 |
|
1141 | |||
1137 | class Container(Instance): |
|
1142 | class Container(Instance): | |
1138 | """An instance of a container (list, set, etc.) |
|
1143 | """An instance of a container (list, set, etc.) | |
1139 |
|
1144 | |||
1140 | To be subclassed by overriding klass. |
|
1145 | To be subclassed by overriding klass. | |
1141 | """ |
|
1146 | """ | |
1142 | klass = None |
|
1147 | klass = None | |
1143 | _valid_defaults = SequenceTypes |
|
1148 | _valid_defaults = SequenceTypes | |
1144 | _trait = None |
|
1149 | _trait = None | |
1145 |
|
1150 | |||
1146 | def __init__(self, trait=None, default_value=None, allow_none=True, |
|
1151 | def __init__(self, trait=None, default_value=None, allow_none=True, | |
1147 | **metadata): |
|
1152 | **metadata): | |
1148 | """Create a container trait type from a list, set, or tuple. |
|
1153 | """Create a container trait type from a list, set, or tuple. | |
1149 |
|
1154 | |||
1150 | The default value is created by doing ``List(default_value)``, |
|
1155 | The default value is created by doing ``List(default_value)``, | |
1151 | which creates a copy of the ``default_value``. |
|
1156 | which creates a copy of the ``default_value``. | |
1152 |
|
1157 | |||
1153 | ``trait`` can be specified, which restricts the type of elements |
|
1158 | ``trait`` can be specified, which restricts the type of elements | |
1154 | in the container to that TraitType. |
|
1159 | in the container to that TraitType. | |
1155 |
|
1160 | |||
1156 | If only one arg is given and it is not a Trait, it is taken as |
|
1161 | If only one arg is given and it is not a Trait, it is taken as | |
1157 | ``default_value``: |
|
1162 | ``default_value``: | |
1158 |
|
1163 | |||
1159 | ``c = List([1,2,3])`` |
|
1164 | ``c = List([1,2,3])`` | |
1160 |
|
1165 | |||
1161 | Parameters |
|
1166 | Parameters | |
1162 | ---------- |
|
1167 | ---------- | |
1163 |
|
1168 | |||
1164 | trait : TraitType [ optional ] |
|
1169 | trait : TraitType [ optional ] | |
1165 | the type for restricting the contents of the Container. If unspecified, |
|
1170 | the type for restricting the contents of the Container. If unspecified, | |
1166 | types are not checked. |
|
1171 | types are not checked. | |
1167 |
|
1172 | |||
1168 | default_value : SequenceType [ optional ] |
|
1173 | default_value : SequenceType [ optional ] | |
1169 | The default value for the Trait. Must be list/tuple/set, and |
|
1174 | The default value for the Trait. Must be list/tuple/set, and | |
1170 | will be cast to the container type. |
|
1175 | will be cast to the container type. | |
1171 |
|
1176 | |||
1172 | allow_none : Bool [ default True ] |
|
1177 | allow_none : Bool [ default True ] | |
1173 | Whether to allow the value to be None |
|
1178 | Whether to allow the value to be None | |
1174 |
|
1179 | |||
1175 | **metadata : any |
|
1180 | **metadata : any | |
1176 | further keys for extensions to the Trait (e.g. config) |
|
1181 | further keys for extensions to the Trait (e.g. config) | |
1177 |
|
1182 | |||
1178 | """ |
|
1183 | """ | |
1179 | # allow List([values]): |
|
1184 | # allow List([values]): | |
1180 | if default_value is None and not is_trait(trait): |
|
1185 | if default_value is None and not is_trait(trait): | |
1181 | default_value = trait |
|
1186 | default_value = trait | |
1182 | trait = None |
|
1187 | trait = None | |
1183 |
|
1188 | |||
1184 | if default_value is None: |
|
1189 | if default_value is None: | |
1185 | args = () |
|
1190 | args = () | |
1186 | elif isinstance(default_value, self._valid_defaults): |
|
1191 | elif isinstance(default_value, self._valid_defaults): | |
1187 | args = (default_value,) |
|
1192 | args = (default_value,) | |
1188 | else: |
|
1193 | else: | |
1189 | raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value)) |
|
1194 | raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value)) | |
1190 |
|
1195 | |||
1191 | if is_trait(trait): |
|
1196 | if is_trait(trait): | |
1192 | self._trait = trait() if isinstance(trait, type) else trait |
|
1197 | self._trait = trait() if isinstance(trait, type) else trait | |
1193 | self._trait.name = 'element' |
|
1198 | self._trait.name = 'element' | |
1194 | elif trait is not None: |
|
1199 | elif trait is not None: | |
1195 | raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait)) |
|
1200 | raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait)) | |
1196 |
|
1201 | |||
1197 | super(Container,self).__init__(klass=self.klass, args=args, |
|
1202 | super(Container,self).__init__(klass=self.klass, args=args, | |
1198 | allow_none=allow_none, **metadata) |
|
1203 | allow_none=allow_none, **metadata) | |
1199 |
|
1204 | |||
1200 | def element_error(self, obj, element, validator): |
|
1205 | def element_error(self, obj, element, validator): | |
1201 | e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \ |
|
1206 | e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \ | |
1202 | % (self.name, class_of(obj), validator.info(), repr_type(element)) |
|
1207 | % (self.name, class_of(obj), validator.info(), repr_type(element)) | |
1203 | raise TraitError(e) |
|
1208 | raise TraitError(e) | |
1204 |
|
1209 | |||
1205 | def validate(self, obj, value): |
|
1210 | def validate(self, obj, value): | |
1206 | value = super(Container, self).validate(obj, value) |
|
1211 | value = super(Container, self).validate(obj, value) | |
1207 | if value is None: |
|
1212 | if value is None: | |
1208 | return value |
|
1213 | return value | |
1209 |
|
1214 | |||
1210 | value = self.validate_elements(obj, value) |
|
1215 | value = self.validate_elements(obj, value) | |
1211 |
|
1216 | |||
1212 | return value |
|
1217 | return value | |
1213 |
|
1218 | |||
1214 | def validate_elements(self, obj, value): |
|
1219 | def validate_elements(self, obj, value): | |
1215 | validated = [] |
|
1220 | validated = [] | |
1216 | if self._trait is None or isinstance(self._trait, Any): |
|
1221 | if self._trait is None or isinstance(self._trait, Any): | |
1217 | return value |
|
1222 | return value | |
1218 | for v in value: |
|
1223 | for v in value: | |
1219 | try: |
|
1224 | try: | |
1220 | v = self._trait.validate(obj, v) |
|
1225 | v = self._trait.validate(obj, v) | |
1221 | except TraitError: |
|
1226 | except TraitError: | |
1222 | self.element_error(obj, v, self._trait) |
|
1227 | self.element_error(obj, v, self._trait) | |
1223 | else: |
|
1228 | else: | |
1224 | validated.append(v) |
|
1229 | validated.append(v) | |
1225 | return self.klass(validated) |
|
1230 | return self.klass(validated) | |
1226 |
|
1231 | |||
1227 |
|
1232 | |||
1228 | class List(Container): |
|
1233 | class List(Container): | |
1229 | """An instance of a Python list.""" |
|
1234 | """An instance of a Python list.""" | |
1230 | klass = list |
|
1235 | klass = list | |
1231 |
|
1236 | |||
1232 | def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize, |
|
1237 | def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize, | |
1233 | allow_none=True, **metadata): |
|
1238 | allow_none=True, **metadata): | |
1234 | """Create a List trait type from a list, set, or tuple. |
|
1239 | """Create a List trait type from a list, set, or tuple. | |
1235 |
|
1240 | |||
1236 | The default value is created by doing ``List(default_value)``, |
|
1241 | The default value is created by doing ``List(default_value)``, | |
1237 | which creates a copy of the ``default_value``. |
|
1242 | which creates a copy of the ``default_value``. | |
1238 |
|
1243 | |||
1239 | ``trait`` can be specified, which restricts the type of elements |
|
1244 | ``trait`` can be specified, which restricts the type of elements | |
1240 | in the container to that TraitType. |
|
1245 | in the container to that TraitType. | |
1241 |
|
1246 | |||
1242 | If only one arg is given and it is not a Trait, it is taken as |
|
1247 | If only one arg is given and it is not a Trait, it is taken as | |
1243 | ``default_value``: |
|
1248 | ``default_value``: | |
1244 |
|
1249 | |||
1245 | ``c = List([1,2,3])`` |
|
1250 | ``c = List([1,2,3])`` | |
1246 |
|
1251 | |||
1247 | Parameters |
|
1252 | Parameters | |
1248 | ---------- |
|
1253 | ---------- | |
1249 |
|
1254 | |||
1250 | trait : TraitType [ optional ] |
|
1255 | trait : TraitType [ optional ] | |
1251 | the type for restricting the contents of the Container. If unspecified, |
|
1256 | the type for restricting the contents of the Container. If unspecified, | |
1252 | types are not checked. |
|
1257 | types are not checked. | |
1253 |
|
1258 | |||
1254 | default_value : SequenceType [ optional ] |
|
1259 | default_value : SequenceType [ optional ] | |
1255 | The default value for the Trait. Must be list/tuple/set, and |
|
1260 | The default value for the Trait. Must be list/tuple/set, and | |
1256 | will be cast to the container type. |
|
1261 | will be cast to the container type. | |
1257 |
|
1262 | |||
1258 | minlen : Int [ default 0 ] |
|
1263 | minlen : Int [ default 0 ] | |
1259 | The minimum length of the input list |
|
1264 | The minimum length of the input list | |
1260 |
|
1265 | |||
1261 | maxlen : Int [ default sys.maxsize ] |
|
1266 | maxlen : Int [ default sys.maxsize ] | |
1262 | The maximum length of the input list |
|
1267 | The maximum length of the input list | |
1263 |
|
1268 | |||
1264 | allow_none : Bool [ default True ] |
|
1269 | allow_none : Bool [ default True ] | |
1265 | Whether to allow the value to be None |
|
1270 | Whether to allow the value to be None | |
1266 |
|
1271 | |||
1267 | **metadata : any |
|
1272 | **metadata : any | |
1268 | further keys for extensions to the Trait (e.g. config) |
|
1273 | further keys for extensions to the Trait (e.g. config) | |
1269 |
|
1274 | |||
1270 | """ |
|
1275 | """ | |
1271 | self._minlen = minlen |
|
1276 | self._minlen = minlen | |
1272 | self._maxlen = maxlen |
|
1277 | self._maxlen = maxlen | |
1273 | super(List, self).__init__(trait=trait, default_value=default_value, |
|
1278 | super(List, self).__init__(trait=trait, default_value=default_value, | |
1274 | allow_none=allow_none, **metadata) |
|
1279 | allow_none=allow_none, **metadata) | |
1275 |
|
1280 | |||
1276 | def length_error(self, obj, value): |
|
1281 | def length_error(self, obj, value): | |
1277 | e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \ |
|
1282 | e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \ | |
1278 | % (self.name, class_of(obj), self._minlen, self._maxlen, value) |
|
1283 | % (self.name, class_of(obj), self._minlen, self._maxlen, value) | |
1279 | raise TraitError(e) |
|
1284 | raise TraitError(e) | |
1280 |
|
1285 | |||
1281 | def validate_elements(self, obj, value): |
|
1286 | def validate_elements(self, obj, value): | |
1282 | length = len(value) |
|
1287 | length = len(value) | |
1283 | if length < self._minlen or length > self._maxlen: |
|
1288 | if length < self._minlen or length > self._maxlen: | |
1284 | self.length_error(obj, value) |
|
1289 | self.length_error(obj, value) | |
1285 |
|
1290 | |||
1286 | return super(List, self).validate_elements(obj, value) |
|
1291 | return super(List, self).validate_elements(obj, value) | |
1287 |
|
1292 | |||
1288 |
|
1293 | |||
1289 | class Set(Container): |
|
1294 | class Set(Container): | |
1290 | """An instance of a Python set.""" |
|
1295 | """An instance of a Python set.""" | |
1291 | klass = set |
|
1296 | klass = set | |
1292 |
|
1297 | |||
1293 | class Tuple(Container): |
|
1298 | class Tuple(Container): | |
1294 | """An instance of a Python tuple.""" |
|
1299 | """An instance of a Python tuple.""" | |
1295 | klass = tuple |
|
1300 | klass = tuple | |
1296 |
|
1301 | |||
1297 | def __init__(self, *traits, **metadata): |
|
1302 | def __init__(self, *traits, **metadata): | |
1298 | """Tuple(*traits, default_value=None, allow_none=True, **medatata) |
|
1303 | """Tuple(*traits, default_value=None, allow_none=True, **medatata) | |
1299 |
|
1304 | |||
1300 | Create a tuple from a list, set, or tuple. |
|
1305 | Create a tuple from a list, set, or tuple. | |
1301 |
|
1306 | |||
1302 | Create a fixed-type tuple with Traits: |
|
1307 | Create a fixed-type tuple with Traits: | |
1303 |
|
1308 | |||
1304 | ``t = Tuple(Int, Str, CStr)`` |
|
1309 | ``t = Tuple(Int, Str, CStr)`` | |
1305 |
|
1310 | |||
1306 | would be length 3, with Int,Str,CStr for each element. |
|
1311 | would be length 3, with Int,Str,CStr for each element. | |
1307 |
|
1312 | |||
1308 | If only one arg is given and it is not a Trait, it is taken as |
|
1313 | If only one arg is given and it is not a Trait, it is taken as | |
1309 | default_value: |
|
1314 | default_value: | |
1310 |
|
1315 | |||
1311 | ``t = Tuple((1,2,3))`` |
|
1316 | ``t = Tuple((1,2,3))`` | |
1312 |
|
1317 | |||
1313 | Otherwise, ``default_value`` *must* be specified by keyword. |
|
1318 | Otherwise, ``default_value`` *must* be specified by keyword. | |
1314 |
|
1319 | |||
1315 | Parameters |
|
1320 | Parameters | |
1316 | ---------- |
|
1321 | ---------- | |
1317 |
|
1322 | |||
1318 | *traits : TraitTypes [ optional ] |
|
1323 | *traits : TraitTypes [ optional ] | |
1319 | the tsype for restricting the contents of the Tuple. If unspecified, |
|
1324 | the tsype for restricting the contents of the Tuple. If unspecified, | |
1320 | types are not checked. If specified, then each positional argument |
|
1325 | types are not checked. If specified, then each positional argument | |
1321 | corresponds to an element of the tuple. Tuples defined with traits |
|
1326 | corresponds to an element of the tuple. Tuples defined with traits | |
1322 | are of fixed length. |
|
1327 | are of fixed length. | |
1323 |
|
1328 | |||
1324 | default_value : SequenceType [ optional ] |
|
1329 | default_value : SequenceType [ optional ] | |
1325 | The default value for the Tuple. Must be list/tuple/set, and |
|
1330 | The default value for the Tuple. Must be list/tuple/set, and | |
1326 | will be cast to a tuple. If `traits` are specified, the |
|
1331 | will be cast to a tuple. If `traits` are specified, the | |
1327 | `default_value` must conform to the shape and type they specify. |
|
1332 | `default_value` must conform to the shape and type they specify. | |
1328 |
|
1333 | |||
1329 | allow_none : Bool [ default True ] |
|
1334 | allow_none : Bool [ default True ] | |
1330 | Whether to allow the value to be None |
|
1335 | Whether to allow the value to be None | |
1331 |
|
1336 | |||
1332 | **metadata : any |
|
1337 | **metadata : any | |
1333 | further keys for extensions to the Trait (e.g. config) |
|
1338 | further keys for extensions to the Trait (e.g. config) | |
1334 |
|
1339 | |||
1335 | """ |
|
1340 | """ | |
1336 | default_value = metadata.pop('default_value', None) |
|
1341 | default_value = metadata.pop('default_value', None) | |
1337 | allow_none = metadata.pop('allow_none', True) |
|
1342 | allow_none = metadata.pop('allow_none', True) | |
1338 |
|
1343 | |||
1339 | # allow Tuple((values,)): |
|
1344 | # allow Tuple((values,)): | |
1340 | if len(traits) == 1 and default_value is None and not is_trait(traits[0]): |
|
1345 | if len(traits) == 1 and default_value is None and not is_trait(traits[0]): | |
1341 | default_value = traits[0] |
|
1346 | default_value = traits[0] | |
1342 | traits = () |
|
1347 | traits = () | |
1343 |
|
1348 | |||
1344 | if default_value is None: |
|
1349 | if default_value is None: | |
1345 | args = () |
|
1350 | args = () | |
1346 | elif isinstance(default_value, self._valid_defaults): |
|
1351 | elif isinstance(default_value, self._valid_defaults): | |
1347 | args = (default_value,) |
|
1352 | args = (default_value,) | |
1348 | else: |
|
1353 | else: | |
1349 | raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value)) |
|
1354 | raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value)) | |
1350 |
|
1355 | |||
1351 | self._traits = [] |
|
1356 | self._traits = [] | |
1352 | for trait in traits: |
|
1357 | for trait in traits: | |
1353 | t = trait() if isinstance(trait, type) else trait |
|
1358 | t = trait() if isinstance(trait, type) else trait | |
1354 | t.name = 'element' |
|
1359 | t.name = 'element' | |
1355 | self._traits.append(t) |
|
1360 | self._traits.append(t) | |
1356 |
|
1361 | |||
1357 | if self._traits and default_value is None: |
|
1362 | if self._traits and default_value is None: | |
1358 | # don't allow default to be an empty container if length is specified |
|
1363 | # don't allow default to be an empty container if length is specified | |
1359 | args = None |
|
1364 | args = None | |
1360 | super(Container,self).__init__(klass=self.klass, args=args, |
|
1365 | super(Container,self).__init__(klass=self.klass, args=args, | |
1361 | allow_none=allow_none, **metadata) |
|
1366 | allow_none=allow_none, **metadata) | |
1362 |
|
1367 | |||
1363 | def validate_elements(self, obj, value): |
|
1368 | def validate_elements(self, obj, value): | |
1364 | if not self._traits: |
|
1369 | if not self._traits: | |
1365 | # nothing to validate |
|
1370 | # nothing to validate | |
1366 | return value |
|
1371 | return value | |
1367 | if len(value) != len(self._traits): |
|
1372 | if len(value) != len(self._traits): | |
1368 | e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \ |
|
1373 | e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \ | |
1369 | % (self.name, class_of(obj), len(self._traits), repr_type(value)) |
|
1374 | % (self.name, class_of(obj), len(self._traits), repr_type(value)) | |
1370 | raise TraitError(e) |
|
1375 | raise TraitError(e) | |
1371 |
|
1376 | |||
1372 | validated = [] |
|
1377 | validated = [] | |
1373 | for t,v in zip(self._traits, value): |
|
1378 | for t,v in zip(self._traits, value): | |
1374 | try: |
|
1379 | try: | |
1375 | v = t.validate(obj, v) |
|
1380 | v = t.validate(obj, v) | |
1376 | except TraitError: |
|
1381 | except TraitError: | |
1377 | self.element_error(obj, v, t) |
|
1382 | self.element_error(obj, v, t) | |
1378 | else: |
|
1383 | else: | |
1379 | validated.append(v) |
|
1384 | validated.append(v) | |
1380 | return tuple(validated) |
|
1385 | return tuple(validated) | |
1381 |
|
1386 | |||
1382 |
|
1387 | |||
1383 | class Dict(Instance): |
|
1388 | class Dict(Instance): | |
1384 | """An instance of a Python dict.""" |
|
1389 | """An instance of a Python dict.""" | |
1385 |
|
1390 | |||
1386 | def __init__(self, default_value=None, allow_none=True, **metadata): |
|
1391 | def __init__(self, default_value=None, allow_none=True, **metadata): | |
1387 | """Create a dict trait type from a dict. |
|
1392 | """Create a dict trait type from a dict. | |
1388 |
|
1393 | |||
1389 | The default value is created by doing ``dict(default_value)``, |
|
1394 | The default value is created by doing ``dict(default_value)``, | |
1390 | which creates a copy of the ``default_value``. |
|
1395 | which creates a copy of the ``default_value``. | |
1391 | """ |
|
1396 | """ | |
1392 | if default_value is None: |
|
1397 | if default_value is None: | |
1393 | args = ((),) |
|
1398 | args = ((),) | |
1394 | elif isinstance(default_value, dict): |
|
1399 | elif isinstance(default_value, dict): | |
1395 | args = (default_value,) |
|
1400 | args = (default_value,) | |
1396 | elif isinstance(default_value, SequenceTypes): |
|
1401 | elif isinstance(default_value, SequenceTypes): | |
1397 | args = (default_value,) |
|
1402 | args = (default_value,) | |
1398 | else: |
|
1403 | else: | |
1399 | raise TypeError('default value of Dict was %s' % default_value) |
|
1404 | raise TypeError('default value of Dict was %s' % default_value) | |
1400 |
|
1405 | |||
1401 | super(Dict,self).__init__(klass=dict, args=args, |
|
1406 | super(Dict,self).__init__(klass=dict, args=args, | |
1402 | allow_none=allow_none, **metadata) |
|
1407 | allow_none=allow_none, **metadata) | |
1403 |
|
1408 | |||
1404 | class TCPAddress(TraitType): |
|
1409 | class TCPAddress(TraitType): | |
1405 | """A trait for an (ip, port) tuple. |
|
1410 | """A trait for an (ip, port) tuple. | |
1406 |
|
1411 | |||
1407 | This allows for both IPv4 IP addresses as well as hostnames. |
|
1412 | This allows for both IPv4 IP addresses as well as hostnames. | |
1408 | """ |
|
1413 | """ | |
1409 |
|
1414 | |||
1410 | default_value = ('127.0.0.1', 0) |
|
1415 | default_value = ('127.0.0.1', 0) | |
1411 | info_text = 'an (ip, port) tuple' |
|
1416 | info_text = 'an (ip, port) tuple' | |
1412 |
|
1417 | |||
1413 | def validate(self, obj, value): |
|
1418 | def validate(self, obj, value): | |
1414 | if isinstance(value, tuple): |
|
1419 | if isinstance(value, tuple): | |
1415 | if len(value) == 2: |
|
1420 | if len(value) == 2: | |
1416 | if isinstance(value[0], basestring) and isinstance(value[1], int): |
|
1421 | if isinstance(value[0], basestring) and isinstance(value[1], int): | |
1417 | port = value[1] |
|
1422 | port = value[1] | |
1418 | if port >= 0 and port <= 65535: |
|
1423 | if port >= 0 and port <= 65535: | |
1419 | return value |
|
1424 | return value | |
1420 | self.error(obj, value) |
|
1425 | self.error(obj, value) | |
1421 |
|
1426 | |||
1422 | class CRegExp(TraitType): |
|
1427 | class CRegExp(TraitType): | |
1423 | """A casting compiled regular expression trait. |
|
1428 | """A casting compiled regular expression trait. | |
1424 |
|
1429 | |||
1425 | Accepts both strings and compiled regular expressions. The resulting |
|
1430 | Accepts both strings and compiled regular expressions. The resulting | |
1426 | attribute will be a compiled regular expression.""" |
|
1431 | attribute will be a compiled regular expression.""" | |
1427 |
|
1432 | |||
1428 | info_text = 'a regular expression' |
|
1433 | info_text = 'a regular expression' | |
1429 |
|
1434 | |||
1430 | def validate(self, obj, value): |
|
1435 | def validate(self, obj, value): | |
1431 | try: |
|
1436 | try: | |
1432 | return re.compile(value) |
|
1437 | return re.compile(value) | |
1433 | except: |
|
1438 | except: | |
1434 | self.error(obj, value) |
|
1439 | self.error(obj, value) |
@@ -1,417 +1,412 b'' | |||||
1 | """Attempt to generate templates for module reference with Sphinx |
|
1 | """Attempt to generate templates for module reference with Sphinx | |
2 |
|
2 | |||
3 | XXX - we exclude extension modules |
|
3 | XXX - we exclude extension modules | |
4 |
|
4 | |||
5 | To include extension modules, first identify them as valid in the |
|
5 | To include extension modules, first identify them as valid in the | |
6 | ``_uri2path`` method, then handle them in the ``_parse_module`` script. |
|
6 | ``_uri2path`` method, then handle them in the ``_parse_module`` script. | |
7 |
|
7 | |||
8 | We get functions and classes by parsing the text of .py files. |
|
8 | We get functions and classes by parsing the text of .py files. | |
9 | Alternatively we could import the modules for discovery, and we'd have |
|
9 | Alternatively we could import the modules for discovery, and we'd have | |
10 | to do that for extension modules. This would involve changing the |
|
10 | to do that for extension modules. This would involve changing the | |
11 | ``_parse_module`` method to work via import and introspection, and |
|
11 | ``_parse_module`` method to work via import and introspection, and | |
12 | might involve changing ``discover_modules`` (which determines which |
|
12 | might involve changing ``discover_modules`` (which determines which | |
13 | files are modules, and therefore which module URIs will be passed to |
|
13 | files are modules, and therefore which module URIs will be passed to | |
14 | ``_parse_module``). |
|
14 | ``_parse_module``). | |
15 |
|
15 | |||
16 | NOTE: this is a modified version of a script originally shipped with the |
|
16 | NOTE: this is a modified version of a script originally shipped with the | |
17 | PyMVPA project, which we've adapted for NIPY use. PyMVPA is an MIT-licensed |
|
17 | PyMVPA project, which we've adapted for NIPY use. PyMVPA is an MIT-licensed | |
18 | project.""" |
|
18 | project.""" | |
19 |
|
19 | |||
20 | # Stdlib imports |
|
20 | # Stdlib imports | |
21 | import ast |
|
21 | import ast | |
22 | import os |
|
22 | import os | |
23 | import re |
|
23 | import re | |
24 |
|
24 | |||
25 | class Obj(object): |
|
25 | class Obj(object): | |
26 | '''Namespace to hold arbitrary information.''' |
|
26 | '''Namespace to hold arbitrary information.''' | |
27 | def __init__(self, **kwargs): |
|
27 | def __init__(self, **kwargs): | |
28 | for k, v in kwargs.items(): |
|
28 | for k, v in kwargs.items(): | |
29 | setattr(self, k, v) |
|
29 | setattr(self, k, v) | |
30 |
|
30 | |||
31 | # Functions and classes |
|
31 | # Functions and classes | |
32 | class ApiDocWriter(object): |
|
32 | class ApiDocWriter(object): | |
33 | ''' Class for automatic detection and parsing of API docs |
|
33 | ''' Class for automatic detection and parsing of API docs | |
34 | to Sphinx-parsable reST format''' |
|
34 | to Sphinx-parsable reST format''' | |
35 |
|
35 | |||
36 | # only separating first two levels |
|
36 | # only separating first two levels | |
37 | rst_section_levels = ['*', '=', '-', '~', '^'] |
|
37 | rst_section_levels = ['*', '=', '-', '~', '^'] | |
38 |
|
38 | |||
39 | def __init__(self, |
|
39 | def __init__(self, | |
40 | package_name, |
|
40 | package_name, | |
41 | rst_extension='.rst', |
|
41 | rst_extension='.rst', | |
42 | package_skip_patterns=None, |
|
42 | package_skip_patterns=None, | |
43 | module_skip_patterns=None, |
|
43 | module_skip_patterns=None, | |
44 | ): |
|
44 | ): | |
45 | ''' Initialize package for parsing |
|
45 | ''' Initialize package for parsing | |
46 |
|
46 | |||
47 | Parameters |
|
47 | Parameters | |
48 | ---------- |
|
48 | ---------- | |
49 | package_name : string |
|
49 | package_name : string | |
50 | Name of the top-level package. *package_name* must be the |
|
50 | Name of the top-level package. *package_name* must be the | |
51 | name of an importable package |
|
51 | name of an importable package | |
52 | rst_extension : string, optional |
|
52 | rst_extension : string, optional | |
53 | Extension for reST files, default '.rst' |
|
53 | Extension for reST files, default '.rst' | |
54 | package_skip_patterns : None or sequence of {strings, regexps} |
|
54 | package_skip_patterns : None or sequence of {strings, regexps} | |
55 | Sequence of strings giving URIs of packages to be excluded |
|
55 | Sequence of strings giving URIs of packages to be excluded | |
56 | Operates on the package path, starting at (including) the |
|
56 | Operates on the package path, starting at (including) the | |
57 | first dot in the package path, after *package_name* - so, |
|
57 | first dot in the package path, after *package_name* - so, | |
58 | if *package_name* is ``sphinx``, then ``sphinx.util`` will |
|
58 | if *package_name* is ``sphinx``, then ``sphinx.util`` will | |
59 | result in ``.util`` being passed for earching by these |
|
59 | result in ``.util`` being passed for earching by these | |
60 | regexps. If is None, gives default. Default is: |
|
60 | regexps. If is None, gives default. Default is: | |
61 | ['\.tests$'] |
|
61 | ['\.tests$'] | |
62 | module_skip_patterns : None or sequence |
|
62 | module_skip_patterns : None or sequence | |
63 | Sequence of strings giving URIs of modules to be excluded |
|
63 | Sequence of strings giving URIs of modules to be excluded | |
64 | Operates on the module name including preceding URI path, |
|
64 | Operates on the module name including preceding URI path, | |
65 | back to the first dot after *package_name*. For example |
|
65 | back to the first dot after *package_name*. For example | |
66 | ``sphinx.util.console`` results in the string to search of |
|
66 | ``sphinx.util.console`` results in the string to search of | |
67 | ``.util.console`` |
|
67 | ``.util.console`` | |
68 | If is None, gives default. Default is: |
|
68 | If is None, gives default. Default is: | |
69 | ['\.setup$', '\._'] |
|
69 | ['\.setup$', '\._'] | |
70 | ''' |
|
70 | ''' | |
71 | if package_skip_patterns is None: |
|
71 | if package_skip_patterns is None: | |
72 | package_skip_patterns = ['\\.tests$'] |
|
72 | package_skip_patterns = ['\\.tests$'] | |
73 | if module_skip_patterns is None: |
|
73 | if module_skip_patterns is None: | |
74 | module_skip_patterns = ['\\.setup$', '\\._'] |
|
74 | module_skip_patterns = ['\\.setup$', '\\._'] | |
75 | self.package_name = package_name |
|
75 | self.package_name = package_name | |
76 | self.rst_extension = rst_extension |
|
76 | self.rst_extension = rst_extension | |
77 | self.package_skip_patterns = package_skip_patterns |
|
77 | self.package_skip_patterns = package_skip_patterns | |
78 | self.module_skip_patterns = module_skip_patterns |
|
78 | self.module_skip_patterns = module_skip_patterns | |
79 |
|
79 | |||
80 | def get_package_name(self): |
|
80 | def get_package_name(self): | |
81 | return self._package_name |
|
81 | return self._package_name | |
82 |
|
82 | |||
83 | def set_package_name(self, package_name): |
|
83 | def set_package_name(self, package_name): | |
84 | ''' Set package_name |
|
84 | ''' Set package_name | |
85 |
|
85 | |||
86 | >>> docwriter = ApiDocWriter('sphinx') |
|
86 | >>> docwriter = ApiDocWriter('sphinx') | |
87 | >>> import sphinx |
|
87 | >>> import sphinx | |
88 | >>> docwriter.root_path == sphinx.__path__[0] |
|
88 | >>> docwriter.root_path == sphinx.__path__[0] | |
89 | True |
|
89 | True | |
90 | >>> docwriter.package_name = 'docutils' |
|
90 | >>> docwriter.package_name = 'docutils' | |
91 | >>> import docutils |
|
91 | >>> import docutils | |
92 | >>> docwriter.root_path == docutils.__path__[0] |
|
92 | >>> docwriter.root_path == docutils.__path__[0] | |
93 | True |
|
93 | True | |
94 | ''' |
|
94 | ''' | |
95 | # It's also possible to imagine caching the module parsing here |
|
95 | # It's also possible to imagine caching the module parsing here | |
96 | self._package_name = package_name |
|
96 | self._package_name = package_name | |
97 | self.root_module = __import__(package_name) |
|
97 | self.root_module = __import__(package_name) | |
98 | self.root_path = self.root_module.__path__[0] |
|
98 | self.root_path = self.root_module.__path__[0] | |
99 | self.written_modules = None |
|
99 | self.written_modules = None | |
100 |
|
100 | |||
101 | package_name = property(get_package_name, set_package_name, None, |
|
101 | package_name = property(get_package_name, set_package_name, None, | |
102 | 'get/set package_name') |
|
102 | 'get/set package_name') | |
103 |
|
103 | |||
104 | def _uri2path(self, uri): |
|
104 | def _uri2path(self, uri): | |
105 | ''' Convert uri to absolute filepath |
|
105 | ''' Convert uri to absolute filepath | |
106 |
|
106 | |||
107 | Parameters |
|
107 | Parameters | |
108 | ---------- |
|
108 | ---------- | |
109 | uri : string |
|
109 | uri : string | |
110 | URI of python module to return path for |
|
110 | URI of python module to return path for | |
111 |
|
111 | |||
112 | Returns |
|
112 | Returns | |
113 | ------- |
|
113 | ------- | |
114 | path : None or string |
|
114 | path : None or string | |
115 | Returns None if there is no valid path for this URI |
|
115 | Returns None if there is no valid path for this URI | |
116 | Otherwise returns absolute file system path for URI |
|
116 | Otherwise returns absolute file system path for URI | |
117 |
|
117 | |||
118 | Examples |
|
118 | Examples | |
119 | -------- |
|
119 | -------- | |
120 | >>> docwriter = ApiDocWriter('sphinx') |
|
120 | >>> docwriter = ApiDocWriter('sphinx') | |
121 | >>> import sphinx |
|
121 | >>> import sphinx | |
122 | >>> modpath = sphinx.__path__[0] |
|
122 | >>> modpath = sphinx.__path__[0] | |
123 | >>> res = docwriter._uri2path('sphinx.builder') |
|
123 | >>> res = docwriter._uri2path('sphinx.builder') | |
124 | >>> res == os.path.join(modpath, 'builder.py') |
|
124 | >>> res == os.path.join(modpath, 'builder.py') | |
125 | True |
|
125 | True | |
126 | >>> res = docwriter._uri2path('sphinx') |
|
126 | >>> res = docwriter._uri2path('sphinx') | |
127 | >>> res == os.path.join(modpath, '__init__.py') |
|
127 | >>> res == os.path.join(modpath, '__init__.py') | |
128 | True |
|
128 | True | |
129 | >>> docwriter._uri2path('sphinx.does_not_exist') |
|
129 | >>> docwriter._uri2path('sphinx.does_not_exist') | |
130 |
|
130 | |||
131 | ''' |
|
131 | ''' | |
132 | if uri == self.package_name: |
|
132 | if uri == self.package_name: | |
133 | return os.path.join(self.root_path, '__init__.py') |
|
133 | return os.path.join(self.root_path, '__init__.py') | |
134 | path = uri.replace('.', os.path.sep) |
|
134 | path = uri.replace('.', os.path.sep) | |
135 | path = path.replace(self.package_name + os.path.sep, '') |
|
135 | path = path.replace(self.package_name + os.path.sep, '') | |
136 | path = os.path.join(self.root_path, path) |
|
136 | path = os.path.join(self.root_path, path) | |
137 | # XXX maybe check for extensions as well? |
|
137 | # XXX maybe check for extensions as well? | |
138 | if os.path.exists(path + '.py'): # file |
|
138 | if os.path.exists(path + '.py'): # file | |
139 | path += '.py' |
|
139 | path += '.py' | |
140 | elif os.path.exists(os.path.join(path, '__init__.py')): |
|
140 | elif os.path.exists(os.path.join(path, '__init__.py')): | |
141 | path = os.path.join(path, '__init__.py') |
|
141 | path = os.path.join(path, '__init__.py') | |
142 | else: |
|
142 | else: | |
143 | return None |
|
143 | return None | |
144 | return path |
|
144 | return path | |
145 |
|
145 | |||
146 | def _path2uri(self, dirpath): |
|
146 | def _path2uri(self, dirpath): | |
147 | ''' Convert directory path to uri ''' |
|
147 | ''' Convert directory path to uri ''' | |
148 | relpath = dirpath.replace(self.root_path, self.package_name) |
|
148 | relpath = dirpath.replace(self.root_path, self.package_name) | |
149 | if relpath.startswith(os.path.sep): |
|
149 | if relpath.startswith(os.path.sep): | |
150 | relpath = relpath[1:] |
|
150 | relpath = relpath[1:] | |
151 | return relpath.replace(os.path.sep, '.') |
|
151 | return relpath.replace(os.path.sep, '.') | |
152 |
|
152 | |||
153 | def _parse_module(self, uri): |
|
153 | def _parse_module(self, uri): | |
154 | ''' Parse module defined in *uri* ''' |
|
154 | ''' Parse module defined in *uri* ''' | |
155 | filename = self._uri2path(uri) |
|
155 | filename = self._uri2path(uri) | |
156 | if filename is None: |
|
156 | if filename is None: | |
157 | # nothing that we could handle here. |
|
157 | # nothing that we could handle here. | |
158 | return ([],[]) |
|
158 | return ([],[]) | |
159 | with open(filename, 'rb') as f: |
|
159 | with open(filename, 'rb') as f: | |
160 | mod = ast.parse(f.read()) |
|
160 | mod = ast.parse(f.read()) | |
161 | return self._find_functions_classes(mod) |
|
161 | return self._find_functions_classes(mod) | |
162 |
|
162 | |||
163 | @staticmethod |
|
163 | @staticmethod | |
164 | def _find_functions_classes(mod): |
|
164 | def _find_functions_classes(mod): | |
165 | """Extract top-level functions and classes from a module AST. |
|
165 | """Extract top-level functions and classes from a module AST. | |
166 |
|
166 | |||
167 | Skips objects with an @undoc decorator, or a name starting with '_'. |
|
167 | Skips objects with an @undoc decorator, or a name starting with '_'. | |
168 | """ |
|
168 | """ | |
169 | def has_undoc_decorator(node): |
|
169 | def has_undoc_decorator(node): | |
170 | return any(isinstance(d, ast.Name) and d.id == 'undoc' \ |
|
170 | return any(isinstance(d, ast.Name) and d.id == 'undoc' \ | |
171 | for d in node.decorator_list) |
|
171 | for d in node.decorator_list) | |
172 |
|
172 | |||
173 | functions, classes = [], [] |
|
173 | functions, classes = [], [] | |
174 | for node in mod.body: |
|
174 | for node in mod.body: | |
175 | if isinstance(node, ast.FunctionDef) and \ |
|
175 | if isinstance(node, ast.FunctionDef) and \ | |
176 | not node.name.startswith('_') and \ |
|
176 | not node.name.startswith('_') and \ | |
177 | not has_undoc_decorator(node): |
|
177 | not has_undoc_decorator(node): | |
178 | functions.append(node.name) |
|
178 | functions.append(node.name) | |
179 | elif isinstance(node, ast.ClassDef) and \ |
|
179 | elif isinstance(node, ast.ClassDef) and \ | |
180 | not node.name.startswith('_') and \ |
|
180 | not node.name.startswith('_') and \ | |
181 | not has_undoc_decorator(node): |
|
181 | not has_undoc_decorator(node): | |
182 | cls = Obj(name=node.name) |
|
182 | cls = Obj(name=node.name) | |
183 | cls.has_init = any(isinstance(n, ast.FunctionDef) and \ |
|
183 | cls.has_init = any(isinstance(n, ast.FunctionDef) and \ | |
184 | n.name=='__init__' for n in node.body) |
|
184 | n.name=='__init__' for n in node.body) | |
185 | classes.append(cls) |
|
185 | classes.append(cls) | |
186 |
|
186 | |||
187 | return functions, classes |
|
187 | return functions, classes | |
188 |
|
188 | |||
189 | def generate_api_doc(self, uri): |
|
189 | def generate_api_doc(self, uri): | |
190 | '''Make autodoc documentation template string for a module |
|
190 | '''Make autodoc documentation template string for a module | |
191 |
|
191 | |||
192 | Parameters |
|
192 | Parameters | |
193 | ---------- |
|
193 | ---------- | |
194 | uri : string |
|
194 | uri : string | |
195 | python location of module - e.g 'sphinx.builder' |
|
195 | python location of module - e.g 'sphinx.builder' | |
196 |
|
196 | |||
197 | Returns |
|
197 | Returns | |
198 | ------- |
|
198 | ------- | |
199 | S : string |
|
199 | S : string | |
200 | Contents of API doc |
|
200 | Contents of API doc | |
201 | ''' |
|
201 | ''' | |
202 | # get the names of all classes and functions |
|
202 | # get the names of all classes and functions | |
203 | functions, classes = self._parse_module(uri) |
|
203 | functions, classes = self._parse_module(uri) | |
204 | if not len(functions) and not len(classes): |
|
204 | if not len(functions) and not len(classes): | |
205 | print 'WARNING: Empty -',uri # dbg |
|
205 | print 'WARNING: Empty -',uri # dbg | |
206 | return '' |
|
206 | return '' | |
207 |
|
207 | |||
208 | # Make a shorter version of the uri that omits the package name for |
|
208 | # Make a shorter version of the uri that omits the package name for | |
209 | # titles |
|
209 | # titles | |
210 | uri_short = re.sub(r'^%s\.' % self.package_name,'',uri) |
|
210 | uri_short = re.sub(r'^%s\.' % self.package_name,'',uri) | |
211 |
|
211 | |||
212 | ad = '.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n' |
|
212 | ad = '.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n' | |
213 |
|
213 | |||
214 | # Set the chapter title to read 'Module:' for all modules except for the |
|
214 | # Set the chapter title to read 'Module:' for all modules except for the | |
215 | # main packages |
|
215 | # main packages | |
216 | if '.' in uri: |
|
216 | if '.' in uri: | |
217 | chap_title = 'Module: :mod:`' + uri_short + '`' |
|
217 | chap_title = 'Module: :mod:`' + uri_short + '`' | |
218 | else: |
|
218 | else: | |
219 | chap_title = ':mod:`' + uri_short + '`' |
|
219 | chap_title = ':mod:`' + uri_short + '`' | |
220 | ad += chap_title + '\n' + self.rst_section_levels[1] * len(chap_title) |
|
220 | ad += chap_title + '\n' + self.rst_section_levels[1] * len(chap_title) | |
221 |
|
221 | |||
222 | if len(classes): |
|
|||
223 | ad += '\nInheritance diagram for ``%s``:\n\n' % uri |
|
|||
224 | ad += '.. inheritance-diagram:: %s \n' % uri |
|
|||
225 | ad += ' :parts: 3\n' |
|
|||
226 |
|
||||
227 | ad += '\n.. automodule:: ' + uri + '\n' |
|
222 | ad += '\n.. automodule:: ' + uri + '\n' | |
228 | ad += '\n.. currentmodule:: ' + uri + '\n' |
|
223 | ad += '\n.. currentmodule:: ' + uri + '\n' | |
229 | multi_class = len(classes) > 1 |
|
224 | multi_class = len(classes) > 1 | |
230 | multi_fx = len(functions) > 1 |
|
225 | multi_fx = len(functions) > 1 | |
231 | if multi_class: |
|
226 | if multi_class: | |
232 | ad += '\n' + 'Classes' + '\n' + \ |
|
227 | ad += '\n' + 'Classes' + '\n' + \ | |
233 | self.rst_section_levels[2] * 7 + '\n' |
|
228 | self.rst_section_levels[2] * 7 + '\n' | |
234 | elif len(classes) and multi_fx: |
|
229 | elif len(classes) and multi_fx: | |
235 | ad += '\n' + 'Class' + '\n' + \ |
|
230 | ad += '\n' + 'Class' + '\n' + \ | |
236 | self.rst_section_levels[2] * 5 + '\n' |
|
231 | self.rst_section_levels[2] * 5 + '\n' | |
237 | for c in classes: |
|
232 | for c in classes: | |
238 | ad += '\n:class:`' + c.name + '`\n' \ |
|
233 | ad += '\n:class:`' + c.name + '`\n' \ | |
239 | + self.rst_section_levels[multi_class + 2 ] * \ |
|
234 | + self.rst_section_levels[multi_class + 2 ] * \ | |
240 | (len(c.name)+9) + '\n\n' |
|
235 | (len(c.name)+9) + '\n\n' | |
241 | ad += '\n.. autoclass:: ' + c.name + '\n' |
|
236 | ad += '\n.. autoclass:: ' + c.name + '\n' | |
242 | # must NOT exclude from index to keep cross-refs working |
|
237 | # must NOT exclude from index to keep cross-refs working | |
243 | ad += ' :members:\n' \ |
|
238 | ad += ' :members:\n' \ | |
244 | ' :show-inheritance:\n' |
|
239 | ' :show-inheritance:\n' | |
245 | if c.has_init: |
|
240 | if c.has_init: | |
246 | ad += '\n .. automethod:: __init__\n' |
|
241 | ad += '\n .. automethod:: __init__\n' | |
247 | if multi_fx: |
|
242 | if multi_fx: | |
248 | ad += '\n' + 'Functions' + '\n' + \ |
|
243 | ad += '\n' + 'Functions' + '\n' + \ | |
249 | self.rst_section_levels[2] * 9 + '\n\n' |
|
244 | self.rst_section_levels[2] * 9 + '\n\n' | |
250 | elif len(functions) and multi_class: |
|
245 | elif len(functions) and multi_class: | |
251 | ad += '\n' + 'Function' + '\n' + \ |
|
246 | ad += '\n' + 'Function' + '\n' + \ | |
252 | self.rst_section_levels[2] * 8 + '\n\n' |
|
247 | self.rst_section_levels[2] * 8 + '\n\n' | |
253 | for f in functions: |
|
248 | for f in functions: | |
254 | # must NOT exclude from index to keep cross-refs working |
|
249 | # must NOT exclude from index to keep cross-refs working | |
255 | ad += '\n.. autofunction:: ' + uri + '.' + f + '\n\n' |
|
250 | ad += '\n.. autofunction:: ' + uri + '.' + f + '\n\n' | |
256 | return ad |
|
251 | return ad | |
257 |
|
252 | |||
258 | def _survives_exclude(self, matchstr, match_type): |
|
253 | def _survives_exclude(self, matchstr, match_type): | |
259 | ''' Returns True if *matchstr* does not match patterns |
|
254 | ''' Returns True if *matchstr* does not match patterns | |
260 |
|
255 | |||
261 | ``self.package_name`` removed from front of string if present |
|
256 | ``self.package_name`` removed from front of string if present | |
262 |
|
257 | |||
263 | Examples |
|
258 | Examples | |
264 | -------- |
|
259 | -------- | |
265 | >>> dw = ApiDocWriter('sphinx') |
|
260 | >>> dw = ApiDocWriter('sphinx') | |
266 | >>> dw._survives_exclude('sphinx.okpkg', 'package') |
|
261 | >>> dw._survives_exclude('sphinx.okpkg', 'package') | |
267 | True |
|
262 | True | |
268 | >>> dw.package_skip_patterns.append('^\\.badpkg$') |
|
263 | >>> dw.package_skip_patterns.append('^\\.badpkg$') | |
269 | >>> dw._survives_exclude('sphinx.badpkg', 'package') |
|
264 | >>> dw._survives_exclude('sphinx.badpkg', 'package') | |
270 | False |
|
265 | False | |
271 | >>> dw._survives_exclude('sphinx.badpkg', 'module') |
|
266 | >>> dw._survives_exclude('sphinx.badpkg', 'module') | |
272 | True |
|
267 | True | |
273 | >>> dw._survives_exclude('sphinx.badmod', 'module') |
|
268 | >>> dw._survives_exclude('sphinx.badmod', 'module') | |
274 | True |
|
269 | True | |
275 | >>> dw.module_skip_patterns.append('^\\.badmod$') |
|
270 | >>> dw.module_skip_patterns.append('^\\.badmod$') | |
276 | >>> dw._survives_exclude('sphinx.badmod', 'module') |
|
271 | >>> dw._survives_exclude('sphinx.badmod', 'module') | |
277 | False |
|
272 | False | |
278 | ''' |
|
273 | ''' | |
279 | if match_type == 'module': |
|
274 | if match_type == 'module': | |
280 | patterns = self.module_skip_patterns |
|
275 | patterns = self.module_skip_patterns | |
281 | elif match_type == 'package': |
|
276 | elif match_type == 'package': | |
282 | patterns = self.package_skip_patterns |
|
277 | patterns = self.package_skip_patterns | |
283 | else: |
|
278 | else: | |
284 | raise ValueError('Cannot interpret match type "%s"' |
|
279 | raise ValueError('Cannot interpret match type "%s"' | |
285 | % match_type) |
|
280 | % match_type) | |
286 | # Match to URI without package name |
|
281 | # Match to URI without package name | |
287 | L = len(self.package_name) |
|
282 | L = len(self.package_name) | |
288 | if matchstr[:L] == self.package_name: |
|
283 | if matchstr[:L] == self.package_name: | |
289 | matchstr = matchstr[L:] |
|
284 | matchstr = matchstr[L:] | |
290 | for pat in patterns: |
|
285 | for pat in patterns: | |
291 | try: |
|
286 | try: | |
292 | pat.search |
|
287 | pat.search | |
293 | except AttributeError: |
|
288 | except AttributeError: | |
294 | pat = re.compile(pat) |
|
289 | pat = re.compile(pat) | |
295 | if pat.search(matchstr): |
|
290 | if pat.search(matchstr): | |
296 | return False |
|
291 | return False | |
297 | return True |
|
292 | return True | |
298 |
|
293 | |||
299 | def discover_modules(self): |
|
294 | def discover_modules(self): | |
300 | ''' Return module sequence discovered from ``self.package_name`` |
|
295 | ''' Return module sequence discovered from ``self.package_name`` | |
301 |
|
296 | |||
302 |
|
297 | |||
303 | Parameters |
|
298 | Parameters | |
304 | ---------- |
|
299 | ---------- | |
305 | None |
|
300 | None | |
306 |
|
301 | |||
307 | Returns |
|
302 | Returns | |
308 | ------- |
|
303 | ------- | |
309 | mods : sequence |
|
304 | mods : sequence | |
310 | Sequence of module names within ``self.package_name`` |
|
305 | Sequence of module names within ``self.package_name`` | |
311 |
|
306 | |||
312 | Examples |
|
307 | Examples | |
313 | -------- |
|
308 | -------- | |
314 | >>> dw = ApiDocWriter('sphinx') |
|
309 | >>> dw = ApiDocWriter('sphinx') | |
315 | >>> mods = dw.discover_modules() |
|
310 | >>> mods = dw.discover_modules() | |
316 | >>> 'sphinx.util' in mods |
|
311 | >>> 'sphinx.util' in mods | |
317 | True |
|
312 | True | |
318 | >>> dw.package_skip_patterns.append('\.util$') |
|
313 | >>> dw.package_skip_patterns.append('\.util$') | |
319 | >>> 'sphinx.util' in dw.discover_modules() |
|
314 | >>> 'sphinx.util' in dw.discover_modules() | |
320 | False |
|
315 | False | |
321 | >>> |
|
316 | >>> | |
322 | ''' |
|
317 | ''' | |
323 | modules = [self.package_name] |
|
318 | modules = [self.package_name] | |
324 | # raw directory parsing |
|
319 | # raw directory parsing | |
325 | for dirpath, dirnames, filenames in os.walk(self.root_path): |
|
320 | for dirpath, dirnames, filenames in os.walk(self.root_path): | |
326 | # Check directory names for packages |
|
321 | # Check directory names for packages | |
327 | root_uri = self._path2uri(os.path.join(self.root_path, |
|
322 | root_uri = self._path2uri(os.path.join(self.root_path, | |
328 | dirpath)) |
|
323 | dirpath)) | |
329 | for dirname in dirnames[:]: # copy list - we modify inplace |
|
324 | for dirname in dirnames[:]: # copy list - we modify inplace | |
330 | package_uri = '.'.join((root_uri, dirname)) |
|
325 | package_uri = '.'.join((root_uri, dirname)) | |
331 | if (self._uri2path(package_uri) and |
|
326 | if (self._uri2path(package_uri) and | |
332 | self._survives_exclude(package_uri, 'package')): |
|
327 | self._survives_exclude(package_uri, 'package')): | |
333 | modules.append(package_uri) |
|
328 | modules.append(package_uri) | |
334 | else: |
|
329 | else: | |
335 | dirnames.remove(dirname) |
|
330 | dirnames.remove(dirname) | |
336 | # Check filenames for modules |
|
331 | # Check filenames for modules | |
337 | for filename in filenames: |
|
332 | for filename in filenames: | |
338 | module_name = filename[:-3] |
|
333 | module_name = filename[:-3] | |
339 | module_uri = '.'.join((root_uri, module_name)) |
|
334 | module_uri = '.'.join((root_uri, module_name)) | |
340 | if (self._uri2path(module_uri) and |
|
335 | if (self._uri2path(module_uri) and | |
341 | self._survives_exclude(module_uri, 'module')): |
|
336 | self._survives_exclude(module_uri, 'module')): | |
342 | modules.append(module_uri) |
|
337 | modules.append(module_uri) | |
343 | return sorted(modules) |
|
338 | return sorted(modules) | |
344 |
|
339 | |||
345 | def write_modules_api(self, modules,outdir): |
|
340 | def write_modules_api(self, modules,outdir): | |
346 | # write the list |
|
341 | # write the list | |
347 | written_modules = [] |
|
342 | written_modules = [] | |
348 | for m in modules: |
|
343 | for m in modules: | |
349 | api_str = self.generate_api_doc(m) |
|
344 | api_str = self.generate_api_doc(m) | |
350 | if not api_str: |
|
345 | if not api_str: | |
351 | continue |
|
346 | continue | |
352 | # write out to file |
|
347 | # write out to file | |
353 | outfile = os.path.join(outdir, |
|
348 | outfile = os.path.join(outdir, | |
354 | m + self.rst_extension) |
|
349 | m + self.rst_extension) | |
355 | fileobj = open(outfile, 'wt') |
|
350 | fileobj = open(outfile, 'wt') | |
356 | fileobj.write(api_str) |
|
351 | fileobj.write(api_str) | |
357 | fileobj.close() |
|
352 | fileobj.close() | |
358 | written_modules.append(m) |
|
353 | written_modules.append(m) | |
359 | self.written_modules = written_modules |
|
354 | self.written_modules = written_modules | |
360 |
|
355 | |||
361 | def write_api_docs(self, outdir): |
|
356 | def write_api_docs(self, outdir): | |
362 | """Generate API reST files. |
|
357 | """Generate API reST files. | |
363 |
|
358 | |||
364 | Parameters |
|
359 | Parameters | |
365 | ---------- |
|
360 | ---------- | |
366 | outdir : string |
|
361 | outdir : string | |
367 | Directory name in which to store files |
|
362 | Directory name in which to store files | |
368 | We create automatic filenames for each module |
|
363 | We create automatic filenames for each module | |
369 |
|
364 | |||
370 | Returns |
|
365 | Returns | |
371 | ------- |
|
366 | ------- | |
372 | None |
|
367 | None | |
373 |
|
368 | |||
374 | Notes |
|
369 | Notes | |
375 | ----- |
|
370 | ----- | |
376 | Sets self.written_modules to list of written modules |
|
371 | Sets self.written_modules to list of written modules | |
377 | """ |
|
372 | """ | |
378 | if not os.path.exists(outdir): |
|
373 | if not os.path.exists(outdir): | |
379 | os.mkdir(outdir) |
|
374 | os.mkdir(outdir) | |
380 | # compose list of modules |
|
375 | # compose list of modules | |
381 | modules = self.discover_modules() |
|
376 | modules = self.discover_modules() | |
382 | self.write_modules_api(modules,outdir) |
|
377 | self.write_modules_api(modules,outdir) | |
383 |
|
378 | |||
384 | def write_index(self, outdir, froot='gen', relative_to=None): |
|
379 | def write_index(self, outdir, froot='gen', relative_to=None): | |
385 | """Make a reST API index file from written files |
|
380 | """Make a reST API index file from written files | |
386 |
|
381 | |||
387 | Parameters |
|
382 | Parameters | |
388 | ---------- |
|
383 | ---------- | |
389 | path : string |
|
384 | path : string | |
390 | Filename to write index to |
|
385 | Filename to write index to | |
391 | outdir : string |
|
386 | outdir : string | |
392 | Directory to which to write generated index file |
|
387 | Directory to which to write generated index file | |
393 | froot : string, optional |
|
388 | froot : string, optional | |
394 | root (filename without extension) of filename to write to |
|
389 | root (filename without extension) of filename to write to | |
395 | Defaults to 'gen'. We add ``self.rst_extension``. |
|
390 | Defaults to 'gen'. We add ``self.rst_extension``. | |
396 | relative_to : string |
|
391 | relative_to : string | |
397 | path to which written filenames are relative. This |
|
392 | path to which written filenames are relative. This | |
398 | component of the written file path will be removed from |
|
393 | component of the written file path will be removed from | |
399 | outdir, in the generated index. Default is None, meaning, |
|
394 | outdir, in the generated index. Default is None, meaning, | |
400 | leave path as it is. |
|
395 | leave path as it is. | |
401 | """ |
|
396 | """ | |
402 | if self.written_modules is None: |
|
397 | if self.written_modules is None: | |
403 | raise ValueError('No modules written') |
|
398 | raise ValueError('No modules written') | |
404 | # Get full filename path |
|
399 | # Get full filename path | |
405 | path = os.path.join(outdir, froot+self.rst_extension) |
|
400 | path = os.path.join(outdir, froot+self.rst_extension) | |
406 | # Path written into index is relative to rootpath |
|
401 | # Path written into index is relative to rootpath | |
407 | if relative_to is not None: |
|
402 | if relative_to is not None: | |
408 | relpath = outdir.replace(relative_to + os.path.sep, '') |
|
403 | relpath = outdir.replace(relative_to + os.path.sep, '') | |
409 | else: |
|
404 | else: | |
410 | relpath = outdir |
|
405 | relpath = outdir | |
411 | idx = open(path,'wt') |
|
406 | idx = open(path,'wt') | |
412 | w = idx.write |
|
407 | w = idx.write | |
413 | w('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n') |
|
408 | w('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n') | |
414 | w('.. toctree::\n\n') |
|
409 | w('.. toctree::\n\n') | |
415 | for f in self.written_modules: |
|
410 | for f in self.written_modules: | |
416 | w(' %s\n' % os.path.join(relpath,f)) |
|
411 | w(' %s\n' % os.path.join(relpath,f)) | |
417 | idx.close() |
|
412 | idx.close() |
General Comments 0
You need to be logged in to leave comments.
Login now