Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: 0.7 compatibility #469

Merged
merged 2 commits into from
Mar 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ Here are solutions to some common problems:
* As mentioned above, use `foo[:bar]` and `foo[:bar](...)` rather than `foo.bar` and `foo.bar(...)`,
respectively, to access attributes and methods of Python objects.

* By default, PyCall [doesn't include the current directory in the Python search path](https://github.com/stevengj/PyCall.jl/issues/48). If you want to do that (in order to load a Python module from the current directory), just run `unshift!(PyVector(pyimport("sys")["path"]), "")`.
* By default, PyCall [doesn't include the current directory in the Python search path](https://github.com/stevengj/PyCall.jl/issues/48). If you want to do that (in order to load a Python module from the current directory), just run `pushfirst!(PyVector(pyimport("sys")["path"]), "")`.

## Python object interfaces

Expand Down
2 changes: 1 addition & 1 deletion REQUIRE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
julia 0.6
Compat 0.32.0
Compat 0.55.0
Conda 0.2
MacroTools 0.3
28 changes: 14 additions & 14 deletions deps/build.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# will probably need to re-run Pkg.build("PyCall").

using Compat
import Conda
import Conda, Compat.Libdl

struct UseCondaPython <: Exception end

Expand Down Expand Up @@ -53,9 +53,9 @@ function find_libpython(python::AbstractString)
v = pyconfigvar(python,"VERSION","")
libs = [ dlprefix*"python"*v, dlprefix*"python" ]
lib = pyconfigvar(python, "LIBRARY")
lib != "None" && unshift!(libs, splitext(lib)[1])
lib != "None" && pushfirst!(libs, splitext(lib)[1])
lib = pyconfigvar(python, "LDLIBRARY")
lib != "None" && unshift!(unshift!(libs, basename(lib)), lib)
lib != "None" && pushfirst!(pushfirst!(libs, basename(lib)), lib)
libs = unique(libs)

# it is ridiculous that it is this hard to find the path of libpython
Expand Down Expand Up @@ -106,13 +106,13 @@ function find_libpython(python::AbstractString)
end

if "yes" == get(ENV, "PYCALL_DEBUG_BUILD", "no") # print out extra info to help with remote debugging
println(STDERR, "------------------------------------- exceptions -----------------------------------------")
println(stderr, "------------------------------------- exceptions -----------------------------------------")
for s in error_strings
print(s, "\n\n")
end
println(STDERR, "---------------------------------- get_config_vars ---------------------------------------")
print(STDERR, read(`python -c "import distutils.sysconfig; print(distutils.sysconfig.get_config_vars())"`, String))
println(STDERR, "--------------------------------- directory contents -------------------------------------")
println(stderr, "---------------------------------- get_config_vars ---------------------------------------")
print(stderr, read(`python -c "import distutils.sysconfig; print(distutils.sysconfig.get_config_vars())"`, String))
println(stderr, "--------------------------------- directory contents -------------------------------------")
for libpath in libpaths
if isdir(libpath)
print(libpath, ":\n")
Expand All @@ -123,7 +123,7 @@ function find_libpython(python::AbstractString)
end
end
end
println(STDERR, "------------------------------------------------------------------------------------------")
println(stderr, "------------------------------------------------------------------------------------------")
end

error("""
Expand All @@ -150,18 +150,18 @@ wstringconst(s) = string("Base.cconvert(Cwstring, \"", escape_string(s), "\")")
# problems in the unlikely event of read-only directories.
function writeifchanged(filename, str)
if !isfile(filename) || read(filename, String) != str
info(abspath(filename), " has been updated")
Compat.@info string(abspath(filename), " has been updated")
write(filename, str)
else
info(abspath(filename), " has not changed")
Compat.@info string(abspath(filename), " has not changed")
end
end

try # make sure deps.jl file is removed on error
python = try
let py = get(ENV, "PYTHON", isfile("PYTHON") ? readchomp("PYTHON") :
(Compat.Sys.isunix() && !Compat.Sys.isapple()) || Sys.ARCH ∉ (:i686, :x86_64) ? "python" : ""),
vers = isempty(py) ? v"0.0" : convert(VersionNumber, pyconfigvar(py,"VERSION","0.0"))
vers = isempty(py) ? v"0.0" : VersionNumber(pyconfigvar(py,"VERSION","0.0"))
if vers < v"2.7"
if isempty(py)
throw(UseCondaPython())
Expand All @@ -182,10 +182,10 @@ try # make sure deps.jl file is removed on error
catch e1
if Sys.ARCH in (:i686, :x86_64)
if isa(e1, UseCondaPython)
info("Using the Python distribution in the Conda package by default.\n",
Compat.@info string("Using the Python distribution in the Conda package by default.\n",
"To use a different Python version, set ENV[\"PYTHON\"]=\"pythoncommand\" and re-run Pkg.build(\"PyCall\").")
else
info( "No system-wide Python was found; got the following error:\n",
Compat.@info string( "No system-wide Python was found; got the following error:\n",
"$e1\nusing the Python distribution in the Conda package")
end
abspath(Conda.PYTHONDIR, "python" * (Compat.Sys.iswindows() ? ".exe" : ""))
Expand Down Expand Up @@ -222,7 +222,7 @@ try # make sure deps.jl file is removed on error
# cache the Python version as a Julia VersionNumber
pyversion = VersionNumber(pyvar(python, "platform", "python_version()"))

info("PyCall is using $python (Python $pyversion) at $programname, libpython = $libpy_name")
Compat.@info "PyCall is using $python (Python $pyversion) at $programname, libpython = $libpy_name"

if pyversion < v"2.7"
error("Python 2.7 or later is required for PyCall")
Expand Down
6 changes: 4 additions & 2 deletions deps/depsutils.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Compat.Libdl

hassym(lib, sym) = Libdl.dlsym_e(lib, sym) != C_NULL

# call dlsym_e on a sequence of symbols and return the symbol that gives
Expand All @@ -20,9 +22,9 @@ end
function Py_SetPythonHome(libpy, PYTHONHOME, wPYTHONHOME, pyversion)
if !isempty(PYTHONHOME)
if pyversion.major < 3
ccall(Libdl.dlsym(libpy, :Py_SetPythonHome), Void, (Cstring,), PYTHONHOME)
ccall(Libdl.dlsym(libpy, :Py_SetPythonHome), Cvoid, (Cstring,), PYTHONHOME)
else
ccall(Libdl.dlsym(libpy, :Py_SetPythonHome), Void, (Ptr{Cwchar_t},), wPYTHONHOME)
ccall(Libdl.dlsym(libpy, :Py_SetPythonHome), Cvoid, (Ptr{Cwchar_t},), wPYTHONHOME)
end
end
end
Expand Down
52 changes: 23 additions & 29 deletions src/PyCall.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ __precompile__()

module PyCall

using Compat

export pycall, pyimport, pybuiltin, PyObject, PyReverseDims,
PyPtr, pyincref, pydecref, pyversion, PyArray, PyArray_Info,
pyerr_check, pyerr_clear, pytype_query, PyAny, @pyimport, PyDict,
Expand All @@ -14,24 +16,19 @@ import Base: size, ndims, similar, copy, getindex, setindex!, stride,
convert, pointer, summary, convert, show, haskey, keys, values,
eltype, get, delete!, empty!, length, isempty, start, done,
next, filter!, hash, splice!, pop!, ==, isequal, push!,
unshift!, shift!, append!, insert!, prepend!, mimewritable
append!, insert!, prepend!, mimewritable, unsafe_convert
import Compat: pushfirst!, popfirst!

# Python C API is not interrupt-save. In principle, we should
# Python C API is not interrupt-safe. In principle, we should
# use sigatomic for every ccall to the Python library, but this
# should really be fixed in Julia (#2622). However, we will
# use the sigatomic_begin/end functions to protect pycall and
# similar long-running (or potentially long-running) code.
import Base: sigatomic_begin, sigatomic_end

## Compatibility import for v0.5
using Compat
import Conda
import Base.unsafe_convert
import MacroTools # because of issue #270

if isdefined(Base, :Iterators)
import Base.Iterators: filter
end
import Base.Iterators: filter

#########################################################################

Expand All @@ -47,7 +44,7 @@ include("startup.jl")
# while we're at it.
struct PyObject_struct
ob_refcnt::Int
ob_type::Ptr{Void}
ob_type::Ptr{Cvoid}
end

const PyPtr = Ptr{PyObject_struct} # type for PythonObject* in ccall
Expand All @@ -71,7 +68,7 @@ mutable struct PyObject
o::PyPtr # the actual PyObject*
function PyObject(o::PyPtr)
po = new(o)
finalizer(po, pydecref)
@compat finalizer(pydecref, po)
return po
end
end
Expand All @@ -93,13 +90,13 @@ someobject)`. This procedure will properly handle Python's reference counting
PyNULL() = PyObject(PyPtr_NULL)

function pydecref(o::PyObject)
ccall(@pysym(:Py_DecRef), Void, (PyPtr,), o.o)
ccall(@pysym(:Py_DecRef), Cvoid, (PyPtr,), o.o)
o.o = PyPtr_NULL
o
end

function pyincref_(o::PyPtr)
ccall((@pysym :Py_IncRef), Void, (PyPtr,), o)
ccall((@pysym :Py_IncRef), Cvoid, (PyPtr,), o)
return o
end

Expand Down Expand Up @@ -134,10 +131,10 @@ end
pyisinstance(o::PyObject, t::PyObject) =
t.o != C_NULL && ccall((@pysym :PyObject_IsInstance), Cint, (PyPtr,PyPtr), o, t.o) == 1

pyisinstance(o::PyObject, t::Union{Ptr{Void},PyPtr}) =
pyisinstance(o::PyObject, t::Union{Ptr{Cvoid},PyPtr}) =
t != C_NULL && ccall((@pysym :PyObject_IsInstance), Cint, (PyPtr,PyPtr), o, t) == 1

pyquery(q::Ptr{Void}, o::PyObject) =
pyquery(q::Ptr{Cvoid}, o::PyObject) =
ccall(q, Cint, (PyPtr,), o) == 1

# conversion to pass PyObject as ccall arguments:
Expand Down Expand Up @@ -347,19 +344,19 @@ end
# during import.

struct ACTIVATION_CONTEXT_BASIC_INFORMATION
handle::Ptr{Void}
handle::Ptr{Cvoid}
dwFlags::UInt32
end
const ActivationContextBasicInformation = 1
const QUERY_ACTCTX_FLAG_ACTCTX_IS_ADDRESS = 0x10
const PyActCtx = Ref{Ptr{Void}}(C_NULL)
const PyActCtx = Ref{Ptr{Cvoid}}(C_NULL)

function ComputePyActCtx()
if PyActCtx[] == C_NULL
some_address_in_libpython = @pyglobal(:PyImport_ImportModule)
ActCtxBasicInfo = Ref{ACTIVATION_CONTEXT_BASIC_INFORMATION}()
succeeded = ccall(:QueryActCtxW,stdcall,Bool,
(UInt32,Ptr{Void},Ptr{Void},Culong,Ptr{Void},Csize_t,Ptr{Csize_t}),
(UInt32,Ptr{Cvoid},Ptr{Cvoid},Culong,Ptr{Cvoid},Csize_t,Ptr{Csize_t}),
QUERY_ACTCTX_FLAG_ACTCTX_IS_ADDRESS, some_address_in_libpython,
C_NULL, ActivationContextBasicInformation, ActCtxBasicInfo,
sizeof(ActCtxBasicInfo), C_NULL)
Expand All @@ -370,17 +367,17 @@ end
end

function ActivatePyActCtx()
cookie = Ref{Ptr{Void}}()
cookie = Ref{Ptr{Cvoid}}()
succeeded = ccall(:ActivateActCtx,stdcall, Bool,
(Ptr{Void}, Ptr{Ptr{Void}}),
(Ptr{Cvoid}, Ptr{Ptr{Cvoid}}),
ComputePyActCtx(), cookie)
@assert succeeded
cookie[]
end

function DeactivatePyActCtx(cookie)
succeeded = ccall(:DeactivateActCtx, stdcall, Bool,
(UInt32, Ptr{Void}), 0, cookie)
(UInt32, Ptr{Cvoid}), 0, cookie)
@assert succeeded
end
else
Expand Down Expand Up @@ -614,15 +611,15 @@ function pyimport_conda(modulename::AbstractString, condapkg::AbstractString,
pyimport(modulename)
catch e
if conda
info("Installing $modulename via the Conda $condapkg package...")
Compat.@info "Installing $modulename via the Conda $condapkg package..."
isempty(channel) || Conda.add_channel(channel)
Conda.add(condapkg)
pyimport(modulename)
else
aconda = anaconda_conda()
if !isempty(aconda)
try
info("Installing $modulename via Anaconda's $aconda...")
Compat.@info "Installing $modulename via Anaconda's $aconda..."
isempty(channel) || run(`$aconda config --add channels $channel --force`)
run(`$aconda install -y $condapkg`)
catch e2
Expand Down Expand Up @@ -797,7 +794,7 @@ function splice!(a::PyObject, i::Integer)
end

pop!(a::PyObject) = splice!(a, length(a))
shift!(a::PyObject) = splice!(a, 1)
popfirst!(a::PyObject) = splice!(a, 1)

function empty!(a::PyObject)
for i in length(a):-1:1
Expand All @@ -819,7 +816,7 @@ function insert!(a::PyObject, i::Integer, item)
a
end

unshift!(a::PyObject, item) = insert!(a, 1, item)
pushfirst!(a::PyObject, item) = insert!(a, 1, item)

function prepend!(a::PyObject, items)
if isa(items,PyObject) && items.o == a.o
Expand Down Expand Up @@ -880,10 +877,7 @@ end
#########################################################################
# Expose Python docstrings to the Julia doc system

if isdefined(Docs, :getdoc) # Introduced in Julia .6
Docs.getdoc(o::PyObject) = Text(String(o["__doc__"]))
end

Docs.getdoc(o::PyObject) = Text(String(o["__doc__"]))

#########################################################################

Expand Down
36 changes: 13 additions & 23 deletions src/callback.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function pymethod(f::Function, name::AbstractString, flags::Integer)
def = gensym("PyMethodDef")
@eval const $def = PyMethodDef[PyMethodDef($name, $f, $flags)]
PyObject(@pycheckn ccall((@pysym :PyCFunction_NewEx), PyPtr,
(Ptr{PyMethodDef}, Ptr{Void}, Ptr{Void}),
(Ptr{PyMethodDef}, Ptr{Cvoid}, Ptr{Cvoid}),
eval(def), C_NULL, C_NULL))
end

Expand All @@ -37,28 +37,18 @@ function _pyjlwrap_call(f, args_::PyPtr, kw_::PyPtr)
try
jlargs = julia_args(f, args)

# on 0.6 we need to use invokelatest to get execution in newest world
@static if isdefined(Base, :invokelatest)
if kw_ == C_NULL
ret = PyObject(Base.invokelatest(f, jlargs...))
else
kw = PyDict{Symbol,PyObject}(pyincref(kw_))
kwargs = [ (k,julia_kwarg(f,k,v)) for (k,v) in kw ]

# 0.6 `invokelatest` doesn't support kwargs, instead
# use a closure over kwargs. see:
# https://github.com/JuliaLang/julia/pull/22646
f_kw_closure() = f(jlargs...; kwargs...)
ret = PyObject(Core._apply_latest(f_kw_closure))
end
else # 0.5 support
if kw_ == C_NULL
ret = PyObject(f(jlargs...))
else
kw = PyDict{Symbol,PyAny}(pyincref(kw_))
kwargs = [ (k,julia_kwarg(f,k,v)) for (k,v) in kw ]
ret = PyObject(f(jlargs...; kwargs...))
end
# we need to use invokelatest to get execution in newest world
if kw_ == C_NULL
ret = PyObject(Base.invokelatest(f, jlargs...))
else
kw = PyDict{Symbol,PyObject}(pyincref(kw_))
kwargs = [ (k,julia_kwarg(f,k,v)) for (k,v) in kw ]

# 0.6 `invokelatest` doesn't support kwargs, instead
# use a closure over kwargs. see:
# https://github.com/JuliaLang/julia/pull/22646
f_kw_closure() = f(jlargs...; kwargs...)
ret = PyObject(Core._apply_latest(f_kw_closure))
end

return pystealref!(ret)
Expand Down
Loading