Olive features native, bidirectional Python integration. You can import any Python module directly, call its functions with native Olive values, and get back typed results with full compile-time type information derived automatically from Python type stubs.
import py "glm" as glm
import py "math" as math
import py "numpy" as np
That single line is enough. If the module ships .pyi type stubs, Olive reads them at compile time and registers all exported types, functions, and class members automatically. No manual stub blocks needed.
When Olive can determine the type of a Python value from stubs, it tracks it as a qualified type (glm.vec3, glm.mat4, etc.) rather than collapsing everything to PyObject. This means the compiler catches type mismatches and resolves return types without any annotations from you.
import py "glm" as glm
let v = glm.vec3(1.0, 0.0, 0.0) # type: glm.vec3
let n = glm.normalize(v) # type: glm.vec3
let x = v.x # type: float
let w = v + n # type: glm.vec3
let proj = glm.perspective(fov, ar, near, far) # type: glm.mat4
The type checker resolves:
- Constructors:
glm.vec3(...)returnsglm.vec3 - Module-level functions: return types from stubs, with TypeVar constraints expanded into concrete per-type overloads
- Arithmetic operators:
+,-,*,/look up the appropriate dunder method on the left operand's class - Field access:
v.xreturnsfloatwhen the class stub annotatesx: float - Method calls: return types resolved from class method stubs
- Type mismatches: assigning a
glm.mat4where aglm.vec3is expected is a compile error
PyObject represents any Python value whose type is unknown or unknowable at compile time. It behaves like Any: operations on it are always permitted and always return PyObject. Use it when working with highly dynamic Python APIs where stub-based inference isn't possible.
import py "json" as json
let raw: PyObject = json.loads(data) # dynamic, type unknown
let val = raw["key"] # PyObject, resolved at runtime
PyObject and qualified types like glm.vec3 unify freely. You can always widen a typed Python value to PyObject when interoperability requires it.
Python values can be coerced into Olive primitives using the standard built-in constructors:
let n = float(py_val) # PyObject -> Olive float
let i = int(py_val) # PyObject -> Olive int
let s = str(py_val) # PyObject -> Olive string
Going the other direction, Olive primitives are automatically converted when passed to Python functions.
| Olive Type | Python Type | Notes |
|---|---|---|
int / i64 |
int |
Coerces via c_long |
float / f64 |
float |
Coerces via c_double |
str |
str |
UTF-8 |
list |
OliveListProxy |
Zero-copy wrapper, mutations propagate both ways |
dict |
OliveDictProxy |
Zero-copy wrapper, mutations propagate both ways |
None |
None |
Py_None |
glm.vec3 etc. |
native Python object | Tracked type; erased to PyObject at runtime boundary |
For modules without .pyi stubs, or when you want explicit control over which types are exposed, you can declare types and functions inline:
import py "mymodule" as mm:
type Foo
type Bar
fn create(x: float) -> Foo
fn process(f: Foo) -> Bar
Manual stub blocks take priority over auto-introspection. PyObject in a stub declaration is the explicit escape hatch for return types you don't want to track.
Olive locates the active Python shared library at startup using a three-tier fallback:
OLIVE_PYTHON_PATHorPYTHON_LIBRARYenvironment variables: checked first; set either to an absolute path to force a specific installation.- Subprocess query: Olive spawns
python3and readssysconfigto find the exact library for the active environment (venv,pyenv,conda, system). - Hardcoded search paths: fallback scan of common OS-specific locations (
libpython3.soon Linux, Homebrew paths on macOS,python3.dllon Windows).
export OLIVE_PYTHON_PATH="/usr/lib/libpython3.14.so"