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

check for null in multimedia show functions #526

Merged
merged 2 commits into from
Jul 20, 2024
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
1 change: 1 addition & 0 deletions docs/src/releasenotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* `numpy.bool_` can now be converted to `Bool` and other number types.
* `datetime.timedelta` can now be converted to `Dates.Nanosecond`, `Microsecond`, `Millisecond` and `Second`. This behaviour was already documented.
* In JuliaCall, the Julia runtime is now properly terminated when Python exits. This means all finalizers should always run.
* NULL Python objects (such as from `pynew()`) can be safely displayed in multimedia contexts (VSCode/Pluto/etc.)

## 0.9.20 (2024-05-01)
* The IPython extension is now automatically loaded upon import if IPython is detected.
Expand Down
12 changes: 8 additions & 4 deletions src/Compat/multimedia.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,20 @@ end

function pyshow(io::IO, mime::MIME, x)
x_ = Py(x)
for rule in PYSHOW_RULES
rule(io, string(mime), x_) && return
if !pyisnull(x_)
for rule in PYSHOW_RULES
rule(io, string(mime), x_) && return
end
end
throw(MethodError(show, (io, mime, x_)))
end

function pyshowable(mime::MIME, x)
x_ = Py(x)
for rule in PYSHOW_RULES
rule(devnull, string(mime), x_) && return true
if !pyisnull(x_)
for rule in PYSHOW_RULES
rule(devnull, string(mime), x_) && return true
end
end
return false
end
Expand Down
80 changes: 47 additions & 33 deletions test/Core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@
@test pylen(x) == 0
end
@testset "pydir" begin
x = pytype("Foo", (), ["foo"=>1, "bar"=>2])()
x = pytype("Foo", (), ["foo" => 1, "bar" => 2])()
d = pydir(x)
@test pycontains(d, "__class__")
@test pycontains(d, "foo")
Expand Down Expand Up @@ -256,7 +256,7 @@ end
end
@testset "pyinv" begin
for n in -2:2
@test pyeq(Bool, pyinv(pyint(n)), pyint(-n-1))
@test pyeq(Bool, pyinv(pyint(n)), pyint(-n - 1))
end
end
@testset "pyindex" begin
Expand All @@ -267,21 +267,21 @@ end
@testset "pyadd" begin
for x in -2:2
for y in -2:2
@test pyeq(Bool, pyadd(pyint(x), pyint(y)), pyint(x+y))
@test pyeq(Bool, pyadd(pyint(x), pyint(y)), pyint(x + y))
end
end
end
@testset "pysub" begin
for x in -2:2
for y in -2:2
@test pyeq(Bool, pysub(pyint(x), pyint(y)), pyint(x-y))
@test pyeq(Bool, pysub(pyint(x), pyint(y)), pyint(x - y))
end
end
end
@testset "pymul" begin
for x in -2:2
for y in -2:2
@test pyeq(Bool, pymul(pyint(x), pyint(y)), pyint(x*y))
@test pyeq(Bool, pymul(pyint(x), pyint(y)), pyint(x * y))
end
end
end
Expand All @@ -299,7 +299,7 @@ end
if y == 0
@test_throws PyException pytruediv(pyint(x), pyint(y))
else
@test pyeq(Bool, pytruediv(pyint(x), pyint(y)), pyfloat(x/y))
@test pyeq(Bool, pytruediv(pyint(x), pyint(y)), pyfloat(x / y))
end
end
end
Expand Down Expand Up @@ -409,15 +409,15 @@ end
@test pyeq(Bool, sys.__name__, "sys")
@test pyeq(Bool, os.__name__, "os")
sysos = pyimport("sys", "os")
@test sysos isa Tuple{Py, Py}
@test sysos isa Tuple{Py,Py}
@test pyis(sysos[1], sys)
@test pyis(sysos[2], os)
ver = pyimport("sys" => "version")
@test pyis(ver, sys.version)
path = pyimport("sys" => "path")
@test pyis(path, sys.path)
verpath = pyimport("sys" => ("version", "path"))
@test verpath isa Tuple{Py, Py}
@test verpath isa Tuple{Py,Py}
@test pyis(verpath[1], ver)
@test pyis(verpath[2], path)
end
Expand All @@ -438,12 +438,12 @@ end
end

