Interop¶
A plain .py file importing a .ppy module, with no build step. import
ppy installs a sys.meta_path finder, and import geometry then loads
geometry.ppy: as source with no compiler installed, from its native build
with one.
Run it¶
What it prints¶
python consumer.py
The hook is explicit¶
import ppy
import geometry
print("area :", geometry.area(3.0, 4.0))
print("hook :", ppy.is_installed())
Without import ppy the module is invisible and the import raises
ModuleNotFoundError. The hook is never implicit.
- If
geometry.pyandgeometry.ppyboth exist, the.ppywins and aPPyAmbiguousModuleWarningsays so. ppy checkrejects the ambiguity outright (E1003), which is whyppy convert --in-placeremoves the.pyit replaces.ppy convertinsertsimport ppyahead of first-party imports, so a converted entry point can import its siblings.
Native when it can be, source when it cannot¶
With the compiler installed, the first process to import geometry.ppy
builds it into the project's .ppy-cache (the artifact ppy run would
build) and binds its functions through the prebuilt binder. Every later
process finds the build. A module that does not check clean, or needs the
in-process JIT, loads as Python source with one line on stderr saying why.
You can turn the native path off:
PPY_IMPORT=pythonturns it off for a process.[tool.ppy] native-import = falseturns it off for a project.
This is also how a program under someone else's launcher gets native
kernels. torchrun, accelerate launch, or a scheduler starts ordinary
interpreters, and each one's import ppy finds the same build
(torchrun).
Where the code comes from¶
geometry.ppy is hand-written; there is no .py source and no conversion
step.
Read on: Migrating a real project ยท A multi-module project
24_interop/geometry.ppy¶
import ppy
@ppy.pure
def area(width: float, height: float) -> float:
return width * height
@ppy.pure
def perimeter(width: float, height: float) -> float:
return 2.0 * (width + height)
Source: examples/24_interop.