##// END OF EJS Templates
added second example
Jan-Hendrik Müller -
Show More
@@ -1,278 +1,312 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
41 Here is an elaborated example that uses default parameters in `argument` and calls the `args` in the cell magic::
42
43 from IPython.core.magic import register_cell_magic
44 from IPython.core.magic_arguments import (argument, magic_arguments,
45 parse_argstring)
46
47
48 @magic_arguments()
49 @argument(
50 "--option",
51 "-o",
52 help=("Add an option here"),
53 )
54 @argument(
55 "--style",
56 "-s",
57 default="foo",
58 help=("Add some style arguments"),
59 )
60 @register_cell_magic
61 def my_cell_magic(line, cell):
62 args = parse_argstring(my_cell_magic, line)
63 print(f"{args.option=}")
64 print(f"{args.style=}")
65 print(f"{cell=}")
66
67 In a jupyter notebook, this cell magic can be executed like this::
68
69 %%my_cell_magic -o Hello
70 print("bar")
71 i = 42
72
73
40 Inheritance diagram:
74 Inheritance diagram:
41
75
42 .. inheritance-diagram:: IPython.core.magic_arguments
76 .. inheritance-diagram:: IPython.core.magic_arguments
43 :parts: 3
77 :parts: 3
44
78
45 '''
79 '''
46 #-----------------------------------------------------------------------------
80 #-----------------------------------------------------------------------------
47 # Copyright (C) 2010-2011, IPython Development Team.
81 # Copyright (C) 2010-2011, IPython Development Team.
48 #
82 #
49 # Distributed under the terms of the Modified BSD License.
83 # Distributed under the terms of the Modified BSD License.
50 #
84 #
51 # The full license is in the file COPYING.txt, distributed with this software.
85 # The full license is in the file COPYING.txt, distributed with this software.
52 #-----------------------------------------------------------------------------
86 #-----------------------------------------------------------------------------
53 import argparse
87 import argparse
54 import re
88 import re
55
89
56 # Our own imports
90 # Our own imports
57 from IPython.core.error import UsageError
91 from IPython.core.error import UsageError
58 from IPython.utils.decorators import undoc
92 from IPython.utils.decorators import undoc
59 from IPython.utils.process import arg_split
93 from IPython.utils.process import arg_split
60 from IPython.utils.text import dedent
94 from IPython.utils.text import dedent
61
95
62 NAME_RE = re.compile(r"[a-zA-Z][a-zA-Z0-9_-]*$")
96 NAME_RE = re.compile(r"[a-zA-Z][a-zA-Z0-9_-]*$")
63
97
64 @undoc
98 @undoc
65 class MagicHelpFormatter(argparse.RawDescriptionHelpFormatter):
99 class MagicHelpFormatter(argparse.RawDescriptionHelpFormatter):
66 """A HelpFormatter with a couple of changes to meet our needs.
100 """A HelpFormatter with a couple of changes to meet our needs.
67 """
101 """
68 # Modified to dedent text.
102 # Modified to dedent text.
69 def _fill_text(self, text, width, indent):
103 def _fill_text(self, text, width, indent):
70 return argparse.RawDescriptionHelpFormatter._fill_text(self, dedent(text), width, indent)
104 return argparse.RawDescriptionHelpFormatter._fill_text(self, dedent(text), width, indent)
71
105
72 # Modified to wrap argument placeholders in <> where necessary.
106 # Modified to wrap argument placeholders in <> where necessary.
73 def _format_action_invocation(self, action):
107 def _format_action_invocation(self, action):
74 if not action.option_strings:
108 if not action.option_strings:
75 metavar, = self._metavar_formatter(action, action.dest)(1)
109 metavar, = self._metavar_formatter(action, action.dest)(1)
76 return metavar
110 return metavar
77
111
78 else:
112 else:
79 parts = []
113 parts = []
80
114
81 # if the Optional doesn't take a value, format is:
115 # if the Optional doesn't take a value, format is:
82 # -s, --long
116 # -s, --long
83 if action.nargs == 0:
117 if action.nargs == 0:
84 parts.extend(action.option_strings)
118 parts.extend(action.option_strings)
85
119
86 # if the Optional takes a value, format is:
120 # if the Optional takes a value, format is:
87 # -s ARGS, --long ARGS
121 # -s ARGS, --long ARGS
88 else:
122 else:
89 default = action.dest.upper()
123 default = action.dest.upper()
90 args_string = self._format_args(action, default)
124 args_string = self._format_args(action, default)
91 # IPYTHON MODIFICATION: If args_string is not a plain name, wrap
125 # IPYTHON MODIFICATION: If args_string is not a plain name, wrap
92 # it in <> so it's valid RST.
126 # it in <> so it's valid RST.
93 if not NAME_RE.match(args_string):
127 if not NAME_RE.match(args_string):
94 args_string = "<%s>" % args_string
128 args_string = "<%s>" % args_string
95 for option_string in action.option_strings:
129 for option_string in action.option_strings:
96 parts.append('%s %s' % (option_string, args_string))
130 parts.append('%s %s' % (option_string, args_string))
97
131
98 return ', '.join(parts)
132 return ', '.join(parts)
99
133
100 # Override the default prefix ('usage') to our % magic escape,
134 # Override the default prefix ('usage') to our % magic escape,
101 # in a code block.
135 # in a code block.
102 def add_usage(self, usage, actions, groups, prefix="::\n\n %"):
136 def add_usage(self, usage, actions, groups, prefix="::\n\n %"):
103 super(MagicHelpFormatter, self).add_usage(usage, actions, groups, prefix)
137 super(MagicHelpFormatter, self).add_usage(usage, actions, groups, prefix)
104
138
105 class MagicArgumentParser(argparse.ArgumentParser):
139 class MagicArgumentParser(argparse.ArgumentParser):
106 """ An ArgumentParser tweaked for use by IPython magics.
140 """ An ArgumentParser tweaked for use by IPython magics.
107 """
141 """
108 def __init__(self,
142 def __init__(self,
109 prog=None,
143 prog=None,
110 usage=None,
144 usage=None,
111 description=None,
145 description=None,
112 epilog=None,
146 epilog=None,
113 parents=None,
147 parents=None,
114 formatter_class=MagicHelpFormatter,
148 formatter_class=MagicHelpFormatter,
115 prefix_chars='-',
149 prefix_chars='-',
116 argument_default=None,
150 argument_default=None,
117 conflict_handler='error',
151 conflict_handler='error',
118 add_help=False):
152 add_help=False):
119 if parents is None:
153 if parents is None:
120 parents = []
154 parents = []
121 super(MagicArgumentParser, self).__init__(prog=prog, usage=usage,
155 super(MagicArgumentParser, self).__init__(prog=prog, usage=usage,
122 description=description, epilog=epilog,
156 description=description, epilog=epilog,
123 parents=parents, formatter_class=formatter_class,
157 parents=parents, formatter_class=formatter_class,
124 prefix_chars=prefix_chars, argument_default=argument_default,
158 prefix_chars=prefix_chars, argument_default=argument_default,
125 conflict_handler=conflict_handler, add_help=add_help)
159 conflict_handler=conflict_handler, add_help=add_help)
126
160
127 def error(self, message):
161 def error(self, message):
128 """ Raise a catchable error instead of exiting.
162 """ Raise a catchable error instead of exiting.
129 """
163 """
130 raise UsageError(message)
164 raise UsageError(message)
131
165
132 def parse_argstring(self, argstring):
166 def parse_argstring(self, argstring):
133 """ Split a string into an argument list and parse that argument list.
167 """ Split a string into an argument list and parse that argument list.
134 """
168 """
135 argv = arg_split(argstring)
169 argv = arg_split(argstring)
136 return self.parse_args(argv)
170 return self.parse_args(argv)
137
171
138
172
139 def construct_parser(magic_func):
173 def construct_parser(magic_func):
140 """ Construct an argument parser using the function decorations.
174 """ Construct an argument parser using the function decorations.
141 """
175 """
142 kwds = getattr(magic_func, 'argcmd_kwds', {})
176 kwds = getattr(magic_func, 'argcmd_kwds', {})
143 if 'description' not in kwds:
177 if 'description' not in kwds:
144 kwds['description'] = getattr(magic_func, '__doc__', None)
178 kwds['description'] = getattr(magic_func, '__doc__', None)
145 arg_name = real_name(magic_func)
179 arg_name = real_name(magic_func)
146 parser = MagicArgumentParser(arg_name, **kwds)
180 parser = MagicArgumentParser(arg_name, **kwds)
147 # Reverse the list of decorators in order to apply them in the
181 # Reverse the list of decorators in order to apply them in the
148 # order in which they appear in the source.
182 # order in which they appear in the source.
149 group = None
183 group = None
150 for deco in magic_func.decorators[::-1]:
184 for deco in magic_func.decorators[::-1]:
151 result = deco.add_to_parser(parser, group)
185 result = deco.add_to_parser(parser, group)
152 if result is not None:
186 if result is not None:
153 group = result
187 group = result
154
188
155 # Replace the magic function's docstring with the full help text.
189 # Replace the magic function's docstring with the full help text.
156 magic_func.__doc__ = parser.format_help()
190 magic_func.__doc__ = parser.format_help()
157
191
158 return parser
192 return parser
159
193
160
194
161 def parse_argstring(magic_func, argstring):
195 def parse_argstring(magic_func, argstring):
162 """ Parse the string of arguments for the given magic function.
196 """ Parse the string of arguments for the given magic function.
163 """
197 """
164 return magic_func.parser.parse_argstring(argstring)
198 return magic_func.parser.parse_argstring(argstring)
165
199
166
200
167 def real_name(magic_func):
201 def real_name(magic_func):
168 """ Find the real name of the magic.
202 """ Find the real name of the magic.
169 """
203 """
170 magic_name = magic_func.__name__
204 magic_name = magic_func.__name__
171 if magic_name.startswith('magic_'):
205 if magic_name.startswith('magic_'):
172 magic_name = magic_name[len('magic_'):]
206 magic_name = magic_name[len('magic_'):]
173 return getattr(magic_func, 'argcmd_name', magic_name)
207 return getattr(magic_func, 'argcmd_name', magic_name)
174
208
175
209
176 class ArgDecorator(object):
210 class ArgDecorator(object):
177 """ Base class for decorators to add ArgumentParser information to a method.
211 """ Base class for decorators to add ArgumentParser information to a method.
178 """
212 """
179
213
180 def __call__(self, func):
214 def __call__(self, func):
181 if not getattr(func, 'has_arguments', False):
215 if not getattr(func, 'has_arguments', False):
182 func.has_arguments = True
216 func.has_arguments = True
183 func.decorators = []
217 func.decorators = []
184 func.decorators.append(self)
218 func.decorators.append(self)
185 return func
219 return func
186
220
187 def add_to_parser(self, parser, group):
221 def add_to_parser(self, parser, group):
188 """ Add this object's information to the parser, if necessary.
222 """ Add this object's information to the parser, if necessary.
189 """
223 """
190 pass
224 pass
191
225
192
226
193 class magic_arguments(ArgDecorator):
227 class magic_arguments(ArgDecorator):
194 """ Mark the magic as having argparse arguments and possibly adjust the
228 """ Mark the magic as having argparse arguments and possibly adjust the
195 name.
229 name.
196 """
230 """
197
231
198 def __init__(self, name=None):
232 def __init__(self, name=None):
199 self.name = name
233 self.name = name
200
234
201 def __call__(self, func):
235 def __call__(self, func):
202 if not getattr(func, 'has_arguments', False):
236 if not getattr(func, 'has_arguments', False):
203 func.has_arguments = True
237 func.has_arguments = True
204 func.decorators = []
238 func.decorators = []
205 if self.name is not None:
239 if self.name is not None:
206 func.argcmd_name = self.name
240 func.argcmd_name = self.name
207 # This should be the first decorator in the list of decorators, thus the
241 # This should be the first decorator in the list of decorators, thus the
208 # last to execute. Build the parser.
242 # last to execute. Build the parser.
209 func.parser = construct_parser(func)
243 func.parser = construct_parser(func)
210 return func
244 return func
211
245
212
246
213 class ArgMethodWrapper(ArgDecorator):
247 class ArgMethodWrapper(ArgDecorator):
214
248
215 """
249 """
216 Base class to define a wrapper for ArgumentParser method.
250 Base class to define a wrapper for ArgumentParser method.
217
251
218 Child class must define either `_method_name` or `add_to_parser`.
252 Child class must define either `_method_name` or `add_to_parser`.
219
253
220 """
254 """
221
255
222 _method_name = None
256 _method_name = None
223
257
224 def __init__(self, *args, **kwds):
258 def __init__(self, *args, **kwds):
225 self.args = args
259 self.args = args
226 self.kwds = kwds
260 self.kwds = kwds
227
261
228 def add_to_parser(self, parser, group):
262 def add_to_parser(self, parser, group):
229 """ Add this object's information to the parser.
263 """ Add this object's information to the parser.
230 """
264 """
231 if group is not None:
265 if group is not None:
232 parser = group
266 parser = group
233 getattr(parser, self._method_name)(*self.args, **self.kwds)
267 getattr(parser, self._method_name)(*self.args, **self.kwds)
234 return None
268 return None
235
269
236
270
237 class argument(ArgMethodWrapper):
271 class argument(ArgMethodWrapper):
238 """ Store arguments and keywords to pass to add_argument().
272 """ Store arguments and keywords to pass to add_argument().
239
273
240 Instances also serve to decorate command methods.
274 Instances also serve to decorate command methods.
241 """
275 """
242 _method_name = 'add_argument'
276 _method_name = 'add_argument'
243
277
244
278
245 class defaults(ArgMethodWrapper):
279 class defaults(ArgMethodWrapper):
246 """ Store arguments and keywords to pass to set_defaults().
280 """ Store arguments and keywords to pass to set_defaults().
247
281
248 Instances also serve to decorate command methods.
282 Instances also serve to decorate command methods.
249 """
283 """
250 _method_name = 'set_defaults'
284 _method_name = 'set_defaults'
251
285
252
286
253 class argument_group(ArgMethodWrapper):
287 class argument_group(ArgMethodWrapper):
254 """ Store arguments and keywords to pass to add_argument_group().
288 """ Store arguments and keywords to pass to add_argument_group().
255
289
256 Instances also serve to decorate command methods.
290 Instances also serve to decorate command methods.
257 """
291 """
258
292
259 def add_to_parser(self, parser, group):
293 def add_to_parser(self, parser, group):
260 """ Add this object's information to the parser.
294 """ Add this object's information to the parser.
261 """
295 """
262 return parser.add_argument_group(*self.args, **self.kwds)
296 return parser.add_argument_group(*self.args, **self.kwds)
263
297
264
298
265 class kwds(ArgDecorator):
299 class kwds(ArgDecorator):
266 """ Provide other keywords to the sub-parser constructor.
300 """ Provide other keywords to the sub-parser constructor.
267 """
301 """
268 def __init__(self, **kwds):
302 def __init__(self, **kwds):
269 self.kwds = kwds
303 self.kwds = kwds
270
304
271 def __call__(self, func):
305 def __call__(self, func):
272 func = super(kwds, self).__call__(func)
306 func = super(kwds, self).__call__(func)
273 func.argcmd_kwds = self.kwds
307 func.argcmd_kwds = self.kwds
274 return func
308 return func
275
309
276
310
277 __all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds',
311 __all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds',
278 'parse_argstring']
312 'parse_argstring']
General Comments 0
You need to be logged in to leave comments. Login now