Modules and host callbacks
A module holds Python globals. Native modules created with py_newmodule()
can be imported by scripts in the current VM, just like source modules.
Create or look up a module
This fragment runs after initialization:
py_GlobalRef settings = py_newmodule("settings");
py_newint(py_emplacedict(settings, py_name("screen_width")), 1280);
bool ok = py_exec("import settings\nprint(settings.screen_width)",
"app.py", EXEC_MODE, NULL);
if(!ok) py_printexc();
Create a module only once per VM: recreating an existing name is an error at the
native API level. py_getmodule("settings") looks up an already registered
module and returns NULL if it is absent; it does not perform an import.
py_import(name) attempts to load a module and returns:
Do not treat py_import() as a boolean: -1 is true in a C condition.
Where imports come from
The importer first checks registered modules, then the host's optional
lazyimport callback, then bundled Python modules. For a file-based module
such as game.level, it asks importfile for these paths in order:
game/level.pygame/level.pycgame/level/__init__.pygame/level/__init__.pyc
Path separators follow the platform. Parent packages are loaded first. Supported desktop builds may then try a native dynamic module if enabled.
The default file callback opens paths relative to the process working
directory. Running main scripts/app.py does not automatically add
scripts/ to an import path. Start the host in the intended script directory
or provide your own loader. pocketpy does not provide CPython's sys.path
search mechanism.
When both source and bytecode exist, source is preferred. See bytecode deployment for the distribution format.
Load a module from memory
Replace py_callbacks()->importfile to load scripts from a game asset bundle,
a virtual filesystem, or embedded strings. This complete example provides one
module without reading a file:
#include "pocketpy.h"
#include <string.h>
static char* import_source(const char* path, int* data_size) {
if(strcmp(path, "settings.py") != 0) return NULL;
const char* source = "screen_width = 1280\n";
size_t size = strlen(source);
char* buffer = py_malloc(size + 1);
memcpy(buffer, source, size + 1);
if(data_size != NULL) *data_size = (int)size;
return buffer;
}
int main(void) {
py_initialize();
py_callbacks()->importfile = import_source;
bool ok = py_exec("import settings\nassert settings.screen_width == 1280",
"app.py", EXEC_MODE, NULL);
if(!ok) py_printexc();
py_finalize();
return ok ? 0 : 1;
}
The loader contract matters:
-
Return
NULLwhen the requested path is unavailable. -
Allocate a fresh buffer with
py_malloc(); the interpreter owns and frees it. Do not return a string literal, stack buffer, or shared asset pointer. -
NUL-terminate source text. For bytecode, report the exact byte length.
-
Check whether
data_sizeisNULL: source reloads can omit this output. -
Handle every path variant that your application supports.
Callbacks belong to the current VM. Configure each VM that needs a custom loader, and configure it again after a reset.
Redirect script output
py_callbacks()->print receives NUL-terminated UTF-8 text.
A single Python print() can call it more than once; do not assume each
callback contains a whole line. Set flush if your output backend buffers
data. py_printexc() also uses this output callback.
The getchr callback supplies input for input(). More specialized hooks,
including gc_mark and displayhook, are declared in
the public header.