Skip to content

Commit 7164f25

Browse files
authored
Fix more 0.7 deprecations (#505)
* silence cfunction deprecations * silence some more deprecations * define and use constructors rather than convert where possible, but also define convert methods * pymethod is used in only one place, so inline it so that we can avoid using local vars in at-cfunction * PyError is no longer mutable * detect changes to Conda location that may occur in Pkg3 * whoops, be consistent * separate precompile step to get more Travis info, I hope * missing Conda check * don't use at-cfunction interpolation in PyGetSetDef (which can be removed entirely for now, as it is unused) * rm another unused function * Revert "define and use constructors rather than convert where possible, but also define convert methods" This reverts commit 65cb003. * add constructors for convert methods in 0.7 * rm redundant constructor methods * defining constructor methods seems to give a huge load time slowdown even in 0.7 * more undef deprecations * PYTHON=Conda to force conda * make sure bytes conversions work for codeunits arrays
1 parent 53d870d commit 7164f25

17 files changed

+148
-132
lines changed

.travis.yml

+7-2
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,18 @@ env:
1616
matrix:
1717
- PYTHON=python # python 2.7
1818
- PYTHON=python3 # python 3.5
19-
- PYTHON=Conda-python # not actually a python version, here to test Conda.jl's python
19+
- PYTHON=Conda # not actually a python version, here to test Conda.jl's python
2020
matrix:
2121
exclude:
2222
- os: osx
2323
env: PYTHON=python3 # I'm not sure how to install Python 3 on Travis OSX
2424
before_install:
25-
- test "x$TRAVIS_OS_NAME" = xosx -o "x$PYTHON" = xConda-python || (sudo apt-get -qq update && sudo apt-get install -y $PYTHON)
25+
- test "x$TRAVIS_OS_NAME" = xosx -o "x$PYTHON" = xConda || (sudo apt-get -qq update && sudo apt-get install -y $PYTHON)
26+
script:
27+
- if [[ -a .git/shallow ]]; then git fetch --unshallow; fi
28+
- julia -e 'Pkg.clone(pwd()); Pkg.build("PyCall")'
29+
- julia -e 'import PyCall' # precompile
30+
- julia -e 'Pkg.test("PyCall"; coverage=true)'
2631
after_success:
2732
- julia -e 'cd(Pkg.dir("PyCall")); Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())'
2833
notifications:

REQUIRE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
julia 0.6
2-
Compat 0.62.0
2+
Compat 0.68.0
33
Conda 0.2
44
MacroTools 0.3
55
VersionParsing

appveyor.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ environment:
1616

1717
- JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe"
1818
PYTHONDIR: "use_conda"
19-
PYTHON: "use_conda"
19+
PYTHON: "Conda"
2020

2121
- JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe"
2222
PYTHONDIR: "C:\\Python27-x64"
@@ -36,7 +36,7 @@ environment:
3636

3737
- JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe"
3838
PYTHONDIR: "use_conda"
39-
PYTHON: "use_conda"
39+
PYTHON: "Conda"
4040

4141
# Nightlies
4242
- JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe"
@@ -57,7 +57,7 @@ environment:
5757

5858
- JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe"
5959
PYTHONDIR: "use_conda"
60-
PYTHON: "use_conda"
60+
PYTHON: "Conda"
6161

6262
- JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe"
6363
PYTHONDIR: "C:\\Python27-x64"
@@ -77,7 +77,7 @@ environment:
7777

7878
- JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe"
7979
PYTHONDIR: "use_conda"
80-
PYTHON: "use_conda"
80+
PYTHON: "Conda"
8181

8282
branches:
8383
only:

deps/build.jl

+3-3
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,9 @@ try # make sure deps.jl file is removed on error
161161
python = try
162162
let py = get(ENV, "PYTHON", isfile("PYTHON") ? readchomp("PYTHON") :
163163
(Compat.Sys.isunix() && !Compat.Sys.isapple()) || Sys.ARCH (:i686, :x86_64) ? "python" : ""),
164-
vers = isempty(py) ? v"0.0" : vparse(pyconfigvar(py,"VERSION","0.0"))
164+
vers = isempty(py) || py == "Conda" ? v"0.0" : vparse(pyconfigvar(py,"VERSION","0.0"))
165165
if vers < v"2.7"
166-
if isempty(py)
166+
if isempty(py) || py == "Conda"
167167
throw(UseCondaPython())
168168
else
169169
error("Python version $vers < 2.7 is not supported")
@@ -242,7 +242,7 @@ try # make sure deps.jl file is removed on error
242242
""")
243243

244244
# Make subsequent builds (e.g. Pkg.update) use the same Python by default:
245-
writeifchanged("PYTHON", isfile(programname) ? programname : python)
245+
writeifchanged("PYTHON", use_conda ? "Conda" : isfile(programname) ? programname : python)
246246

247247
#########################################################################
248248

src/PyCall.jl

+16-13
Original file line numberDiff line numberDiff line change
@@ -155,22 +155,13 @@ PyObject(o::PyObject) = o
155155

156156
#########################################################################
157157

158-
include("pyinit.jl")
159158
include("exception.jl")
160159
include("gui.jl")
161160

162161
pytypeof(o::PyObject) = ispynull(o) ? throw(ArgumentError("NULL PyObjects have no Python type")) : PyObject(@pycheckn ccall(@pysym(:PyObject_Type), PyPtr, (PyPtr,), o))
163162

164163
#########################################################################
165164

166-
include("gc.jl")
167-
168-
# make a PyObject that embeds a reference to keep, to prevent Julia
169-
# from garbage-collecting keep until o is finalized.
170-
PyObject(o::PyPtr, keep::Any) = pyembed(PyObject(o), keep)
171-
172-
#########################################################################
173-
174165
const TypeTuple = Union{Type,NTuple{N, Type}} where {N}
175166
include("pybuffer.jl")
176167
include("conversions.jl")
@@ -180,6 +171,14 @@ include("pyclass.jl")
180171
include("callback.jl")
181172
include("io.jl")
182173

174+
#########################################################################
175+
176+
include("gc.jl")
177+
178+
# make a PyObject that embeds a reference to keep, to prevent Julia
179+
# from garbage-collecting keep until o is finalized.
180+
PyObject(o::PyPtr, keep::Any) = pyembed(PyObject(o), keep)
181+
183182
#########################################################################
184183
# Pretty-printing PyObject
185184

@@ -333,8 +332,8 @@ function pywrap(o::PyObject, mname::Symbol=:__anon__)
333332
catch
334333
[Symbol(x[1]) for x in filter(x -> x[1][1] != '_', members)]
335334
end
336-
eval(m, Expr(:toplevel, consts..., :(pymember(s) = $(getindex)($(o), s)),
337-
Expr(:export, exports...)))
335+
Core.eval(m, Expr(:toplevel, consts..., :(pymember(s) = $(getindex)($(o), s)),
336+
Expr(:export, exports...)))
338337
m
339338
end
340339

@@ -469,7 +468,7 @@ or alternatively you can use the Conda package directly (via
469468
`using Conda` followed by `Conda.add` etcetera).
470469
"""
471470
end
472-
e.msg *= "\n\n" * msg * "\n"
471+
e = PyError(string(e.msg, "\n\n", msg, "\n"), e)
473472
end
474473
throw(e)
475474
else
@@ -907,13 +906,17 @@ end
907906
#########################################################################
908907
# Expose Python docstrings to the Julia doc system
909908

910-
Docs.getdoc(o::PyObject) = Text(String(o["__doc__"]))
909+
Docs.getdoc(o::PyObject) = Text(convert(String, o["__doc__"]))
911910

912911
#########################################################################
913912

914913
include("pyeval.jl")
915914
include("serialize.jl")
916915

916+
#########################################################################
917+
918+
include("pyinit.jl")
919+
917920
#########################################################################
918921
# Precompilation: just an optimization to speed up initialization.
919922
# Here, we precompile functions that are passed to cfunction by __init__,

src/callback.jl

-14
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,6 @@
77

88
################################################################
99

10-
# Define a Python method/function object from f(PyPtr,PyPtr)::PyPtr.
11-
# Requires f to be a top-level function.
12-
function pymethod(f::Function, name::AbstractString, flags::Integer)
13-
# Python expects the PyMethodDef structure to be a *constant*,
14-
# so we define an anonymous global to hold it.
15-
def = gensym("PyMethodDef")
16-
@eval const $def = PyMethodDef[PyMethodDef($name, $f, $flags)]
17-
PyObject(@pycheckn ccall((@pysym :PyCFunction_NewEx), PyPtr,
18-
(Ptr{PyMethodDef}, Ptr{Cvoid}, Ptr{Cvoid}),
19-
eval(def), C_NULL, C_NULL))
20-
end
21-
22-
################################################################
23-
2410
# To pass an arbitrary Julia object to Python, we wrap it
2511
# in a jlwrap Python class, where jlwrap.__call__
2612
# executes the pyjlwrap_call function in Julia, which

src/conversions.jl

+9-1
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,17 @@ convert(::Type{Symbol}, po::PyObject) = Symbol(convert(AbstractString, po))
123123
#########################################################################
124124
# ByteArray conversions
125125

126-
PyObject(a::Vector{UInt8}) =
126+
function PyObject(a::DenseVector{UInt8})
127+
if stride(a,1) != 1
128+
try
129+
return NpyArray(a, true)
130+
catch
131+
return array2py(a) # fallback to non-NumPy version
132+
end
133+
end
127134
PyObject(@pycheckn ccall((@pysym :PyByteArray_FromStringAndSize),
128135
PyPtr, (Ptr{UInt8}, Int), a, length(a)))
136+
end
129137

130138
ispybytearray(po::PyObject) =
131139
pyisinstance(po, @pyglobalobj :PyByteArray_Type)

src/exception.jl

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#########################################################################
22
# Wrapper around Python exceptions
33

4-
mutable struct PyError <: Exception
5-
msg::AbstractString # message string from Julia context, or "" if none
4+
struct PyError <: Exception
5+
msg::String # message string from Julia context, or "" if none
66

77
# info returned by PyErr_Fetch/PyErr_Normalize
88
T::PyObject
@@ -20,8 +20,10 @@ mutable struct PyError <: Exception
2020
pexc, pexc + sizeof(PyPtr), pexc + 2*sizeof(PyPtr))
2121
ccall((@pysym :PyErr_NormalizeException), Cvoid, (UInt,UInt,UInt),
2222
pexc, pexc + sizeof(PyPtr), pexc + 2*sizeof(PyPtr))
23-
new(msg, exc[1], exc[2], exc[3])
23+
new(msg, PyObject(exc[1]), PyObject(exc[2]), PyObject(exc[3]))
2424
end
25+
26+
PyError(msg::AbstractString, e::PyError) = new(msg, e.T, e.val, e.traceback)
2527
end
2628

