using typer
in ipython magics¤
- wrap function to consume system arguments using
typer
- register the magic with
IPython
- examples
- ipython extensions
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.
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.
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
register the magic with IPython
¤
register
a line, cell or line/cell magic method.
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
getting the help¤
when rich
is installed, which it often is, we need to do some tomfoolery
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.
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"])
when the Console
is consistent we can capture it's output and reclaim the help information
def get_help(ctx):
with typer.rich_utils._get_rich_console().capture() as console: ctx.get_help()
return console.get()
examples¤
- register a line magic
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
- register a cell magic because the cell parameter was found in the signature. this is by convention, and should be configurable.
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."
verify that we can import register_magic
for reuse.
if Ø:
with __import__("importnb").Notebook():
from tonyfast.xxii.__typer_magic import register_magic as imported_register_magic
def load_ipython_extension(shell): shell.user_ns.setdefault(register_magic.__name__, register_magic)
def unload_ipython_extension(shell): pass