Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/core/magic_arguments.py: 88%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

105 statements  

1'''A decorator-based method of constructing IPython magics with `argparse` 

2option handling. 

3 

4New magic functions can be defined like so:: 

5 

6 from IPython.core.magic_arguments import (argument, magic_arguments, 

7 parse_argstring) 

8 

9 @magic_arguments() 

10 @argument('-o', '--option', help='An optional argument.') 

11 @argument('arg', type=int, help='An integer positional argument.') 

12 def magic_cool(self, arg): 

13 """ A really cool magic command. 

14 

15 """ 

16 args = parse_argstring(magic_cool, arg) 

17 ... 

18 

19The `@magic_arguments` decorator marks the function as having argparse arguments. 

20The `@argument` decorator adds an argument using the same syntax as argparse's 

21`add_argument()` method. More sophisticated uses may also require the 

22`@argument_group` or `@kwds` decorator to customize the formatting and the 

23parsing. 

24 

25Help text for the magic is automatically generated from the docstring and the 

26arguments:: 

27 

28 In[1]: %cool? 

29 %cool [-o OPTION] arg 

30 

31 A really cool magic command. 

32 

33 positional arguments: 

34 arg An integer positional argument. 

35 

36 optional arguments: 

37 -o OPTION, --option OPTION 

38 An optional argument. 

39 

40Here is an elaborated example that uses default parameters in `argument` and calls the `args` in the cell magic:: 

41 

42 from IPython.core.magic import register_cell_magic 

43 from IPython.core.magic_arguments import (argument, magic_arguments, 

44 parse_argstring) 

45 

46 

47 @magic_arguments() 

48 @argument( 

49 "--option", 

50 "-o", 

51 help=("Add an option here"), 

52 ) 

53 @argument( 

54 "--style", 

55 "-s", 

56 default="foo", 

57 help=("Add some style arguments"), 

58 ) 

59 @register_cell_magic 

60 def my_cell_magic(line, cell): 

61 args = parse_argstring(my_cell_magic, line) 

62 print(f"{args.option=}") 

63 print(f"{args.style=}") 

64 print(f"{cell=}") 

65 

66In a jupyter notebook, this cell magic can be executed like this:: 

67 

68 %%my_cell_magic -o Hello 

69 print("bar") 

70 i = 42 

71 

72Inheritance diagram: 

73 

74.. inheritance-diagram:: IPython.core.magic_arguments 

75 :parts: 3 

76 

77''' 

78#----------------------------------------------------------------------------- 

79# Copyright (C) 2010-2011, IPython Development Team. 

80# 

81# Distributed under the terms of the Modified BSD License. 

82# 

83# The full license is in the file COPYING.txt, distributed with this software. 

84#----------------------------------------------------------------------------- 

85import argparse 

86import re 

87 

88# Our own imports 

89from IPython.core.error import UsageError 

90from IPython.utils.decorators import undoc 

91from IPython.utils.process import arg_split 

92from IPython.utils.text import dedent 

93 

94NAME_RE = re.compile(r"[a-zA-Z][a-zA-Z0-9_-]*$") 

95 

96@undoc 

97class MagicHelpFormatter(argparse.RawDescriptionHelpFormatter): 

98 """A HelpFormatter with a couple of changes to meet our needs. 

99 """ 

100 # Modified to dedent text. 

101 def _fill_text(self, text, width, indent): 

102 return argparse.RawDescriptionHelpFormatter._fill_text(self, dedent(text), width, indent) 

103 

104 # Modified to wrap argument placeholders in <> where necessary. 

105 def _format_action_invocation(self, action): 

106 if not action.option_strings: 

107 metavar, = self._metavar_formatter(action, action.dest)(1) 

108 return metavar 

109 

110 else: 

111 parts = [] 

112 

113 # if the Optional doesn't take a value, format is: 

114 # -s, --long 

115 if action.nargs == 0: 

116 parts.extend(action.option_strings) 

117 

118 # if the Optional takes a value, format is: 

119 # -s ARGS, --long ARGS 

120 else: 

121 default = action.dest.upper() 

122 args_string = self._format_args(action, default) 

123 # IPYTHON MODIFICATION: If args_string is not a plain name, wrap 

124 # it in <> so it's valid RST. 

125 if not NAME_RE.match(args_string): 

126 args_string = "<%s>" % args_string 

127 for option_string in action.option_strings: 

128 parts.append('%s %s' % (option_string, args_string)) 

129 

130 return ', '.join(parts) 

131 

132 # Override the default prefix ('usage') to our % magic escape, 

133 # in a code block. 

134 def add_usage(self, usage, actions, groups, prefix="::\n\n %"): 

135 super(MagicHelpFormatter, self).add_usage(usage, actions, groups, prefix) 

136 

137class MagicArgumentParser(argparse.ArgumentParser): 

138 """ An ArgumentParser tweaked for use by IPython magics. 

139 """ 

140 def __init__(self, 

141 prog=None, 

142 usage=None, 

143 description=None, 

144 epilog=None, 

145 parents=None, 

146 formatter_class=MagicHelpFormatter, 

147 prefix_chars='-', 

148 argument_default=None, 

149 conflict_handler='error', 

150 add_help=False): 

