Spaces:
Runtime error
Runtime error
| '''A decorator-based method of constructing IPython magics with `argparse` | |
| option handling. | |
| New magic functions can be defined like so:: | |
| from IPython.core.magic_arguments import (argument, magic_arguments, | |
| parse_argstring) | |
| @magic_arguments() | |
| @argument('-o', '--option', help='An optional argument.') | |
| @argument('arg', type=int, help='An integer positional argument.') | |
| def magic_cool(self, arg): | |
| """ A really cool magic command. | |
| """ | |
| args = parse_argstring(magic_cool, arg) | |
| ... | |
| The `@magic_arguments` decorator marks the function as having argparse arguments. | |
| The `@argument` decorator adds an argument using the same syntax as argparse's | |
| `add_argument()` method. More sophisticated uses may also require the | |
| `@argument_group` or `@kwds` decorator to customize the formatting and the | |
| parsing. | |
| Help text for the magic is automatically generated from the docstring and the | |
| arguments:: | |
| In[1]: %cool? | |
| %cool [-o OPTION] arg | |
| A really cool magic command. | |
| positional arguments: | |
| arg An integer positional argument. | |
| optional arguments: | |
| -o OPTION, --option OPTION | |
| An optional argument. | |
| Here is an elaborated example that uses default parameters in `argument` and calls the `args` in the cell magic:: | |
| from IPython.core.magic import register_cell_magic | |
| from IPython.core.magic_arguments import (argument, magic_arguments, | |
| parse_argstring) | |
| @magic_arguments() | |
| @argument( | |
| "--option", | |
| "-o", | |
| help=("Add an option here"), | |
| ) | |
| @argument( | |
| "--style", | |
| "-s", | |
| default="foo", | |
| help=("Add some style arguments"), | |
| ) | |
| @register_cell_magic | |
| def my_cell_magic(line, cell): | |
| args = parse_argstring(my_cell_magic, line) | |
| print(f"{args.option=}") | |
| print(f"{args.style=}") | |
| print(f"{cell=}") | |
| In a jupyter notebook, this cell magic can be executed like this:: | |
| %%my_cell_magic -o Hello | |
| print("bar") | |
| i = 42 | |
| Inheritance diagram: | |
| .. inheritance-diagram:: IPython.core.magic_arguments | |
| :parts: 3 | |
| ''' | |
| #----------------------------------------------------------------------------- | |
| # Copyright (C) 2010-2011, IPython Development Team. | |
| # | |
| # Distributed under the terms of the Modified BSD License. | |
| # | |
| # The full license is in the file COPYING.txt, distributed with this software. | |
| #----------------------------------------------------------------------------- | |
| import argparse | |
| import re | |
| # Our own imports | |
| from IPython.core.error import UsageError | |
| from IPython.utils.decorators import undoc | |
| from IPython.utils.process import arg_split | |
| from IPython.utils.text import dedent | |
| NAME_RE = re.compile(r"[a-zA-Z][a-zA-Z0-9_-]*$") | |
| class MagicHelpFormatter(argparse.RawDescriptionHelpFormatter): | |
| """A HelpFormatter with a couple of changes to meet our needs. | |
| """ | |
| # Modified to dedent text. | |
| def _fill_text(self, text, width, indent): | |
| return argparse.RawDescriptionHelpFormatter._fill_text(self, dedent(text), width, indent) | |
| # Modified to wrap argument placeholders in <> where necessary. | |
| def _format_action_invocation(self, action): | |
| if not action.option_strings: | |
| metavar, = self._metavar_formatter(action, action.dest)(1) | |
| return metavar | |
| else: | |
| parts = [] | |
| # if the Optional doesn't take a value, format is: | |
| # -s, --long | |
| if action.nargs == 0: | |
| parts.extend(action.option_strings) | |
| # if the Optional takes a value, format is: | |
| # -s ARGS, --long ARGS | |
| else: | |
| default = action.dest.upper() | |
| args_string = self._format_args(action, default) | |
| # IPYTHON MODIFICATION: If args_string is not a plain name, wrap | |
| # it in <> so it's valid RST. | |
| if not NAME_RE.match(args_string): | |
| args_string = "<%s>" % args_string | |
| for option_string in action.option_strings: | |
| parts.append('%s %s' % (option_string, args_string)) | |
| return ', '.join(parts) | |
| # Override the default prefix ('usage') to our % magic escape, | |
| # in a code block. | |
| def add_usage(self, usage, actions, groups, prefix="::\n\n %"): | |
| super(MagicHelpFormatter, self).add_usage(usage, actions, groups, prefix) | |
| class MagicArgumentParser(argparse.ArgumentParser): | |
| """ An ArgumentParser tweaked for use by IPython magics. | |
| """ | |
| def __init__(self, | |
| prog=None, | |
| usage=None, | |
| description=None, | |
| epilog=None, | |
| parents=None, | |
| formatter_class=MagicHelpFormatter, | |
| prefix_chars='-', | |
| argument_default=None, | |
| conflict_handler='error', | |
| add_help=False): | |
| if parents is None: | |
| parents = [] | |
| super(MagicArgumentParser, self).__init__(prog=prog, usage=usage, | |
| description=description, epilog=epilog, | |
| parents=parents, formatter_class=formatter_class, | |
| prefix_chars=prefix_chars, argument_default=argument_default, | |
| conflict_handler=conflict_handler, add_help=add_help) | |
| def error(self, message): | |
| """ Raise a catchable error instead of exiting. | |
| """ | |
| raise UsageError(message) | |
| def parse_argstring(self, argstring, *, partial=False): | |
| """ Split a string into an argument list and parse that argument list. | |
| """ | |
| argv = arg_split(argstring, strict=not partial) | |
| if partial: | |
| return self.parse_known_args(argv) | |
| return self.parse_args(argv) | |
| def construct_parser(magic_func): | |
| """ Construct an argument parser using the function decorations. | |
| """ | |
| kwds = getattr(magic_func, 'argcmd_kwds', {}) | |
| if 'description' not in kwds: | |
| kwds['description'] = getattr(magic_func, '__doc__', None) | |
| arg_name = real_name(magic_func) | |
| parser = MagicArgumentParser(arg_name, **kwds) | |
| # Reverse the list of decorators in order to apply them in the | |
| # order in which they appear in the source. | |
| group = None | |
| for deco in magic_func.decorators[::-1]: | |
| result = deco.add_to_parser(parser, group) | |
| if result is not None: | |
| group = result | |
| # Replace the magic function's docstring with the full help text. | |
| magic_func.__doc__ = parser.format_help() | |
| return parser | |
| def parse_argstring(magic_func, argstring, *, partial=False): | |
| """ Parse the string of arguments for the given magic function. | |
| """ | |
| return magic_func.parser.parse_argstring(argstring, partial=partial) | |
| def real_name(magic_func): | |
| """ Find the real name of the magic. | |
| """ | |
| magic_name = magic_func.__name__ | |
| if magic_name.startswith('magic_'): | |
| magic_name = magic_name[len('magic_'):] | |
| return getattr(magic_func, 'argcmd_name', magic_name) | |
| class ArgDecorator: | |
| """ Base class for decorators to add ArgumentParser information to a method. | |
| """ | |
| def __call__(self, func): | |
| if not getattr(func, 'has_arguments', False): | |
| func.has_arguments = True | |
| func.decorators = [] | |
| func.decorators.append(self) | |
| return func | |
| def add_to_parser(self, parser, group): | |
| """ Add this object's information to the parser, if necessary. | |
| """ | |
| pass | |
| class magic_arguments(ArgDecorator): | |
| """ Mark the magic as having argparse arguments and possibly adjust the | |
| name. | |
| """ | |
| def __init__(self, name=None): | |
| self.name = name | |
| def __call__(self, func): | |
| if not getattr(func, 'has_arguments', False): | |
| func.has_arguments = True | |
| func.decorators = [] | |
| if self.name is not None: | |
| func.argcmd_name = self.name | |
| # This should be the first decorator in the list of decorators, thus the | |
| # last to execute. Build the parser. | |
| func.parser = construct_parser(func) | |
| return func | |
| class ArgMethodWrapper(ArgDecorator): | |
| """ | |
| Base class to define a wrapper for ArgumentParser method. | |
| Child class must define either `_method_name` or `add_to_parser`. | |
| """ | |
| _method_name: str | |
| def __init__(self, *args, **kwds): | |
| self.args = args | |
| self.kwds = kwds | |
| def add_to_parser(self, parser, group): | |
| """ Add this object's information to the parser. | |
| """ | |
| if group is not None: | |
| parser = group | |
| getattr(parser, self._method_name)(*self.args, **self.kwds) | |
| return None | |
| class argument(ArgMethodWrapper): | |
| """ Store arguments and keywords to pass to add_argument(). | |
| Instances also serve to decorate command methods. | |
| """ | |
| _method_name = 'add_argument' | |
| class defaults(ArgMethodWrapper): | |
| """ Store arguments and keywords to pass to set_defaults(). | |
| Instances also serve to decorate command methods. | |
| """ | |
| _method_name = 'set_defaults' | |
| class argument_group(ArgMethodWrapper): | |
| """ Store arguments and keywords to pass to add_argument_group(). | |
| Instances also serve to decorate command methods. | |
| """ | |
| def add_to_parser(self, parser, group): | |
| """ Add this object's information to the parser. | |
| """ | |
| return parser.add_argument_group(*self.args, **self.kwds) | |
| class kwds(ArgDecorator): | |
| """ Provide other keywords to the sub-parser constructor. | |
| """ | |
| def __init__(self, **kwds): | |
| self.kwds = kwds | |
| def __call__(self, func): | |
| func = super(kwds, self).__call__(func) | |
| func.argcmd_kwds = self.kwds | |
| return func | |
| __all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds', | |
| 'parse_argstring'] | |