Preload Code

View Source

This guide covers preloading Python code that executes during interpreter initialization.

Overview

The py_preload module allows you to define Python code that runs once per interpreter at creation time. The resulting globals (imports, functions, variables) become available to all process-local environments.

Use Cases

  • Share common imports across all contexts
  • Define utility functions used throughout your application
  • Set up configuration or constants
  • Avoid repeated initialization overhead

API

FunctionDescription
set_code/1Set preload code (binary or iolist)
get_code/0Get current preload code or undefined
clear_code/0Remove preload code
has_preload/0Check if preload is configured

Basic Usage

%% Set preload code at application startup
py_preload:set_code(<<"
import json
import os

def shared_helper(x):
    return x * 2

CONFIG = {'debug': True, 'version': '1.0'}
">>).

%% Create a context - preload is automatically applied
{ok, Ctx} = py_context:new(#{mode => worker}).

%% Use preloaded imports
{ok, <<"{\"a\": 1}">>} = py:eval(Ctx, <<"json.dumps({'a': 1})">>).

%% Use preloaded functions
{ok, 10} = py:eval(Ctx, <<"shared_helper(5)">>).

%% Use preloaded variables
{ok, true} = py:eval(Ctx, <<"CONFIG['debug']">>).

Execution Flow

Interpreter/Context Creation
    
    
apply_registered_paths()      sys.path updates
    
    
apply_registered_imports()    module imports
    
    
apply_preload()               preload code execution
    
    
(interpreter ready)
    
    
create_local_env()            copies from interpreter globals

Process Isolation

Each process-local environment gets an isolated copy of preloaded globals:

py_preload:set_code(<<"COUNTER = 0">>).

{ok, Ctx1} = py_context:new(#{mode => worker}).
{ok, Ctx2} = py_context:new(#{mode => worker}).

%% Modify in Ctx1
ok = py:exec(Ctx1, <<"COUNTER = 100">>).
{ok, 100} = py:eval(Ctx1, <<"COUNTER">>).

%% Ctx2 still has original value
{ok, 0} = py:eval(Ctx2, <<"COUNTER">>).

Clearing Preload

Clearing preload only affects new contexts:

py_preload:set_code(<<"PRELOADED = 42">>).

{ok, Ctx1} = py_context:new(#{mode => worker}).
{ok, 42} = py:eval(Ctx1, <<"PRELOADED">>).

%% Clear preload
py_preload:clear_code().

%% Existing context still has it
{ok, 42} = py:eval(Ctx1, <<"PRELOADED">>).

%% New context does not
{ok, Ctx2} = py_context:new(#{mode => worker}).
{error, _} = py:eval(Ctx2, <<"PRELOADED">>).

Best Practices

  1. Set preload early - Configure before creating any contexts
  2. Keep it focused - Only include truly shared code
  3. Avoid side effects - Preload runs once per interpreter
  4. Use for imports - Common imports benefit most from preloading

Limitations

  • Changes to preload code don't affect existing contexts
  • Same preload applies to all context modes (worker, subinterp, owngil)
  • Preload errors during context creation will fail the context