Cython compilation support for marimo notebooks
Project description
marimo-cython
Cython compilation support for marimo notebooks. Write Cython-accelerated functions directly in notebook cells and run them at native speed — no build system boilerplate required.
This is the marimo equivalent of Jupyter's %%cython magic, redesigned for marimo's reactive DAG model.
Installation
Requires Python 3.10+.
uv add marimo-cython
Or with pip:
pip install marimo-cython
Runtime dependencies: cython>=3.0 and setuptools (for C extension building).
Quick start
import cython
from marimo_cython import cy
@cy.compile
def fib(n: cython.int) -> cython.int:
a: cython.int = 0
b: cython.int = 1
i: cython.int
for i in range(n):
a, b = b, a + b
return a
fib(50) # runs compiled native code
API
Three entry points, all available via cy.* or as direct imports from marimo_cython:
@cy.compile — decorator
Compiles a pure-Python Cython function. The function must use Cython's pure Python syntax (cython.int, cython.double, typed memoryviews, etc.).
@cy.compile
def fib(n: cython.int) -> cython.int:
a: cython.int = 0
b: cython.int = 1
i: cython.int
for i in range(n):
a, b = b, a + b
return a
With options:
@cy.compile(boundscheck=False, wraparound=False, cdivision=True)
def mandelbrot(
out: cython.int[:, :],
xmin: cython.double,
xmax: cython.double,
ymin: cython.double,
ymax: cython.double,
max_iter: cython.int,
) -> None:
rows: cython.Py_ssize_t = out.shape[0]
cols: cython.Py_ssize_t = out.shape[1]
# ...
Using C library functions
To call C library functions (like sqrt, fabs, etc.) from a decorated function, use from cython.cimports.* at cell/module level above the function. Cython provides runtime Python stubs for these, so they are valid Python imports:
from cython.cimports.libc.math import sqrt, fabs
@cy.compile(boundscheck=False)
def fast_sqrt(x: cython.double) -> cython.double:
return sqrt(fabs(x))
The decorator automatically picks up from cython.cimports.* imports written above the function in the same cell or file, and includes them in the compiled module.
As a fallback, the cimports parameter accepts raw cimport strings (which use Cython syntax, not valid Python):
@cy.compile(cimports=["from libc.math cimport sqrt, fabs"])
def fast_sqrt(x: cython.double) -> cython.double:
return sqrt(fabs(x))
cy.compile_module(source) — compile a string
Compiles a Cython source string into a module. Supports full .pyx syntax including cdef, cimport, and typed function signatures.
mod = cy.compile_module("""
from libc.math cimport sqrt
def norm(double[:] v):
cdef Py_ssize_t i
cdef double s = 0.0
for i in range(v.shape[0]):
s += v[i] * v[i]
return sqrt(s)
""", boundscheck=False, wraparound=False)
mod.norm(np.array([3.0, 4.0])) # => 5.0
cy.compile_file(path) — compile a .pyx file
mod = cy.compile_file("solver.pyx", cplus=True, libraries=["lapack"])
mod.solve(matrix)
cy.clear_cache() — remove compiled artifacts
cy.clear_cache() # removes .marimo_cython_cache/ and all contents
cy.clear_cache(cache_dir="custom_cache/") # custom cache directory
Returns the number of files removed.
Compile options
All three entry points accept the same keyword arguments. These map to the CompileOptions dataclass:
C/C++ compilation
| Option | Type | Default | Description |
|---|---|---|---|
cplus |
bool |
False |
Compile as C++ instead of C |
compile_args |
list[str] |
[] |
Extra compiler flags (e.g. ["-O3", "-march=native"]) |
link_args |
list[str] |
[] |
Extra linker flags |
libraries |
list[str] |
[] |
Libraries to link against |
include_dirs |
list[str] |
[] |
Additional include directories |
library_dirs |
list[str] |
[] |
Additional library directories |
runtime_library_dirs |
list[str] |
[] |
Runtime library search paths |
define_macros |
list[tuple] |
[] |
Preprocessor macros |
undef_macros |
list[str] |
[] |
Macros to undefine |
extra_objects |
list[str] |
[] |
Extra object files to link |
Cython compiler directives
| Option | Type | Default | Description |
|---|---|---|---|
boundscheck |
bool |
None |
Array bounds checking |
wraparound |
bool |
None |
Negative indexing support |
cdivision |
bool |
None |
C-style division (no zero-division check) |
nonecheck |
bool |
None |
Check for None on extension types |
initializedcheck |
bool |
None |
Check memoryview initialization |
overflowcheck |
bool |
None |
Integer overflow checking |
infer_types |
bool |
None |
Automatic type inference |
profile |
bool |
None |
Enable profiling hooks |
linetrace |
bool |
None |
Enable line tracing |
embedsignature |
bool |
None |
Embed function signatures in docstrings |
emit_code_comments |
bool |
None |
Emit source comments in generated C |
freethreading_compatible |
bool |
None |
Mark as free-threading compatible |
language_level |
int|str |
3 |
Cython language level |
compiler_directives |
dict |
{} |
Catch-all for any Cython directive |
None means "use Cython's default". Set to True/False to override.
Other options
| Option | Type | Default | Description |
|---|---|---|---|
module_name |
str |
auto | Name for the compiled module |
cimports |
list[str]|str |
[] |
Fallback: raw cimport statements (for @cy.compile) |
annotate |
bool|str |
False |
Generate Cython annotation HTML |
nthreads |
int |
0 |
Parallel cythonization threads |
force |
bool |
False |
Force recompilation (ignore cache) |
cache_dir |
str|Path |
.marimo_cython_cache/ |
Build artifact directory |
How it works
Pipeline
All three APIs converge into the same path: normalize source, compute cache key, build or load.
Source → Normalize → Cache check → Cythonize (.pyx → .c) → Build (.c → .so) → Load
Source extraction (@cy.compile)
The decorator uses inspect.getsource(fn) to get the function source. This works in marimo because marimo populates linecache for exec'd cell code (the same mechanism Python uses for tracebacks — not a private API). Decorator lines are stripped before compilation.
Cimport context recovery
inspect.getsource only returns the function body. from cython.cimports.* imports written above the function in the same cell are not included. To recover them, _collect_context_cimports AST-parses all lines above the function definition, finds ImportFrom nodes matching cython.cimports.*, and prepends them to the source.
Auto import cython
import cython is auto-prepended if missing (needed for cython.int, cython.double annotations). Detection uses ast.parse, not regex. Critically, from cython.cimports.* does NOT count as a cython import — it imports C symbols, not the cython module. Getting this wrong causes cython.double to fail with "Unknown type declaration".
Content-addressed caching
The cache key is a SHA-256 of: source (post-normalization), all C/C++ flags, all compiler directives, EXT_SUFFIX (Python ABI), and Cython.__version__. Module name becomes {prefix}_{hash[:16]}. On hit, the .so loads directly via importlib.
Build
On cache miss: write .pyx → cythonize() to .c → setuptools build_ext to .so → load with importlib. Build temp uses a short-lived tempfile.mkdtemp to avoid path-length issues. Intermediate .c/.cpp files are cleaned after success; .pyx is kept for debugging.
Other details
- Numpy: auto-detected from source;
numpy.get_include()added toinclude_dirsautomatically - Linker warnings:
-Wl,-wsuppresses CPython 3.14's staleModules/_haclpath warnings CythonModule: wrapper that delegates attribute access to the compiled module and supports marimo rich display (_mime_()) for annotation HTML- Cache dir:
.marimo_cython_cache/by default (add to.gitignore)
Annotations
Compile with annotate=True to get Cython's annotation HTML rendered in marimo cell output:
mod = cy.compile_module(source, annotate=True)
mod # displays annotation HTML in marimo
Cross-cell interaction
Compiled modules and functions work across marimo cells like any other Python object. Define a module in one cell and call its functions from another:
# Cell 1
linalg = cy.compile_module("""
from libc.math cimport sqrt
def norm(double[:] v):
...
""")
# Cell 2 — uses linalg from Cell 1
result = linalg.norm(my_array)
Running the demos
uv sync --extra dev
uv run marimo edit demo.py
The demo covers the core API: fibonacci benchmark, cimport patterns, compile_module, and cross-cell interaction.
Examples
The examples/ directory contains standalone notebooks:
examples/mandelbrot.py— Interactive Mandelbrot set with a resolution slider. Runs Cython and Python side-by-side with progressive rendering (Cython result appears instantly, Python fills in when done). PEP 723 compatible — run withuv run --sandbox examples/mandelbrot.py.
uv sync --extra dev
uv run marimo run examples/mandelbrot.py
Development
uv sync --extra dev
uv run ruff check src/
uv run pytest
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file marimo_cython-0.1.2.tar.gz.
File metadata
- Download URL: marimo_cython-0.1.2.tar.gz
- Upload date:
- Size: 13.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c8135f5b3ccca142b56c1bc67f8613dbec82f051d785bfc9f16e147356573d9
|
|
| MD5 |
64b24cf67b220ac11bf362626136f313
|
|
| BLAKE2b-256 |
dabba4bc1e739a8e434791c6f1d8e0c8576214892b66a4b595312e4b6b74faf4
|
Provenance
The following attestation bundles were made for marimo_cython-0.1.2.tar.gz:
Publisher:
publish.yml on cemrehancavdar/marimo-cython
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
marimo_cython-0.1.2.tar.gz -
Subject digest:
8c8135f5b3ccca142b56c1bc67f8613dbec82f051d785bfc9f16e147356573d9 - Sigstore transparency entry: 1033383511
- Sigstore integration time:
-
Permalink:
cemrehancavdar/marimo-cython@5df651080c695aefc2f49b7110c2e3d7b8170865 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/cemrehancavdar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5df651080c695aefc2f49b7110c2e3d7b8170865 -
Trigger Event:
release
-
Statement type:
File details
Details for the file marimo_cython-0.1.2-py3-none-any.whl.
File metadata
- Download URL: marimo_cython-0.1.2-py3-none-any.whl
- Upload date:
- Size: 15.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
700c4e586af31d1a50e12d53c986cf77f4097a2903924145418ecc9b9dfab9ee
|
|
| MD5 |
3eda221a19ee28a3c4e033c48b2d0e78
|
|
| BLAKE2b-256 |
9a47356b4bdc897ef41233ec2aa1a0a3bcbd9d3cf4d1319940e301eae22a9190
|
Provenance
The following attestation bundles were made for marimo_cython-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on cemrehancavdar/marimo-cython
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
marimo_cython-0.1.2-py3-none-any.whl -
Subject digest:
700c4e586af31d1a50e12d53c986cf77f4097a2903924145418ecc9b9dfab9ee - Sigstore transparency entry: 1033383558
- Sigstore integration time:
-
Permalink:
cemrehancavdar/marimo-cython@5df651080c695aefc2f49b7110c2e3d7b8170865 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/cemrehancavdar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5df651080c695aefc2f49b7110c2e3d7b8170865 -
Trigger Event:
release
-
Statement type: