euporie 2.8.14__tar.gz → 2.8.15__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. {euporie-2.8.14 → euporie-2.8.15}/PKG-INFO +2 -1
  2. {euporie-2.8.14 → euporie-2.8.15}/euporie/console/tabs/console.py +4 -0
  3. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/__init__.py +1 -1
  4. euporie-2.8.15/euporie/core/cache.py +36 -0
  5. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/config.py +1 -1
  6. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/datum.py +1 -1
  7. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/ft/html.py +1 -1
  8. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/kernel/__init__.py +7 -3
  9. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/kernel/base.py +13 -0
  10. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/kernel/local.py +7 -2
  11. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/layout/containers.py +5 -0
  12. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/tabs/kernel.py +6 -1
  13. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/widgets/cell_outputs.py +3 -3
  14. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/widgets/forms.py +11 -5
  15. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/widgets/menu.py +4 -3
  16. {euporie-2.8.14 → euporie-2.8.15}/euporie/preview/tabs/notebook.py +15 -4
  17. {euporie-2.8.14 → euporie-2.8.15}/pyproject.toml +3 -2
  18. {euporie-2.8.14 → euporie-2.8.15}/.gitignore +0 -0
  19. {euporie-2.8.14 → euporie-2.8.15}/LICENSE +0 -0
  20. {euporie-2.8.14 → euporie-2.8.15}/README.rst +0 -0
  21. {euporie-2.8.14 → euporie-2.8.15}/euporie/console/__init__.py +0 -0
  22. {euporie-2.8.14 → euporie-2.8.15}/euporie/console/__main__.py +0 -0
  23. {euporie-2.8.14 → euporie-2.8.15}/euporie/console/_commands.py +0 -0
  24. {euporie-2.8.14 → euporie-2.8.15}/euporie/console/_settings.py +0 -0
  25. {euporie-2.8.14 → euporie-2.8.15}/euporie/console/app.py +0 -0
  26. {euporie-2.8.14 → euporie-2.8.15}/euporie/console/py.typed +0 -0
  27. {euporie-2.8.14 → euporie-2.8.15}/euporie/console/tabs/__init__.py +0 -0
  28. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/__main__.py +0 -0
  29. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/_settings.py +0 -0
  30. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/app/__init__.py +0 -0
  31. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/app/_commands.py +0 -0
  32. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/app/_settings.py +0 -0
  33. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/app/app.py +0 -0
  34. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/app/base.py +0 -0
  35. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/app/current.py +0 -0
  36. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/app/cursor.py +0 -0
  37. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/app/dummy.py +0 -0
  38. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/app/launch.py +0 -0
  39. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/bars/__init__.py +0 -0
  40. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/bars/command.py +0 -0
  41. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/bars/menu.py +0 -0
  42. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/bars/search.py +0 -0
  43. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/bars/status.py +0 -0
  44. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/border.py +0 -0
  45. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/clipboard.py +0 -0
  46. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/comm/__init__.py +0 -0
  47. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/comm/base.py +0 -0
  48. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/comm/ipywidgets.py +0 -0
  49. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/comm/registry.py +0 -0
  50. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/commands.py +0 -0
  51. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/completion.py +0 -0
  52. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/__init__.py +0 -0
  53. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/formats/__init__.py +0 -0
  54. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/formats/ansi.py +0 -0
  55. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/formats/base64.py +0 -0
  56. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/formats/common.py +0 -0
  57. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/formats/ft.py +0 -0
  58. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/formats/html.py +0 -0
  59. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/formats/jpeg.py +0 -0
  60. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/formats/markdown.py +0 -0
  61. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/formats/pdf.py +0 -0
  62. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/formats/pil.py +0 -0
  63. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/formats/png.py +0 -0
  64. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/formats/rich.py +0 -0
  65. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/formats/sixel.py +0 -0
  66. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/formats/svg.py +0 -0
  67. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/mime.py +0 -0
  68. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/registry.py +0 -0
  69. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/convert/utils.py +0 -0
  70. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/data_structures.py +0 -0
  71. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/diagnostics.py +0 -0
  72. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/filters.py +0 -0
  73. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/format.py +0 -0
  74. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/ft/__init__.py +0 -0
  75. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/ft/ansi.py +0 -0
  76. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/ft/table.py +0 -0
  77. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/ft/utils.py +0 -0
  78. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/graphics.py +0 -0
  79. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/history.py +0 -0
  80. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/inspection.py +0 -0
  81. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/io.py +0 -0
  82. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/kernel/_settings.py +0 -0
  83. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/kernel/jupyter.py +0 -0
  84. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/kernel/jupyter_manager.py +0 -0
  85. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/key_binding/__init__.py +0 -0
  86. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/key_binding/bindings/__init__.py +0 -0
  87. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/key_binding/bindings/basic.py +0 -0
  88. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/key_binding/bindings/completion.py +0 -0
  89. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/key_binding/bindings/micro.py +0 -0
  90. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/key_binding/bindings/mouse.py +0 -0
  91. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/key_binding/bindings/page_navigation.py +0 -0
  92. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/key_binding/bindings/terminal.py +0 -0
  93. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/key_binding/bindings/vi.py +0 -0
  94. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/key_binding/key_processor.py +0 -0
  95. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/key_binding/micro_state.py +0 -0
  96. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/key_binding/registry.py +0 -0
  97. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/key_binding/utils.py +0 -0
  98. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/key_binding/vi_state.py +0 -0
  99. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/keys.py +0 -0
  100. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/layout/__init__.py +0 -0
  101. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/layout/cache.py +0 -0
  102. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/layout/controls.py +0 -0
  103. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/layout/decor.py +0 -0
  104. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/layout/mouse.py +0 -0
  105. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/layout/print.py +0 -0
  106. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/layout/screen.py +0 -0
  107. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/layout/scroll.py +0 -0
  108. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/lexers.py +0 -0
  109. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/log.py +0 -0
  110. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/lsp.py +0 -0
  111. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/margins.py +0 -0
  112. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/path.py +0 -0
  113. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/processors.py +0 -0
  114. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/py.typed +0 -0
  115. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/pygments.py +0 -0
  116. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/reference.py +0 -0
  117. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/renderer.py +0 -0
  118. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/style.py +0 -0
  119. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/suggest.py +0 -0
  120. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/tabs/__init__.py +0 -0
  121. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/tabs/_commands.py +0 -0
  122. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/tabs/_settings.py +0 -0
  123. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/tabs/base.py +0 -0
  124. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/tabs/notebook.py +0 -0
  125. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/utils.py +0 -0
  126. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/validation.py +0 -0
  127. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/widgets/__init__.py +0 -0
  128. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/widgets/_settings.py +0 -0
  129. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/widgets/cell.py +0 -0
  130. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/widgets/decor.py +0 -0
  131. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/widgets/dialog.py +0 -0
  132. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/widgets/display.py +0 -0
  133. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/widgets/file_browser.py +0 -0
  134. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/widgets/formatted_text_area.py +0 -0
  135. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/widgets/inputs.py +0 -0
  136. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/widgets/layout.py +0 -0
  137. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/widgets/logo.py +0 -0
  138. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/widgets/pager.py +0 -0
  139. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/widgets/palette.py +0 -0
  140. {euporie-2.8.14 → euporie-2.8.15}/euporie/core/widgets/tree.py +0 -0
  141. {euporie-2.8.14 → euporie-2.8.15}/euporie/data/desktop/euporie-console.desktop +0 -0
  142. {euporie-2.8.14 → euporie-2.8.15}/euporie/data/desktop/euporie-notebook.desktop +0 -0
  143. {euporie-2.8.14 → euporie-2.8.15}/euporie/hub/__init__.py +0 -0
  144. {euporie-2.8.14 → euporie-2.8.15}/euporie/hub/__main__.py +0 -0
  145. {euporie-2.8.14 → euporie-2.8.15}/euporie/hub/app.py +0 -0
  146. {euporie-2.8.14 → euporie-2.8.15}/euporie/hub/py.typed +0 -0
  147. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/__init__.py +0 -0
  148. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/__main__.py +0 -0
  149. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/_commands.py +0 -0
  150. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/_settings.py +0 -0
  151. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/app.py +0 -0
  152. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/current.py +0 -0
  153. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/enums.py +0 -0
  154. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/filters.py +0 -0
  155. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/py.typed +0 -0
  156. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/tabs/__init__.py +0 -0
  157. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/tabs/_commands.py +0 -0
  158. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/tabs/_settings.py +0 -0
  159. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/tabs/display.py +0 -0
  160. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/tabs/edit.py +0 -0
  161. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/tabs/json.py +0 -0
  162. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/tabs/log.py +0 -0
  163. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/tabs/notebook.py +0 -0
  164. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/widgets/__init__.py +0 -0
  165. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/widgets/_commands.py +0 -0
  166. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/widgets/_settings.py +0 -0
  167. {euporie-2.8.14 → euporie-2.8.15}/euporie/notebook/widgets/side_bar.py +0 -0
  168. {euporie-2.8.14 → euporie-2.8.15}/euporie/preview/__init__.py +0 -0
  169. {euporie-2.8.14 → euporie-2.8.15}/euporie/preview/__main__.py +0 -0
  170. {euporie-2.8.14 → euporie-2.8.15}/euporie/preview/_settings.py +0 -0
  171. {euporie-2.8.14 → euporie-2.8.15}/euporie/preview/app.py +0 -0
  172. {euporie-2.8.14 → euporie-2.8.15}/euporie/preview/py.typed +0 -0
  173. {euporie-2.8.14 → euporie-2.8.15}/euporie/preview/tabs/__init__.py +0 -0
  174. {euporie-2.8.14 → euporie-2.8.15}/euporie/web/__init__.py +0 -0
  175. {euporie-2.8.14 → euporie-2.8.15}/euporie/web/tabs/__init__.py +0 -0
  176. {euporie-2.8.14 → euporie-2.8.15}/euporie/web/tabs/web.py +0 -0
  177. {euporie-2.8.14 → euporie-2.8.15}/euporie/web/widgets/__init__.py +0 -0
  178. {euporie-2.8.14 → euporie-2.8.15}/euporie/web/widgets/webview.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: euporie
