1515% %% @doc Import and path registry for Python interpreters.
1616% %%
1717% %% This module manages the global import and path registries that are
18- % %% applied to all Python interpreters. When new interpreters are created,
19- % %% they automatically get all registered imports and paths applied .
18+ % %% applied to all Python interpreters. Imports and paths are applied
19+ % %% immediately to all running interpreters and stored for new interpreters .
2020% %%
2121% %% == Examples ==
2222% %%
2323% %% ```
24- % %% %% Register modules for import in all interpreters
24+ % %% %% Register modules for import in all interpreters (immediate + future)
2525% %% ok = py_import:ensure_imported(json).
2626% %% ok = py_import:ensure_imported(math, sqrt).
2727% %%
28- % %% %% Add paths to sys.path in all interpreters
28+ % %% %% Add paths to sys.path in all interpreters (immediate + future)
2929% %% ok = py_import:add_path("/path/to/my/modules").
3030% %%
3131% %% %% Check registry contents
@@ -99,48 +99,42 @@ init() ->
9999
100100% % @doc Register a module for import in all interpreters.
101101% %
102- % % Adds the module to the global import registry. When new interpreters
103- % % are created, they will automatically import all registered modules.
104- % % The module will be imported lazily when first used.
102+ % % Imports the module immediately in all running interpreters and adds it
103+ % % to the registry for future interpreters.
105104% %
106105% % The `__main__' module is never cached.
107106% %
108107% % Example:
109108% % ```
110109% % ok = py_import:ensure_imported(json),
111- % % {ok, Result} = py:call(json, dumps, [Data]). %% Module imported on first use
110+ % % {ok, Result} = py:call(json, dumps, [Data]).
112111% % '''
113112% %
114113% % @param Module Python module name
115114% % @returns ok | {error, Reason}
116115-spec ensure_imported (py_module ()) -> ok | {error , term ()}.
117116ensure_imported (Module ) ->
118117 ModuleBin = ensure_binary (Module ),
119- % % Reject __main__
120118 case ModuleBin of
121119 <<" __main__" >> ->
122120 {error , main_not_cacheable };
123121 _ ->
124- % % Add to global registry - module will be imported lazily
125- case ets :info (? IMPORT_REGISTRY ) of
126- undefined -> ok ;
127- _ -> ets :insert (? IMPORT_REGISTRY , {ModuleBin , all })
128- end ,
122+ ets :insert (? IMPORT_REGISTRY , {ModuleBin , all }),
123+ apply_import_to_interpreters (ModuleBin ),
129124 ok
130125 end .
131126
132127% % @doc Register a module/function for import in all interpreters.
133128% %
134- % % Adds the module/function to the global import registry. When new
135- % % interpreters are created, they will automatically import the module.
136- % % The module will be imported lazily when first used.
129+ % % Imports the module immediately in all running interpreters and adds it
130+ % % to the registry for future interpreters.
137131% %
138132% % The `__main__' module is never cached.
139133% %
140134% % Example:
141135% % ```
142136% % ok = py_import:ensure_imported(json, dumps),
143- % % {ok, Result} = py:call(json, dumps, [Data]). %% Module imported on first use
137+ % % {ok, Result} = py:call(json, dumps, [Data]).
144138% % '''
145139% %
146140% % @param Module Python module name
@@ -150,16 +144,12 @@ ensure_imported(Module) ->
150144ensure_imported (Module , Func ) ->
151145 ModuleBin = ensure_binary (Module ),
152146 FuncBin = ensure_binary (Func ),
153- % % Reject __main__
154147 case ModuleBin of
155148 <<" __main__" >> ->
156149 {error , main_not_cacheable };
157150 _ ->
158- % % Add to global registry - module will be imported lazily
159- case ets :info (? IMPORT_REGISTRY ) of
160- undefined -> ok ;
161- _ -> ets :insert (? IMPORT_REGISTRY , {ModuleBin , FuncBin })
162- end ,
151+ ets :insert (? IMPORT_REGISTRY , {ModuleBin , FuncBin }),
152+ apply_import_to_interpreters (ModuleBin ),
163153 ok
164154 end .
165155
@@ -268,9 +258,8 @@ import_list() ->
268258
269259% % @doc Add a path to sys.path in all interpreters.
270260% %
271- % % Adds the path to the global path registry. When new interpreters
272- % % are created, they will automatically have this path in sys.path.
273- % % The path is inserted at the beginning of sys.path to take precedence.
261+ % % Adds the path immediately to sys.path in all running interpreters
262+ % % (contexts and event loops) and stores it for future interpreters.
274263% %
275264% % Example:
276265% % ```
@@ -283,13 +272,9 @@ import_list() ->
283272-spec add_path (string () | binary () | atom ()) -> ok .
284273add_path (Path ) ->
285274 PathBin = ensure_binary (Path ),
286- case ets :info (? PATH_REGISTRY ) of
287- undefined -> ok ;
288- _ ->
289- % % Use monotonic time as key to preserve insertion order
290- Key = erlang :monotonic_time (),
291- ets :insert (? PATH_REGISTRY , {Key , PathBin })
292- end ,
275+ Key = erlang :monotonic_time (),
276+ ets :insert (? PATH_REGISTRY , {Key , PathBin }),
277+ apply_path_to_interpreters (PathBin ),
293278 ok .
294279
295280% % @doc Add multiple paths to sys.path in all interpreters.
@@ -367,3 +352,75 @@ is_path_added(Path) ->
367352% % @private
368353ensure_binary (S ) ->
369354 py_util :to_binary (S ).
355+
356+ % % @private Apply import to all running interpreters (contexts + event loops)
357+ apply_import_to_interpreters (ModuleBin ) ->
358+ Imports = [{ModuleBin , all }],
359+ % % Apply to all contexts
360+ lists :foreach (
361+ fun (Ctx ) ->
362+ try
363+ Ref = py_context :get_nif_ref (Ctx ),
364+ py_nif :interp_apply_imports (Ref , Imports )
365+ catch _ :_ -> ok
366+ end
367+ end ,
368+ get_all_contexts ()
369+ ),
370+ % % Apply to main event loop
371+ case py_event_loop :get_loop () of
372+ {ok , LoopRef } ->
373+ catch py_nif :interp_apply_imports (LoopRef , Imports );
374+ _ -> ok
375+ end ,
376+ % % Apply to all pool event loops
377+ case py_event_loop_pool :get_all_loops () of
378+ {ok , Loops } ->
379+ lists :foreach (
380+ fun ({LoopRef , _WorkerPid }) ->
381+ catch py_nif :interp_apply_imports (LoopRef , Imports )
382+ end ,
383+ Loops
384+ );
385+ _ -> ok
386+ end ,
387+ ok .
388+
389+ % % @private Apply path to all running interpreters (contexts + event loops)
390+ apply_path_to_interpreters (PathBin ) ->
391+ Paths = [PathBin ],
392+ % % Apply to all contexts
393+ lists :foreach (
394+ fun (Ctx ) ->
395+ try
396+ Ref = py_context :get_nif_ref (Ctx ),
397+ py_nif :interp_apply_paths (Ref , Paths )
398+ catch _ :_ -> ok
399+ end
400+ end ,
401+ get_all_contexts ()
402+ ),
403+ % % Apply to main event loop
404+ case py_event_loop :get_loop () of
405+ {ok , LoopRef } ->
406+ catch py_nif :interp_apply_paths (LoopRef , Paths );
407+ _ -> ok
408+ end ,
409+ % % Apply to all pool event loops
410+ case py_event_loop_pool :get_all_loops () of
411+ {ok , Loops } ->
412+ lists :foreach (
413+ fun ({LoopRef , _WorkerPid }) ->
414+ catch py_nif :interp_apply_paths (LoopRef , Paths )
415+ end ,
416+ Loops
417+ );
418+ _ -> ok
419+ end ,
420+ ok .
421+
422+ % % @private Get all context pids from all pools
423+ get_all_contexts () ->
424+ DefaultCtxs = try py_context_router :contexts () catch _ :_ -> [] end ,
425+ % % Could add other pools here if needed
426+ DefaultCtxs .
0 commit comments