1/* Part of SWI-Prolog 2 3 Author: Jan Wielemaker 4 E-mail: jan@swi-prolog.org 5 WWW: http://www.swi-prolog.org 6 Copyright (c) 2023-2024, SWI-Prolog Solutions b.v. 7 All rights reserved. 8 9 Redistribution and use in source and binary forms, with or without 10 modification, are permitted provided that the following conditions 11 are met: 12 13 1. Redistributions of source code must retain the above copyright 14 notice, this list of conditions and the following disclaimer. 15 16 2. Redistributions in binary form must reproduce the above copyright 17 notice, this list of conditions and the following disclaimer in 18 the documentation and/or other materials provided with the 19 distribution. 20 21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 POSSIBILITY OF SUCH DAMAGE. 33*/ 34 35:- module(janus, 36 [ py_version/0, 37 38 py_call/1, % +Call 39 py_call/2, % +Call, -Return 40 py_call/3, % +Call, -Return, +Options 41 py_iter/2, % +Call, -Return 42 py_iter/3, % +Call, -Return, +Options 43 py_setattr/3, % +On, +Name, +Value 44 py_free/1, % +Obj 45 py_is_object/1, % @Term 46 py_is_dict/1, % @Term 47 py_with_gil/1, % :Goal 48 py_gil_owner/1, % -ThreadID 49 50 py_func/3, % +Module, +Func, -Return 51 py_func/4, % +Module, +Func, -Return, +Options 52 py_dot/3, % +ObjRef, +Meth, ?Ret 53 py_dot/4, % +ObjRef, +Meth, -Ret, +Options 54 55 values/3, % +Dict, +Path, ?Val 56 keys/2, % +Dict, ?Keys 57 key/2, % +Dict, ?Key 58 items/2, % +Dict, ?Items 59 60 py_shell/0, 61 62 py_pp/1, % +Term 63 py_pp/2, % +Stream, +Term 64 py_pp/3, % +Stream, +Term, +Options 65 66 py_object_dir/2, % +ObjRef, -List 67 py_object_dict/2, % +ObjRef, -Dict 68 py_obj_dir/2, % +ObjRef, -List (deprecated) 69 py_obj_dict/2, % +ObjRef, -Dict (deprecated) 70 py_type/2, % +ObjRef, -Type:atom 71 py_isinstance/2, % +ObjRef, +Type 72 py_module_exists/1, % +Module 73 py_hasattr/2, % +Module, ?Symbol 74 75 py_import/2, % +Spec, +Options 76 py_module/2, % +Module:atom, +Source:string 77 78 py_initialize/3, % +Program, +Argv, +Options 79 py_lib_dirs/1, % -Dirs 80 py_add_lib_dir/1, % +Dir 81 py_add_lib_dir/2, % +Dir,+Where 82 83 op(200, fy, @), % @constant 84 op(50, fx, #) % #Value 85 ]). 86:- meta_predicate py_with_gil(). 87 88:- use_module(library(apply_macros), []). 89:- autoload(library(lists), [append/3, member/2, append/2, last/2]). 90:- autoload(library(apply), 91 [maplist/2, exclude/3, maplist/3, convlist/3, partition/4]). 92:- autoload(library(error), [must_be/2, domain_error/2]). 93:- autoload(library(dicts), [dict_keys/2]). 94:- autoload(library(option), [dict_options/2, select_option/4, option/2]). 95:- autoload(library(prolog_code), [comma_list/2]). 96:- autoload(library(readutil), [read_line_to_string/2, read_file_to_string/3]). 97:- autoload(library(wfs), [call_delays/2, delays_residual_program/2]). 98:- autoload(library(dcg/high_order), [sequence//2, sequence//3]). 99 100:- if(\+current_predicate(py_call/1)). 101:- if(current_prolog_flag(windows, true)). 102:- use_module(library(shlib), [win_add_dll_directory/1]). 103 104% Just having the Python dir in PATH seems insufficient. We also need to 105% add the directory to the DLL search path. 106add_python_dll_dir :- 107 ( current_prolog_flag(msys2, true) 108 -> absolute_file_name(path('libpython3.dll'), DLL, [access(read)]) 109 ; absolute_file_name(path('python3.dll'), DLL, [access(read)]) 110 ), 111 file_directory_name(DLL, Dir), 112 win_add_dll_directory(Dir). 113:- initialization(add_python_dll_dir, now). 114:- endif. 115 116:- use_foreign_library(foreign(janus), [visibility(global)]). 117:- endif. 118 119:- predicate_options(py_call/3, 3, 120 [ py_object(boolean), 121 py_string_as(oneof([string,atom])) 122 ]). 123:- predicate_options(py_func/4, 4, 124 [ pass_to(py_call/3, 3) 125 ]). 126:- predicate_options(py_dot/5, 5, 127 [ pass_to(py_call/3, 3) 128 ]). 129 130:- public 131 py_initialize/0, 132 py_call_string/3, 133 py_write/2, 134 py_readline/4. 135 136:- create_prolog_flag(py_backtrace, true, [type(boolean), keep(true)]). 137:- create_prolog_flag(py_backtrace_depth, 4, [type(integer), keep(true)]). 138:- create_prolog_flag(py_argv, [], [type(term), keep(true)]).
sys.version. If a Python virtual environment (venv) is
active, indicate this with the location of this environment found.
160py_version :-
161 py_call(sys:version, PythonVersion),
162 py_call(janus_swi:version_str(), JanusVersion),
163 print_message(information, janus(version(JanusVersion, PythonVersion))),
164 ( py_venv(VEnvDir, EnvSiteDir)
165 -> print_message(information, janus(venv(VEnvDir, EnvSiteDir)))
166 ; true
167 ).
Arguments to Python functions use the Python conventions. Both
positional and keyword arguments are supported. Keyword
arguments are written as Name = Value and must appear after the
positional arguments.
Below are some examples.
% call a built-in
?- py_call(print("Hello World!\n")).
true.
% call a built-in (alternative)
?- py_call(builtins:print("Hello World!\n")).
true.
% call function in a module
?- py_call(sys:getsizeof([1,2,3]), Size).
Size = 80.
% call function on an attribute of a module
?- py_call(sys:path:append("/home/bob/janus")).
true
% get attribute from a module
?- py_call(sys:path, Path)
Path = ["dir1", "dir2", ...]
% reference a Python builtin (class or function)
?- py_call(builtins:list, L, [py_object(true)]).
L = <py>(...,type).
Given a class in a file dog.py such as the following example from
the Python documentation
class Dog:
tricks = []
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
We can interact with this class as below. Note that $Doc in the
SWI-Prolog toplevel refers to the last toplevel binding for the
variable Dog.
?- py_call(dog:'Dog'("Fido"), Dog).
Dog = <py>(0x7f095c9d02e0,'Dog').
?- py_call($Dog:add_trick("roll_over")).
Dog = <py>(0x7f095c9d02e0,'Dog').
?- py_call($Dog:tricks, Tricks).
Dog = <py>(0x7f095c9d02e0,'Dog'),
Tricks = ["roll_over"]
If the principal term of the first argument is not Target:Func,
The argument is evaluated as the initial target, i.e., it must be an
object reference or a module. For example:
?- py_call(dog:'Dog'("Fido"), Dog),
py_call(Dog, X).
Dog = X, X = <py>(0x7fa8cbd12050,'Dog').
?- py_call(sys, S).
S = <py>(0x7fa8cd582390,module).
Options processed:
true (default false), translate the return as a Python
object reference. Some objects are always translated to
Prolog, regardless of this flag. These are the Python constants
None, True and False as well as instances of the
Python base classes int, float, str or tuple. Instances
of sub classes of these base classes are controlled by this
option.atom (default), translate a Python String into a
Prolog atom. If Type is string, translate into a Prolog string.
Strings are more efficient if they are short lived.dict (default) to map a Python dict to a SWI-Prolog
dict if all keys can be represented. If {} or not all keys
can be represented, Return is unified to a term {k:v, ...}
or py({}) if the Python dict is empty.Obj:Attr = Value construct is not accepted.__iter__ on the result to get the iterator itself.__next__ function of the iterator.
The example below uses the built-in iterator range():
?- py_iter(range(1,3), X). X = 1 ; X = 2.
Note that the implementation performs a look ahead, i.e., after successful unification it calls `next()` again. On failure the Prolog predicate succeeds deterministically. On success, the next candidate is stored.
Note that a Python generator is a Python iterator. Therefore,
given the Python generator expression below, we can use
py_iter(squares(1,5),X) to generate the squares on backtracking.
def squares(start, stop):
for i in range(start, stop):
yield i * i
py_setattr(Target, Name, Value) :-
py_call(Target, Obj, [py_object(true)]),
py_call(setattr(Obj, Name, Value)).
eval, file (default) or single.368py_is_dict(Dict), is_dict(Dict) => true. 369py_is_dict(py({})) => true. 370py_is_dict(py({KV})) => is_kv(KV). 371py_is_dict({KV}) => is_kv(KV). 372 373is_kv((K:V,T)) => ground(K), ground(V), is_kv(T). 374is_kv(K:V) => ground(K), ground(V).
existence_error. Note that by decrementing the
reference count, we make the reference invalid from Prolog. This may
not actually delete the object because the object may have
references inside Python.
Prolog references to Python objects are subject to atom garbage collection and thus normally do not need to be freed explicitly.
once(Goal) while holding the Phyton GIL (Global
Interpreter Lock). Note that all predicates that interact with
Python lock the GIL. This predicate is only required if we wish to
make multiple calls to Python while keeping the GIL. The GIL is a
recursive lock and thus calling py_call/1,2 while holding the GIL
does not deadlock.Note that this predicate returns the Prolog threads that locked the GIL. It is however possible that Python releases the GIL, for example if it performs a blocking call. In this scenario, some other thread or no thread may hold the gil.
418 /******************************* 419 * COMPATIBILIY * 420 *******************************/
py_call(Module:Function, Return). See py_call/2 for
details.
434py_func(Module, Function, Return) :- 435 py_call(Module:Function, Return). 436py_func(Module, Function, Return, Options) :- 437 py_call(Module:Function, Return, Options).
py_call(ObjRef:MethAttr,
Return). See py_call/2 for details.
448py_dot(ObjRef, MethAttr, Ret) :- 449 py_call(ObjRef:MethAttr, Ret). 450py_dot(ObjRef, MethAttr, Ret, Options) :- 451 py_call(ObjRef:MethAttr, Ret, Options). 452 453 454 /******************************* 455 * PORTABLE ACCESS TO DICTS * 456 *******************************/
466values(Dict, Key, Val), is_dict(Dict), atom(Key) => 467 get_dict(Key, Dict, Val). 468values(Dict, Keys, Val), is_dict(Dict), is_list(Keys) => 469 get_dict_path(Keys, Dict, Val). 470values(py({CommaDict}), Key, Val) => 471 comma_values(CommaDict, Key, Val). 472values({CommaDict}, Key, Val) => 473 comma_values(CommaDict, Key, Val). 474 475get_dict_path([], Val, Val). 476get_dict_path([H|T], Dict, Val) :- 477 get_dict(H, Dict, Val0), 478 get_dict_path(T, Val0, Val). 479 480comma_values(CommaDict, Key, Val), atom(Key) => 481 comma_value(Key, CommaDict, Val). 482comma_values(CommaDict, Keys, Val), is_list(Keys) => 483 comma_value_path(Keys, CommaDict, Val). 484 485comma_value(Key, Key:Val0, Val) => 486 Val = Val0. 487comma_value(Key, (_,Tail), Val) => 488 comma_value(Key, Tail, Val). 489 490comma_value_path([], Val, Val). 491comma_value_path([H|T], Dict, Val) :- 492 comma_value(H, Dict, Val0), 493 comma_value_path(T, Val0, Val).
502keys(Dict, Keys), is_dict(Dict) => 503 dict_keys(Dict, Keys). 504keys(py({CommaDict}), Keys) => 505 comma_dict_keys(CommaDict, Keys). 506keys({CommaDict}, Keys) => 507 comma_dict_keys(CommaDict, Keys). 508 509comma_dict_keys((Key:_,T), Keys) => 510 Keys = [Key|KT], 511 comma_dict_keys(T, KT). 512comma_dict_keys(Key:_, Keys) => 513 Keys = [Key].
523key(Dict, Key), is_dict(Dict) => 524 dict_pairs(Dict, _Tag, Pairs), 525 member(Key-_, Pairs). 526key(py({CommaDict}), Keys) => 527 comma_dict_key(CommaDict, Keys). 528key({CommaDict}, Keys) => 529 comma_dict_key(CommaDict, Keys). 530 531comma_dict_key((Key:_,_), Key). 532comma_dict_key((_,T), Key) :- 533 comma_dict_key(T, Key).
542items(Dict, Items), is_dict(Dict) => 543 dict_pairs(Dict, _, Pairs), 544 maplist(pair_item, Pairs, Items). 545items(py({CommaDict}), Keys) => 546 comma_dict_items(CommaDict, Keys). 547items({CommaDict}, Keys) => 548 comma_dict_items(CommaDict, Keys). 549 550pair_item(K-V, K:V). 551 552comma_dict_items((Key:Value,T), Keys) => 553 Keys = [Key:Value|KT], 554 comma_dict_items(T, KT). 555comma_dict_items(Key:Value, Keys) => 556 Keys = [Key:Value]. 557 558 559 /******************************* 560 * SHELL * 561 *******************************/
janus as below.
from janus import *
So, we can do
?- py_shell.
...
>>> query_once("writeln(X)", {"X":"Hello world"})
Hello world
{'truth': True}
If possible, we enable command line editing using the GNU readline library.
When used in an environment where Prolog does not use the file
handles 0,1,2 for the standard streams, e.g., in swipl-win,
Python's I/O is rebound to use Prolog's I/O. This includes Prolog's
command line editor, resulting in a mixed history of Prolog and
Pythin commands.
587py_shell :- 588 import_janus, 589 py_call(janus_swi:interact(), _). 590 591import_janus :- 592 py_call(sys:hexversion, V), 593 V >= 0x030A0000, % >= 3.10 594 !, 595 py_run("from janus_swi import *", py{}, py{}, _, []). 596import_janus :- 597 print_message(warning, janus(py_shell(no_janus))). 598 599 600 /******************************* 601 * UTILITIES * 602 *******************************/
pformat() from the Python module
pprint to do the actual formatting. Options is translated into
keyword arguments passed to pprint.pformat(). In addition, the
option nl(Bool) is processed. When true (default), we use
pprint.pp(), which makes the output followed by a newline. For
example:
?- py_pp(py{a:1, l:[1,2,3], size:1000000},
[underscore_numbers(true)]).
{'a': 1, 'l': [1, 2, 3], 'size': 1_000_000}
624py_pp(Term) :- 625 py_pp(current_output, Term, []). 626 627py_pp(Term, Options) :- 628 py_pp(current_output, Term, Options). 629 630py_pp(Stream, Term, Options) :- 631 select_option(nl(NL), Options, Options1, true), 632 ( NL == true 633 -> Method = pp 634 ; Method = pformat 635 ), 636 opts_kws(Options1, Kws), 637 PFormat =.. [Method, Term|Kws], 638 py_call(pprint:PFormat, String), 639 write(Stream, String). 640 641opts_kws(Options, Kws) :- 642 dict_options(Dict, Options), 643 dict_pairs(Dict, _, Pairs), 644 maplist(pair_kws, Pairs, Kws). 645 646pair_kws(Name-Value, Name=Value).
658py_object_dir(ObjRef, List) :- 659 py_call(ObjRef:'__dir__'(), List). 660 661py_object_dict(ObjRef, Dict) :- 662 py_call(ObjRef:'__dict__', Dict).
669py_obj_dir(ObjRef, List) :- 670 py_object_dir(ObjRef, List). 671 672py_obj_dict(ObjRef, Dict) :- 673 py_object_dict(ObjRef, Dict).
type(ObjRef).__name__ in Python.
683py_type(ObjRef, Type) :-
684 py_call(type(ObjRef):'__name__', Type).isinstance(ObjRef) in
Python.
697py_isinstance(Obj, Module:Type) => 698 py_call(isinstance(Obj, eval(Module:Type)), @true). 699py_isinstance(Obj, Type) => 700 py_call(isinstance(Obj, eval(sys:modules:'__getitem__'(builtins):Type)), @true).
709py_module_exists(Module) :- 710 must_be(atom, Module), 711 py_call(sys:modules:'__contains__'(Module), @true), 712 !. 713py_module_exists(Module) :- 714 py_call(importlib:util:find_spec(Module), R), 715 R \== @none, 716 py_free(R).
hasattr(). If Name is unbound, this enumerates
the members of py_object_dir/2.
729py_hasattr(ModuleOrObj, Name) :- 730 var(Name), 731 !, 732 py_object_dir(ModuleOrObj, Names), 733 member(Name, Names). 734py_hasattr(ModuleOrObj, Name) :- 735 must_be(atom, Name), 736 ( atom(ModuleOrObj) 737 -> py_call(ModuleOrObj:'__name__'), % force loading 738 py_call(hasattr(eval(sys:modules:'__getitem__'(ModuleOrObj)), Name), @true) 739 ; py_call(hasattr(ModuleOrObj, Name), @true) 740 ).
__import__()
built-in and added to a table that maps Prolog atoms to imported
modules. This predicate explicitly imports a module and allows it to
be associated with a different name. This is useful for loading
nested modules, i.e., a specific module from a Python package as
well as for avoiding conflicts. For example, with the Python
selenium package installed, we can do in Python:
>>> from selenium import webdriver >>> browser = webdriver.Chrome()
Without this predicate, we can do
?- py_call('selenium.webdriver':'Chrome'(), Chrome).
For a single call this is fine, but for making multiple calls it gets cumbersome. With this predicate we can write this.
?- py_import('selenium.webdriver', []).
?- py_call(webdriver:'Chrome'(), Chrome).
By default, the imported module is associated to an atom created from the last segment of the dotted name. Below we use an explicit name.
?- py_import('selenium.webdriver', [as(browser)]).
?- py_call(browser:'Chrome'(), Chrome).
778py_import(Spec, Options) :- 779 option(as(_), Options), 780 !, 781 py_import_(Spec, Options). 782py_import(Spec, Options) :- 783 split_string(Spec, ".", "", Parts), 784 last(Parts, Last), 785 atom_string(As, Last), 786 py_import_(Spec, [as(As)|Options]).
string quasi quotation that supports long
strings in SWI-Prolog. For example:
:- use_module(library(strings)).
:- py_module(hello,
{|string||
| def say_hello_to(s):
| print(f"hello {s}")
|}).
Calling this predicate multiple times with the same Module and Source is a no-op. Called with a different source creates a new Python module that replaces the old in the global namespace.
809:- dynamic py_dyn_module/2 as volatile. 810 811py_module(Module, Source) :- 812 variant_sha1(Source, Hash), 813 ( py_dyn_module(Module, Hash) 814 -> true 815 ; py_call(janus:import_module_from_string(Module, Source)), 816 ( retract(py_dyn_module(Module, _)) 817 -> py_update_module_cache(Module) 818 ; true 819 ), 820 asserta(py_dyn_module(Module, Hash)) 821 ). 822 823 824 /******************************* 825 * INIT * 826 *******************************/ 827 828:- dynamic py_venv/2 as volatile. 829:- dynamic py_is_initialized/0 as volatile. 830 831% py_initialize is det. 832% 833% Used as a callback from C for lazy initialization of Python. 834 835py_initialize :- 836 getenv('VIRTUAL_ENV', VEnv), 837 prolog_to_os_filename(VEnvDir, VEnv), 838 atom_concat(VEnvDir, '/pyvenv.cfg', Cfg), 839 venv_config(Cfg, Config), 840 !, 841 current_prolog_flag(executable, Program), 842 current_prolog_flag(py_argv, Argv), 843 py_initialize(Program, ['-I'|Argv], []), 844 py_setattr(sys, prefix, VEnv), 845 venv_update_path(VEnvDir, Config). 846py_initialize :- 847 current_prolog_flag(executable, Program), 848 current_prolog_flag(py_argv, Argv), 849 py_initialize(Program, Argv, []). 850 851venv_config(File, Config) :- 852 access_file(File, read), 853 read_file_to_string(File, String, []), 854 split_string(String, "\n", "\n\r", Lines), 855 convlist(venv_config_line, Lines, Config). 856 857venv_config_line(Line, Config) :- 858 sub_string(Line, B, _, A, "="), 859 !, 860 sub_string(Line, 0, B, _, NameS), 861 split_string(NameS, "", "\t\s", [NameS2]), 862 atom_string(Name, NameS2), 863 sub_string(Line, _, A, 0, ValueS), 864 split_string(ValueS, "", "\t\s", [ValueS2]), 865 ( number_string(Value, ValueS2) 866 -> true 867 ; atom_string(Value, ValueS2) 868 ), 869 Config =.. [Name,Value]. 870 871venv_update_path(VEnvDir, Options) :- 872 py_call(sys:version_info, Info), % Tuple 873 Info =.. [_,Major,Minor|_], 874 format(string(EnvSiteDir), 875 '~w/lib/python~w.~w/site-packages', 876 [VEnvDir, Major, Minor]), 877 prolog_to_os_filename(EnvSiteDir, PyEnvSiteDir), 878 ( exists_directory(EnvSiteDir) 879 -> true 880 ; print_message(warning, 881 janus(venv(no_site_package_dir(VEnvDir, EnvSiteDir)))) 882 ), 883 py_call(sys:path, Path0), 884 ( option('include-system-site-packages'(true), Options) 885 -> partition(is_site_dir, Path0, PkgPath, SysPath), 886 append([SysPath,[PyEnvSiteDir], PkgPath], Path) 887 ; exclude(is_site_dir, Path0, Path1), 888 append(Path1, [PyEnvSiteDir], Path) 889 ), 890 py_setattr(sys, path, Path), 891 print_message(silent, janus(venv(VEnvDir, EnvSiteDir))), 892 asserta(py_venv(VEnvDir, EnvSiteDir)). 893 894is_site_dir(OsDir) :- 895 prolog_to_os_filename(PlDir, OsDir), 896 file_base_name(PlDir, Dir0), 897 downcase_atom(Dir0, Dir), 898 no_env_dir(Dir). 899 900no_env_dir('site-packages'). 901no_env_dir('dist-packages').
py_argv and an empty
Options list.
Calling this predicate while the Python is already initialized is a no-op. This predicate is thread-safe, where the first call initializes Python.
In addition to initializing the Python system, it
janus.py to the Python module
search path.
925py_initialize(Program, Argv, Options) :-
926 ( py_initialize_(Program, Argv, Options)
927 -> absolute_file_name(library('python/janus.py'), Janus,
928 [ access(read) ]),
929 file_directory_name(Janus, PythonDir),
930 py_add_lib_dir(PythonDir, first),
931 py_connect_io,
932 repl_add_cwd,
933 asserta(py_is_initialized)
934 ; true
935 ).942py_connect_io :- 943 maplist(non_file_stream, 944 [0-user_input, 1-user_output, 2-user_error], 945 NonFiles), 946 Call =.. [connect_io|NonFiles], 947 py_call(janus_swi:Call). 948 949non_file_stream(Expect-Stream, Bool) :- 950 ( stream_property(Stream, file_no(Expect)) 951 -> Bool = @false 952 ; Bool = @true 953 ). 954 955 /******************************* 956 * PATHS * 957 *******************************/
966py_lib_dirs(Dirs) :-
967 py_call(sys:path, Dirs0),
968 maplist(prolog_to_os_filename, Dirs, Dirs0).first or last. py_add_lib_dir/1 adds the
directory as last. The property sys:path is not modified if it
already contains Dir.
Dir is in Prolog notation. The added directory is converted to an absolute path using the OS notation using prolog_to_os_filename/2.
If Dir is a relative path, it is taken relative to Prolog source file when used as a directive and relative to the process working directory when called as a predicate.
990:- multifile system:term_expansion/2. 991 992systemterm_expansion((:- py_add_lib_dir(Dir0)), Directive) :- 993 system:term_expansion((:- py_add_lib_dir(Dir0, last)), Directive). 994systemterm_expansion((:- py_add_lib_dir(Dir0, Where)), 995 (:- initialization(py_add_lib_dir(Dir, Where), now))) :- 996 \+ (atomic(Dir0), is_absolute_file_name(Dir0)), 997 prolog_load_context(directory, CWD), 998 absolute_file_name(Dir0, Dir, 999 [ relative_to(CWD), 1000 file_type(directory), 1001 access(read) 1002 ]). 1003 1004py_add_lib_dir(Dir) :- 1005 py_add_lib_dir(Dir, last). 1006 1007py_add_lib_dir(Dir, Where) :- 1008 atomic(Dir), 1009 !, 1010 absolute_file_name(Dir, AbsDir), 1011 prolog_to_os_filename(AbsDir, OSDir), 1012 py_add_lib_dir_(OSDir, Where). 1013py_add_lib_dir(Alias, Where) :- 1014 absolute_file_name(Alias, AbsDir, 1015 [ file_type(directory), 1016 access(read) 1017 ]), 1018 prolog_to_os_filename(AbsDir, OSDir), 1019 py_add_lib_dir_(OSDir, Where). 1020 1021py_add_lib_dir_(OSDir, Where) :- 1022 ( py_call(sys:path, Dirs0), 1023 memberchk(OSDir, Dirs0) 1024 -> true 1025 ; Where == last 1026 -> py_call(sys:path:append(OSDir), _) 1027 ; Where == first 1028 -> py_call(sys:path:insert(0, OSDir), _) 1029 ; must_be(oneof([first,last]), Where) 1030 ). 1031 1032:- det(repl_add_cwd/0). 1033repl_add_cwd :- 1034 current_prolog_flag(break_level, Level), 1035 Level >= 0, 1036 !, 1037 ( py_call(sys:path:count(''), N), 1038 N > 0 1039 -> true 1040 ; print_message(informational, janus(add_cwd)), 1041 py_add_lib_dir_('', first) 1042 ). 1043repl_add_cwd. 1044 1045:- multifile 1046 prolog:repl_loop_hook/2. 1047 1048prologrepl_loop_hook(begin, Level) :- 1049 Level >= 0, 1050 py_is_initialized, 1051 repl_add_cwd. 1052 1053 1054 /******************************* 1055 * CALLBACK * 1056 *******************************/ 1057 1058:- dynamic py_call_cache/8 as volatile. 1059 1060:- meta_predicate py_call_string(, , ). 1061 1062% py_call_string(:String, +DictIn, -Dict) is nondet. 1063% 1064% Support janus.query_once() and janus.query(). Parses String into a goal 1065% term. Next, all variables from the goal term that appear in DictIn 1066% are bound to the value from this dict. Dict is created from the 1067% remaining variables, unless they start with an underscore (e.g., 1068% `_Time`) and the key `truth. On success, the Dict values contain 1069% the bindings from the answer and `truth` is either `true` or 1070% `Undefined`. On failure, the Dict values are bound to `None` and the 1071% `truth` is `false`. 1072% 1073% Parsing and distributing the variables over the two dicts is cached. 1074 1075py_call_string(M:String, Input, Dict) :- 1076 py_call_cache(String, Input, TV, M, Goal, Dict, Truth, OutVars), 1077 !, 1078 py_call(TV, M:Goal, Truth, OutVars). 1079py_call_string(M:String, Input, Dict) :- 1080 term_string(Goal, String, [variable_names(Map)]), 1081 unbind_dict(Input, VInput), 1082 exclude(not_in_projection(VInput), Map, OutBindings), 1083 dict_create(Dict, bindings, [truth=Truth|OutBindings]), 1084 maplist(arg(2), OutBindings, OutVars), 1085 TV = Input.get(truth, 'PLAIN_TRUTHVALS'), 1086 asserta(py_call_cache(String, VInput, TV, M, Goal, Dict, Truth, OutVars)), 1087 VInput = Input, 1088 py_call(TV, M:Goal, Truth, OutVars). 1089 1090py_call('NO_TRUTHVALS', M:Goal, Truth, OutVars) => 1091 ( call(M:Goal) 1092 *-> bind_status_no_no_truthvals(Truth) 1093 ; Truth = @false, 1094 maplist(bind_none, OutVars) 1095 ). 1096py_call('PLAIN_TRUTHVALS', M:Goal, Truth, OutVars) => 1097 ( call(M:Goal) 1098 *-> bind_status_plain_truthvals(Truth) 1099 ; Truth = @false, 1100 maplist(bind_none, OutVars) 1101 ). 1102py_call('DELAY_LISTS', M:Goal, Truth, OutVars) => 1103 ( call_delays(M:Goal, Delays) 1104 *-> bind_status_delay_lists(Delays, Truth) 1105 ; Truth = @false, 1106 maplist(bind_none, OutVars) 1107 ). 1108py_call('RESIDUAL_PROGRAM', M:Goal, Truth, OutVars) => 1109 ( call_delays(M:Goal, Delays) 1110 *-> bind_status_residual_program(Delays, Truth) 1111 ; Truth = @false, 1112 maplist(bind_none, OutVars) 1113 ). 1114 1115not_in_projection(Input, Name=Value) :- 1116 ( get_dict(Name, Input, Value) 1117 -> true 1118 ; sub_atom(Name, 0, _, _, '_') 1119 ). 1120 1121bind_none(@none). 1122 1123bind_status_no_no_truthvals(@true). 1124 1125bind_status_plain_truthvals(Truth) => 1126 ( '$tbl_delay_list'([]) 1127 -> Truth = @true 1128 ; py_undefined(Truth) 1129 ). 1130 1131bind_status_delay_lists(true, Truth) => 1132 Truth = @true. 1133bind_status_delay_lists(Delays, Truth) => 1134 py_call(janus:'Undefined'(prolog(Delays)), Truth). 1135 1136bind_status_residual_program(true, Truth) => 1137 Truth = @true. 1138bind_status_residual_program(Delays, Truth) => 1139 delays_residual_program(Delays, Program), 1140 py_call(janus:'Undefined'(prolog(Program)), Truth). 1141 1142py_undefined(X) :- 1143 py_call(janus:undefined, X). 1144 1145unbind_dict(Dict0, Dict) :- 1146 dict_pairs(Dict0, Tag, Pairs0), 1147 maplist(unbind, Pairs0, Pairs), 1148 dict_pairs(Dict, Tag, Pairs). 1149 1150unbind(Name-_, Name-_) :- 1151 sub_atom(Name, 0, 1, _, Char1), 1152 char_type(Char1, prolog_var_start), 1153 !. 1154unbind(NonVar, NonVar). 1155 1156 1157 /******************************* 1158 * SUPPORT PYTHON CALLS * 1159 *******************************/ 1160 1161:- public 1162 px_cmd/3, 1163 px_call/4, 1164 px_comp/7. 1165 1166% These predicates are helpers for the corresponding Python functions 1167% in janus.py. 1168 1169 1170% px_call(+Input:tuple, +Module, -Pred, -Ret) 1171% 1172% Supports px_qdet() and apply(). Note that these predicates 1173% explicitly address predicates in a particular module. For meta 1174% predicates, this implies they also control the context module. This 1175% leads to ``janus.cmd("consult", "consult", file)`` to consult _file_ 1176% into the module `consult`, which is not what we want. Therefore we 1177% set the context module to `user`, which is better, but probably also 1178% not what we want. 1179 1180px_call(-(), Module, Pred, Ret) => 1181 @(call(Module:Pred, Ret), user). 1182px_call(-(A1), Module, Pred, Ret) => 1183 @(call(Module:Pred, A1, Ret), user). 1184px_call(-(A1,A2), Module, Pred, Ret) => 1185 @(call(Module:Pred, A1, A2, Ret), user). 1186px_call(-(A1,A2,A3), Module, Pred, Ret) => 1187 @(call(Module:Pred, A1, A2, A3, Ret), user). 1188px_call(-(A1,A2,A3,A4), Module, Pred, Ret) => 1189 @(call(Module:Pred, A1, A2, A3, A4, Ret), user). 1190px_call(Tuple, Module, Pred, Ret) => 1191 compound_name_arguments(Tuple, _, Args), 1192 append(Args, [Ret], GArgs), 1193 Goal =.. [Pred|GArgs], 1194 @(Module:Goal, user). 1195 1196px_cmd(Module, Pred, Tuple) :- 1197 ( compound(Tuple) 1198 -> compound_name_arguments(Tuple, _, Args), 1199 Goal =.. [Pred|Args] 1200 ; Goal = Pred 1201 ), 1202 @(Module:Goal, user). 1203 1204px_comp(Module, Pred, Tuple, Vars, Set, TV, Ret) :- 1205 length(Out, Vars), 1206 ( compound(Tuple) 1207 -> compound_name_arguments(Tuple, _, Args), 1208 append(Args, Out, GArgs), 1209 Goal =.. [Pred|GArgs] 1210 ; Goal =.. [Pred|Out] 1211 ), 1212 compound_name_arguments(OTempl0, -, Out), 1213 tv_goal_and_template(TV, @(Module:Goal, user), FGoal, OTempl0, OTempl), 1214 findall(OTempl, FGoal, Ret0), 1215 ( Set == @true 1216 -> sort(Ret0, Ret) 1217 ; Ret = Ret0 1218 ). 1219 1220:- meta_predicate 1221 call_delays_py(, ). 1222 1223% 0,1,2: TruthVal(Enum) from janus.py 1224tv_goal_and_template('NO_TRUTHVALS', 1225 Goal, Goal, Templ, Templ) :- !. 1226tv_goal_and_template('PLAIN_TRUTHVALS', 1227 Goal, ucall(Goal, TV), Templ, -(Templ,TV)) :- !. 1228tv_goal_and_template('DELAY_LISTS', 1229 Goal, call_delays_py(Goal, TV), Templ, -(Templ,TV)) :- !. 1230tv_goal_and_template(Mode, _, _, _, _) :- 1231 domain_error("px_comp() truth", Mode). 1232 1233:- public 1234 ucall/2, 1235 call_delays_py/2. 1236 1237ucall(Goal, TV) :- 1238 call(Goal), 1239 ( '$tbl_delay_list'([]) 1240 -> TV = 1 1241 ; TV = 2 1242 ). 1243 1244call_delays_py(Goal, PyDelays) :- 1245 call_delays(Goal, Delays), 1246 ( Delays == true 1247 -> PyDelays = [] 1248 ; comma_list(Delays, Array), 1249 maplist(term_string, Array, PyDelays) 1250 ). 1251 1252 1253 /******************************* 1254 * PYTHON I/O * 1255 *******************************/ 1256 1257% py_write(+Stream, -String) is det. 1258% py_readline(+Stream, +Size, +Prompt, +Line) is det. 1259% 1260% Called from redefined Python console I/O to write/read using the 1261% Prolog streams. 1262 1263:- '$hide'((py_write/1, 1264 py_readline/4)). 1265 1266py_write(Stream, String) :- 1267 notrace(format(Stream, '~s', [String])). 1268 1269py_readline(Stream, Size, Prompt, Line) :- 1270 notrace(py_readline_(Stream, Size, Prompt, Line)). 1271 1272py_readline_(Stream, _Size, Prompt, Line) :- 1273 prompt1(Prompt), 1274 read_line_to_string(Stream, Read), 1275 ( Read == end_of_file 1276 -> Line = "" 1277 ; string_concat(Read, "\n", Line), 1278 py_add_history(Read) 1279 ). 1280 1281py_add_history(Line) :- 1282 ignore(catch(prolog:history(user_input, add(Line)), _, true)). 1283 1284 1285 /******************************* 1286 * COMPILING * 1287 *******************************/ 1288 1289% py_consult(+File, +Data, +Module) is det. 1290% 1291% Support janus.consult(file, data=None, module='user'). 1292 1293:- public py_consult/3. 1294py_consult(File, @none, Module) => 1295 consult(Module:File). 1296py_consult(File, Data, Module) => 1297 setup_call_cleanup( 1298 open_string(Data, In), 1299 load_files(Module:File, [stream(In)]), 1300 close(In)). 1301 1302 1303 /******************************* 1304 * MESSAGES * 1305 *******************************/ 1306 1307:- multifile 1308 prolog:error_message//1, 1309 prolog:message_context//1, 1310 prolog:message//1. 1311 1312prologerror_message(python_error(Class, Value)) --> 1313 { py_str(Value, Message) 1314 }, 1315 [ 'Python ', ansi(code, "'~w'", [Class]), ':', nl, 1316 ' ~w'-[Message] 1317 ]. 1318prologerror_message(permission_error(import_as, py_module, As)) --> 1319 [ 'Janus: No permission to import a module as ', ansi(code, '~q', As), 1320 ': module exists.' 1321 ]. 1322 1323prologmessage_context(context(_, PythonCtx)) --> 1324 { nonvar(PythonCtx), 1325 PythonCtx = python_stack(Stack), 1326 current_prolog_flag(py_backtrace, true), 1327 py_is_object(Stack), 1328 !, 1329 current_prolog_flag(py_backtrace_depth, Depth), 1330 py_call(traceback:format_tb(Stack, Depth), Frames) 1331 }, 1332 [ nl, 'Python stack:', nl ], 1333 sequence(py_stack_frame, Frames). 1334 1335py_stack_frame(String) --> 1336 { split_string(String, "\n", "", Lines) 1337 }, 1338 sequence(msg_line, [nl], Lines). 1339 1340msg_line(Line) --> 1341 [ '~s'-[Line] ]. 1342 1343prologmessage(janus(Msg)) --> 1344 message(Msg). 1345 1346message(version(Janus, Python)) --> 1347 [ 'Janus ~w embeds Python ~w'-[Janus, Python] ]. 1348message(venv(Dir, _EnvSiteDir)) --> 1349 [ 'Janus: using venv from ~p'-[Dir] ]. 1350message(venv(no_site_package_dir(VEnvDir, Dir))) --> 1351 [ 'Janus: venv dirrectory ~p does not contain ~p'-[VEnvDir, Dir] ]. 1352message(py_shell(no_janus)) --> 1353 [ 'Janus: py_shell/0: Importing janus into the Python shell requires Python 3.10 or later.', nl, 1354 'Run "', ansi(code, 'from janus import *', []), '" in the Python shell to import janus.' 1355 ]. 1356message(add_cwd) --> 1357 [ 'Interactive session; added `.` to Python `sys.path`'-[] ]
Call Python from Prolog
This library implements calling Python from Prolog. It is available directly from Prolog if the janus package is bundled. The library provides access to an embedded Python instance. If SWI-Prolog is embedded into Python using the Python package
janus-swi, this library is provided either from Prolog or from the Python package.Normally, the Prolog user can simply start calling Python using py_call/2 or friends. In special cases it may be needed to initialize Python with options using py_initialize/3 and optionally the Python search path may be extended using py_add_lib_dir/1.