Skip to content

Commit df422a1

Browse files
committed
Add config-based initialization for imports and paths
py_import:init/0 now loads imports and paths from application config: {erlang_python, [ {imports, [{json, dumps}, {math, sqrt}]}, {paths, ["/path/to/modules"]} ]}
1 parent 9031d00 commit df422a1

File tree

2 files changed

+98
-6
lines changed

2 files changed

+98
-6
lines changed

src/py_import.erl

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@
1818
%%% applied to all Python interpreters. Imports and paths are applied
1919
%%% immediately to all running interpreters and stored for new interpreters.
2020
%%%
21+
%%% == Configuration ==
22+
%%%
23+
%%% Imports and paths can be configured in the application environment:
24+
%%%
25+
%%% ```
26+
%%% {erlang_python, [
27+
%%% {imports, [{json, dumps}, {math, sqrt}, {os, getcwd}]},
28+
%%% {paths, ["/path/to/modules"]}
29+
%%% ]}
30+
%%% '''
31+
%%%
2132
%%% == Examples ==
2233
%%%
2334
%%% ```
@@ -70,28 +81,27 @@
7081
%% @doc Initialize the import and path registry ETS tables.
7182
%%
7283
%% This is called automatically during application startup.
84+
%% Also loads imports and paths from application config.
7385
%% Safe to call multiple times - does nothing if already initialized.
7486
%%
7587
%% @returns ok
7688
-spec init() -> ok.
7789
init() ->
7890
case ets:info(?IMPORT_REGISTRY) of
7991
undefined ->
80-
%% Use bag type to allow multiple entries with same module name
81-
%% e.g., {<<"json">>, all} and {<<"json">>, <<"dumps">>}
8292
ets:new(?IMPORT_REGISTRY, [bag, public, named_table]),
8393
ok;
8494
_ ->
8595
ok
8696
end,
8797
case ets:info(?PATH_REGISTRY) of
8898
undefined ->
89-
%% Use set type - paths are unique, ordered by insertion
9099
ets:new(?PATH_REGISTRY, [ordered_set, public, named_table]),
91100
ok;
92101
_ ->
93102
ok
94-
end.
103+
end,
104+
load_config().
95105

96106
%%% ============================================================================
97107
%%% Module Import Registry
@@ -353,6 +363,30 @@ is_path_added(Path) ->
353363
ensure_binary(S) ->
354364
py_util:to_binary(S).
355365

366+
%% @private Load imports and paths from application config
367+
load_config() ->
368+
%% Load imports: [{Module, Func}]
369+
Imports = application:get_env(erlang_python, imports, []),
370+
lists:foreach(
371+
fun({Module, Func}) ->
372+
ModuleBin = ensure_binary(Module),
373+
FuncBin = ensure_binary(Func),
374+
ets:insert(?IMPORT_REGISTRY, {ModuleBin, FuncBin})
375+
end,
376+
Imports
377+
),
378+
%% Load paths
379+
Paths = application:get_env(erlang_python, paths, []),
380+
lists:foreach(
381+
fun(Path) ->
382+
PathBin = ensure_binary(Path),
383+
Key = erlang:monotonic_time(),
384+
ets:insert(?PATH_REGISTRY, {Key, PathBin})
385+
end,
386+
Paths
387+
),
388+
ok.
389+
356390
%% @private Apply import to all running interpreters (contexts + event loops)
357391
apply_import_to_interpreters(ModuleBin) ->
358392
Imports = [{ModuleBin, all}],

test/py_import_SUITE.erl

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@
4545
add_path_test/1,
4646
%% Immediate application tests
4747
import_applies_to_running_interpreter_test/1,
48-
path_applies_to_running_interpreter_test/1
48+
path_applies_to_running_interpreter_test/1,
49+
%% Config initialization tests
50+
init_from_config_test/1
4951
]).
5052

5153
all() ->
@@ -82,7 +84,9 @@ groups() ->
8284
add_path_test,
8385
%% Immediate application tests
8486
import_applies_to_running_interpreter_test,
85-
path_applies_to_running_interpreter_test
87+
path_applies_to_running_interpreter_test,
88+
%% Config initialization tests
89+
init_from_config_test
8690
]}].
8791

8892
init_per_suite(Config) ->
@@ -845,3 +849,57 @@ path_applies_to_running_interpreter_test(Config) ->
845849
ok = py_import:clear_paths(),
846850

847851
ct:pal("add_path applies immediately to running interpreter").
852+
853+
%% ============================================================================
854+
%% Config Initialization Tests
855+
%% ============================================================================
856+
857+
%% @doc Test that imports and paths are loaded from application config
858+
init_from_config_test(Config) ->
859+
%% Clear existing state
860+
ok = py_import:clear_imports(),
861+
ok = py_import:clear_paths(),
862+
863+
%% Create test module in priv_dir
864+
PrivDir = ?config(priv_dir, Config),
865+
ModuleDir = filename:join(PrivDir, "config_test"),
866+
ok = filelib:ensure_dir(filename:join(ModuleDir, "dummy")),
867+
ModulePath = filename:join(ModuleDir, "config_test_mod.py"),
868+
ok = file:write_file(ModulePath, <<"CONFIG_VALUE = 123\n">>),
869+
870+
%% Set application config
871+
ok = application:set_env(erlang_python, imports, [{json, dumps}, {base64, b64encode}]),
872+
ok = application:set_env(erlang_python, paths, [ModuleDir]),
873+
874+
%% Re-run init to load config
875+
ok = py_import:init(),
876+
877+
%% Verify imports were loaded in registry
878+
Imports = py_import:all_imports(),
879+
?assert(lists:member({<<"json">>, <<"dumps">>}, Imports)),
880+
?assert(lists:member({<<"base64">>, <<"b64encode">>}, Imports)),
881+
882+
%% Verify paths were loaded in registry
883+
Paths = py_import:all_paths(),
884+
ModuleDirBin = list_to_binary(ModuleDir),
885+
?assert(lists:member(ModuleDirBin, Paths)),
886+
887+
%% Create a new context and verify imports/paths are applied
888+
{ok, Ctx} = py_context:new(#{mode => worker}),
889+
890+
%% Verify json.dumps works (from config imports)
891+
{ok, JsonResult} = py_context:call(Ctx, json, dumps, [[1, 2, 3]], #{}),
892+
?assertEqual(<<"[1, 2, 3]">>, JsonResult),
893+
894+
%% Verify custom module from config path works
895+
{ok, ConfigValue} = py_context:eval(Ctx, <<"__import__('config_test_mod').CONFIG_VALUE">>),
896+
?assertEqual(123, ConfigValue),
897+
898+
%% Clean up
899+
py_context:destroy(Ctx),
900+
ok = application:unset_env(erlang_python, imports),
901+
ok = application:unset_env(erlang_python, paths),
902+
ok = py_import:clear_imports(),
903+
ok = py_import:clear_paths(),
904+
905+
ct:pal("init loads imports and paths from config and applies to new contexts").

0 commit comments

Comments
 (0)