@@ -15,29 +15,22 @@ A handle to a loaded instance of libpython, its interpreter, function pointers,
1515 pyprogname_w :: Any = missing
1616 pyhome :: Union{String, Missing} = missing
1717 pyhome_w :: Any = missing
18+ which :: Symbol = :unknown # :CondaPkg, :PyCall, :embedded or :unknown
1819 version :: Union{VersionNumber, Missing} = missing
19- jlenv :: String = " "
2020 matches_pycall :: Union{Bool, Missing} = missing
2121end
2222
2323const CTX = Context ()
2424
25- function init_context ()
26-
27- # Find the topmost project which depends on PythonCall.
28- # This is the project in which we manage Python dependences.
29- for env in Base. load_path ()
30- proj = Base. env_project_file (env)
31- is_pythoncall = proj isa String && Base. project_file_name_uuid (proj, " " ). uuid == PYTHONCALL_UUID
32- depends_on_pythoncall = Base. manifest_uuid_path (env, PYTHONCALL_PKGID) != = nothing
33- if is_pythoncall || depends_on_pythoncall
34- jlenv = proj isa String ? dirname (proj) : env
35- CTX. jlenv = jlenv
36- Deps. _meta_file[] = joinpath (jlenv, " PythonCallMeta" )
37- break
38- end
25+ function _atpyexit ()
26+ if CTX. is_initialized && CTX. which != :PyCall
27+ @warn " Python exited unexpectedly"
3928 end
40- isempty (CTX. jlenv) && error (" could not find the environment containing PythonCall (this is a bug, please report it)" )
29+ CTX. is_initialized = false
30+ return
31+ end
32+
33+ function init_context ()
4134
4235 CTX. is_embedded = haskey (ENV , " JULIA_PYTHONCALL_LIBPTR" )
4336
@@ -48,41 +41,35 @@ function init_context()
4841 # Check Python is initialized
4942 Py_IsInitialized () == 0 && error (" Python is not already initialized." )
5043 CTX. is_initialized = CTX. is_preinitialized = true
44+ CTX. which = :embedded
5145 exe_path = get (ENV , " JULIA_PYTHONCALL_EXE" , " " )
5246 if exe_path != " "
5347 CTX. exe_path = exe_path
5448 end
5549 else
5650 # Find Python executable
57- # TODO : PyCall compatibility mode
58- # TODO : when JULIA_PYTHONCALL_EXE is given, determine if we are in a conda environment
5951 exe_path = get (ENV , " JULIA_PYTHONCALL_EXE" , " " )
60- if exe_path == " "
61- # By default, we use a conda environment inside the first Julia environment in
62- # the LOAD_PATH in which PythonCall is installed (in the manifest as an
63- # indirect dependency).
64- #
65- # Note that while Julia environments are stacked, Python environments are not,
66- # so it is possible that two Julia environments contain two different packages
67- # depending on PythonCall which install into this one conda environment.
68- #
69- # Regarding the LOAD_PATH as getting "less specific" as we go through, this
70- # choice of location is the "most specific" place which actually depends on
71- # PythonCall.
72- Deps. _conda_env[] = joinpath (CTX. jlenv, " conda_env" )
73- Deps. resolve ()
74- exe_path = Deps. python_exe ()
75- isfile (exe_path) || error (" cannot find python executable" )
52+ if exe_path == " " || exe_path == " @CondaPkg"
53+ # By default, we use Python installed by CondaPkg.
54+ exe_path = CondaPkg. which (" python" )
55+ CTX. which = :CondaPkg
7656 elseif exe_path == " @PyCall"
77- haskey (Base. loaded_modules, PYCALL_PKGID) || Base. require (PYCALL_PKGID)
78- PyCall = Base. loaded_modules[PYCALL_PKGID]
57+ PyCall = Base. require (PYCALL_PKGID)
7958 exe_path = PyCall. python:: String
8059 CTX. lib_path = PyCall. libpython:: String
60+ CTX. which = :PyCall
61+ elseif startswith (exe_path, " @" )
62+ error (" invalid JULIA_PYTHONCALL_EXE=$exe_path " )
63+ else
64+ CTX. which = :unknown
8165 end
8266
67+ # Call f() in a suitable environment for running Python
68+ withenv (f) = CTX. which == :CondaPkg ? CondaPkg. withenv (f) : f ()
69+
8370 # Ensure Python is runnable
8471 try
85- run (pipeline (` $exe_path --version` , stdout = devnull , stderr = devnull ))
72+ withenv (() -> run (pipeline (` $exe_path --version` , stdout = devnull , stderr = devnull ) ))
8673 catch
8774 error (" Python executable $(repr (exe_path)) is not executable." )
8875 end
@@ -102,16 +89,16 @@ function init_context()
10289 Some (nothing )
10390 )
10491 if lib_path != = nothing
105- lib_ptr = dlopen_e (lib_path, CTX. dlopen_flags)
92+ lib_ptr = withenv (() -> dlopen_e (lib_path, CTX. dlopen_flags) )
10693 if lib_ptr == C_NULL
10794 error (" Python library $(repr (lib_path)) could not be opened." )
10895 else
10996 CTX. lib_path = lib_path
11097 CTX. lib_ptr = lib_ptr
11198 end
11299 else
113- for lib_path in readlines (python_cmd ([joinpath (@__DIR__ , " find_libpython.py" ), " --list-all" ]))
114- lib_ptr = dlopen_e (lib_path, CTX. dlopen_flags)
100+ for lib_path in withenv (() -> readlines (python_cmd ([joinpath (@__DIR__ , " find_libpython.py" ), " --list-all" ]) ))
101+ lib_ptr = withenv (() -> dlopen_e (lib_path, CTX. dlopen_flags) )
115102 if lib_ptr == C_NULL
116103 @warn " Python library $(repr (lib_path)) could not be opened."
117104 else
@@ -132,7 +119,9 @@ function init_context()
132119 with_gil () do
133120 if Py_IsInitialized () != 0
134121 # Already initialized (maybe you're using PyCall as well)
122+ @assert CTX. which in (:embedded , :PyCall )
135123 else
124+ @assert CTX. which in (:unknown , :CondaPkg )
136125 # Find ProgramName and PythonHome
137126 script = if Sys. iswindows ()
138127 """
@@ -157,7 +146,7 @@ function init_context()
157146 sys.stdout.write(sys.exec_prefix)
158147 """
159148 end
160- CTX. pyprogname, CTX. pyhome = readlines (python_cmd ([" -c" , script]))
149+ CTX. pyprogname, CTX. pyhome = withenv (() -> readlines (python_cmd ([" -c" , script]) ))
161150
162151 # Set PythonHome
163152 CTX. pyhome_w = Base. cconvert (Cwstring, CTX. pyhome)
@@ -181,7 +170,7 @@ function init_context()
181170 end
182171 end
183172 CTX. is_initialized = true
184- if Py_AtExit (@cfunction (() -> (CTX . is_initialized && @warn ( " Python exited unexpectedly " ); CTX . is_initialized = false ; nothing ) , Cvoid, ())) == - 1
173+ if Py_AtExit (@cfunction (_atpyexit , Cvoid, ())) == - 1
185174 @warn " Py_AtExit() error"
186175 end
187176 end
@@ -222,7 +211,7 @@ function init_context()
222211
223212 end
224213
225- @debug " Initialized PythonCall.jl" CTX. is_embedded CTX. is_initialized CTX. exe_path CTX. lib_path CTX. lib_ptr CTX. pyprogname CTX. pyhome CTX. version CTX . jlenv Deps . _meta_file[] Deps . _conda_env[]
214+ @debug " Initialized PythonCall.jl" CTX. is_embedded CTX. is_initialized CTX. exe_path CTX. lib_path CTX. lib_ptr CTX. pyprogname CTX. pyhome CTX. version
226215
227216 return
228217end
@@ -248,4 +237,7 @@ function init_pycall(PyCall::Module)
248237 ptr1 = Py_GetVersion ()
249238 ptr2 = @eval PyCall ccall (@pysym (:Py_GetVersion ), Ptr{Cchar}, ())
250239 CTX. matches_pycall = ptr1 == ptr2
240+ if CTX. which == :PyCall
241+ @assert CTX. matches_pycall
242+ end
251243end
0 commit comments