@testitem "bytes" begin
@test pyisinstance(pybytes(UInt8[1,2,3]), pybuiltins.bytes)
@test pyeq(Bool, pybytes(pylist([1,2,3])), pybytes(UInt8[1,2,3]))
@test pyisinstance(pybytes(UInt8[1, 2, 3]), pybuiltins.bytes)
@test pyeq(Bool, pybytes(pylist([1, 2, 3])), pybytes(UInt8[1, 2, 3]))
@test pyeq(Bool, pybytes(b"foo"), pystr("foo").encode("ascii"))
@test pyeq(Bool, pybytes(codeunits(SubString("foobarbaz", 4:6))), pystr("bar").encode("ascii"))
@test pybytes(Vector, pylist([1,2,3])) == UInt8[1,2,3]
@test pybytes(Vector{UInt8}, pylist([1,2,3])) == UInt8[1,2,3]
@test pybytes(Vector, pylist([1, 2, 3])) == UInt8[1, 2, 3]
@test pybytes(Vector{UInt8}, pylist([1, 2, 3])) == UInt8[1, 2, 3]
@test pybytes(Base.CodeUnits, pystr("foo").encode("ascii")) == b"foo"
@test pybytes(Base.CodeUnits{UInt8,String}, pystr("bar").encode("ascii")) == b"bar"
end
Expand All @@ -452,36 +452,36 @@ end
z = pytuple()
@test pyisinstance(z, pybuiltins.tuple)
@test pylen(z) == 0
x = pytuple((1,2,3))
x = pytuple((1, 2, 3))
@test pyisinstance(x, pybuiltins.tuple)
@test pylen(x) == 3
@test pyeq(Bool, pygetitem(x, 0), 1)
@test pyeq(Bool, pygetitem(x, 1), 2)
@test pyeq(Bool, pygetitem(x, 2), 3)
@test pyeq(Bool, pytuple([1,2,3]), x)
@test pyeq(Bool, pytuple(i+1 for i in 0:10 if i<3), x)
@test pyeq(Bool, pytuple(pytuple((1,2,3))), x)
@test pyeq(Bool, pytuple(pylist([1,2,3])), x)
@test pyeq(Bool, pytuple([1, 2, 3]), x)
@test pyeq(Bool, pytuple(i + 1 for i in 0:10 if i < 3), x)
@test pyeq(Bool, pytuple(pytuple((1, 2, 3))), x)
@test pyeq(Bool, pytuple(pylist([1, 2, 3])), x)
end

@testitem "list" begin
z = pylist()
@test pyisinstance(z, pybuiltins.list)
@test pylen(z) == 0
x = pylist((1,2,3))
x = pylist((1, 2, 3))
@test pyisinstance(x, pybuiltins.list)
@test pylen(x) == 3
@test pyeq(Bool, pygetitem(x, 0), 1)
@test pyeq(Bool, pygetitem(x, 1), 2)
@test pyeq(Bool, pygetitem(x, 2), 3)
@test pyeq(Bool, pylist([1,2,3]), x)
@test pyeq(Bool, pylist(i+1 for i in 0:10 if i<3), x)
@test pyeq(Bool, pylist(pylist((1,2,3))), x)
@test pyeq(Bool, pylist(pytuple([1,2,3])), x)
@test pyeq(Bool, pycollist([1,2,3]), pylist([1,2,3]))
@test pyeq(Bool, pycollist([1 2; 3 4]), pylist((pylist([1,3]), pylist([2,4]))))
@test pyeq(Bool, pyrowlist([1,2,3]), pylist([1,2,3]))
@test pyeq(Bool, pyrowlist([1 2; 3 4]), pylist((pylist([1,2]), pylist([3,4]))))
@test pyeq(Bool, pylist([1, 2, 3]), x)
@test pyeq(Bool, pylist(i + 1 for i in 0:10 if i < 3), x)
@test pyeq(Bool, pylist(pylist((1, 2, 3))), x)
@test pyeq(Bool, pylist(pytuple([1, 2, 3])), x)
@test pyeq(Bool, pycollist([1, 2, 3]), pylist([1, 2, 3]))
@test pyeq(Bool, pycollist([1 2; 3 4]), pylist((pylist([1, 3]), pylist([2, 4]))))
@test pyeq(Bool, pyrowlist([1, 2, 3]), pylist([1, 2, 3]))
@test pyeq(Bool, pyrowlist([1 2; 3 4]), pylist((pylist([1, 2]), pylist([3, 4]))))
end

