Skip to content

Commit a1b72f7

Browse files
authored
check for null in multimedia show functions (#526)
* check for null in multimedia show functions * add tests for show/showable on null --------- Co-authored-by: Christopher Doris <github.com/cjdoris>
1 parent 662da02 commit a1b72f7

File tree

3 files changed

+56
-37
lines changed

3 files changed

+56
-37
lines changed

docs/src/releasenotes.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* `numpy.bool_` can now be converted to `Bool` and other number types.
66
* `datetime.timedelta` can now be converted to `Dates.Nanosecond`, `Microsecond`, `Millisecond` and `Second`. This behaviour was already documented.
77
* In JuliaCall, the Julia runtime is now properly terminated when Python exits. This means all finalizers should always run.
8+
* NULL Python objects (such as from `pynew()`) can be safely displayed in multimedia contexts (VSCode/Pluto/etc.)
89

910
## 0.9.20 (2024-05-01)
1011
* The IPython extension is now automatically loaded upon import if IPython is detected.

src/Compat/multimedia.jl

+8-4
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,20 @@ end
99

1010
function pyshow(io::IO, mime::MIME, x)
1111
x_ = Py(x)
12-
for rule in PYSHOW_RULES
13-
rule(io, string(mime), x_) && return
12+
if !pyisnull(x_)
13+
for rule in PYSHOW_RULES
14+
rule(io, string(mime), x_) && return
15+
end
1416
end
1517
throw(MethodError(show, (io, mime, x_)))
1618
end
1719

1820
function pyshowable(mime::MIME, x)
1921
x_ = Py(x)
20-
for rule in PYSHOW_RULES
21-
rule(devnull, string(mime), x_) && return true
22+
if !pyisnull(x_)
23+
for rule in PYSHOW_RULES
24+
rule(devnull, string(mime), x_) && return true
25+
end
2226
end
2327
return false
2428
end

test/Core.jl

+47-33
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@
130130
@test pylen(x) == 0
131131
end
132132
@testset "pydir" begin
133-
x = pytype("Foo", (), ["foo"=>1, "bar"=>2])()
133+
x = pytype("Foo", (), ["foo" => 1, "bar" => 2])()
134134
d = pydir(x)
135135
@test pycontains(d, "__class__")
136136
@test pycontains(d, "foo")
@@ -256,7 +256,7 @@ end
256256
end
257257
@testset "pyinv" begin
258258
for n in -2:2
259-
@test pyeq(Bool, pyinv(pyint(n)), pyint(-n-1))
259+
@test pyeq(Bool, pyinv(pyint(n)), pyint(-n - 1))
260260
end
261261
end
262262
@testset "pyindex" begin
@@ -267,21 +267,21 @@ end
267267
@testset "pyadd" begin
268268
for x in -2:2
269269
for y in -2:2
270-
@test pyeq(Bool, pyadd(pyint(x), pyint(y)), pyint(x+y))
270+
@test pyeq(Bool, pyadd(pyint(x), pyint(y)), pyint(x + y))
271271
end
272272
end
273273
end
274274
@testset "pysub" begin
275275
for x in -2:2
276276
for y in -2:2
277-
@test pyeq(Bool, pysub(pyint(x), pyint(y)), pyint(x-y))
277+
@test pyeq(Bool, pysub(pyint(x), pyint(y)), pyint(x - y))
278278
end
279279
end
280280
end
281281
@testset "pymul" begin
282282
for x in -2:2
283283
for y in -2:2
284-
@test pyeq(Bool, pymul(pyint(x), pyint(y)), pyint(x*y))
284+
@test pyeq(Bool, pymul(pyint(x), pyint(y)), pyint(x * y))
285285
end
286286
end
287287
end
@@ -299,7 +299,7 @@ end
299299
if y == 0
300300
@test_throws PyException pytruediv(pyint(x), pyint(y))
301301
else
302-
@test pyeq(Bool, pytruediv(pyint(x), pyint(y)), pyfloat(x/y))
302+
@test pyeq(Bool, pytruediv(pyint(x), pyint(y)), pyfloat(x / y))
303303
end
304304
end
305305
end
@@ -409,15 +409,15 @@ end
409409
@test pyeq(Bool, sys.__name__, "sys")
410410
@test pyeq(Bool, os.__name__, "os")
411411
sysos = pyimport("sys", "os")
412-
@test sysos isa Tuple{Py, Py}
412+
@test sysos isa Tuple{Py,Py}
413413
@test pyis(sysos[1], sys)
414414
@test pyis(sysos[2], os)
415415
ver = pyimport("sys" => "version")
416416
@test pyis(ver, sys.version)
417417
path = pyimport("sys" => "path")
418418
@test pyis(path, sys.path)
419419
verpath = pyimport("sys" => ("version", "path"))
420-
@test verpath isa Tuple{Py, Py}
420+
@test verpath isa Tuple{Py,Py}
421421
@test pyis(verpath[1], ver)
422422
@test pyis(verpath[2], path)
423423
end
@@ -438,12 +438,12 @@ end
438438
end
439439

440440
@testitem "bytes" begin
441-
@test pyisinstance(pybytes(UInt8[1,2,3]), pybuiltins.bytes)
442-
@test pyeq(Bool, pybytes(pylist([1,2,3])), pybytes(UInt8[1,2,3]))
441+
@test pyisinstance(pybytes(UInt8[1, 2, 3]), pybuiltins.bytes)
442+
@test pyeq(Bool, pybytes(pylist([1, 2, 3])), pybytes(UInt8[1, 2, 3]))
443443
@test pyeq(Bool, pybytes(b"foo"), pystr("foo").encode("ascii"))
444444
@test pyeq(Bool, pybytes(codeunits(SubString("foobarbaz", 4:6))), pystr("bar").encode("ascii"))
445-
@test pybytes(Vector, pylist([1,2,3])) == UInt8[1,2,3]
446-
@test pybytes(Vector{UInt8}, pylist([1,2,3])) == UInt8[1,2,3]
445+
@test pybytes(Vector, pylist([1, 2, 3])) == UInt8[1, 2, 3]
446+
@test pybytes(Vector{UInt8}, pylist([1, 2, 3])) == UInt8[1, 2, 3]
447447
@test pybytes(Base.CodeUnits, pystr("foo").encode("ascii")) == b"foo"
448448
@test pybytes(Base.CodeUnits{UInt8,String}, pystr("bar").encode("ascii")) == b"bar"
449449
end
@@ -452,36 +452,36 @@ end
452452
z = pytuple()
453453
@test pyisinstance(z, pybuiltins.tuple)
454454
@test pylen(z) == 0
455-
x = pytuple((1,2,3))
455+
x = pytuple((1, 2, 3))
456456
@test pyisinstance(x, pybuiltins.tuple)
457457
@test pylen(x) == 3
458458
@test pyeq(Bool, pygetitem(x, 0), 1)
459459
@test pyeq(Bool, pygetitem(x, 1), 2)
460460
@test pyeq(Bool, pygetitem(x, 2), 3)
461-
@test pyeq(Bool, pytuple([1,2,3]), x)
462-
@test pyeq(Bool, pytuple(i+1 for i in 0:10 if i<3), x)
463-
@test pyeq(Bool, pytuple(pytuple((1,2,3))), x)
464-
@test pyeq(Bool, pytuple(pylist([1,2,3])), x)
461+
@test pyeq(Bool, pytuple([1, 2, 3]), x)
462+
@test pyeq(Bool, pytuple(i + 1 for i in 0:10 if i < 3), x)
463+
@test pyeq(Bool, pytuple(pytuple((1, 2, 3))), x)
464+
@test pyeq(Bool, pytuple(pylist([1, 2, 3])), x)
465465
end
466466

467467
@testitem "list" begin
468468
z = pylist()
469469
@test pyisinstance(z, pybuiltins.list)
470470
@test pylen(z) == 0
471-
x = pylist((1,2,3))
471+
x = pylist((1, 2, 3))
472472
@test pyisinstance(x, pybuiltins.list)
473473
@test pylen(x) == 3
474474
@test pyeq(Bool, pygetitem(x, 0), 1)
475475
@test pyeq(Bool, pygetitem(x, 1), 2)
476476
@test pyeq(Bool, pygetitem(x, 2), 3)
477-
@test pyeq(Bool, pylist([1,2,3]), x)
478-
@test pyeq(Bool, pylist(i+1 for i in 0:10 if i<3), x)
479-
@test pyeq(Bool, pylist(pylist((1,2,3))), x)
480-
@test pyeq(Bool, pylist(pytuple([1,2,3])), x)
481-
@test pyeq(Bool, pycollist([1,2,3]), pylist([1,2,3]))
482-
@test pyeq(Bool, pycollist([1 2; 3 4]), pylist((pylist([1,3]), pylist([2,4]))))
483-
@test pyeq(Bool, pyrowlist([1,2,3]), pylist([1,2,3]))
484-
@test pyeq(Bool, pyrowlist([1 2; 3 4]), pylist((pylist([1,2]), pylist([3,4]))))
477+
@test pyeq(Bool, pylist([1, 2, 3]), x)
478+
@test pyeq(Bool, pylist(i + 1 for i in 0:10 if i < 3), x)
479+
@test pyeq(Bool, pylist(pylist((1, 2, 3))), x)
480+
@test pyeq(Bool, pylist(pytuple([1, 2, 3])), x)
481+
@test pyeq(Bool, pycollist([1, 2, 3]), pylist([1, 2, 3]))
482+
@test pyeq(Bool, pycollist([1 2; 3 4]), pylist((pylist([1, 3]), pylist([2, 4]))))
483+
@test pyeq(Bool, pyrowlist([1, 2, 3]), pylist([1, 2, 3]))
484+
@test pyeq(Bool, pyrowlist([1 2; 3 4]), pylist((pylist([1, 2]), pylist([3, 4]))))
485485
end
486486

487487
@testitem "dict" begin
@@ -493,9 +493,9 @@ end
493493
@test pylen(x) == 2
494494
@test pyeq(Bool, pygetitem(x, "foo"), 1)
495495
@test pyeq(Bool, pygetitem(x, "bar"), 2)
496-
@test pyeq(Bool, pydict(["foo"=>1, "bar"=>2]), x)
497-
@test pyeq(Bool, pydict([("foo"=>1), ("bar"=>2)]), x)
498-
@test pyeq(Bool, pydict(Dict("foo"=>1, "bar"=>2)), x)
496+
@test pyeq(Bool, pydict(["foo" => 1, "bar" => 2]), x)
497+
@test pyeq(Bool, pydict([("foo" => 1), ("bar" => 2)]), x)
498+
@test pyeq(Bool, pydict(Dict("foo" => 1, "bar" => 2)), x)
499499
@test pyeq(Bool, pydict((foo=1, bar=2)), x)
500500
@test pyeq(Bool, pydict(x), x)
501501
end
@@ -508,7 +508,7 @@ end
508508
@test pyis(pybool(-1.2), pybuiltins.True)
509509
@test pyis(pybool(pybuiltins.None), pybuiltins.False)
510510
@test pyis(pybool(pylist()), pybuiltins.False)
511-
@test pyis(pybool(pylist([1,2,3])), pybuiltins.True)
511+
@test pyis(pybool(pylist([1, 2, 3])), pybuiltins.True)
512512
end
513513

514514
@testitem "int" begin
@@ -546,7 +546,7 @@ end
546546
y = pyfloat(x)
547547
@test pyisinstance(y, pybuiltins.float)
548548
@test pyeq(Bool, y, pytruediv(1, 4))
549-
x = 1//4
549+
x = 1 // 4
550550
y = pyfloat(x)
551551
@test pyisinstance(y, pybuiltins.float)
552552
@test pyeq(Bool, y, pyfloat(float(x)))
@@ -584,7 +584,7 @@ end
584584
@test pyisinstance(yf, pybuiltins.frozenset)
585585
@test pylen(yf) == 0
586586
@test pyeq(Bool, y, yf)
587-
x = [1,2,3,2,1]
587+
x = [1, 2, 3, 2, 1]
588588
y = pyset(x)
589589
yf = pyfrozenset(x)
590590
@test pyisinstance(y, pybuiltins.set)
@@ -649,7 +649,7 @@ end
649649
x = pytype(pybuiltins.type)
650650
@test pyisinstance(x, pybuiltins.type)
651651
@test pyis(x, pybuiltins.type)
652-
x = pytype("Foo", (), ["foo"=>1, "bar"=>2])
652+
x = pytype("Foo", (), ["foo" => 1, "bar" => 2])
653653
@test pyisinstance(x, pybuiltins.type)
654654
@test pyeq(Bool, x.__name__, "Foo")
655655
@test pyeq(Bool, x.foo, 1)
@@ -782,6 +782,20 @@ end
782782
# but now tries to do `1 + [1, 2]` which properly fails
783783
@test_throws PyException [1 2; 3 4] .+ pylist([1, 2])
784784
end
785+
@testset "showable" begin
786+
@test showable(MIME("text/plain"), Py(nothing))
787+
@test showable(MIME("text/plain"), Py(12))
788+
# https://github.com/JuliaPy/PythonCall.jl/issues/522
789+
@test showable(MIME("text/plain"), PythonCall.pynew())
790+
@test !showable(MIME("text/html"), PythonCall.pynew())
791+
end
792+
@testset "show" begin
793+
@test sprint(show, MIME("text/plain"), Py(nothing)) == "Python: None"
794+
@test sprint(show, MIME("text/plain"), Py(12)) == "Python: 12"
795+
# https://github.com/JuliaPy/PythonCall.jl/issues/522
796+
@test sprint(show, MIME("text/plain"), PythonCall.pynew()) == "Python: NULL"
797+
@test_throws MethodError sprint(show, MIME("text/html"), PythonCall.pynew())
798+
end
785799
end
786800

787801
@testitem "pywith" begin

0 commit comments

Comments
 (0)