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: override get/setproperty and propertynames #517

Merged
merged 26 commits into from
Jan 23, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0ca66fd
switch to 0.7/1.0 properties
carstenbauer Oct 27, 2018
c51bd59
still support 0.6
carstenbauer Oct 27, 2018
2ebfa6a
adjust runtests
carstenbauer Oct 27, 2018
89da2c5
updated tests
carstenbauer Oct 31, 2018
da5f08e
test fix (0.6)
carstenbauer Oct 31, 2018
77736c8
modified tests
carstenbauer Oct 31, 2018
5fef94d
added getindex and haskey deprectations; fixed some internal warnings
carstenbauer Jan 20, 2019
bfd11e9
starequal in pynothing comparisons
carstenbauer Jan 20, 2019
a2a4394
separate depwarns for getindex
carstenbauer Jan 22, 2019
ac7b641
Merge commit 'ca792ed' into dotoverride
carstenbauer Jan 22, 2019
c58de0d
Merge commit '31296cd' into dotoverride
carstenbauer Jan 22, 2019
fd08cf9
Merge commit '48d730f' into dotoverride
carstenbauer Jan 22, 2019
2e6da51
Merge commit '8eb62f2' into dotoverride
carstenbauer Jan 22, 2019
c590d92
fix tests introduced in #594
carstenbauer Jan 22, 2019
ca2df13
Merge commit 'fe67ef2' into dotoverride
carstenbauer Jan 22, 2019
edb8778
Merge commit 'e74bf61' into dotoverride
carstenbauer Jan 22, 2019
a34eca5
Merge commit '1e0dce1' into dotoverride
carstenbauer Jan 22, 2019
10c88ea
Merge commit '1b0bbc0' into dotoverride
carstenbauer Jan 22, 2019
efde3b2
Merge commit '093342b' into dotoverride
carstenbauer Jan 22, 2019
3488c27
Merge commit '45cbcee' into dotoverride
carstenbauer Jan 22, 2019
114e222
Merge commit 'b5b458c' into dotoverride
carstenbauer Jan 22, 2019
27f6d10
fixing deprecations...
carstenbauer Jan 22, 2019
a90dce2
fixing deprecations again...
carstenbauer Jan 22, 2019
d7955de
fixed all deprecation warnings
carstenbauer Jan 22, 2019
52e9b13
deprecate setindex! and fix warnings etc
carstenbauer Jan 22, 2019
631bcb0
first (insufficient) modification of README
carstenbauer Jan 22, 2019
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
33 changes: 18 additions & 15 deletions src/PyCall.jl
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,7 @@ PyPtr(o::PyObject) = getfield(o, :o)

`PyPtr` based comparison of `x` and `y`, which can be of type `PyObject` or `PyPtr`.
"""
≛(o::PyObject, p::PyPtr) = PyPtr(o) == p
≛(p::PyPtr, o::PyObject) = o ≛ p
≛(o1::PyObject, o2::PyObject) = PyPtr(o1) == PyPtr(o2)
≛(p1::PyPtr, p2::PyPtr) = p1 == p2
≛(o1::Union{PyObject,PyPtr}, o2::Union{PyObject,PyPtr}) = PyPtr(o1) == PyPtr(o2)

"""
PyNULL()
Expand Down Expand Up @@ -260,8 +257,8 @@ end

function Base.Docs.doc(o::PyObject)
if hasproperty(o, "__doc__")
d = o["__doc__"]
if !(PyPtr(d) ≛ pynothing[])
d = o."__doc__"
if !(d ≛ pynothing[])
return Base.Docs.Text(convert(AbstractString, d))
end
end
Expand Down Expand Up @@ -311,7 +308,7 @@ end
getproperty(o::PyObject, s::Symbol) = convert(PyAny, getproperty(o, string(s)))
stevengj marked this conversation as resolved.
Show resolved Hide resolved

propertynames(o::PyObject) = map(x->Symbol(first(x)),
pycall(inspect["getmembers"], PyObject, o))
pycall(inspect."getmembers", PyObject, o))

function setproperty!(o::PyObject, s::Union{Symbol,AbstractString}, v)
if ispynull(o)
stevengj marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -325,7 +322,10 @@ function setproperty!(o::PyObject, s::Union{Symbol,AbstractString}, v)
o
end

