Exports and Modules

The entry __init__.pyi controls the extension's Python namespace. Leaf .pyi files describe native modules and their declarations.

Choose the Package Shape

Generated entry contract:

from . import module1
from . import module2

Python then uses package.module1 and package.module2. To place both modules' public names directly in package, edit the entry contract:

from .module1 import *
from .module2 import *

Selective imports and aliases also work:

from .module1 import solve
from .module2 import reset as clear

Changing entry imports changes only the Python namespace. It does not rename a native module or select a native object file.

Only declarations reachable from __init__.pyi are public. Missing files, import cycles, and two different exports using the same Python name are errors. Explicit aliases share the same native target. A module-variable alias reads and writes the declaring variable's live native storage; it does not create another variable.

Remove or Hide a Declaration

Delete a declaration to remove it from the Python API:

from prik.contracts import Int32

counter: Int32

def summarize() -> Int32: ...

The removed declaration is not regenerated during this build. This works for functions, variables, classes, methods, fields, constructors, and individual overload candidates.

Use @private for a function or class that another contract declaration still needs:

from prik.contracts import Float64, private

@private
def scaled_counter() -> Float64: ...

Use private[...] for a variable or argument:

from prik.contracts import Float64, private

scale: private[Float64]

Both forms keep the declaration in the contract while hiding it from Python.

Add or Rename a Native Procedure

You may add a declaration when the procedure already exists in the supplied native implementation:

from prik.contracts import Float64

def norm2(values: Float64[:]) -> Float64: ...

In a module leaf, the filename identifies the native module and the function name selects the native procedure. Use @bind(...) when the Python name differs:

from prik.contracts import Float64, Int32, bind

@bind("solver_step")
def step(values: Float64[:]) -> Int32: ...

For a standalone native symbol, also use @standalone:

from prik.contracts import Float64, bind, standalone

@standalone
@bind("vendor_norm2")
def norm2(values: Float64[:]) -> Float64: ...

When an original Fortran procedure was declared bind(C), preserve that ABI fact separately with @native_abi("c"). An optional @bind(...) then names its linkable label; neither decorator changes the procedure's module or standalone placement:

from prik.contracts import Float64, bind, native_abi

@native_abi("c")
@bind("vendor_norm2")
def norm2(values: Float64[:]) -> Float64: ...

This marker does not guarantee direct routing. Policy still validates the complete operation before choosing the C ABI entrypoint or a generated Fortran adapter.

The declaration must include the correct native arguments, types, ranks, and call shape. Adding Python syntax cannot create a native procedure that is not present in the linked implementation.

Set Module Values at Import

A writable scalar module variable may have a literal initial value:

from prik.contracts import Int32

counter: Int32 = 41

PRIK sets the module variable when the extension is imported. It remains writable. This works only when PRIK can write that native variable, and the initializer must be a literal rather than a call, name, or expression.

Use Final[...] only for a true read-only constant:

from prik.contracts import Final, Int32

nmax: Final[Int32] = 12

See Wrapping Modules for the resulting Python usage.

Re-export Module State

Import a variable into another leaf and include it in that leaf's __all__:

# state.pyi
from prik.contracts import Int32

counter: Int32
__all__ = ["counter"]
# facade.pyi
from .state import counter

__all__ = ["counter"]

Both package.state.counter and package.facade.counter access the same native variable. Assignment, array mutation, allocation, pointer association, and derived-object changes made through either namespace are immediately visible through the other. PRIK completes the variable's access and ownership policy once; the second namespace changes publication only.

A re-exported Final[...] parameter behaves differently, because a Fortran parameter has no native storage to share and no setter. Each namespace receives the same native constant value as an ordinary Python attribute:

  • every publication starts at the value the Fortran parameter declares;
  • assigning to one, such as facade.limit, rebinds that Python name and does not modify the Fortran parameter;
  • assigning to one does not rebind the others, so the namespaces can disagree afterwards.

Assignment is not refused. Nothing enforces the constant at runtime, so treat a published parameter as a value each namespace holds rather than a shared read-only view of native state.

Next

Use Functions and Classes to add methods, overloads, or a custom constructor.