2729
function show(io::IO, e::PyError)

src/gc.jl

+9-7
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,22 @@ end
2121

2222
const weakref_callback_obj = PyNULL() # weakref_callback Python method
2323

24-
function pygc_finalize()
25-
pydecref(weakref_callback_obj)
26-
empty!(pycall_gc)
27-
end
24+
# Python expects the PyMethodDef structure to be a constant, so
25+
# we put it in a global to prevent gc.
26+
const weakref_callback_meth = Ref{PyMethodDef}()
2827

2928
# "embed" a reference to jo in po, using the weak-reference mechanism
3029
function pyembed(po::PyObject, jo::Any)
3130
# If there's a need to support immutable embedding,
3231
# the API needs to be changed to return the pointer.
3332
isimmutable(jo) && ArgumentError("pyembed: immutable argument not allowed")
3433
if ispynull(weakref_callback_obj)
35-
weakref_callback_obj.o = pyincref(pymethod(weakref_callback,
36-
"weakref_callback",
37-
METH_O)).o
34+
cf = @cfunction(weakref_callback, PyPtr, (PyPtr,PyPtr))
35+
weakref_callback_meth[] = PyMethodDef("weakref_callback", cf, METH_O)
36+
copy!(weakref_callback_obj,
37+
PyObject(@pycheckn ccall((@pysym :PyCFunction_NewEx), PyPtr,
38+
(Ref{PyMethodDef}, Ptr{Cvoid}, Ptr{Cvoid}),
39+
weakref_callback_meth, C_NULL, C_NULL)))
3840
end
3941
wo = @pycheckn ccall((@pysym :PyWeakref_NewRef), PyPtr, (PyPtr,PyPtr),
4042
po, weakref_callback_obj)