@testitem "dict" begin
Expand All @@ -493,9 +493,9 @@ end
@test pylen(x) == 2
@test pyeq(Bool, pygetitem(x, "foo"), 1)
@test pyeq(Bool, pygetitem(x, "bar"), 2)
@test pyeq(Bool, pydict(["foo"=>1, "bar"=>2]), x)
@test pyeq(Bool, pydict([("foo"=>1), ("bar"=>2)]), x)
@test pyeq(Bool, pydict(Dict("foo"=>1, "bar"=>2)), x)
@test pyeq(Bool, pydict(["foo" => 1, "bar" => 2]), x)
@test pyeq(Bool, pydict([("foo" => 1), ("bar" => 2)]), x)
@test pyeq(Bool, pydict(Dict("foo" => 1, "bar" => 2)), x)
@test pyeq(Bool, pydict((foo=1, bar=2)), x)
@test pyeq(Bool, pydict(x), x)
end
Expand All @@ -508,7 +508,7 @@ end
@test pyis(pybool(-1.2), pybuiltins.True)
@test pyis(pybool(pybuiltins.None), pybuiltins.False)
@test pyis(pybool(pylist()), pybuiltins.False)
@test pyis(pybool(pylist([1,2,3])), pybuiltins.True)
@test pyis(pybool(pylist([1, 2, 3])), pybuiltins.True)
end

@testitem "int" begin
Expand Down Expand Up @@ -546,7 +546,7 @@ end
y = pyfloat(x)
@test pyisinstance(y, pybuiltins.float)
@test pyeq(Bool, y, pytruediv(1, 4))
x = 1//4
x = 1 // 4
y = pyfloat(x)
@test pyisinstance(y, pybuiltins.float)
@test pyeq(Bool, y, pyfloat(float(x)))
Expand Down Expand Up @@ -584,7 +584,7 @@ end
@test pyisinstance(yf, pybuiltins.frozenset)
@test pylen(yf) == 0
@test pyeq(Bool, y, yf)
x = [1,2,3,2,1]
x = [1, 2, 3, 2, 1]
y = pyset(x)
yf = pyfrozenset(x)
@test pyisinstance(y, pybuiltins.set)
Expand Down Expand Up @@ -649,7 +649,7 @@ end
x = pytype(pybuiltins.type)
@test pyisinstance(x, pybuiltins.type)
@test pyis(x, pybuiltins.type)
x = pytype("Foo", (), ["foo"=>1, "bar"=>2])
x = pytype("Foo", (), ["foo" => 1, "bar" => 2])
@test pyisinstance(x, pybuiltins.type)
@test pyeq(Bool, x.__name__, "Foo")
@test pyeq(Bool, x.foo, 1)
Expand Down Expand Up @@ -782,6 +782,20 @@ end
# but now tries to do `1 + [1, 2]` which properly fails
@test_throws PyException [1 2; 3 4] .+ pylist([1, 2])
end
@testset "showable" begin
@test showable(MIME("text/plain"), Py(nothing))
@test showable(MIME("text/plain"), Py(12))
# https://github.com/JuliaPy/PythonCall.jl/issues/522
@test showable(MIME("text/plain"), PythonCall.pynew())
@test !showable(MIME("text/html"), PythonCall.pynew())
end
@testset "show" begin
@test sprint(show, MIME("text/plain"), Py(nothing)) == "Python: None"
@test sprint(show, MIME("text/plain"), Py(12)) == "Python: 12"
# https://github.com/JuliaPy/PythonCall.jl/issues/522
@test sprint(show, MIME("text/plain"), PythonCall.pynew()) == "Python: NULL"
@test_throws MethodError sprint(show, MIME("text/html"), PythonCall.pynew())
end
end

@testitem "pywith" begin
Expand Down
Loading