Get Started

Install Smelt and Hatchling as build requirements, and declare a mypyc module to compile - here, a plain recursive fib function:

src/fib_demo/fib.py:

def fib(n: int) -> int:
    if n <= 1:
        return n
    return fib(n - 2) + fib(n - 1)

pyproject.toml:

[build-system]
requires = ["hatchling", "smelt"]
build-backend = "hatchling.build"

[project]
name = "fib-demo"
version = "0.1.0"
requires-python = ">=3.12"

[tool.hatch.build.targets.wheel]
packages = ["src/fib_demo"]

[tool.hatch.build.hooks.smelt]
packages_location = { "fib_demo" = "src/fib_demo" }

[[tool.hatch.build.hooks.smelt.mypyc_modules]]
import_path = "fib_demo.fib"

That's it - no hello.c, no manual setup.py. Building the project (e.g. pip install ., or python -m build) compiles fib.py with mypyc and drops a native fib*.so right next to it, which then shadows the pure-Python source: import fib_demo.fib transparently loads the compiled extension.

See Build Hook for the full configuration reference (mypyc, Cython, Nuitka, and handwritten C/Zig extensions), and CLI for driving builds outside of a Hatchling build.

Basic commands

Installing Smelt with the cli extra (pip install "smelt[cli]") also gives you the smelt command, which can drive the same fib-demo project by hand - handy to inspect or rebuild extensions without going through a full pip install/build. smelt --help lists every available command; see CLI for the full reference. A few of them, run from the fib-demo project root:

smelt show-config parses pyproject.toml and prints the config Smelt actually resolved - useful to confirm your mypyc_modules/c_extensions/... declarations were picked up correctly:

$ smelt show-config
SmeltConfig(packages_location={'fib_demo': 'src/fib_demo'},
            ...
            mypyc_modules=[MypycModule(import_path='fib_demo.fib',
                                       source=None,
                                       extras=[])],
            ...)

smelt build-extensions runs the same compilation step the build hook runs during pip install/build, without needing a full build. For fib-demo this compiles fib.py with mypyc and drops two files next to it: the compiled module itself, and its small mypyc runtime:

$ smelt build-extensions
$ ls src/fib_demo
__init__.py  fib.cpython-312-x86_64-linux-gnu.so  fib__mypyc.cpython-312-x86_64-linux-gnu.so  fib.py

fib.py is still there, but it's now shadowed: import fib_demo.fib loads the compiled .so, not the source file.

smelt clean-artifacts reverses that: it deletes the compiled .sos (and the mypyc runtime alongside them), unshadowing fib.py back to being the one Python imports - handy to reset a checkout to pure Python:

$ smelt clean-artifacts
Deleted following artifacts:
fib_demo.fib:
  - src/fib_demo/fib.cpython-312-x86_64-linux-gnu.so (was shadowing src/fib_demo/fib.py)
  - src/fib_demo/fib__mypyc.cpython-312-x86_64-linux-gnu.so