Decorator API for simpler Mojo-from-Python usage

Hi all, thanks for the great work on Mojo and Python interop so far! I’ve been reading about the importer and wanted to share a suggestion that could make the workflow much simpler for everyday Python users.

Right now, creating a Mojo module that Python can import requires writing the PyInit_ boilerplate and manually setting up a PythonModuleBuilder. This is totally workable, but a bit heavy for quick experiments, notebooks, or cases where someone just wants “a fast Mojo function” inside a Python script.

Would this work: a decorator-based Python API that handles the Mojo file creation, the PyInit_ wrapper, the compilation, and the import automatically. Something like:

from mojo.easy import mojo_function

@mojo_function
def add_one(x: int) -> int:
    """
    fn add_one(x: Int) -> Int:
        return x + 1
    """

The decorator could:

  • write the Mojo source to a .mojo file in a cache directory

  • auto-generate the PyInit_... initialization wrapper

  • run the Mojo compiler (reusing cached builds when possible)

  • import the resulting module into Python

  • expose the compiled Mojo functions directly as normal Python functions

This would make the “call Mojo from Python” workflow ergonomic, especially for notebooks (Jupyter, Marimo, etc.) or for people porting bottleneck functions one function at a time…

Curious if something like this is already planned, or if the team thinks it fits into the roadmap. Thanks again for the great work, this interop direction is really exciting.

2 Likes

Hi Sasha, Mojo/Python bindings dev here—this is a great suggestion! :slightly_smiling_face:

We’ve done a bit of brainstorming internally about how we might like to use decorators in the future to make Python/Mojo bindings more ergonomic. However, Mojo doesn’t currently have “general” support for decorators; instead there are only a handful that are specially known to the compiler.

One idea that comes to mind as I’m writing this: As of recently, we have a mechanism for automatically iterating over the functions in a module, which we use for writing our test files:

def main():
    TestSuite.discover_tests[__functions_in_module()]().run()

__functions_in_modules() yields all of the functions in the current file/module, and then TestSuite.discover_tests filters that list looking for functions that have a name matching the pattern test_*.

We could potentially do something similar, and look for functions named py_*, and semi-automatically bind those to Python.

That’s just some idle brainstorming from me though.

As you’re bringing up, there are a number of potential interesting future directions for improving these bindings :slightly_smiling_face:

1 Like

Thanks Connor, really appreciate the response.

Just to clarify my original idea: the decorator I had in mind would actually live entirely on the Python side, not inside Mojo. So it wouldn’t depend on Mojo gaining general decorator support. Python would do all the orchestration. The model I had in mind is similar to things in numba.jit, cython.inline, mypyc, rustpy, and even how marimo handles SQL cells or how IPython magics work. All of these let Python accept a block of foreign-source code, compile it, cache it, and expose a normal Python function.

In that spirit, a Python decorator could:

• read a code block or docstring containing the Mojo function body
• write a temporary .mojo file into a cache directory
• auto-generate the PyInit_ wrapper
• call the Mojo compiler (with caching)
• import the resulting module
• return a normal Python function whose call forwards to the compiled Mojo function

The result would be an ergonomic “fast path” for quick experiments or notebooks, while still producing real compiled Mojo extension modules under the hood.

This also seems complementary to the idea you mentioned about scanning functions inside a Mojo module (like py_*). A Python-side workflow could use a similar scanning step to bind multiple Mojo functions automatically, while giving Python-first users a simple entry point for speeding up isolated functions.

Totally understand this might be more of a future direction idea, but it feels like a natural bridge for people prototyping in Python and gradually moving logic into Mojo.

Thanks again for thinking through it.