title: Modules and Host Callbacks icon: dot
A module holds Python globals. Native modules created with py_newmodule()
can be imported by scripts in the current VM, just like source modules.
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:
| Return | Meaning |
|---|---|
1 |
Success; the module is in py_retval(). |
0 |
The module was not found; the caller decides how to report this. |
-1 |
Loading failed with a Python exception. |
Do not treat py_import() as a boolean: -1 is true in a C condition.
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__.pycPath 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.
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:
NULL when the requested path is unavailable.py_malloc(); the interpreter owns and frees it.
Do not return a string literal, stack buffer, or shared asset pointer.data_size is NULL: source reloads can omit this output.Callbacks belong to the current VM. Configure each VM that needs a custom loader, and configure it again after a reset.
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.