@@ -15,7 +15,7 @@ import PrecompileTools: @compile_workload
1515# the MIME's an object supports at once.
1616function IJulia. display_dict (x:: Py )
1717 if hasproperty (x, :_repr_mimebundle_ ) && ! pyis (x. _repr_mimebundle_, pybuiltins. None)
18- pyconvert (Dict, x. _repr_mimebundle_ ())
18+ recursive_pyconvert ( x. _repr_mimebundle_ ())
1919 else
2020 IJulia. _display_dict (x)
2121 end
@@ -40,9 +40,20 @@ function recursive_pyconvert(x)
4040 return x
4141end
4242
43+ function recursive_pydict (x)
44+ dict_py = pydict (x)
45+ for (key, value) in x
46+ if value isa Dict
47+ dict_py[key] = recursive_pydict (value)
48+ end
49+ end
50+
51+ dict_py
52+ end
53+
4354function convert_buffers (buffers)
4455 if ! (buffers isa Py)
45- x
56+ buffers
4657 elseif pyis (buffers, pybuiltins. None)
4758 Vector{UInt8}[]
4859 else
@@ -65,14 +76,28 @@ function arrays_to_pylist!(dict::Dict)
6576 end
6677end
6778
68- function pycomm_init (self; target_name= " comm" , data= nothing , metadata= nothing , buffers= nothing , comm_id= IJulia. uuid4 ())
79+ function pycomm_init (self; target_name= " comm" , data= nothing , metadata= nothing , buffers= nothing ,
80+ comm_id= IJulia. uuid4 (), comm= nothing )
6981 try
70- target_name = pyconvert (String, target_name)
71- data = recursive_pyconvert (data)
72- metadata = recursive_pyconvert (metadata)
82+ if target_name isa Py
83+ target_name = pyconvert (String, target_name)
84+ end
85+ if data isa Py
86+ data = recursive_pyconvert (data)
87+ end
88+ if metadata isa Py
89+ metadata = recursive_pyconvert (metadata)
90+ end
91+ if comm_id isa Py
92+ comm_id = pyconvert (String, comm_id)
93+ end
7394 buffers = convert_buffers (buffers)
7495
75- self. _comm = IJulia. Comm (target_name, comm_id, true ; data, metadata, buffers)
96+ if isnothing (comm)
97+ comm = IJulia. Comm (target_name, comm_id, true ; data, metadata, buffers)
98+ end
99+ self. _comm = comm
100+ IJuliaPythonCallExt. pycomm_registry[comm_id] = self
76101 catch e
77102 @error " pycomm_init() failed" exception= (e, catch_backtrace ())
78103 end
@@ -91,11 +116,11 @@ function pycomm_on_msg(self, callback)
91116 arrays_to_pylist! (msg. content)
92117
93118 msg_dict = Dict (" idents" => msg. idents,
94- " header" => msg. header,
95- " content" => msg. content,
96- " parent_header" => msg. parent_header,
97- " metadata" => msg. metadata,
98- " buffers" => msg. buffers
119+ " header" => msg. header,
120+ " content" => msg. content,
121+ " parent_header" => msg. parent_header,
122+ " metadata" => msg. metadata,
123+ " buffers" => msg. buffers
99124 )
100125 callback (msg_dict)
101126 catch e
@@ -122,11 +147,17 @@ function pycomm_send(self; data=Dict(), metadata=Dict(), buffers=nothing)
122147 end
123148end
124149
150+ # This method is needed by ipywidgets. Unlike Julia, Python allows mixing
151+ # keyword and positional arguments so we need to have overloads for all the
152+ # calls with different numbers of positional arguments.
153+ pycomm_send (self, data; buffers= nothing ) = pycomm_send (self; data, buffers)
154+
125155function pycomm_close (self)
126156 try
127157 if ! isnothing (IJulia. _default_kernel)
128158 comm = IJulia. _default_kernel. comms[pyconvert (String, self. comm_id)]
129159 IJulia. CommManager. close_comm (comm)
160+ delete! (IJuliaPythonCallExt. pycomm_registry, comm. id)
130161 end
131162 catch e
132163 @error " pycomm_close() failed" exception= (e, catch_backtrace ())
@@ -143,6 +174,8 @@ pycommmanager_notimplemented(func_name::String) = pyfunc(Base.Fix1(py_notimpleme
143174PyComm:: Union{Py, Nothing} = nothing
144175PyCommManager:: Union{Py, Nothing} = nothing
145176
177+ const pycomm_registry = Dict {String, Py} ()
178+
146179function manager_register_target (self, target_name, callback)
147180 try
148181 if callback isa String
@@ -152,11 +185,33 @@ function manager_register_target(self, target_name, callback)
152185
153186 target_name = pyconvert (String, target_name)
154187 comm_sym = Symbol (target_name)
188+ self. on_open_callbacks[comm_sym] = callback
155189
156190 if @ccall (jl_generating_output ():: Cint ) == 0
157191 # Only create the method if we aren't precompiling
158- @eval function IJulia. CommManager. register_comm (comm:: IJulia.CommManager.Comm{$(QuoteNode(comm_sym))} , msg)
159- comm. on_msg = (msg) -> callback (comm, msg)
192+ @eval function IJulia. CommManager. register_comm (comm:: IJulia.CommManager.Comm{$(QuoteNode(comm_sym))} , msg:: IJulia.Msg )
193+ msg_dict = Dict (" idents" => msg. idents,
194+ " header" => msg. header,
195+ " content" => msg. content,
196+ " parent_header" => msg. parent_header,
197+ " metadata" => msg. metadata,
198+ " buffers" => msg. buffers)
199+ # We need to convert the Msg to a Python dict because that's
200+ # what the callbacks expect, and they may call `.get()` etc on
201+ # the dict.
202+ msg_dict_py = recursive_pydict (msg_dict)
203+ callback = IJuliaPythonCallExt. PyCommManager. on_open_callbacks[$ (QuoteNode (comm_sym))]
204+
205+ # Register a PyComm corresponding to the new `comm`
206+ if ! haskey (IJuliaPythonCallExt. pycomm_registry, comm. id)
207+ PyComm (; target_name= $ (target_name), comm_id= comm. id, comm)
208+ end
209+
210+ try
211+ callback (IJuliaPythonCallExt. pycomm_registry[comm. id], msg_dict_py)
212+ catch ex
213+ @error " PyCommManager.register_target() callback failed" exception= (ex, catch_backtrace ())
214+ end
160215 end
161216 end
162217 catch e
191246
192247function create_pycommmanager ()
193248 pytype (" PyCommManager" , (), [
249+ " on_open_callbacks" => Dict {Symbol, Py} (),
194250 pyfunc (manager_register_target; name= " register_target" ),
195251 pycommmanager_notimplemented (" unregister_target" ),
196252 pycommmanager_notimplemented (" register_comm" ),
@@ -225,7 +281,7 @@ function IJulia.init_ipywidgets()
225281 nothing
226282end
227283
228- function IJulia. init_matplotlib (backend:: String = " widget " )
284+ function IJulia. init_matplotlib (backend:: String = " module://ipympl.backend_nbagg " )
229285 IJulia. init_ipywidgets ()
230286
231287 # Make sure it's in interactive mode and it's using the backend
@@ -256,6 +312,8 @@ precompile(pycomm_close, (Py,))
256312 create_pycomm ()
257313 create_pycommmanager ()
258314
315+ recursive_pydict (Dict {String, Any} (" foo" => 2 , " bar" => Dict (" baz" => " quux" )))
316+
259317 # If ipywidgets is installed in the environment try to precompile its
260318 # initializer. This is useful because the `ipywigets.register_comm_target()`
261319 # line is pretty heavy.
@@ -269,6 +327,7 @@ precompile(pycomm_close, (Py,))
269327 finally
270328 global PyComm = nothing
271329 global PyCommManager = nothing
330+ empty! (pycomm_registry)
272331 end
273332end
274333
0 commit comments