Skip to content

Commit 883d84b

Browse files
author
Christopher Doris
committed
rewrite multimedia display to be extensible
1 parent 2f66c4c commit 883d84b

File tree

4 files changed

+115
-51
lines changed

4 files changed

+115
-51
lines changed

src/Py.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,10 @@ function Base.show(io::IO, mime::MIME"text/plain", o::Py)
210210
end
211211
end
212212

213-
Base.show(io::IO, mime::MIME, o::Py) = py_mime_show(io, mime, o)
214-
Base.show(io::IO, mime::MIME"text/csv", o::Py) = py_mime_show(io, mime, o)
215-
Base.show(io::IO, mime::MIME"text/tab-separated-values", o::Py) = py_mime_show(io, mime, o)
216-
Base.showable(mime::MIME, o::Py) = py_mime_showable(mime, o)
213+
Base.show(io::IO, mime::MIME, o::Py) = pyshow(io, mime, o)
214+
Base.show(io::IO, mime::MIME"text/csv", o::Py) = pyshow(io, mime, o)
215+
Base.show(io::IO, mime::MIME"text/tab-separated-values", o::Py) = pyshow(io, mime, o)
216+
Base.showable(mime::MIME, o::Py) = pyshowable(mime, o)
217217

218218
Base.getproperty(x::Py, k::Symbol) = pygetattr(x, string(k))
219219
Base.getproperty(x::Py, k::String) = pygetattr(x, k)

src/PythonCall.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ function __init__()
8484
init_consts()
8585
init_pyconvert()
8686
init_datetime()
87+
init_pyshow()
8788
# juliacall/jlwrap
8889
init_juliacall()
8990
init_jlwrap_base()

src/compat/multimedia.jl

Lines changed: 105 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,117 @@
1-
py_mime_reprmethod(::MIME) = nothing
2-
py_mime_reprmethod(::MIME"text/plain") = "__repr__"
3-
py_mime_reprmethod(::MIME"text/html") = "_repr_html_"
4-
py_mime_reprmethod(::MIME"text/markdown") = "_repr_markdown_"
5-
py_mime_reprmethod(::MIME"text/json") = "_repr_json_"
6-
py_mime_reprmethod(::MIME"text/latex") = "_repr_latex_"
7-
py_mime_reprmethod(::MIME"application/javascript") = "_repr_javascript_"
8-
py_mime_reprmethod(::MIME"application/pdf") = "_repr_pdf_"
9-
py_mime_reprmethod(::MIME"image/jpeg") = "_repr_jpeg_"
10-
py_mime_reprmethod(::MIME"image/png") = "_repr_png_"
11-
py_mime_reprmethod(::MIME"image/svg+xml") = "_repr_svg_"
12-
13-
py_mime_data(m::MIME, o) = begin
14-
o = Py(o)
15-
r = py_mime_reprmethod(m)
16-
data = nothing
17-
meta = nothing
1+
### Extensible system for multimedia display of Python objects
2+
3+
const PYSHOW_RULES = Function[]
4+
5+
function pyshow_add_rule(rule::Function)
6+
push!(PYSHOW_RULES, rule)
7+
return
8+
end
9+
10+
function pyshow(io::IO, mime::MIME, x)
11+
x_ = Py(x)
12+
for rule in PYSHOW_RULES
13+
rule(io, string(mime), x_) && return
14+
end
15+
throw(MethodError(show, (io, mime, x_)))
16+
end
17+
18+
function pyshowable(mime::MIME, x)
19+
x_ = Py(x)
20+
for rule in PYSHOW_RULES
21+
rule(devnull, string(mime), x_) && return true
22+
end
23+
return false
24+
end
25+
26+
### Particular rules
27+
28+
function pyshow_rule_mimebundle(io::IO, mime::String, x::Py)
1829
try
19-
x = o.__class__._repr_mimebundle_(o, include=pylist((string(m),)))
20-
if pyisinstance(x, pybuiltins.tuple)
21-
data = x[0][string(m)]
22-
meta = x[1].get(string(m))
30+
ans = x._repr_mimebundle_(include=pylist((mime,)))
31+
if pyisinstance(ans, pybuiltins.tuple)
32+
data = ans[0][mime]
33+
meta = ans[1].get(mime)
2334
else
24-
data = x[m]
35+
data = ans[mime]
36+
meta = pybuiltins.None
2537
end
38+
write(io, pyconvert(Union{String,Vector{UInt8}}, data))
39+
return true
2640
catch exc
27-
exc isa PyException || rethrow()
41+
if exc isa PyException
42+
return false
43+
else
44+
rethrow()
45+
end
2846
end
29-
if data === nothing && r !== nothing
30-
try
31-
x = pygetattr(o.__class__, r)(o)
32-
if pyisinstance(x, pybuiltins.tuple)
33-
data = x[0]
34-
meta = x[1]
35-
else
36-
data = x
37-
end
38-
catch exc
39-
exc isa PyException || rethrow()
47+
end
48+
49+
const MIME_TO_REPR_METHOD = Dict(
50+
"text/plain" => "__repr__",
51+
"text/html" => "_repr_html_",
52+
"text/markdown" => "_repr_markdown_",
53+
"text/json" => "_repr_json_",
54+
"text/latex" => "_repr_latex_",
55+
"application/javascript" => "_repr_javascript_",
56+
"application/pdf" => "_repr_pdf_",
57+
"image/jpeg" => "_repr_jpeg_",
58+
"image/png" => "_repr_png_",
59+
"image/svg+xml" => "_repr_svg_",
60+
)
61+
62+
function pyshow_rule_repr(io::IO, mime::String, x::Py)
63+
method = get(MIME_TO_REPR_METHOD, mime, "")
64+
isempty(method) && return false
65+
try
66+
ans = pygetattr(x, method)()
67+
if pyisinstance(ans, pybuiltins.tuple)
68+
data = ans[0]
69+
meta = ans[1]
70+
else
71+
data = ans
72+
meta = pybuiltins.None
73+
end
74+
write(io, pyconvert(Union{String,Vector{UInt8}}, data))
75+
return true
76+
catch exc
77+
if exc isa PyException
78+
return false
79+
else
80+
rethrow()
4081
end
4182
end
42-
data, meta
4383
end
4484