src/numpy.jl

+5-1
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,11 @@ function f_contiguous(T::Type, sz::Vector{Int}, st::Vector{Int})
282282
end
283283

284284
f_contiguous(i::PyArray_Info) = f_contiguous(i.T, i.sz, i.st)
285-
c_contiguous(i::PyArray_Info) = f_contiguous(i.T, flipdim(i.sz,1), flipdim(i.st,1))
285+
@static if VERSION >= v"0.7.0-DEV.4534" # julia#26369
286+
c_contiguous(i::PyArray_Info) = f_contiguous(i.T, reverse(i.sz,dims=1), reverse(i.st,dims=1))
287+
else
288+
c_contiguous(i::PyArray_Info) = f_contiguous(i.T, flipdim(i.sz,1), flipdim(i.st,1))
289+
end
286290

287291
#########################################################################
288292
# PyArray: no-copy wrapper around NumPy ndarray

src/pyeval.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ macro py_str(code, options...)
196196
:(m[$v] = PyObject($(esc(ex)))) :
197197
nothing) for (v,ex) in locals]...)
198198
code_expr = Expr(:call, esc(:(Base.string)))
199-
i0 = start(code)
199+
i0 = firstindex(code)
200200
for i in sort!(collect(filter(k -> isa(k,Integer), keys(locals))))
201201
push!(code_expr.args, code[i0:prevind(code,i)], esc(locals[i]))
202202
i0 = i

