14 — Python Interop
Chapter goal: call the Python ecosystem from Aura. Reference:
../language-reference/python-interop.md. Example:../../examples/python_interop.aura.
Why interop is explicit
Aura runs on CPython, so every installed Python module is reachable. But a plain import re is an Aura module, not a Python one. To import a host Python module, prefix the path with py.:
import py.math as math
import py.os as os
from py.json import dumps, loads
def main() {
print(math.sqrt(16)) // 4.0
print(os.name) // posix
let encoded = dumps({"n": 1})
print(loads(encoded)["n"]) // 1
}
The py. prefix is stripped before code generation, so import py.math as math emits import math as math.
Binding rules
| Form | Bound name |
|---|---|
import py.re | re (the last path segment) |
import py.os.path | path — not os |
import py.re as regex | regex (an explicit alias wins) |
from py.math import sqrt, pi as PI | sqrt, PI |
Keyword collisions must be aliased. A Python name that is an Aura keyword cannot be bound under that name. Here type is an Aura keyword, so it must be aliased (this one runs):
from py.builtins import type as py_type
def main() {
print(py_type(5)) // <class 'int'>
}
Without the alias the parser rejects the binding:
// from py.re import type // error: 'type' is a reserved keyword
// from py.re import type as re_type // ok — aliased
Names that commonly need an alias: type, from, in, is, class, match, new, self, and every other reserved word. When no alias is possible — or the name is only known at runtime — use the dynamic bridge.
The python bridge
For runtime access, import the python bridge:
import python
def main() {
let re = python.import_module("re") // dynamic import
print(python.hasattr(re, "match")) // True
print(python.eval("1 + 2")) // 3
print(python.type_name(42)) // builtins.int
print(python.is_available("requests")) // True/False, never raises
}
| Function | Purpose |
|---|---|
import_module(name) / load(name) | import a module by name |
reload(module) | reload a module |
is_available(name) | True/False, never raises |
eval(expr) | evaluate an expression string |
exec_code(src) / compile_source(src) | execute / compile source |
call(func, *args) | call any Python callable |
getattr(obj, name, default) | attribute by name (escapes collisions) |
setattr(obj, name, value) | attribute assignment by name |
hasattr(obj, name) / dir(obj) | introspection |
type_name(obj) | fully-qualified type name |
to_aura(obj) / to_python(obj) | convert between the two worlds |
add_path(path) / site_packages() / modules() | environment and discovery |
The bridge is a thin, total wrapper: failures raise ordinary Python exceptions, so Aura try/catch works as expected.
Choosing
- Use
import py.x/from py.x import ywhen the module and names are known at compile time — direct, fast, readable. - Use
import pythonfor dynamic names,eval, introspection, or a keyword- colliding attribute (python.getattr(mod, "type")).
A framework example
Because a py. import is a real Python import, standard libraries and frameworks work directly. Here is a value built with py.json and read back through ordinary Aura indexing:
import py.json as json
def main() {
let payload = {"name": "aura", "ok": true}
let encoded = json.dumps(payload)
print(encoded) // {"name": "aura", "ok": true}
let decoded = json.loads(encoded)
print(decoded["name"], decoded["ok"]) // aura True
}
Python collections are Aura collections
Values cross the boundary unchanged: a Python dict is an Aura dict, a Python list is an Aura list. So Aura conveniences apply to returned objects:
import py.json as json
def main() {
let data = json.loads('{"items": [1, 2, 3]}')
print(data["items"].length()) // 3
}
Errors across the boundary
A Python exception raised by a host call is an ordinary exception to Aura, so try/catch works. Catch Error for any exception, or a specific type:
import py.json as json
def main() {
let data = try { json.loads("not json") } catch Error as e { none }
print(data is none) // true
}
The bridge itself is a thin wrapper: it does not swallow failures, it lets the underlying Python exception propagate.
What is NOT interop
| Spelling | Why | Use instead |
|---|---|---|
import re expecting Python | plain imports are Aura modules | import py.re |
from re import type | type is reserved | from py.re import type as re_type |
python.import_module(...) with no import python | the bridge must be imported | import python first |
| raw CPython internals through the bridge | the bridge is the supported surface | the listed helpers |
What you learned
py.marks a host Python module; the bound name is the last segment.- Keyword collisions require an alias.
- The
pythonbridge for dynamic access and introspection. - Values cross the boundary unchanged.