151 if parents is None: 

152 parents = [] 

153 super(MagicArgumentParser, self).__init__(prog=prog, usage=usage, 

154 description=description, epilog=epilog, 

155 parents=parents, formatter_class=formatter_class, 

156 prefix_chars=prefix_chars, argument_default=argument_default, 

157 conflict_handler=conflict_handler, add_help=add_help) 

158 

159 def error(self, message): 

160 """ Raise a catchable error instead of exiting. 

161 """ 

162 raise UsageError(message) 

163 

164 def parse_argstring(self, argstring, *, partial=False): 

165 """ Split a string into an argument list and parse that argument list. 

166 """ 

167 argv = arg_split(argstring) 

168 if partial: 

169 return self.parse_known_args(argv) 

170 return self.parse_args(argv) 

171 

172 

173def construct_parser(magic_func): 

174 """ Construct an argument parser using the function decorations. 

175 """ 

176 kwds = getattr(magic_func, 'argcmd_kwds', {}) 

177 if 'description' not in kwds: 

178 kwds['description'] = getattr(magic_func, '__doc__', None) 

179 arg_name = real_name(magic_func) 

180 parser = MagicArgumentParser(arg_name, **kwds) 

181 # Reverse the list of decorators in order to apply them in the 

182 # order in which they appear in the source. 

183 group = None 

184 for deco in magic_func.decorators[::-1]: 

185 result = deco.add_to_parser(parser, group) 

186 if result is not None: 

187 group = result 

188 

189 # Replace the magic function's docstring with the full help text. 

190 magic_func.__doc__ = parser.format_help() 

191 

192 return parser 

193 

194 

195def parse_argstring(magic_func, argstring, *, partial=False): 

196 """ Parse the string of arguments for the given magic function. 

197 """ 

198 return magic_func.parser.parse_argstring(argstring, partial=partial) 

199 

200 

201def real_name(magic_func): 

202 """ Find the real name of the magic. 

203 """ 

204 magic_name = magic_func.__name__ 

205 if magic_name.startswith('magic_'): 

206 magic_name = magic_name[len('magic_'):] 

207 return getattr(magic_func, 'argcmd_name', magic_name) 

208 

209 

210class ArgDecorator: 

211 """ Base class for decorators to add ArgumentParser information to a method. 

212 """ 

213 

214 def __call__(self, func): 

215 if not getattr(func, 'has_arguments', False): 

216 func.has_arguments = True 

217 func.decorators = [] 

218 func.decorators.append(self) 

219 return func 

220 

221 def add_to_parser(self, parser, group): 

222 """ Add this object's information to the parser, if necessary. 

223 """ 

224 pass 

225 

226 

227class magic_arguments(ArgDecorator): 

228 """ Mark the magic as having argparse arguments and possibly adjust the 

229 name. 

230 """ 

231 

232 def __init__(self, name=None): 

233 self.name = name 

234 

235 def __call__(self, func): 

236 if not getattr(func, 'has_arguments', False): 

237 func.has_arguments = True 

238 func.decorators = [] 

239 if self.name is not None: 

240 func.argcmd_name = self.name 

241 # This should be the first decorator in the list of decorators, thus the 

242 # last to execute. Build the parser. 

243 func.parser = construct_parser(func) 

244 return func 

245 

246 

247class ArgMethodWrapper(ArgDecorator): 

248 

249 """ 

250 Base class to define a wrapper for ArgumentParser method. 

251 

252 Child class must define either `_method_name` or `add_to_parser`. 

253 

254 """ 

255 

256 _method_name: str 

257 

258 def __init__(self, *args, **kwds): 

259 self.args = args 

260 self.kwds = kwds 

261 

262 def add_to_parser(self, parser, group): 

263 """ Add this object's information to the parser. 

264 """ 

265 if group is not None: 

266 parser = group 

267 getattr(parser, self._method_name)(*self.args, **self.kwds) 

268 return None 

269 

270 

271class argument(ArgMethodWrapper): 

272 """ Store arguments and keywords to pass to add_argument(). 

273 

274 Instances also serve to decorate command methods. 

275 """ 

276 _method_name = 'add_argument' 

277 

278 

279class defaults(ArgMethodWrapper): 

280 """ Store arguments and keywords to pass to set_defaults(). 

281 

282 Instances also serve to decorate command methods. 

283 """ 

284 _method_name = 'set_defaults' 

285 

286 

287class argument_group(ArgMethodWrapper): 

288 """ Store arguments and keywords to pass to add_argument_group(). 

289 

290 Instances also serve to decorate command methods. 

291 """ 

292 

293 def add_to_parser(self, parser, group): 

294 """ Add this object's information to the parser. 

295 """ 

296 return parser.add_argument_group(*self.args, **self.kwds) 

297 

298 

299class kwds(ArgDecorator): 

300 """ Provide other keywords to the sub-parser constructor. 

301 """ 

302 def __init__(self, **kwds): 

303 self.kwds = kwds 

304 

305 def __call__(self, func): 

306 func = super(kwds, self).__call__(func) 

307 func.argcmd_kwds = self.kwds 

308 return func 

309 

310 

311__all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds', 

312 'parse_argstring']