Skip to main content
This guide uses NumPy to multiply matrices from Sifr. NumPy is a good Python interop example because most useful work is a small workflow, not a single primitive conversion, and the package loads a native extension.

Why this shape

NumPy is both a Python import root and a native extension. Sifr treats those as separate trust decisions. Matrix multiplication also tends to involve temporary arrays, dtype choices, and result shaping. That adaptation belongs in a package-local Python bridge under src/python_bridges/. Five rules drive the NumPy path:
  1. Authorize Python and native roots separately. [trust].python allows execution; [trust].python-native allows loading the native extension in-process.
  2. Keep the Sifr boundary typed. Expose multiply_matrices(...) -> Result[...] from a bridge instead of leaking ndarray details into every caller.
  3. Opaque Python values are non-send by default. Do not assume a NumPy object can move across threads without an explicit contract.
  4. Public Python calls are blocking. Call NumPy from @blocking_io code, or offload from async Sifr.
  5. Zero-copy is opt-in and fail-closed. Buffer and DLPack declarations never silently copy. Use them only when you need a view, not for ordinary multiply-and-return workflows.

Package setup

Install NumPy in the root uv project, then declare trust:
sifr.toml
Native trust is required because NumPy loads extension modules. That is the explicit exception to Sifr-attributable no-panic guarantees for the native root.

Bridge the multiply workflow

Put adaptation code in the owning package’s src/python_bridges/:
src/python_bridges/linalg.py
Ordinary static imports are inventoried. The bridge converts Sifr-friendly lists into NumPy arrays, multiplies, and returns plain nested lists again.

Declare and call

bridge.linalg.multiply resolves only against this package’s inventoried bridge modules. Generated binaries embed that bridge under an isolated runtime namespace; they do not read the .py file from disk at runtime.

When to use buffers instead

Use @python.buffer when Sifr code must read or write array memory without copying the whole payload into nested lists:
Buffer values are affine and non-send. Release them explicitly or let ownership drop exactly once. Do not use buffer declarations for ordinary multiply-and-return helpers; keep those on the typed bridge path above.

Next steps