skip to main content

@tonyfast s notebooks

site navigation
notebook summary
title
using typer in ipython magics
description
we needs to process system arguments as a string when use magics. the line in a line magic or the first line in a cell magic may take arguments.
cells
21 total
8 code
state
executed out of order
kernel
Python [conda env:root] *
language
python
name
conda-root-py
lines of code
83
outputs
3
table of contents
{"kernelspec": {"display_name": "Python [conda env:root] *", "language": "python", "name": "conda-root-py"}, "language_info": {"codemirror_mode": {"name": "ipython", "version": 3}, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13"}, "tags": ["ipython-extension"], "title": "using typer in ipython magics", "description": "we needs to process system arguments as a string when use magics.\nthe line in a line magic or the first line in a cell magic may take arguments."}
notebook toolbar
Activate
cell ordering
1

using in ipython magics

  • wrap function to consume system arguments using typer
  • register the magic with IPython
  • examples
  • ipython extensions
2

wrapping the function

we needs to process system arguments as a string when use magics. the line in a line magic or the first line in a cell magic may take arguments.

3

wrap_magic transforms our function into a magic style method that has the following signature.

def magicish(line: str, cell: str =None): ...

it uses click by way of typer to deserialize the line of system arguments. these parameters are passed to the original function. the magic function's help/docstring is replaced with the help from the command line interface.

4
    def wrap_magic(function, cell_key="cell"):
        import typer, click, shlex, functools
        app = typer.Typer(add_completion=False, context_settings={"help_option_names": ["-h", "--help"]})
        app.command()(function)
        ctx = click.Context(typer.main.get_command(app))
        
        @functools.wraps(function)
        def magic(line, cell=None):
            try:
                ctx.command.parse_args(ctx, shlex.split(line))
            except click.exceptions.Exit:
                return
            if cell_key in ctx.params:
                ctx.params[cell_key] = cell
            return function(**ctx.params)
        
        magic.__doc__ = "\n".join((function.__doc__ or "", get_help(ctx)))
        return magic
5

register the magic with

6

register a line, cell or line/cell magic method.

7
    def register_magic(function, name=None,  cell_key = "cell"):
        import inspect
        from IPython import get_ipython
        shell = get_ipython()
        cache_rich_console()
        signature = inspect.signature(function)
        wrapper = wrap_magic(function, cell_key=cell_key)
        kind = "line"
        if cell_key in signature.parameters:
            kind = "line_cell"
            if signature.parameters[cell_key].default is inspect._empty:
                kind = "cell"
        shell.register_magic_function(wrapper, kind, name)
        shell.log.info(F"registered {repr(function)} as magic named {name or function.__name__}")
        return function
8

getting the help

9

when rich is installed, which it often is, we need to do some tomfoolery

10

in our interactive condition we want access to the rich console that typer uses. we're going to wrap a cache around typer s method so that each we recieve the same rich.console.Console each time the function is invoked.

11
    def cache_rich_console(cache={}):
        import typer, functools
        if not cache:
            cache.setdefault("_get_rich_console", typer.rich_utils._get_rich_console)
        typer.rich_utils._get_rich_console = functools.lru_cache(cache["_get_rich_console"])
12

when the Console is consistent we can capture it's output and reclaim the help information

13
    def get_help(ctx):
        with typer.rich_utils._get_rich_console().capture() as console: ctx.get_help()
        return console.get()
14

examples

15
  • register a line magic
16
    if (Ø := "__file__" not in locals()):
        import typer
        @register_magic
        def hello(count:int = 5, name:str = typer.Option("world", help="a name to repeat"), msg: str = "<3"):
            """a function that says hello"""
            print(name*count, msg)
        assert "hello" not in (shell := get_ipython()).magics_manager.magics["cell"]
        %hello 
2 outputs.

         
worldworldworldworldworld <3

17
  • register a cell magic because the cell parameter was found in the signature. this is by convention, and should be configurable.
18
    if Ø:
        @register_magic
        def yall(count:int = 5, name:str = typer.Option("world", help="a name to repeat"),  cell: str = "xoxo"):
            """a function that says hello to yall"""
            hello(count, name, msg=cell)
        assert "yall" in shell.magics_manager.magics["cell"], "the method didn't get registered."
1 outputs.

        
19

verify that we can import register_magic for reuse.

20
    if Ø:
        with __import__("importnb").Notebook():
            from tonyfast.xxii.__typer_magic import register_magic as imported_register_magic
21
    def load_ipython_extension(shell): shell.user_ns.setdefault(register_magic.__name__, register_magic)
    def unload_ipython_extension(shell): pass