3
- Version: 2.8.14
3
+ Version: 2.8.15
4
4
  Summary: Euporie is a suite of terminal applications for interacting with Jupyter kernels
5
5
  Project-URL: Documentation, https://euporie.readthedocs.io/en/latest
6
6
  Project-URL: Issues, https://github.com/joouha/euporie/issues
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
21
22
  Classifier: Programming Language :: Python :: Implementation :: CPython
22
23
  Classifier: Programming Language :: Python :: Implementation :: PyPy
23
24
  Classifier: Topic :: Scientific/Engineering
@@ -78,6 +78,10 @@ class Console(KernelTab):
78
78
 
79
79
  """
80
80
 
81
+ live_output: CellOutputArea
82
+ input_box: KernelInput
83
+ stdin_box: StdInput
84
+
81
85
  def __init__(
82
86
  self,
83
87
  app: BaseApp,
@@ -1,7 +1,7 @@
1
1
  """This package defines the euporie application and its components."""
2
2
 
3
3
  __app_name__ = "euporie"
4
- __version__ = "2.8.14"
4
+ __version__ = "2.8.15"
5
5
  __logo__ = "⚈"
6
6
  __strapline__ = "Jupyter in the terminal"
7
7
  __author__ = "Josiah Outram Halstead"
@@ -0,0 +1,36 @@
1
+ """Updated version of the prompt_toolkit caches."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import threading
6
+ from typing import TYPE_CHECKING
7
+
8
+ from prompt_toolkit.cache import _T, _U
9
+ from prompt_toolkit.cache import SimpleCache as PtkSimpleCache
10
+
11
+ if TYPE_CHECKING:
12
+ from typing import Callable
13
+
14
+
15
+ __all__ = [
16
+ "SimpleCache",
17
+ ]
18
+
19
+
20
+ class SimpleCache(PtkSimpleCache[_T, _U]):
21
+ """Thread safe version of :py:`SimpleCache`."""
22
+
23
+ def __init__(self, maxsize: int = 8) -> None:
24
+ """Create lock at init."""
25
+ super().__init__(maxsize)
26
+ self._lock = threading.Lock()
27
+
28
+ def get(self, key: _T, getter_func: Callable[[], _U]) -> _U:
29
+ """Access cache with thread safety."""
30
+ with self._lock:
31
+ return super().get(key, getter_func)
32
+
33
+ def clear(self) -> None:
34
+ """Clear cache."""
35
+ with self._lock:
36
+ super().clear()
@@ -502,7 +502,7 @@ class Config:
502
502
  def _load_args(self) -> dict[str, Any]:
