Creating a New Magic
This guide explains how to write a custom magic for any MetaKernel-based kernel — whether you are a kernel author bundling magics with your kernel, or an end-user who wants to add a magic to an existing kernel.
Magic file conventions
Each magic lives in its own file named {name}_magic.py. The file must:
- Define a class that inherits from
metakernel.Magic. - Implement one or both of:
line_{name}(self, ...)— invoked by%name arg1 arg2cell_{name}(self, ...)— invoked by%%name arg1\ncell body- Expose a module-level
register_magics(kernel)function so that MetaKernel can load it automatically.
A minimal example
# greet_magic.py
from metakernel import Magic, MetaKernel
class GreetMagic(Magic):
def line_greet(self, name="world"):
"""
%greet [name]
Print a friendly greeting.
Example::
%greet Alice
"""
self.kernel.Print(f"Hello, {name}!")
def register_magics(kernel: MetaKernel) -> None:
kernel.register_magics(GreetMagic)
Save this file and use it as described in the next section.
Installing a magic
There are two ways to make a magic available in your kernel.
User-local installation (any kernel)
Drop the magic file into ~/.ipython/metakernel/magics/. MetaKernel creates this directory automatically and searches it on every startup.
cp greet_magic.py ~/.ipython/metakernel/magics/
Then, in a running notebook cell, reload the magic registry:
%reload_magics
The new magic is now available for the rest of the session without restarting the kernel. On future kernel starts it will be loaded automatically.
Downloading from a URL
Use the built-in %install_magic line magic to fetch a magic file directly from a URL and install it into your local magic directory:
%install_magic https://example.com/path/to/greet_magic.py
%install_magic downloads the file and then runs %reload_magics for you automatically.
Bundling with a kernel package
Place the magic file inside a magics/ subpackage alongside your kernel module. MetaKernel discovers these at startup via reload_magics():
my_kernel/
├── __init__.py
└── magics/
└── greet_magic.py
No extra registration step is required — the register_magics(kernel) function in each file is called automatically.
Debugging magic loading
If a magic is not appearing as expected, use %lsmagic -v to see which directories MetaKernel searched and whether any files failed to load:
%lsmagic -v
The output includes:
- Magic search paths — the directories that were scanned for
*_magic.pyfiles. For a bundled kernel magic, the first entry should be themagics/subdirectory of your kernel package. If your magic file is not in one of these directories, it will never be found. - Load errors — any exceptions raised while importing a magic file or calling its
register_magicsfunction. The error message points directly to the file and the cause.
If your magic is missing after a regular (non-editable) install, verify that the magics/ directory is included in your package. With hatchling add it explicitly if needed:
[tool.hatch.build.targets.wheel]
packages = ["my_kernel"]
After fixing the file placement, restart the kernel and run %lsmagic -v again to confirm the path appears and no errors are reported.
Writing a cell magic
A cell magic receives the rest of the cell as a body via self.code. Set self.evaluate = False if you do not want the kernel to evaluate the cell body as normal code after the magic runs.
# repeat_magic.py
from metakernel import Magic, MetaKernel
class RepeatMagic(Magic):
def cell_repeat(self, times=2):
"""
%%repeat [times]
Print the cell body *times* times.
Example::
%%repeat 3
Hello!
"""
for _ in range(int(times)):
self.kernel.Print(self.code)
self.evaluate = False # don't pass the body to the kernel
def register_magics(kernel: MetaKernel) -> None:
kernel.register_magics(RepeatMagic)
Adding options with @option
Use the option decorator (backed by optparse) to add named flags to a magic. The decorator appends option documentation to the magic's docstring automatically.
# shout_magic.py
from metakernel import Magic, MetaKernel, option
class ShoutMagic(Magic):
@option(
"-u", "--upper",
action="store_true",
default=False,
help="Convert output to upper case.",
)
def line_shout(self, message="hello", upper=False):
"""
%shout [-u] message
Print a message, optionally in upper case.
Example::
%shout -u hello world
"""
if upper:
message = message.upper()
self.kernel.Print(message)
def register_magics(kernel: MetaKernel) -> None:
kernel.register_magics(ShoutMagic)
Options can also be used on cell magics — see tutor_magic.py in the MetaKernel source for a complete example.
Kernel output helpers
Inside a magic method, use the following helpers on self.kernel:
self.kernel.Print(text)-
Send plain text to the notebook output area.
self.kernel.Error(text)-
Send error text (displayed in red by most frontends).
self.kernel.Display(obj)-
Display any rich IPython displayable (e.g.
IPython.display.HTML,IFrame, images). self.kernel.set_variable(name, value)-
Inject a variable into the kernel's namespace so the user can access it after the magic runs.
self.kernel.get_variable(name)-
Read a variable from the kernel's namespace.
self.kernel.schedule_display_output(callback)-
Schedule a zero-argument callable to run on the kernel's main IO loop. Use this when you need to send output from a background thread (for example, in response to an event from a connected application). ZMQ sockets are not thread-safe, so calling
PrintorDisplaydirectly from a background thread is unsafe —schedule_display_outputroutes the call through Tornado's thread-safeadd_callback:
```python
import threading
def line_watch(self, interval="5"):
"""
%watch [interval]
Start a background thread that prints a status message every *interval* seconds.
"""
def _monitor():
import time
while True:
time.sleep(float(interval))
self.kernel.schedule_display_output(
lambda: self.kernel.Print("still running…")
)
threading.Thread(target=_monitor, daemon=True).start()
```
self.code-
The raw cell body (cell magics only); available after
call_magicsets it, or directly insidecell_*methods. self.evaluate-
Boolean (default
True). Set toFalseinside a cell magic to prevent the kernel from evaluatingself.codeas normal code.
Docstrings and help
The docstring of a magic method is displayed when the user runs:
%greet?
%%greet?
It is also executed as a doctest by pytest (--doctest-modules), so keep examples valid. A good docstring follows this structure:
%magic_name [args]
One-line summary.
Longer explanation if needed.
Example::
%magic_name value
IPython / Jupyter notebook compatibility
If you want your magic to work in plain IPython (not just in MetaKernel-based kernels), add a register_ipython_magics() function to the file:
def register_ipython_magics() -> None:
from metakernel import IPythonKernel
from metakernel.magic import register_line_magic
kernel = IPythonKernel()
magic = GreetMagic(kernel)
@register_line_magic
def greet(line: str) -> None:
magic.line_greet(line or "world")
MetaKernel calls register_ipython_magics() automatically when loading magics into an IPython session. For cell magics, use register_cell_magic instead.
See tutor_magic.py for a complete working example of both functions.