getindex(o::PyObject, s::Union{Symbol, AbstractString}) = getproperty(o, s)
function getindex(o::PyObject, s::Union{Symbol, AbstractString})
Base.depwarn("`getindex(o::PyObject, s::Union{Symbol, AbstractString})` is deprecated in favor of dot overloading (`getproperty`) so elements should now be accessed as e.g. `o.s` instead of `o[:s]`.", :getindex)
carstenbauer marked this conversation as resolved.
Show resolved Hide resolved
return getproperty(o, s)
end


# If I make this
Expand All @@ -343,7 +343,10 @@ function setindex!(o::PyObject, v, s::Union{Symbol,AbstractString})
o
end

haskey(o::PyObject, s::Union{Symbol,AbstractString}) = hasproperty(o, s)
function haskey(o::PyObject, s::Union{Symbol,AbstractString})
carstenbauer marked this conversation as resolved.
Show resolved Hide resolved
Base.depwarn("`haskey(o::PyObject, s::Union{Symbol, AbstractString})` is deprecated, use `hasproperty(o, s)` instead.", :haskey)
return hasproperty(o, s)
end

function hasproperty(o::PyObject, s::Union{Symbol,AbstractString})
if ispynull(o)
Expand Down Expand Up @@ -376,12 +379,12 @@ If the Python module contains identifiers that are reserved words in Julia (e.g.
"""
function pywrap(o::PyObject, mname::Symbol=:__anon__)
members = convert(Vector{Tuple{AbstractString,PyObject}},
pycall(inspect["getmembers"], PyObject, o))
pycall(inspect."getmembers", PyObject, o))
filter!(m -> !(m[1] in reserved), members)
m = Module(mname, false)
consts = [Expr(:const, Expr(:(=), Symbol(x[1]), convert(PyAny, x[2]))) for x in members]
exports = try
convert(Vector{Symbol}, o["__all__"])
convert(Vector{Symbol}, o."__all__")
catch
[Symbol(x[1]) for x in filter(x -> x[1][1] != '_', members)]
end
Expand Down Expand Up @@ -625,16 +628,16 @@ function _pywith(EXPR,VAR,TYPE,BLOCK)
end
end
mgrT = pytypeof(mgr)
exit = mgrT["__exit__"]
value = @pycall mgrT["__enter__"](mgr)::$(esc(TYPE))
exit = mgrT."__exit__"
value = @pycall mgrT."__enter__"(mgr)::$(esc(TYPE))
exc = true
try
try
$(VAR==nothing ? :() : :($(esc(VAR)) = value))
$(esc(BLOCK))
catch err
exc = false
if !(@pycall exit(mgr, pyimport(:sys)[:exc_info]()...)::Bool)
if !(@pycall exit(mgr, pyimport(:sys).exc_info()...)::Bool)
throw(err)
end
end
Expand Down Expand Up @@ -835,7 +838,7 @@ end
pushfirst!(a::PyObject, item) = insert!(a, 1, item)

function prepend!(a::PyObject, items)
if isa(items,PyObject) && PyPtr(items) == PyPtr(a)
if isa(items,PyObject) && items ≛ a
# avoid infinite loop in prepending a to itself
return prepend!(a, collect(items))
end
Expand Down
16 changes: 8 additions & 8 deletions src/conversions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,10 @@ PyObject(p::Ptr) = pycall(c_void_p_Type, PyObject, UInt(p))

function convert(::Type{Ptr{Cvoid}}, po::PyObject)
if pyisinstance(po, c_void_p_Type)
v = po["value"]
v = po."value"
# ctypes stores the NULL pointer specially, grrr
pynothing_query(v) == Nothing ? C_NULL :
convert(Ptr{Cvoid}, convert(UInt, po["value"]))
convert(Ptr{Cvoid}, convert(UInt, po."value"))
elseif pyisinstance(po, @pyglobalobj(:PyCapsule_Type))
@pycheck ccall((@pysym :PyCapsule_GetPointer),
Ptr{Cvoid}, (PyPtr,Ptr{UInt8}),
Expand Down Expand Up @@ -657,13 +657,13 @@ const mpc = PyNULL()
function mpmath_init()
if ispynull(mpmath)
copy!(mpmath, pyimport("mpmath"))
copy!(mpf, mpmath["mpf"])
copy!(mpc, mpmath["mpc"])
copy!(mpf, mpmath."mpf")
copy!(mpc, mpmath."mpc")
end
curprec = precision(BigFloat)
if mpprec[1] != curprec
mpprec[1] = curprec
mpmath["mp"]["prec"] = mpprec[1]
mpmath."mp"."prec" = mpprec[1]
end
end

Expand All @@ -686,8 +686,8 @@ convert(::Type{BigFloat}, o::PyObject) = parse(BigFloat, pystr(o))

function convert(::Type{Complex{BigFloat}}, o::PyObject)
try
Complex{BigFloat}(convert(BigFloat, o["real"]),
convert(BigFloat, o["imag"]))
Complex{BigFloat}(convert(BigFloat, o."real"),
convert(BigFloat, o."imag"))
catch
convert(Complex{BigFloat}, convert(Complex{Float64}, o))
end
Expand Down Expand Up @@ -772,7 +772,7 @@ function pysequence_query(o::PyObject)
# only handle PyList for now
return pyisinstance(o, @pyglobalobj :PyList_Type) ? Array : Union{}
else
otypestr = get(o["__array_interface__"], PyObject, "typestr")
otypestr = get(o."__array_interface__", PyObject, "typestr")
typestr = convert(AbstractString, otypestr) # Could this just be String now?
T = npy_typestrs[typestr[2:end]]
if T == PyPtr
Expand Down
2 changes: 1 addition & 1 deletion src/io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ end

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

pyio_jl(self::PyObject) = unsafe_pyjlwrap_to_objref(PyPtr(self["io"]))::IO
pyio_jl(self::PyObject) = unsafe_pyjlwrap_to_objref(self."io")::IO

const PyIO = PyNULL()

Expand Down
14 changes: 7 additions & 7 deletions src/numpy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,14 @@ function npyinitialize()
numpy = pyimport("numpy")

# directory for numpy include files to parse
inc = pycall(numpy["get_include"], AbstractString)
inc = pycall(numpy."get_include", AbstractString)

# numpy.number types
copy!(npy_number, numpy["number"])
copy!(npy_integer, numpy["integer"])
copy!(npy_floating, numpy["floating"])
copy!(npy_complexfloating, numpy["complexfloating"])
copy!(npy_bool, numpy["bool_"])
copy!(npy_number, numpy."number")
copy!(npy_integer, numpy."integer")
copy!(npy_floating, numpy."floating")
copy!(npy_complexfloating, numpy."complexfloating")
copy!(npy_bool, numpy."bool_")

# Parse __multiarray_api.h to obtain length and meaning of PyArray_API
try
Expand Down Expand Up @@ -236,7 +236,7 @@ mutable struct PyArray_Info
readonly::Bool

function PyArray_Info(a::PyObject)
ai = PyDict{AbstractString,PyObject}(a["__array_interface__"])
ai = PyDict{AbstractString,PyObject}(a."__array_interface__")
typestr = convert(AbstractString, ai["typestr"])
T = npy_typestrs[typestr[2:end]]
datatuple = convert(Tuple{Int,Bool}, ai["data"])
Expand Down
2 changes: 1 addition & 1 deletion src/pyfncall.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function __pycall!(ret::PyObject, pyargsptr::PyPtr, o::Union{PyObject,PyPtr},
try
retptr = @pycheckn ccall((@pysym :PyObject_Call), PyPtr, (PyPtr,PyPtr,PyPtr), o,
pyargsptr, kw)
pydecref_(PyPtr(ret))
pydecref_(ret)
setfield!(ret, :o, retptr)
finally
sigatomic_end()
Expand Down
4 changes: 2 additions & 2 deletions src/pyinit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,10 @@ function __init__()
pyxrange[] = @pyglobalobj(:PyRange_Type)

# ctypes.c_void_p for Ptr types
copy!(c_void_p_Type, pyimport("ctypes")["c_void_p"])
copy!(c_void_p_Type, pyimport("ctypes")."c_void_p")

# traceback.format_tb function, for show(PyError)
copy!(format_traceback, pyimport("traceback")["format_tb"])
copy!(format_traceback, pyimport("traceback")."format_tb")

init_datetime()
pyjlwrap_init()
Expand Down
4 changes: 2 additions & 2 deletions src/serialize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function serialize(s::AbstractSerializer, pyo::PyObject)
if ispynull(pyo)
serialize(s, PyPtr(pyo))
else
b = PyBuffer(pycall(pickle()["dumps"], PyObject, pyo))
b = PyBuffer(pycall(pickle()."dumps", PyObject, pyo))
serialize(s, unsafe_wrap(Array, Ptr{UInt8}(pointer(b)), sizeof(b)))
end
end
Expand All @@ -41,6 +41,6 @@ function deserialize(s::AbstractSerializer, t::Type{PyObject})
@assert b == C_NULL
return PyNULL()
else
return pycall(pickle()["loads"], PyObject, pybytes(b))
return pycall(pickle()."loads", PyObject, pybytes(b))
end
end
88 changes: 44 additions & 44 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const PyInt = pyversion < v"3" ? Int : Clonglong
# conversion of NumPy scalars before npy_initialized by array conversions (#481)
np = pyimport_e("numpy")
if !ispynull(np) # numpy is installed, so test
let o = get(pycall(np["array"], PyObject, 1:3), PyObject, 2)
let o = get(pycall(np."array", PyObject, 1:3), PyObject, 2)
@test convert(Int32, o) === Int32(3)
@test convert(Int64, o) === Int64(3)
@test convert(Float64, o) === Float64(3)
Expand Down Expand Up @@ -84,7 +84,7 @@ const PyInt = pyversion < v"3" ? Int : Clonglong
@test roundtrip(testkw)(314157, y=1) == 314159

# check type stability of pycall with an explicit return type
@inferred pycall(PyObject(1)[:__add__], Int, 2)
@inferred pycall(PyObject(1).__add__, Int, 2)

if PyCall.npy_initialized
@test PyArray(PyObject([1. 2 3;4 5 6])) == [1. 2 3;4 5 6]
Expand Down Expand Up @@ -176,51 +176,51 @@ const PyInt = pyversion < v"3" ? Int : Clonglong
# IO (issue #107)
#@test roundtripeq(stdout) # No longer true since #250
let buf = Compat.IOBuffer(read=false, write=true), obuf = PyObject(buf)
@test !obuf[:isatty]()
@test obuf[:writable]()
@test !obuf[:readable]()
@test obuf[:seekable]()
obuf[:write](pyutf8("hello"))
obuf[:flush]() # should be a no-op, since there's no flushing IOBuffer
@test position(buf) == obuf[:tell]() == 5
let p = obuf[:seek](-2, 1)
@test !obuf.isatty()
@test obuf.writable()
@test !obuf.readable()
@test obuf.seekable()
obuf.write(pyutf8("hello"))
obuf.flush() # should be a no-op, since there's no flushing IOBuffer
@test position(buf) == obuf.tell() == 5
let p = obuf.seek(-2, 1)
@test p == position(buf) == 3
end
let p = obuf[:seek](0, 0)
let p = obuf.seek(0, 0)
@test p == position(buf) == 0
end
@test String(take!(buf)) == "hello"
obuf[:writelines](["first\n", "second\n", "third"])
obuf.writelines(["first\n", "second\n", "third"])
@test String(take!(buf)) == "first\nsecond\nthird"
obuf[:write](b"möre stuff")
obuf.write(b"möre stuff")
@test String(take!(buf)) == "möre stuff"
@test isopen(buf) == !obuf[:closed] == true
obuf[:close]()
@test isopen(buf) == !obuf[:closed] == false
@test isopen(buf) == !obuf.closed == true
obuf.close()
@test isopen(buf) == !obuf.closed == false
end
let buf = IOBuffer("hello\nagain"), obuf = PyObject(buf)
@test !obuf[:writable]()
@test obuf[:readable]()
@test obuf[:readlines]() == ["hello\n", "again"]
@test !obuf.writable()
@test obuf.readable()
@test obuf.readlines() == ["hello\n", "again"]
end
let buf = IOBuffer("hello\nagain"), obuf = PyObject(buf)
@test codeunits(obuf[:read](5)) == b"hello"
@test codeunits(obuf[:readall]()) == b"\nagain"
@test codeunits(obuf.read(5)) == b"hello"
@test codeunits(obuf.readall()) == b"\nagain"
end
let buf = IOBuffer("hello\nagain"), obuf = PyTextIO(buf)
@test obuf[:encoding] == "UTF-8"
@test obuf[:read](3) == "hel"
@test obuf[:readall]() == "lo\nagain"
@test obuf.encoding == "UTF-8"
@test obuf.read(3) == "hel"
@test obuf.readall() == "lo\nagain"
end
let nm = tempname()
open(nm, "w") do f
# @test roundtripeq(f) # PR #250
pf = PyObject(f)
@test pf[:fileno]() == fd(f)
@test pf[:writable]()
@test !pf[:readable]()
pf[:write](pyutf8(nm))
pf[:flush]()
@test pf.fileno() == fd(f)
@test pf.writable()
@test !pf.readable()
pf.write(pyutf8(nm))
pf.flush()
end
@test read(nm, String) == nm
end
Expand Down Expand Up @@ -291,7 +291,7 @@ const PyInt = pyversion < v"3" ? Int : Clonglong
let o = PyObject(1+2im)
@test PyCall.hasproperty(o, :real) # replace by Base.hasproperty in the future
@test :real in keys(o)
@test o[:real] == 1
@test o.real == 1
end

# []-based sequence access
Expand Down Expand Up @@ -333,7 +333,7 @@ const PyInt = pyversion < v"3" ? Int : Clonglong
end

# issue #216:
@test length(collect(pyimport("itertools")[:combinations]([1,2,3],2))) == 3
@test length(collect(pyimport("itertools").combinations([1,2,3],2))) == 3

# PyNULL and copy!
let x = PyNULL(), y = copy!(x, PyObject(314159))
Expand Down Expand Up @@ -401,10 +401,10 @@ const PyInt = pyversion < v"3" ? Int : Clonglong

# function attributes
let o = PyObject(foobar)
@test o[:__name__] == o[:func_name] == string(foobar)
@test o[:__doc__] == o[:func_doc] == "foobar doc\n"
@test o[:__module__] == o[:__defaults__] == o[:func_defaults] ==
o[:__closure__] == o[:func_closure] == nothing
@test o.__name__ == o.func_name == string(foobar)
@test o.__doc__ == o.func_doc == "foobar doc\n"
@test o.__module__ == o.__defaults__ == o.func_defaults ==
o.__closure__ == o.func_closure == nothing
end

# issue #345
Expand Down Expand Up @@ -492,7 +492,7 @@ const PyInt = pyversion < v"3" ? Int : Clonglong
try
@test begin
@pywith pybuiltin("open")(fname,"w") as f begin
f[:write]("test")
f.write("test")
end
open(io->read(io, String), fname)=="test"
end
Expand Down Expand Up @@ -522,20 +522,20 @@ const PyInt = pyversion < v"3" ? Int : Clonglong
@test_throws BoundsError convert(NTuple{3,Int}, PyObject((3,34)))

let p = PyCall.pickle(), buf = IOBuffer()
p[:dump]("hello world", buf)
p[:dump](314159, buf)
p[:dump](Any[1,1,2,3,5,8], buf)
@test p[:load](seekstart(buf)) == "hello world"
@test p[:load](buf) == 314159
@test p[:load](buf) == [1,1,2,3,5,8]
p.dump("hello world", buf)
p.dump(314159, buf)
p.dump(Any[1,1,2,3,5,8], buf)
@test p.load(seekstart(buf)) == "hello world"
@test p.load(buf) == 314159
@test p.load(buf) == [1,1,2,3,5,8]
end

# Test that we can call constructors on the python side
@test pycall(PyObject(TestConstruct), PyAny, 1).x == 1

# Test getattr fallback
@test PyObject(TestConstruct(1))[:x] == 1
@test_throws KeyError PyObject(TestConstruct(1))[:y]
@test PyObject(TestConstruct(1)).x == 1
@test_throws KeyError PyObject(TestConstruct(1)).y

# iterating over Julia objects in Python:
@test py"[x**2 for x in $(PyCall.pyjlwrap_new(1:4))]" ==
Expand Down