503
503
  """Attempt to load configuration settings from commandline flags."""
504
504
  # Parse known arguments
505
- namespace, remainder = self._load_parser().parse_known_intermixed_args()
505
+ namespace, _remainder = self._load_parser().parse_known_intermixed_args()
506
506
  # Validate arguments
507
507
  return vars(namespace)
508
508
 
@@ -159,7 +159,7 @@ class Datum(Generic[T], metaclass=_MetaDatum):
159
159
  asyncio.Event,
160
160
  ] = {}
161
161
  self._finalizer: finalize = finalize(self, self._cleanup_datum_sizes, self.hash)
162
- self._finalizer.atexit = False
162
+ self._finalizer.atexit = False # type: ignore [misc]
163
163
 
164
164
  def __repr__(self) -> str:
165
165
  """Return a string representation of object."""
@@ -2851,7 +2851,7 @@ _BROWSER_CSS: CssSelectors = {
2851
2851
  CssSelector(item="*"),
2852
2852
  ),
2853
2853
  ): {
2854
- "pt_class": "markdown,code,block",
2854
+ "_pt_class": "markdown,code,block",
2855
2855
  },
2856
2856
  (
2857
2857
  (
@@ -12,11 +12,15 @@ if TYPE_CHECKING:
12
12
  from euporie.core.kernel.base import BaseKernel, KernelInfo, MsgCallbacks
13
13
  from euporie.core.tabs.kernel import KernelTab
14
14
 
15
- KERNEL_REGISTRY = {
16
- "local": "euporie.core.kernel.local:LocalPythonKernel",
17
- }
15
+ KERNEL_REGISTRY = {}
18
16
  if find_spec("jupyter_client"):
19
17
  KERNEL_REGISTRY["jupyter"] = "euporie.core.kernel.jupyter:JupyterKernel"
18
+ KERNEL_REGISTRY.update(
19
+ {
20
+ "local": "euporie.core.kernel.local:LocalPythonKernel",
21
+ "none": "euporie.core.kernel.base:NoKernel",
22
+ }
23
+ )
20
24
 
21
25
 
22
26
  def list_kernels() -> list[KernelInfo]:
@@ -499,6 +499,19 @@ class BaseKernel(ABC):
499
499
  class NoKernel(BaseKernel):
500
500
  """A `None` kernel."""
501
501
 
502
+ @classmethod
503
+ def variants(cls) -> list[KernelInfo]:
504
+ """Return available kernel specifications."""
505
+ return [
506
+ KernelInfo(
507
+ name="none",
508
+ display_name="No Kernel",
509
+ factory=cls,
510
+ kind="new",
511
+ type=cls,
512
+ )
513
+ ]
514
+
502
515
  @property
503
516
  def spec(self) -> dict[str, str]:
504
517
  """The kernelspec metadata for the current kernel instance."""
@@ -24,7 +24,7 @@ from euporie.core.app.current import get_app
24
24
  from euporie.core.kernel.base import BaseKernel, KernelInfo, MsgCallbacks
25
25
 
26
26
  if TYPE_CHECKING:
27
- from typing import Any, Callable, Unpack
27
+ from typing import Any, Callable, TextIO, Unpack
28
28
 
29
29
  from euporie.core.tabs.kernel import KernelTab
30
30
 
@@ -631,7 +631,12 @@ class InputBuiltin(BaseHook):
631
631
  self._is_password = is_password
632
632
  update_wrapper(self, input)
633
633
 
634
- def __call__(self, prompt: str = "", stream: Any = None) -> str:
634
+ def __call__(
635
+ self,
636
+ prompt: str = "",
637
+ stream: TextIO | None = None,
638
+ echo_char: str | None = None,
639
+ ) -> str:
635
640
  """Get input from user via callback."""
636
641
  if (callbacks := self.callbacks) and (get_input := callbacks.get("get_input")):
637
642
  # Clear any previous input
@@ -37,6 +37,7 @@ from prompt_toolkit.layout.utils import explode_text_fragments
37
37
  from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
38
38
  from prompt_toolkit.utils import get_cwidth, take_using_weights, to_str
39
39
 
40
+ from euporie.core.cache import SimpleCache
40
41
  from euporie.core.data_structures import DiInt
41
42
  from euporie.core.layout.controls import DummyControl
42
43
  from euporie.core.layout.screen import BoundedWritePosition
@@ -557,6 +558,10 @@ class Window(ptk_containers.Window):
557
558
  super().__init__(*args, **kwargs)
558
559
  if isinstance(self.content, PtkDummyControl):
559
560
  self.content = DummyControl()
561
+ # Use thread-safe cache for margin widths
562
+ self._margin_width_cache: SimpleCache[tuple[Margin, int], int] = SimpleCache(
563
+ maxsize=1
564
+ )
560
565
 
561
566
  def write_to_screen(
562
567
  self,
@@ -387,7 +387,12 @@ class KernelTab(Tab, metaclass=ABCMeta):
387
387
  return
388
388
 
389
389
  # Prompt user to select a kernel
390
- self.app.dialogs["change-kernel"].show(tab=self, message=msg)
390
+ if dialog := self.app.dialogs.get("change-kernel"):
391
+ dialog.show(tab=self, message=msg)
392
+ return
393
+
394
+ if msg:
395
+ log.warning(msg)
391
396
 
392
397
  def switch_kernel(self, factory: KernelFactory) -> None:
393
398
  """Shut down the current kernel and change to another."""
@@ -525,9 +525,9 @@ class CellOutputArea:
525
525
 
526
526
  def scroll_right(self) -> None:
527
527
  """Scroll the outputs right."""
528
- max_width = (
529
- max(cell_output.element.width or 0 for cell_output in self.rendered_outputs)
530
- or None
528
+ max_width = max(
529
+ (cell_output.element.width or 0 for cell_output in self.rendered_outputs),
530
+ default=None,
531
531
  )
532
532
  for cell_output in self.rendered_outputs:
533
533
  cell_output.scroll_right(max_width)
@@ -1148,9 +1148,12 @@ class SelectableWidget(metaclass=ABCMeta):
1148
1148
  return next((x for x in self.indices), None)
1149
1149
 
1150
1150
  @index.setter
1151
- def index(self, value: int) -> None:
1151
+ def index(self, value: int | None) -> None:
1152
1152
  """Set the selected indices to a single value."""
1153
- self.indices = [value]
1153
+ if value is None:
1154
+ self.indices = []
1155
+ else:
1156
+ self.indices = [value]
1154
1157
 
1155
1158
  @property
1156
1159
  def indices(self) -> list[int]:
@@ -1161,7 +1164,7 @@ class SelectableWidget(metaclass=ABCMeta):
1161
1164
  return output
1162
1165
 
1163
1166
  @indices.setter
1164
- def indices(self, values: tuple[int]) -> None:
1167
+ def indices(self, values: list[int]) -> None:
1165
1168
  """Set the selected indices."""
1166
1169
  self._selected.clear()
1167
1170
  for i in range(len(self.options)):
@@ -1722,9 +1725,12 @@ class ToggleButtons(SelectableWidget):
1722
1725
  return next((x for x in self.indices), None)
1723
1726
 
1724
1727
  @index.setter
1725
- def index(self, value: int) -> None:
1728
+ def index(self, value: int | None) -> None:
1726
1729
  """Set the selected indices to a single value."""
1727
- self.indices = [value]
1730
+ if value is None:
1731
+ self.indices = []
1732
+ else:
1733
+ self.indices = [value]
1728
1734
  self.update_buttons(self)
1729
1735
 
1730
1736
  def update_buttons(self, widget: SelectableWidget | None = None) -> None:
@@ -618,9 +618,10 @@ class MenuBar:
618
618
  self.refocus()
619
619
  item.handler()
620
620
  else:
621
- self.selected_menu = self.selected_menu[
622
- : level + 1
623
- ] + [i]
621
+ self.selected_menu = [
622
+ *self.selected_menu[: level + 1],
623
+ i,
624
+ ]
624
625
  app.layout.focus(
625
626
  self.menu_containers[
626
627
  len(self.selected_menu) - 1
@@ -39,7 +39,10 @@ class PreviewNotebook(BaseNotebook):
39
39
  self,
40
40
  app: BaseApp,
41
41
  path: Path | None = None,
42
+ kernel: BaseKernel | None = None,
43
+ comms: dict[str, Comm] | None = None,
42
44
  use_kernel_history: bool = False,
45
+ connection_file: Path | None = None,
43
46
  ) -> None:
44
47
  """Create a new instance."""
45
48
  self.cell_index = 0
@@ -49,6 +52,8 @@ class PreviewNotebook(BaseNotebook):
49
52
  self.app.after_render += self.after_render
50
53
 
51
54
  self._cell = Cell(0, {}, self)
55
+ # Start the kernel (if needed) after the tab is fully loaded
56
+ self._init_kernel(kernel, comms, use_kernel_history, connection_file)
52
57
 
53
58
  def pre_init_kernel(self) -> None:
54
59
  """Filter cells before kernel is loaded."""
@@ -72,13 +77,19 @@ class PreviewNotebook(BaseNotebook):
72
77
  use_kernel_history: bool = False,
73
78
  connection_file: Path | None = None,
74
79
  ) -> None:
75
- """Set up the tab's kernel and related components."""
80
+ """We defer starting the kernel until the whole tab has loaded."""
81
+
82
+ def _init_kernel(
83
+ self,
84
+ kernel: BaseKernel | None = None,
85
+ comms: dict[str, Comm] | None = None,
86
+ use_kernel_history: bool = False,
87
+ connection_file: Path | None = None,
88
+ ) -> None:
89
+ """Start the tab's kernel if needed."""
76
90
  # Only load the kernel if running the notebook
77
91
  if self.app.config.run:
78
92
  super().init_kernel(kernel, comms, use_kernel_history, connection_file)
79
- else:
80
- self.pre_init_kernel()
81
- self.post_init_kernel()
82
93
 
83
94
  def print_title(self) -> None:
84
95
  """Print a notebook's filename."""
@@ -21,6 +21,7 @@ classifiers = [
21
21
  "Programming Language :: Python :: 3.11",
22
22
  "Programming Language :: Python :: 3.12",
23
23
  "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
24
25
  "Programming Language :: Python :: Implementation :: CPython",
25
26
  "Programming Language :: Python :: Implementation :: PyPy",
26
27
  "Environment :: Console",
@@ -111,7 +112,7 @@ installer = "uv"
111
112
  features = []
112
113
 
113
114
  [[tool.hatch.envs.multi.matrix]]
114
- python = ["3.9", "3.10", "3.11", "3.12", "3.13", "3.13t", "pypy3"]
115
+ python = ["3.9", "3.10", "3.11", "3.12", "3.13", "3.13t", "3.14", "3.14t", "pypy3"]
115
116
 
116
117
 
117
118
  # Environment used to run `hatch fmt`
@@ -174,7 +175,7 @@ dependencies = [
174
175
  ]
175
176
 
176
177
  [[tool.hatch.envs.test.matrix]]
177
- python = ["3.9", "3.10", "3.11", "3.12", "3.13", "pypy3"]
178
+ python = ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "pypy3"]
178
179
 
179
180
  [tool.hatch.envs.test.scripts]
180
181
  run = "pytest {args}"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes