Flow can generate Python packages (wheels) as a first-class compilation target.
# Generate a Python wheel from a Flow library
./flow python mylib.flow
# Install and use
pip install dist/mylib-0.1.0-*.whl
python -c "import mylib; print(mylib.add(1, 2))"flow pythonruns the Flow tooltools/pywheel. It parses the program and its imports with the flowc front end and decides what to export.- flowc compiles the program to C (
compiler/scripts/flowc_emit.sh --strict). A type error stops here. - The tool writes a CPython extension source around that C, plus
setup.pyandpyproject.toml. python3 -m pip wheelbuilds the wheel intodist/. This is the only step that runs Python, because building a wheel needs setuptools. SetFLOW_PYTHONto use another interpreter.--sourceskips it and runs no Python at all.
Public symbols with ABI-compatible types are exported automatically:
# EXPORTED: Public function with compatible types
function add(a: i32, b: i32) -> i32 {
return a + b
}
# NOT EXPORTED: Private (starts with underscore)
function _internal_helper() -> void { ... }
# NOT EXPORTED: main() is never exported
function main() -> i32 { return 0 }
| Flow Type | Python Type | Notes |
|---|---|---|
i32, i64 |
int |
Full precision |
u32, u64 |
int |
Unsigned |
f32, f64 |
float |
IEEE 754 |
bool |
bool |
|
string |
str |
UTF-8 |
void |
None |
Return only |
Type aliases resolve to their base type for this check.
Types that cannot cross the Python boundary are excluded with diagnostics:
- Pointers, arrays, spans and function types
- Enums and generic types
- Structs. A public struct with fields stops generation with exit status 1, naming the struct. Private structs (leading underscore) and empty structs do not.
./flow python <file.flow> [options]
Options:
--name NAME Python module name (default: filename)
--version VER Package version (default: 0.1.0)
--source Generate C extension source only (no wheel)dist/
├── mylib-0.1.0-cp311-cp311-macosx_14_0_arm64.whl
└── mylib_ext.c (if --source)
# mathlib.flow
function square(x: f64) -> f64 {
return x * x
}
function factorial(n: i32) -> i32 {
if n <= 1 {
return 1
}
return n * factorial(n - 1)
}
Compile and use:
./flow python mathlib.flow --name mathlib
pip install dist/mathlib-0.1.0-*.whl
python3 << 'EOF'
import mathlib
print(mathlib.square(5.0)) # 25.0
print(mathlib.factorial(5)) # 120
EOFThe compiler shows which symbols are exported/excluded:
============================================================
Python Export Analysis: mathlib
============================================================
✅ Exported (2 symbols):
square: Public function with ABI-compatible signature
factorial: Public function with ABI-compatible signature
⚠️ Excluded (2 symbols):
main: Entry point 'main' not exported
_helper: Private symbol (starts with underscore)
For advanced cases, explicit control is available:
# Rename for Python
@python(name="py_func_name")
function flow_func_name() -> void { }
# Add documentation
@python(doc="Computes the square root")
function sqrt(x: f64) -> f64 { return x }
# Force exclude
@python(exclude=true)
function dont_export() -> void { }
-
Python is an output target, not a parent language
- No Python semantics leak into Flow
- All code is compiled, never interpreted
-
Zero boilerplate for common cases
- No explicit export annotations needed
- Automatic type inference
-
Deterministic, transparent inference
- Same code always produces same exports
- Clear diagnostics for every decision
-
Strict semantic separation
- Flow owns memory, lifetimes, errors
- Python sees a clean FFI boundary
- Structs, pointers and arrays do not cross the boundary yet
- Async effects don't map to Python async
- No NumPy array integration (future)
- macOS/Linux only (Windows future)