src/pyinit.jl

+45
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,54 @@ const c_void_p_Type = PyNULL()
2121
const pynothing = Ref{PyPtr}(0)
2222
const pyxrange = Ref{PyPtr}(0)
2323

24+
#########################################################################
25+
# initialize jlWrapType for pytype.jl
26+
27+
function pyjlwrap_init()
28+
# PyMemberDef stores explicit pointers, hence must be initialized at runtime
29+
push!(pyjlwrap_members, PyMemberDef(pyjlwrap_membername,
30+
T_PYSSIZET, sizeof_PyObject_HEAD, READONLY,
31+
pyjlwrap_doc),
32+
PyMemberDef(C_NULL,0,0,0,C_NULL))
33+
34+
# all cfunctions must be compiled at runtime
35+
pyjlwrap_dealloc_ptr = @cfunction(pyjlwrap_dealloc, Cvoid, (PyPtr,))
36+
pyjlwrap_repr_ptr = @cfunction(pyjlwrap_repr, PyPtr, (PyPtr,))
37+
pyjlwrap_hash_ptr = @cfunction(pyjlwrap_hash, UInt, (PyPtr,))
38+
pyjlwrap_hash32_ptr = @cfunction(pyjlwrap_hash32, UInt32, (PyPtr,))
39+
pyjlwrap_call_ptr = @cfunction(pyjlwrap_call, PyPtr, (PyPtr,PyPtr,PyPtr))
40+
pyjlwrap_getattr_ptr = @cfunction(pyjlwrap_getattr, PyPtr, (PyPtr,PyPtr))
41+
pyjlwrap_getiter_ptr = @cfunction(pyjlwrap_getiter, PyPtr, (PyPtr,))
42+
43+
# detect at runtime whether we are using Stackless Python
44+
try
45+
pyimport("stackless")
46+
Py_TPFLAGS_HAVE_STACKLESS_EXTENSION[] = Py_TPFLAGS_HAVE_STACKLESS_EXTENSION_
47+
end
48+
49+
PyTypeObject!(jlWrapType, "PyCall.jlwrap", sizeof(Py_jlWrap)) do t::PyTypeObject
50+
t.tp_flags |= Py_TPFLAGS_BASETYPE
51+
t.tp_members = pointer(pyjlwrap_members);
52+
t.tp_dealloc = pyjlwrap_dealloc_ptr
53+
t.tp_repr = pyjlwrap_repr_ptr
54+
t.tp_call = pyjlwrap_call_ptr
55+
t.tp_getattro = pyjlwrap_getattr_ptr
56+
t.tp_iter = pyjlwrap_getiter_ptr
57+
t.tp_hash = sizeof(Py_hash_t) < sizeof(Int) ?
58+
pyjlwrap_hash32_ptr : pyjlwrap_hash_ptr
59+
end
60+
end
61+
2462
#########################################################################
2563

