22import sys
33import atexit
44from contextlib import contextmanager
5- from distutils .version import LooseVersion
6- from qtpy import QtCore , QtWidgets # must import these first, to set up Qt properly
5+ from typing import Any , Dict , Optional , Type , Union
6+
7+ # Import Qt functionality through qtpy for abstraction
8+ from qtpy import QtCore , QtWidgets
9+
10+ # IPython imports
711import IPython
812from IPython .core .usage import default_banner
13+ from IPython import get_ipython
14+
15+ # ZMQ imports
916from zmq import ZMQError
1017from zmq .eventloop import ioloop
1118from zmq .eventloop .zmqstream import ZMQStream
12- from ..version import __version__
13-
1419
15- from IPython import get_ipython
20+ # Import version
21+ from ..version import __version__
1622
23+ # Import traitlets
1724from traitlets import TraitError
1825
26+ # Import ipykernel components
1927from ipykernel .connect import _find_connection_file as find_connection_file
2028from ipykernel .kernelbase import Kernel
2129from ipykernel .kernelapp import IPKernelApp
2230from ipykernel .iostream import OutStream
2331from ipykernel .inprocess .ipkernel import InProcessInteractiveShell
2432from ipykernel .connect import get_connection_file
2533
34+ # Import qtconsole components
2635from qtconsole .client import QtKernelClient
2736from qtconsole .manager import QtKernelManager
2837from qtconsole .inprocess import QtInProcessKernelManager
2938from qtconsole .rich_jupyter_widget import RichJupyterWidget as RichIPythonWidget
3039
3140
32-
33-
34- def in_process_console (console_class = RichIPythonWidget , ** kwargs ):
41+ def in_process_console (console_class : Type [RichIPythonWidget ] = RichIPythonWidget , ** kwargs : Any ) -> RichIPythonWidget :
3542 """Create a console widget, connected to an in-process Kernel
3643
37- This only works on IPython v 0.13 and above
38-
3944 Parameters:
40- console_class : The class of the console widget to create
41- kwargs : Extra variables to put into the namespace
45+ console_class: The class of the console widget to create
46+ kwargs: Extra variables to put into the namespace
4247 """
43-
4448 km = QtInProcessKernelManager ()
4549 km .start_kernel ()
4650
@@ -58,15 +62,13 @@ def in_process_console(console_class=RichIPythonWidget, **kwargs):
5862 return control
5963
6064
61- def connected_console (console_class = RichIPythonWidget , ** kwargs ) :
65+ def connected_console (console_class : Type [ RichIPythonWidget ] = RichIPythonWidget , ** kwargs : Any ) -> RichIPythonWidget :
6266 """Create a console widget, connected to another kernel running in
6367 the current process
6468
65- This only works on IPython v1.0 and above
66-
6769 Parameters:
68- console_class : The class of the console widget to create
69- kwargs : Extra variables to put into the namespace
70+ console_class: The class of the console widget to create
71+ kwargs: Extra variables to put into the namespace
7072 """
7173 shell = get_ipython ()
7274 if shell is None :
@@ -84,89 +86,27 @@ def connected_console(console_class=RichIPythonWidget, **kwargs):
8486
8587
8688class Terminal (RichIPythonWidget ):
87- def __init__ (self , ** kwargs ):
88- super (Terminal , self ).__init__ (** kwargs )
89-
89+ """IPython terminal widget for embedding in the application."""
90+
91+ def __init__ (self , ** kwargs : Any ) -> None :
92+ super ().__init__ (** kwargs )
9093 self .setAcceptDrops (True )
9194 self .shell = None
9295
9396 @property
94- def namespace (self ):
97+ def namespace (self ) -> Optional [Dict [str , Any ]]:
98+ """Return the namespace dictionary of the shell."""
9599 return self .shell .user_ns if self .shell is not None else None
96100
97- def update_namespace (self , kwargs ):
101+ def update_namespace (self , kwargs : Dict [str , Any ]) -> None :
102+ """Update the namespace with the given variables."""
98103 if self .shell is not None :
99104 self .shell .push (kwargs )
100105
101106
102-
103- # Works for IPython 0.12, 0.13
104- def default_kernel_app ():
105- """ Return a configured IPKernelApp """
106-
107- def event_loop (kernel ):
108- """ Non-blocking qt event loop."""
109- kernel .timer = QtCore .QTimer ()
110- kernel .timer .timeout .connect (kernel .do_one_iteration )
111- kernel .timer .start (1000 * kernel ._poll_interval )
112-
113- app = IPKernelApp .instance ()
114- try :
115- app .initialize (['python' , '--pylab=qt' ])
116- except ZMQError :
117- pass # already set up
118-
119- app .kernel .eventloop = event_loop
120-
121- try :
122- app .start ()
123- except RuntimeError : # already started
124- pass
125-
126- return app
127-
128-
129- def default_manager (kernel ):
130- """ Return a configured QtKernelManager
131-
132- Parameters:
133- kernel: An IPKernelApp instance
134- """
135- connection_file = find_connection_file (kernel .connection_file )
136- manager = QtKernelManager (connection_file = connection_file )
137- manager .load_connection_file ()
138- manager .start_channels ()
139- atexit .register (manager .cleanup_connection_file )
140- return manager
141-
142-
143- def _ipython_terminal_1 (** kwargs ):
144- """ Used for IPython v0.13, v0.12
145- """
146- kernel_app = default_kernel_app ()
147- manager = default_manager (kernel_app )
148-
149- try : # IPython v0.13
150- widget = Terminal (gui_completion = 'droplist' )
151- except TraitError : # IPython v0.12
152- widget = Terminal (gui_completion = True )
153- widget .kernel_manager = manager
154- widget .shell = kernel_app .shell
155-
156- # update namespace
157- widget .update_namespace (kwargs )
158-
159- # IPython v0.12 turns on MPL interactive. Turn it back off
160- import matplotlib
161- matplotlib .interactive (False )
162- return widget
163-
164-
165- # works on IPython v0.13, v0.14
166107@contextmanager
167- def redirect_output (session , pub_socket ):
168- """Prevent any of the widgets from permanently hijacking stdout or
169- stderr"""
108+ def redirect_output (session : Any , pub_socket : Any ) -> None :
109+ """Prevent any of the widgets from permanently hijacking stdout or stderr"""
170110 sys .stdout = OutStream (session , pub_socket , u'stdout' )
171111 sys .stderr = OutStream (session , pub_socket , u'stderr' )
172112 try :
@@ -176,144 +116,122 @@ def redirect_output(session, pub_socket):
176116 sys .stderr = sys .__stderr__
177117
178118
179- def non_blocking_eventloop (kernel ):
119+ def non_blocking_eventloop (kernel : Kernel ) -> None :
120+ """Set up a non-blocking event loop for the kernel."""
180121 kernel .timer = QtCore .QTimer ()
181122 kernel .timer .timeout .connect (kernel .do_one_iteration )
182123 kernel .timer .start (1000 * kernel ._poll_interval )
183124
184125
185126class EmbeddedQtKernel (Kernel ):
127+ """Kernel class that embeds the Qt event loop."""
186128
187- def __init__ (self , * args , ** kwargs ) :
188- super (EmbeddedQtKernel , self ).__init__ (* args , ** kwargs )
129+ def __init__ (self , * args : Any , ** kwargs : Any ) -> None :
130+ super ().__init__ (* args , ** kwargs )
189131 self .eventloop = non_blocking_eventloop
190132
191- def do_one_iteration (self ):
133+ def do_one_iteration (self ) -> None :
192134 with redirect_output (self .session , self .iopub_socket ):
193- super (EmbeddedQtKernel , self ).do_one_iteration ()
135+ super ().do_one_iteration ()
194136
195- def execute_request (self , stream , ident , parent ) :
137+ def execute_request (self , stream : Any , ident : Any , parent : Any ) -> None :
196138 with redirect_output (self .session , self .iopub_socket ):
197- super (EmbeddedQtKernel , self ).execute_request (
198- stream , ident , parent )
139+ super ().execute_request (stream , ident , parent )
199140
200141
201142class EmbeddedQtKernelApp (IPKernelApp ):
143+ """Application class for embedded Qt kernel."""
202144
203- def init_kernel (self ):
145+ def init_kernel (self ) -> None :
204146 shell_stream = ZMQStream (self .shell_socket )
205- kernel = EmbeddedQtKernel (config = self .config , session = self .session ,
206- shell_streams = [shell_stream ],
207- iopub_socket = self .iopub_socket ,
208- stdin_socket = self .stdin_socket ,
209- log = self .log ,
210- profile_dir = self .profile_dir ,
211- )
147+ kernel = EmbeddedQtKernel (
148+ config = self .config ,
149+ session = self .session ,
150+ shell_streams = [shell_stream ],
151+ iopub_socket = self .iopub_socket ,
152+ stdin_socket = self .stdin_socket ,
153+ log = self .log ,
154+ profile_dir = self .profile_dir ,
155+ )
212156 self .kernel = kernel
213157 kernel .record_ports (self .ports )
214158
215- def start (self ):
216- # handoff between IOLoop and QApplication event loops
159+ def start (self ) -> None :
160+ # Handoff between IOLoop and QApplication event loops
217161 loop = ioloop .IOLoop .instance ()
218- # We used to have a value of 0ms as the second argument
219- # (callback_time) in the following call, but this caused the
220- # application to hang on certain setups, so use 1ms instead.
162+ # Use 1ms callback time to prevent application hanging
221163 stopper = ioloop .PeriodicCallback (loop .stop , 1 , loop )
222164 self .timer = QtCore .QTimer ()
223165 self .timer .timeout .connect (loop .start )
224166 self .timer .start (100 )
225167 stopper .start ()
226- super (EmbeddedQtKernelApp , self ).start ()
168+ super ().start ()
227169
228170
229171class EmbeddedIPythonWidget (Terminal ):
172+ """Modern embedded IPython widget."""
173+
230174 gui_completion = 'droplist'
231175
232- def __init__ (self , ** kwargs ) :
233- super (EmbeddedIPythonWidget , self ).__init__ (** kwargs )
176+ def __init__ (self , ** kwargs : Any ) -> None :
177+ super ().__init__ (** kwargs )
234178 self ._init_kernel_app ()
235179 self ._init_kernel_manager ()
236180 self .update_namespace (kwargs )
237181
238- def _init_kernel_app (self ):
182+ def _init_kernel_app (self ) -> None :
239183 app = EmbeddedQtKernelApp .instance ()
240184 try :
241185 app .initialize ([])
242186 except ZMQError :
243- pass # already set up
187+ pass # Already set up
244188 try :
245189 app .start ()
246- except RuntimeError : # already started
190+ except RuntimeError : # Already started
247191 pass
248192 self .app = app
249193 self .shell = app .shell
250194
251- def _init_kernel_manager (self ):
195+ def _init_kernel_manager (self ) -> None :
252196 connection_file = find_connection_file (self .app .connection_file )
253197 manager = QtKernelManager (connection_file = connection_file )
254198 manager .load_connection_file ()
255199 manager .start_channels ()
256200 atexit .register (manager .cleanup_connection_file )
257201 self .kernel_manager = manager
258202
259- def update_namespace (self , ns ) :
203+ def update_namespace (self , ns : Dict [ str , Any ]) -> None :
260204 self .app .shell .user_ns .update (ns )
261205
262206
263- def _ipython_terminal_2 (** kwargs ):
264- """Used for IPython v0.13, v0.14"""
265- return EmbeddedIPythonWidget (** kwargs )
266-
207+ def ipython_terminal (banner : str = '' , ** kwargs : Any ) -> Terminal :
208+ """Return a qt widget which embeds an IPython interpreter.
267209
268- def _ipython_terminal_3 (** kwargs ):
269- """Used for IPython v1.0 and beyond
210+ Extra keywords will be added to the namespace of the shell.
270211
271212 Parameters:
272- kwargs: Keywords which are passed to Widget init,
273- and which are also passed to the current namespace
213+ banner: Text to display at the top of the terminal
214+ kwargs: Extra variables to be added to the namespace
215+
216+ Returns:
217+ Terminal widget with embedded IPython interpreter
274218 """
275- # see IPython/docs/examples/frontends/inprocess_qtconsole.p
219+ Terminal .banner = f"""flika version { __version__ }
220+
221+ { banner }
222+
223+ """
276224
225+ # In modern Python with recent IPython, we only need the in-process console
226+ # (version checking logic simplified for Python 3.13)
277227 shell = get_ipython ()
278228 if shell is None or isinstance (shell , InProcessInteractiveShell ):
279229 return in_process_console (console_class = Terminal , ** kwargs )
280230 return connected_console (console_class = Terminal , ** kwargs )
281231
282232
283- def ipython_terminal (banner = '' , ** kwargs ):
284- """ Return a qt widget which embed an IPython interpreter.
285-
286- Extra keywords will be added to the namespace of the shell
287-
288- Parameters:
289- kwargs (QWidget): Extra variables to be added to the namespace
290-
291- """
292- Terminal .banner = '''flika version {}
293-
294- {}
295-
296- ''' .format (__version__ , banner )
297-
298- from distutils .version import LooseVersion
299- import IPython
300- ver = LooseVersion (IPython .__version__ )
301- v1_0 = LooseVersion ('1.0' )
302- v0_12 = LooseVersion ('0.12' )
303- v0_13 = LooseVersion ('0.13' )
304-
305- if ver >= v1_0 :
306- return _ipython_terminal_3 (** kwargs )
307- if ver >= v0_13 :
308- return _ipython_terminal_2 (** kwargs )
309- if ver >= v0_12 :
310- return _ipython_terminal_1 (** kwargs )
311-
312- raise RuntimeError ("Terminal requires IPython >= 0.12" )
313-
314-
315233if __name__ == '__main__' :
316234 app = QtWidgets .QApplication ([])
317235 new_widget = ipython_terminal ()
318236 new_widget .show ()
319- app .exec_ ()
237+ app .exec () # Using exec() in Python 3 (not exec_() )
0 commit comments