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