2664
function __init__()
65+
# sanity check: in Pkg for Julia 0.7+, the location of Conda can change
66+
# if e.g. you checkout Conda master, and we'll need to re-build PyCall
67+
# for something like pyimport_conda to continue working.
68+
if conda && dirname(python) != abspath(Conda.PYTHONDIR)
69+
error("Using Conda.jl python, but location of $python seems to have moved to $(Conda.PYTHONDIR). Re-run Pkg.build(\"PyCall\") and restart Julia.")
70+
end
71+
2772
# issue #189
2873
libpy_handle = libpython === nothing ? C_NULL :
2974
Libdl.dlopen(libpython, Libdl.RTLD_LAZY|Libdl.RTLD_DEEPBIND|Libdl.RTLD_GLOBAL)

src/pyiterator.jl

+14-14
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,6 @@ Base.collect(o::PyObject) = collect(Any, o)
4242

4343
const jlWrapIteratorType = PyTypeObject()
4444

45-
# Given a Julia object o, return a jlwrap_iterator Python iterator object
46-
# that wraps the Julia iteration protocol with the Python iteration protocol.
47-
# Internally, the jlwrap_iterator object stores the tuple (o, Ref(start(o))),
48-
# where the Ref is used to store the iterator state (which updates during iteration).
49-
function jlwrap_iterator(o::Any)
50-
if jlWrapIteratorType.tp_name == C_NULL # lazily initialize
51-
pyjlwrap_type!(jlWrapIteratorType, "PyCall.jlwrap_iterator") do t
52-
t.tp_iter = cfunction(pyincref_, PyPtr, Tuple{PyPtr}) # new reference to same object
53-
t.tp_iternext = cfunction(pyjlwrap_iternext, PyPtr, Tuple{PyPtr})
54-
end
55-
end
56-
return pyjlwrap_new(jlWrapIteratorType, (o, Ref(start(o))))
57-
end
58-
5945
# tp_iternext object of a jlwrap_iterator object, similar to PyIter_Next
6046
function pyjlwrap_iternext(self_::PyPtr)
6147
try
@@ -84,6 +70,20 @@ function pyjlwrap_getiter(self_::PyPtr)
8470
return PyPtr_NULL
8571
end
8672

73+
# Given a Julia object o, return a jlwrap_iterator Python iterator object
74+
# that wraps the Julia iteration protocol with the Python iteration protocol.
75+
# Internally, the jlwrap_iterator object stores the tuple (o, Ref(start(o))),
76+
# where the Ref is used to store the iterator state (which updates during iteration).
77+
function jlwrap_iterator(o::Any)
78+
if jlWrapIteratorType.tp_name == C_NULL # lazily initialize
79+
pyjlwrap_type!(jlWrapIteratorType, "PyCall.jlwrap_iterator") do t
80+
t.tp_iter = @cfunction(pyincref_, PyPtr, (PyPtr,)) # new reference to same object
81+
t.tp_iternext = @cfunction(pyjlwrap_iternext, PyPtr, (PyPtr,))
82+
end
83+
end
84+
return pyjlwrap_new(jlWrapIteratorType, (o, Ref(start(o))))
85+
end
86+
8787
#########################################################################
8888
# Broadcasting: if the object is iterable, return collect(o), and otherwise
8989
# return o.

0 commit comments

Comments
 (0)