45-
py_mime_showable(m::MIME, o) = begin
46-
data, meta = py_mime_data(m, o)
47-
data !== nothing
85+
const MIME_TO_MATPLOTLIB_FORMAT = Dict(
86+
"image/png" => "png",
87+
"image/jpeg" => "jpeg",
88+
"image/tiff" => "tiff",
89+
"image/svg+xml" => "svg",
90+
"application/pdf" => "pdf",
91+
)
92+
93+
function pyshow_rule_savefig(io::IO, mime::String, x::Py)
94+
# TODO: restrict to types or modules which are known to have a savefig method like this?
95+
format = get(MIME_TO_MATPLOTLIB_FORMAT, mime, "")
96+
isempty(format) && return false
97+
try
98+
# buf = pyimport("io").BytesIO()
99+
# x.savefig(buf, format=format)
100+
# data = pyconvert(Vector{UInt8}, buf.getvalue())
101+
# write(io, data)
102+
x.savefig(io, format=format)
103+
return true
104+
catch exc
105+
if exc isa PyException
106+
return false
107+
else
108+
rethrow()
109+
end
110+
end
48111
end
49112

50-
py_mime_show(io::IO, m::MIME, o) = begin
51-
data, meta = py_mime_data(m, o)
52-
write(io, istextmime(m) ? pystr(String, data) : pybytes(Vector{UInt8}, data))
53-
nothing
113+
function init_pyshow()
114+
pyshow_add_rule(pyshow_rule_mimebundle)
115+
pyshow_add_rule(pyshow_rule_repr)
116+
pyshow_add_rule(pyshow_rule_savefig)
54117
end

src/pywrap/PyPandasDataFrame.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,13 @@ function Base.show(io::IO, mime::MIME"text/plain", df::PyPandasDataFrame)
103103
nrows = pyconvert_and_del(Int, @py df.shape[0])
104104
ncols = pyconvert_and_del(Int, @py df.shape[1])
105105
printstyled(io, nrows, '×', ncols, ' ', typeof(df), '\n', bold=true)
106-
py_mime_show(io, mime, df)
106+
pyshow(io, mime, df)
107107
end
108108

109-
Base.show(io::IO, mime::MIME, df::PyPandasDataFrame) = py_mime_show(io, mime, df)
110-
Base.show(io::IO, mime::MIME"text/csv", df::PyPandasDataFrame) = py_mime_show(io, mime, df)
111-
Base.show(io::IO, mime::MIME"text/tab-separated-values", df::PyPandasDataFrame) = py_mime_show(io, mime, df)
112-
Base.showable(mime::MIME, df::PyPandasDataFrame) = py_mime_showable(mime, df)
109+
Base.show(io::IO, mime::MIME, df::PyPandasDataFrame) = pyshow(io, mime, df)
110+
Base.show(io::IO, mime::MIME"text/csv", df::PyPandasDataFrame) = pyshow(io, mime, df)
111+
Base.show(io::IO, mime::MIME"text/tab-separated-values", df::PyPandasDataFrame) = pyshow(io, mime, df)
112+
Base.showable(mime::MIME, df::PyPandasDataFrame) = pyshowable(mime, df)
113113

114114
### Tables
115115

0 commit comments

Comments
 (0)