Skip to content

Commit 9380a69

Browse files
authored
Don't try to call _repr_*_ on a class (#817)
* Don't try to call `_repr_*_` on a class * Use inspect module * Revert "Use inspect module" This reverts commit 95ee1f1. * Port and use get_real_method from IPython * Make it work with Python 2 * More tests
1 parent 5f4bc27 commit 9380a69

File tree

2 files changed

+74
-7
lines changed

2 files changed

+74
-7
lines changed

src/PyCall.jl

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,11 @@ end
311311
getproperty(o::PyObject, s::AbstractString) = __getproperty(o, s)
312312
getproperty(o::PyObject, s::Symbol) = convert(PyAny, __getproperty(o, s))
313313

314+
function trygetproperty(o::PyObject, s::AbstractString, d)
315+
p = _getproperty(o, s)
316+
return p == C_NULL ? d : PyObject(p)
317+
end
318+
314319
propertynames(o::PyObject) = ispynull(o) ? Symbol[] : map(x->Symbol(first(x)), PyIterator{PyObject}(pycall(inspect."getmembers", PyObject, o)))
315320

316321
# avoiding method ambiguity
@@ -890,20 +895,64 @@ for (mime, method) in ((MIME"text/html", "_repr_html_"),
890895
T = istextmime(mime()) ? AbstractString : Vector{UInt8}
891896
@eval begin
892897
function show(io::IO, mime::$mime, o::PyObject)
893-
if !ispynull(o) && hasproperty(o, $method)
894-
r = pycall(o.$method, PyObject)
898+
m = get_real_method(o, $method)
899+
if m !== nothing
900+
r = pycall(m, PyObject)
895901
!(r pynothing[]) && return write(io, convert($T, r))
896902
end
897903
throw(MethodError(show, (io, mime, o)))
898904
end
899-
Base.showable(::$mime, o::PyObject) =
900-
!ispynull(o) && hasproperty(o, $method) && let meth = o.$method
901-
!(meth pynothing[]) &&
902-
!(pycall(meth, PyObject) pynothing[])
903-
end
905+
function Base.showable(::$mime, o::PyObject)
906+
m = get_real_method(o, $method)
907+
m === nothing && return false
908+
return !(pycall(m, PyObject) pynothing[])
909+
end
904910
end
905911
end
906912

913+
"""
914+
get_real_method(obj::PyObject, name::String) -> method::Union{PyObject,Nothing}
915+
916+
This is a port of `IPython.utils.dir2.get_real_method`:
917+
https://github.com/ipython/ipython/blob/7.9.0/IPython/utils/dir2.py
918+
919+
For Python 2-era
920+
https://github.com/ipython/ipython/blob/5.9.0/IPython/utils/dir2.py
921+
"""
922+
function get_real_method(obj, name)
923+
ispynull(obj) && return nothing
924+
@static if pyversion_build < v"3"
925+
pyisinstance(obj, @pyglobalobj :PyType_Type) && return nothing
926+
end
927+
928+
canary = try
929+
trygetproperty(obj, "_ipython_canary_method_should_not_exist_", nothing)
930+
catch
931+
nothing
932+
end
933+
934+
# It claimed to have an attribute it should never have
935+
canary === nothing || return nothing
936+
937+
m = try
938+
trygetproperty(obj, name, nothing)
939+
catch
940+
nothing
941+
end
942+
m === nothing && return nothing
943+
944+
if (
945+
pyisinstance(obj, @pyglobalobj :PyType_Type) &&
946+
!pyisinstance(m, @pyglobalobj :PyMethod_Type)
947+
)
948+
return nothing
949+
end
950+
951+
ccall((@pysym :PyCallable_Check), Cint, (PyPtr,), m) == 1 && return m
952+
953+
return nothing
954+
end
955+
907956
#########################################################################
908957
# Expose Python docstrings to the Julia doc system
909958

test/runtests.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,24 @@ const PyInt = pyversion < v"3" ? Int : Clonglong
170170

171171
# fixme: is there any nontrivial showable test we can do?
172172
@test !showable("text/html", PyObject(1))
173+
@testset "showable on type (#816)" begin
174+
py"""
175+
class Issue816(object):
176+
def _repr_html_(self):
177+
return "<h1>Issue816</h1>"
178+
179+
class CallableAsSpecialRepr(object):
180+
_repr_html_ = Issue816()._repr_html_
181+
"""
182+
@test showable("text/html", py"Issue816()")
183+
@test !showable("text/html", py"Issue816")
184+
@test showable("text/html", py"CallableAsSpecialRepr()")
185+
if PyCall.pyversion_build < v"3"
186+
@test_broken showable("text/html", py"CallableAsSpecialRepr")
187+
else
188+
@test showable("text/html", py"CallableAsSpecialRepr")
189+
end
190+
end
173191

174192
# in Python 3, we need a specific encoding to write strings or bufferize them
175193
# (http://stackoverflow.com/questions/5471158/typeerror-str-does-not-support-the-buffer-interface)

0 commit comments

Comments
 (0)