async-kernel 0.19.1__tar.gz → 0.19.2__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 (110) hide show
  1. {async_kernel-0.19.1 → async_kernel-0.19.2}/CHANGELOG.md +14 -1
  2. {async_kernel-0.19.1 → async_kernel-0.19.2}/PKG-INFO +1 -1
  3. {async_kernel-0.19.1 → async_kernel-0.19.2}/_version.py +2 -2
  4. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/kernel.py +36 -41
  5. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/shell/base.py +27 -12
  6. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/shell/ipshell.py +119 -105
  7. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/utils.py +3 -13
  8. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_callable_interface.py +22 -0
  9. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_kernel_ipshell.py +3 -8
  10. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_message_spec.py +1 -1
  11. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_zmq_interface.py +4 -4
  12. {async_kernel-0.19.1 → async_kernel-0.19.2}/.github/dependabot.yaml +0 -0
  13. {async_kernel-0.19.1 → async_kernel-0.19.2}/.github/release.yml +0 -0
  14. {async_kernel-0.19.1 → async_kernel-0.19.2}/.github/workflows/ci.yml +0 -0
  15. {async_kernel-0.19.1 → async_kernel-0.19.2}/.github/workflows/enforce-label.yml +0 -0
  16. {async_kernel-0.19.1 → async_kernel-0.19.2}/.github/workflows/new_release.yml +0 -0
  17. {async_kernel-0.19.1 → async_kernel-0.19.2}/.github/workflows/pre-commit.yml +0 -0
  18. {async_kernel-0.19.1 → async_kernel-0.19.2}/.github/workflows/publish-docs.yml +0 -0
  19. {async_kernel-0.19.1 → async_kernel-0.19.2}/.github/workflows/publish-to-pypi.yml +0 -0
  20. {async_kernel-0.19.1 → async_kernel-0.19.2}/.gitignore +0 -0
  21. {async_kernel-0.19.1 → async_kernel-0.19.2}/.pre-commit-config.yaml +0 -0
  22. {async_kernel-0.19.1 → async_kernel-0.19.2}/.vscode/launch.json +0 -0
  23. {async_kernel-0.19.1 → async_kernel-0.19.2}/.vscode/settings.json +0 -0
  24. {async_kernel-0.19.1 → async_kernel-0.19.2}/.vscode/spellright.dict +0 -0
  25. {async_kernel-0.19.1 → async_kernel-0.19.2}/CONTRIBUTING.md +0 -0
  26. {async_kernel-0.19.1 → async_kernel-0.19.2}/IPYTHON_LICENSE +0 -0
  27. {async_kernel-0.19.1 → async_kernel-0.19.2}/LICENSE +0 -0
  28. {async_kernel-0.19.1 → async_kernel-0.19.2}/README.md +0 -0
  29. {async_kernel-0.19.1 → async_kernel-0.19.2}/cliff.toml +0 -0
  30. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/about/changelog.md +0 -0
  31. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/about/contributing.md +0 -0
  32. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/about/index.md +0 -0
  33. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/about/license.md +0 -0
  34. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/index.md +0 -0
  35. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/javascripts/extra.js +0 -0
  36. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/notebooks/caller.ipynb +0 -0
  37. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/notebooks/concurrency.ipynb +0 -0
  38. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/notebooks/custom_kernel.ipynb +0 -0
  39. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/overrides/main.html +0 -0
  40. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/reference/caller.md +0 -0
  41. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/reference/comm.md +0 -0
  42. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/reference/command.md +0 -0
  43. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/reference/common.md +0 -0
  44. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/reference/debugger.md +0 -0
  45. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/reference/event_loop.md +0 -0
  46. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/reference/index.md +0 -0
  47. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/reference/interface.md +0 -0
  48. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/reference/ipshell.md +0 -0
  49. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/reference/kernel.md +0 -0
  50. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/reference/kernelspec.md +0 -0
  51. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/reference/pending.md +0 -0
  52. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/reference/shell.md +0 -0
  53. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/reference/typing.md +0 -0
  54. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/reference/utils.md +0 -0
  55. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/stylesheets/extra.css +0 -0
  56. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/thread_safety.md +0 -0
  57. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/usage/commands.md +0 -0
  58. {async_kernel-0.19.1 → async_kernel-0.19.2}/docs/usage/index.md +0 -0
  59. {async_kernel-0.19.1 → async_kernel-0.19.2}/hatch_build.py +0 -0
  60. {async_kernel-0.19.1 → async_kernel-0.19.2}/mkdocs.yml +0 -0
  61. {async_kernel-0.19.1 → async_kernel-0.19.2}/pyproject.toml +0 -0
  62. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/__init__.py +0 -0
  63. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/__main__.py +0 -0
  64. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/caller.py +0 -0
  65. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/comm.py +0 -0
  66. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/command.py +0 -0
  67. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/common.py +0 -0
  68. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/compat/attr_docs.py +0 -0
  69. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/compat/json.py +0 -0
  70. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/compiler.py +0 -0
  71. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/debugger.py +0 -0
  72. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/event_loop/__init__.py +0 -0
  73. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/event_loop/asyncio_guest.py +0 -0
  74. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/event_loop/qt_host.py +0 -0
  75. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/event_loop/run.py +0 -0
  76. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/event_loop/tk_host.py +0 -0
  77. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/interface/__init__.py +0 -0
  78. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/interface/base.py +0 -0
  79. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/interface/callable.py +0 -0
  80. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/interface/zmq.py +0 -0
  81. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/kernelspec.py +0 -0
  82. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/pending.py +0 -0
  83. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/py.typed +0 -0
  84. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/resources/logo-32x32.png +0 -0
  85. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/resources/logo-64x64.png +0 -0
  86. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/resources/logo-svg.svg +0 -0
  87. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/shell/__init__.py +0 -0
  88. {async_kernel-0.19.1 → async_kernel-0.19.2}/src/async_kernel/typing.py +0 -0
  89. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/__init__.py +0 -0
  90. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/conftest.py +0 -0
  91. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/references.py +0 -0
  92. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_base_interface.py +0 -0
  93. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_caller.py +0 -0
  94. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_comm.py +0 -0
  95. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_command.py +0 -0
  96. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_common.py +0 -0
  97. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_compat.py +0 -0
  98. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_debugger.py +0 -0
  99. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_debugger_static.py +0 -0
  100. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_enter_kernel.py +0 -0
  101. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_event_loop.py +0 -0
  102. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_iostream.py +0 -0
  103. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_kernelspec.py +0 -0
  104. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_pending.py +0 -0
  105. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_subclass.py +0 -0
  106. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_typing.py +0 -0
  107. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_utils.py +0 -0
  108. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/test_zmq_messaging.py +0 -0
  109. {async_kernel-0.19.1 → async_kernel-0.19.2}/tests/utils.py +0 -0
  110. {async_kernel-0.19.1 → async_kernel-0.19.2}/uv.lock +0 -0
@@ -5,7 +5,17 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [0.19.1] - 2026-05-26
8
+ ## [0.19.2] - 2026-06-04
9
+
10
+ ### <!-- 0 --> 🏗️ Breaking changes
11
+
12
+ - Rename methods on the shell to more resemble those on the kerenel. [#477](https://github.com/fleming79/async-kernel/pull/477)
13
+
14
+ ### <!-- 6 --> 🌀 Miscellaneous
15
+
16
+ - Run do_execute in tasks [#478](https://github.com/fleming79/async-kernel/pull/478)
17
+
18
+ ## [0.19.1] - 2026-05-27
9
19
 
10
20
  ### <!-- 2 --> 🐛 Fixes
11
21
 
@@ -15,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
25
 
16
26
  ### <!-- 6 --> 🌀 Miscellaneous
17
27
 
28
+ - Prepare for release v0.19.1 [#476](https://github.com/fleming79/async-kernel/pull/476)
29
+
18
30
  - Bump setup-uv from v7 to v8.1.0. [#473](https://github.com/fleming79/async-kernel/pull/473)
19
31
 
20
32
  ## [0.19.0] - 2026-05-26
@@ -1323,6 +1335,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1323
1335
 
1324
1336
  - Bump the actions group across 1 directory with 2 updates [#3](https://github.com/fleming79/async-kernel/pull/3)
1325
1337
 
1338
+ [0.19.2]: https://github.com/fleming79/async-kernel/compare/v0.19.1..v0.19.2
1326
1339
  [0.19.1]: https://github.com/fleming79/async-kernel/compare/v0.19.0..v0.19.1
1327
1340
  [0.19.0]: https://github.com/fleming79/async-kernel/compare/v0.18.3..v0.19.0
1328
1341
  [0.18.3]: https://github.com/fleming79/async-kernel/compare/v0.18.2..v0.18.3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: async-kernel
3
- Version: 0.19.1
3
+ Version: 0.19.2
4
4
  Summary: A concurrent python kernel for Jupyter supporting AnyIO, AsyncIO and Trio.
5
5
  Project-URL: Homepage, https://fleming79.github.io/async-kernel
6
6
  Project-URL: Documentation, https://fleming79.github.io/async-kernel
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.19.1'
22
- __version_tuple__ = version_tuple = (0, 19, 1)
21
+ __version__ = version = '0.19.2'
22
+ __version_tuple__ = version_tuple = (0, 19, 2)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -44,6 +44,7 @@ if TYPE_CHECKING:
44
44
  from contextvars import ContextVar
45
45
  from types import CoroutineType, FrameType
46
46
 
47
+ from async_kernel.pending import Pending
47
48
  from async_kernel.typing import Content, Message
48
49
 
49
50
  __all__ = ["Kernel", "KernelInterrupt"]
@@ -156,8 +157,10 @@ class Kernel(HasInterface[T_interface_co], LoggingConfigurable, Generic[T_interf
156
157
  comm_manager = Fixed(CommManager)
157
158
  "Creates [async_kernel.comm.Comm][] instances and maintains a mapping to `comm_id` to `Comm` instances."
158
159
 
159
- interrupts: Fixed[Self, set[Callable[[], object]]] = Fixed(set)
160
- "A set for callbacks to register for calling when `interrupt` is called."
160
+ active_execute_requests: Fixed[Self, set[Pending[Any]]] = Fixed(set)
161
+ "A set of active execute requests that gets updated by the shell."
162
+
163
+ _interrupt_message = "Kernel interrupted"
161
164
 
162
165
  _restart = False
163
166
  _handler_cache: ClassVar[dict[tuple[str | None, MsgType, Callable], HandlerType]] = {}
@@ -319,17 +322,15 @@ class Kernel(HasInterface[T_interface_co], LoggingConfigurable, Generic[T_interf
319
322
  else:
320
323
  os.kill(os.getpid(), signal.SIGINT)
321
324
 
322
- def interrupt(self) -> None:
325
+ def do_interrupt(self) -> None:
323
326
  """
324
- Perform a keyboard interrupt.
327
+ Interrupt/cancel non-silent active execute requests.
325
328
  """
326
329
  if (sys.platform != "emscripten") and (not self.debugger.enabled or not self.debugger.stopped_threads):
327
330
  self._interrupt_now()
328
- while self.interrupts:
329
- try:
330
- self.interrupts.pop()()
331
- except Exception:
332
- pass
331
+ for pen in tuple(self.active_execute_requests):
332
+ if not pen.metadata.get("kwargs", {}).get("silent", False):
333
+ pen.cancel(self._interrupt_message)
333
334
 
334
335
  def _patch_signal(self) -> Callable[[], None]:
335
336
 
@@ -541,22 +542,27 @@ class Kernel(HasInterface[T_interface_co], LoggingConfigurable, Generic[T_interf
541
542
 
542
543
  async def execute_request(self, job: Job[ExecuteContent], /) -> Content:
543
544
  """Handle an [execute request](https://jupyter-client.readthedocs.io/en/stable/messaging.html#execute)."""
544
- return await self.shell.execute_request(job)
545
+ return await self.shell.do_execute(
546
+ cell_id=job["msg"]["metadata"].get("cellId"),
547
+ received_time=job["received_time"],
548
+ tags=job["msg"]["metadata"].get("tags", ()),
549
+ **job["msg"]["content"], # pyright: ignore[reportArgumentType]
550
+ )
545
551
 
546
552
  async def complete_request(self, job: Job[Content], /) -> Content:
547
553
  """Handle an [completion request](https://jupyter-client.readthedocs.io/en/stable/messaging.html#completion)."""
548
- return await self.shell.do_complete_request(
554
+ return await self.shell.do_complete(
549
555
  code=job["msg"]["content"].get("code", ""), cursor_pos=job["msg"]["content"].get("cursor_pos", 0)
550
556
  )
551
557
 
552
558
  async def is_complete_request(self, job: Job[Content], /) -> Content:
553
559
  """Handle an [is_complete request](https://jupyter-client.readthedocs.io/en/stable/messaging.html#code-completeness)."""
554
- return await self.shell.is_complete_request(job["msg"]["content"].get("code", ""))
560
+ return await self.shell.is_complete(job["msg"]["content"].get("code", ""))
555
561
 
556
562
  async def inspect_request(self, job: Job[Content], /) -> Content:
557
563
  """Handle an [inspect request](https://jupyter-client.readthedocs.io/en/stable/messaging.html#introspection)."""
558
564
  c = job["msg"]["content"]
559
- return await self.shell.inspect_request(
565
+ return await self.shell.do_inspect(
560
566
  code=c.get("code", ""),
561
567
  cursor_pos=c.get("cursor_pos", 0),
562
568
  detail_level=c.get("detail_level", 0),
@@ -564,7 +570,7 @@ class Kernel(HasInterface[T_interface_co], LoggingConfigurable, Generic[T_interf
564
570
 
565
571
  async def history_request(self, job: Job[Content], /) -> Content:
566
572
  """Handle an [history request](https://jupyter-client.readthedocs.io/en/stable/messaging.html#history)."""
567
- return await self.shell.history_request(**job["msg"]["content"])
573
+ return await self.shell.do_history(**job["msg"]["content"])
568
574
 
569
575
  async def comm_open(self, job: Job[Content], /) -> None:
570
576
  """Handle an [comm open request](https://jupyter-client.readthedocs.io/en/stable/messaging.html#opening-a-comm)."""
@@ -580,7 +586,7 @@ class Kernel(HasInterface[T_interface_co], LoggingConfigurable, Generic[T_interf
580
586
 
581
587
  async def interrupt_request(self, job: Job[Content], /) -> Content:
582
588
  """Handle an [interrupt request](https://jupyter-client.readthedocs.io/en/stable/messaging.html#kernel-interrupt)."""
583
- self.interrupt()
589
+ self.do_interrupt()
584
590
  return {}
585
591
 
586
592
  async def shutdown_request(self, job: Job[Content], /) -> Content:
@@ -624,11 +630,13 @@ class Kernel(HasInterface[T_interface_co], LoggingConfigurable, Generic[T_interf
624
630
 
625
631
  async def do_complete(self, code: str, cursor_pos: int | None) -> Content:
626
632
  "Matches signature of [ipykernel.kernelbase.Kernel.do_complete][]."
627
- return await self.shell.do_complete_request(code=code, cursor_pos=cursor_pos)
633
+ return await self.shell.do_complete(code=code, cursor_pos=cursor_pos)
628
634
 
629
- async def do_inspect(self, code: str, cursor_pos: int | None, detail_level=0, omit_sections=()) -> Content:
635
+ async def do_inspect(
636
+ self, code: str, cursor_pos: int = 0, detail_level: Literal[0, 1] = 0, omit_sections=()
637
+ ) -> Content:
630
638
  "Matches signature of [ipykernel.kernelbase.Kernel.do_inspect][]."
631
- return await self.shell.inspect_request(code=code, cursor_pos=cursor_pos) # pyright: ignore[reportArgumentType]
639
+ return await self.shell.do_inspect(code=code, cursor_pos=cursor_pos, detail_level=detail_level)
632
640
 
633
641
  async def do_history(
634
642
  self,
@@ -643,7 +651,7 @@ class Kernel(HasInterface[T_interface_co], LoggingConfigurable, Generic[T_interf
643
651
  unique=False,
644
652
  ) -> Content:
645
653
  "Matches signature of [ipykernel.kernelbase.Kernel.do_history][]."
646
- return await self.shell.history_request(
654
+ return await self.shell.do_history(
647
655
  output=output,
648
656
  raw=raw,
649
657
  hist_access_type=hist_access_type,
@@ -655,37 +663,24 @@ class Kernel(HasInterface[T_interface_co], LoggingConfigurable, Generic[T_interf
655
663
  async def do_execute(
656
664
  self,
657
665
  code: str,
658
- silent: bool,
666
+ silent: bool = True,
659
667
  store_history: bool = True,
660
668
  user_expressions: dict[str, str] | None = None,
661
669
  allow_stdin: bool = False,
662
670
  *,
663
671
  cell_meta: dict[str, Any] | None = None,
664
672
  cell_id: str | None = None,
673
+ **_ignored,
665
674
  ) -> Content:
666
675
  "Matches signature of [ipykernel.kernelbase.Kernel.do_execute][]."
667
- if cell_meta is None:
668
- cell_meta = {}
669
- if cell_id is not None:
670
- cell_meta["cellId"] = cell_id
671
- msg = self.parent.msg(
672
- "execute_request",
673
- content=ExecuteContent(
674
- code=code,
675
- silent=silent,
676
- store_history=store_history,
677
- user_expressions=user_expressions or {},
678
- allow_stdin=allow_stdin,
679
- stop_on_error=False,
680
- ),
681
- metadata=cell_meta,
676
+ return await self.shell.do_execute(
677
+ code=code,
678
+ silent=silent,
679
+ store_history=store_history,
680
+ user_expressions=user_expressions,
681
+ allow_stdin=allow_stdin,
682
+ cell_id=cell_id,
682
683
  )
683
- job: Job[ExecuteContent] = Job(msg=msg, ident=[], received_time=time.monotonic())
684
- token = utils._job_var.set(job) # pyright: ignore[reportPrivateUsage]
685
- try:
686
- return await self.shell.execute_request(job)
687
- finally:
688
- utils._job_var.reset(token) # pyright: ignore[reportPrivateUsage]
689
684
 
690
685
  def getpass(self, prompt="", stream=None) -> str:
691
686
  "Matches signature of [ipykernel.kernelbase.Kernel.getpass][]."
@@ -16,10 +16,10 @@ from async_kernel import utils
16
16
  from async_kernel.common import Fixed
17
17
  from async_kernel.interface import HasInterface
18
18
  from async_kernel.pending import PendingManager
19
- from async_kernel.typing import ExecuteContent, Job, T_interface_co
19
+ from async_kernel.typing import T_interface_co
20
20
 
21
21
  if TYPE_CHECKING:
22
- from collections.abc import Callable, Generator
22
+ from collections.abc import Callable, Generator, Iterable
23
23
 
24
24
  from async_kernel import Kernel
25
25
  from async_kernel.typing import Content
@@ -207,23 +207,38 @@ class BaseShell(HasInterface[T_interface_co], LoggingConfigurable, Generic[T_int
207
207
  content["metadata"] = {}
208
208
  self.parent.iopub_send("execute_result", content=content)
209
209
 
210
- async def execute_request(self, job: Job[ExecuteContent]) -> Content:
211
- """Handle an [execute request](https://jupyter-client.readthedocs.io/en/stable/messaging.html#execute)."""
210
+ async def do_execute(
211
+ self,
212
+ code: str = "",
213
+ *,
214
+ silent: bool = False,
215
+ store_history: bool = False,
216
+ user_expressions: dict[str, str] | None = None,
217
+ allow_stdin: bool = False,
218
+ stop_on_error: bool = False,
219
+ cell_id: str | None = None,
220
+ received_time: float = 0,
221
+ tags: Iterable[str] = (),
222
+ **_ignored,
223
+ ) -> Content:
224
+ """
225
+ Execute code in the shell.
226
+ """
212
227
  raise NotImplementedError
213
228
 
214
- async def do_complete_request(self, code: str, cursor_pos: int | None = None) -> Content:
215
- """Handle an [completion request](https://jupyter-client.readthedocs.io/en/stable/messaging.html#completion)."""
229
+ async def do_complete(self, code: str, cursor_pos: int | None = None) -> Content:
230
+ ""
216
231
  raise NotImplementedError
217
232
 
218
- async def is_complete_request(self, code: str) -> Content:
219
- """Handle an [is_complete request](https://jupyter-client.readthedocs.io/en/stable/messaging.html#code-completeness)."""
233
+ async def is_complete(self, code: str) -> Content:
234
+ ""
220
235
  raise NotImplementedError
221
236
 
222
- async def inspect_request(self, code: str, cursor_pos: int = 0, detail_level: Literal[0, 1] = 0) -> Content:
223
- """Handle an [inspect request](https://jupyter-client.readthedocs.io/en/stable/messaging.html#introspection)."""
237
+ async def do_inspect(self, code: str, cursor_pos: int = 0, detail_level: Literal[0, 1] = 0) -> Content:
238
+ ""
224
239
  raise NotImplementedError
225
240
 
226
- async def history_request(
241
+ async def do_history(
227
242
  self,
228
243
  *,
229
244
  output: bool = False,
@@ -237,5 +252,5 @@ class BaseShell(HasInterface[T_interface_co], LoggingConfigurable, Generic[T_int
237
252
  unique: bool = False,
238
253
  **_ignored,
239
254
  ) -> Content:
240
- """Handle an [history request](https://jupyter-client.readthedocs.io/en/stable/messaging.html#history)."""
255
+ ""
241
256
  raise NotImplementedError
@@ -6,6 +6,7 @@ from __future__ import annotations
6
6
 
7
7
  import builtins
8
8
  import json
9
+ import math
9
10
  import pathlib
10
11
  import shlex
11
12
  import sys
@@ -15,11 +16,10 @@ import weakref
15
16
  from collections.abc import Callable
16
17
  from contextlib import contextmanager
17
18
  from sqlite3 import OperationalError
18
- from typing import TYPE_CHECKING, Any, Literal, Never, Self, TextIO, cast
19
+ from typing import TYPE_CHECKING, Any, Literal, Never, Self, TextIO
19
20
 
20
21
  import anyio
21
22
  import IPython.core.release
22
- from aiologic.lowlevel import async_checkpoint
23
23
  from anyio.streams.text import TextReceiveStream
24
24
  from IPython.core.completer import provisionalcompleter, rectify_completions
25
25
  from IPython.core.displayhook import DisplayHook
@@ -46,10 +46,10 @@ from async_kernel.compiler import XCachingCompiler
46
46
  from async_kernel.event_loop.run import get_runtime_matplotlib_guis
47
47
  from async_kernel.interface.base import BaseInterface, HasInterface
48
48
  from async_kernel.shell.base import BaseShell
49
- from async_kernel.typing import Content, ExecuteContent, Job, RunMode, Tags
49
+ from async_kernel.typing import Content, RunMode, Tags
50
50
 
51
51
  if TYPE_CHECKING:
52
- from collections.abc import Callable
52
+ from collections.abc import Callable, Iterable
53
53
  from types import CodeType
54
54
 
55
55
  from anyio.abc import ByteReceiveStream
@@ -379,7 +379,7 @@ class IPShell(BaseShell, InteractiveShell): # pyright: ignore[reportUnsafeMulti
379
379
  tempdirs = Fixed(list, mode="ignore")
380
380
 
381
381
  _main_mod_cache = Fixed(dict)
382
- _stop_on_error_pool: Fixed[Self, set[Callable[[], object]]] = Fixed(set)
382
+ _stop_on_error_pool: Fixed[Self, set[Pending[Any]]] = Fixed(set)
383
383
 
384
384
  # Disabled attributes
385
385
  loop_runner_map = None
@@ -641,106 +641,120 @@ class IPShell(BaseShell, InteractiveShell): # pyright: ignore[reportUnsafeMulti
641
641
  )
642
642
 
643
643
  @override
644
- async def execute_request(self, job: Job[ExecuteContent]) -> Content:
645
- """Handle an [execute request](https://jupyter-client.readthedocs.io/en/stable/messaging.html#execute)."""
646
-
647
- content = cast("ExecuteContent", job["msg"]["content"])
648
- code = content["code"]
649
- silent = content.get("silent", False)
650
- cell_id = job["msg"]["metadata"].get("cellId")
651
- stop_on_error = content.get("stop_on_error", False)
644
+ async def run_cell_async(
645
+ self,
646
+ raw_cell: str,
647
+ store_history=False,
648
+ silent=False,
649
+ shell_futures=True,
650
+ *,
651
+ transformed_cell: str | None = None,
652
+ preprocessing_exc_tuple: Any = None,
653
+ cell_id=None,
654
+ ) -> ExecutionResult:
655
+ token = utils._cell_id_var.set(cell_id) # pyright: ignore[reportPrivateUsage]
656
+ result = None
657
+ try:
658
+ result = await super().run_cell_async(
659
+ raw_cell=raw_cell,
660
+ store_history=store_history,
661
+ silent=silent,
662
+ shell_futures=shell_futures,
663
+ transformed_cell=transformed_cell,
664
+ preprocessing_exc_tuple=preprocessing_exc_tuple,
665
+ cell_id=cell_id,
666
+ )
667
+ return result # noqa: RET504
668
+ finally:
669
+ self.events.trigger("post_execute")
670
+ if not silent:
671
+ self.events.trigger("post_run_cell", result)
672
+ utils._cell_id_var.reset(token) # pyright: ignore[reportPrivateUsage]
652
673
 
653
- if (job["received_time"] < self._stop_on_error_info.get("time", 0)) and not silent:
674
+ @override
675
+ async def do_execute(
676
+ self,
677
+ code: str = "",
678
+ *,
679
+ silent: bool = False,
680
+ store_history: bool = False,
681
+ user_expressions: dict[str, str] | None = None,
682
+ allow_stdin: bool = False,
683
+ stop_on_error: bool = False,
684
+ cell_id: str | None = None,
685
+ received_time: float = 0,
686
+ tags: Iterable[str] = (),
687
+ **_ignored,
688
+ ) -> Content:
689
+ """
690
+ Execute code in the shell's user_ns and global_ns.
691
+ """
692
+ if received_time > 0 and (received_time < self._stop_on_error_info.get("time", 0)) and not silent:
654
693
  return utils.error_to_content(RuntimeError("Aborting due to prior exception")) | {
655
694
  "execution_count": self._stop_on_error_info.get("execution_count", 0)
656
695
  }
657
- token = utils._cell_id_var.set(cell_id) # pyright: ignore[reportPrivateUsage]
696
+ if math.isnan(timeout := utils.get_tag_value(Tags.timeout, math.nan, tags=tags)):
697
+ timeout = self.timeout
698
+
699
+ if Tags.stop_on_error in tags:
700
+ stop_on_error = utils.get_tag_value(Tags.stop_on_error, stop_on_error, tags=tags)
701
+ elif Tags.raises_exception in tags or timeout:
702
+ stop_on_error = False
703
+
704
+ if silent:
705
+ execution_count: int = self.execution_count
706
+ else:
707
+ execution_count = self._execution_count = self._execution_count + 1
708
+ self.parent.iopub_send(
709
+ msg_or_type="execute_input",
710
+ content={"code": code, "execution_count": execution_count},
711
+ ident=b"kernel.execute_input",
712
+ )
713
+
714
+ pen = Caller().call_soon(
715
+ self.run_cell_async,
716
+ raw_cell=code,
717
+ store_history=store_history,
718
+ silent=silent,
719
+ transformed_cell=self.transform_cell_async(code),
720
+ shell_futures=True,
721
+ cell_id=cell_id,
722
+ )
723
+ err = result = None
658
724
  try:
659
- tags: list[str] = utils.get_tags()
660
- timeout: float = utils.get_timeout(tags=tags)
661
- raises_exception: bool = Tags.raises_exception in tags
662
- stop_on_error_override: bool = Tags.stop_on_error in tags
663
- if stop_on_error_override:
664
- stop_on_error = utils.get_tag_value(Tags.stop_on_error, stop_on_error)
665
- elif raises_exception:
666
- stop_on_error = False
667
-
668
- if silent:
669
- execution_count: int = self.execution_count
670
- else:
671
- execution_count = self._execution_count = self._execution_count + 1
672
- self.parent.iopub_send(
673
- msg_or_type="execute_input",
674
- content={"code": code, "execution_count": execution_count},
675
- ident=b"kernel.execute_input",
676
- )
677
- caller = Caller()
678
- err = None
679
- result = None
680
- with anyio.CancelScope() as scope:
681
-
682
- def cancel():
683
- if not silent:
684
- caller.call_direct(scope.cancel, "Interrupted")
685
-
686
- try:
687
- self.kernel.interrupts.add(cancel)
688
- if stop_on_error:
689
- self._stop_on_error_pool.add(cancel)
690
- with anyio.fail_after(delay=timeout or None):
691
- result = await self.run_cell_async(
692
- raw_cell=code,
693
- store_history=content.get("store_history", False),
694
- silent=silent,
695
- transformed_cell=self.transform_cell_async(code),
696
- shell_futures=True,
697
- cell_id=cell_id,
698
- )
699
- except (Exception, anyio.get_cancelled_exc_class()) as e:
700
- # A safeguard to catch exceptions not caught by the shell.
701
- if utils.LAUNCHED_BY_DEBUGPY_PYTEST:
702
- raise
703
- err = KernelInterrupt() if self.parent.last_interrupt_frame else e
704
- else:
705
- err = result.error_before_exec or result.error_in_exec if result else KernelInterrupt()
706
- if not err and Tags.raises_exception in tags:
707
- msg = "An expected exception was not raised!"
708
- err = RuntimeError(msg)
709
- finally:
710
- self._stop_on_error_pool.discard(cancel)
711
- self.kernel.interrupts.discard(cancel)
712
- self.events.trigger("post_execute")
713
- if not silent:
714
- self.events.trigger("post_run_cell", result)
715
- if (err) and (isinstance(err, anyio.get_cancelled_exc_class()) and (timeout != 0)):
716
- # Suppress the error due to either:
717
- # 1. tag
718
- # 2. timeout
719
- err = None
720
- content = {
721
- "status": "error" if err else "ok",
722
- "execution_count": execution_count,
723
- "user_expressions": self.user_expressions(content.get("user_expressions", {})),
724
- }
725
- if err:
726
- content |= utils.error_to_content(err)
727
- if (not silent) and stop_on_error:
728
- with anyio.CancelScope(shield=True):
729
- await async_checkpoint(force=True)
730
- self._stop_on_error_info["time"] = time.monotonic() + float(self.stop_on_error_time_offset)
731
- self._stop_on_error_info["execution_count"] = execution_count
732
- self.log.info("An error occurred in a non-silent execution request")
733
- if stop_on_error:
734
- for c in frozenset(self._stop_on_error_pool):
735
- c()
736
- await async_checkpoint(force=True)
737
- return content
738
- finally:
739
- utils._cell_id_var.reset(token) # pyright: ignore[reportPrivateUsage]
725
+ self.kernel.active_execute_requests.add(pen)
726
+ pen.add_done_callback(self.kernel.active_execute_requests.discard)
727
+ if stop_on_error:
728
+ self._stop_on_error_pool.add(pen)
729
+ pen.add_done_callback(self._stop_on_error_pool.discard)
730
+ result = await pen.wait(timeout=timeout or None)
731
+ except Exception as e:
732
+ err = KernelInterrupt() if str(e) == self.kernel._interrupt_message else e # pyright: ignore[reportPrivateUsage]
733
+ else:
734
+ err = result.error_before_exec or result.error_in_exec if result else KernelInterrupt()
735
+ if not err and Tags.raises_exception in tags:
736
+ msg = "An expected exception was not raised!"
737
+ err = RuntimeError(msg)
738
+
739
+ content = {
740
+ "status": "error" if err else "ok",
741
+ "execution_count": execution_count,
742
+ "user_expressions": self.user_expressions(user_expressions if user_expressions is not None else {}),
743
+ }
744
+ if err:
745
+ content |= utils.error_to_content(err)
746
+ if (not silent) and stop_on_error:
747
+ self._stop_on_error_info["time"] = time.monotonic() + float(self.stop_on_error_time_offset)
748
+ self._stop_on_error_info["execution_count"] = execution_count
749
+ self.log.info("An error occurred in %s %s", self, pen)
750
+ if stop_on_error:
751
+ for pen in tuple(self._stop_on_error_pool):
752
+ pen.cancel("Stop on error cancellation")
753
+ return content
740
754
 
741
755
  @override
742
- async def do_complete_request(self, code: str, cursor_pos: int | None = None) -> Content:
743
- """Handle an [completion request](https://jupyter-client.readthedocs.io/en/stable/messaging.html#completion)."""
756
+ async def do_complete(self, code: str, cursor_pos: int | None = None) -> Content:
757
+ ""
744
758
 
745
759
  cursor_pos = cursor_pos or len(code)
746
760
  with provisionalcompleter():
@@ -767,8 +781,8 @@ class IPShell(BaseShell, InteractiveShell): # pyright: ignore[reportUnsafeMulti
767
781
  }
768
782
 
769
783
  @override
770
- async def is_complete_request(self, code: str) -> Content:
771
- """Handle an [is_complete request](https://jupyter-client.readthedocs.io/en/stable/messaging.html#code-completeness)."""
784
+ async def is_complete(self, code: str) -> Content:
785
+ ""
772
786
  status, indent_spaces = self.input_transformer_manager.check_complete(code)
773
787
  content = {"status": status}
774
788
  if status == "incomplete":
@@ -776,8 +790,8 @@ class IPShell(BaseShell, InteractiveShell): # pyright: ignore[reportUnsafeMulti
776
790
  return content
777
791
 
778
792
  @override
779
- async def inspect_request(self, code: str, cursor_pos: int = 0, detail_level: Literal[0, 1] = 0) -> Content:
780
- """Handle an [inspect request](https://jupyter-client.readthedocs.io/en/stable/messaging.html#introspection)."""
793
+ async def do_inspect(self, code: str, cursor_pos: int = 0, detail_level: Literal[0, 1] = 0) -> Content:
794
+ ""
781
795
  content = {"data": {}, "metadata": {}, "found": True}
782
796
  try:
783
797
  oname = token_at_cursor(code, cursor_pos)
@@ -788,7 +802,7 @@ class IPShell(BaseShell, InteractiveShell): # pyright: ignore[reportUnsafeMulti
788
802
  return content
789
803
 
790
804
  @override
791
- async def history_request(
805
+ async def do_history(
792
806
  self,
793
807
  *,
794
808
  output: bool = False,
@@ -802,7 +816,7 @@ class IPShell(BaseShell, InteractiveShell): # pyright: ignore[reportUnsafeMulti
802
816
  unique: bool = False,
803
817
  **_ignored,
804
818
  ) -> Content:
805
- """Handle an [history request](https://jupyter-client.readthedocs.io/en/stable/messaging.html#history)."""
819
+ ""
806
820
  history_manager = self.history_manager
807
821
  assert history_manager
808
822
  match hist_access_type:
@@ -818,7 +832,7 @@ class IPShell(BaseShell, InteractiveShell): # pyright: ignore[reportUnsafeMulti
818
832
 
819
833
  @override
820
834
  def _showtraceback(self, etype, evalue, stb) -> None:
821
- if utils.get_timeout() != 0.0 and etype is anyio.get_cancelled_exc_class():
835
+ if self.timeout != 0.0 and etype is anyio.get_cancelled_exc_class():
822
836
  etype, evalue, stb = TimeoutError, "Cell execute timeout", []
823
837
  if isinstance(evalue, KernelInterrupt):
824
838
  stb = []
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import math
4
3
  import sys
5
4
  import threading
6
5
  import traceback
@@ -13,15 +12,14 @@ from traitlets import traitlets
13
12
  from typing_extensions import TypeVar
14
13
 
15
14
  import async_kernel.interface
16
- from async_kernel.typing import Tags
17
15
 
18
16
  if TYPE_CHECKING:
19
- from collections.abc import Generator, Mapping
17
+ from collections.abc import Generator, Iterable, Mapping
20
18
  from contextlib import _SupportsRedirect, _SupportsRedirectT # pyright: ignore[reportPrivateUsage]
21
19
 
22
20
  from async_kernel.kernel import Kernel
23
21
  from async_kernel.shell import BaseShell
24
- from async_kernel.typing import Content, Job, Message
22
+ from async_kernel.typing import Content, Job, Message, Tags
25
23
 
26
24
  __all__ = [
27
25
  "apply_settings",
@@ -34,7 +32,6 @@ __all__ = [
34
32
  "get_subshell_id",
35
33
  "get_tag_value",
36
34
  "get_tags",
37
- "get_timeout",
38
35
  "mark_thread_pydev_do_not_trace",
39
36
  "redirect_stderr",
40
37
  "redirect_stdout",
@@ -134,7 +131,7 @@ def get_cell_id(job: Job | None = None, /) -> str | None:
134
131
  _TagType = TypeVar("_TagType", str, float, int, bool)
135
132
 
136
133
 
137
- def get_tag_value(tag: Tags, default: _TagType, /, *, tags: list[str] | None = None) -> _TagType:
134
+ def get_tag_value(tag: Tags, default: _TagType, /, *, tags: Iterable[str] | None = None) -> _TagType:
138
135
  """
139
136
  Get the value for the tag from a collection of tags.
140
137
 
@@ -158,13 +155,6 @@ def get_tag_value(tag: Tags, default: _TagType, /, *, tags: list[str] | None = N
158
155
  return default
159
156
 
160
157
 
161
- def get_timeout(*, tags: list[str] | None = None) -> float:
162
- "Gets the timeout from tags or using the current context."
163
- if math.isnan(timeout := get_tag_value(Tags.timeout, math.nan, tags=tags)):
164
- return get_kernel().shell.timeout
165
- return max(timeout, 0.0)
166
-
167
-
168
158
  def setattr_nested(obj: object, name: str, value: str | Any, *, _return_value=False) -> dict[str, Any]:
169
159
  """
170
160
  Replace an existing nested attribute/trait of an object.
@@ -12,6 +12,7 @@ import async_kernel
12
12
  from async_kernel.compat.json import pack_json_str, unpack_json
13
13
  from async_kernel.interface import start_kernel_callable_interface
14
14
  from async_kernel.interface.callable import CallableInterface
15
+ from tests import utils
15
16
 
16
17
  if TYPE_CHECKING:
17
18
  from async_kernel.typing import Message
@@ -92,3 +93,24 @@ class TestCallableInterface:
92
93
  async def test_keyboard_interrupt(self, interface) -> None:
93
94
  with pytest.raises(KeyboardInterrupt):
94
95
  signal.raise_signal(signal.SIGINT)
96
+
97
+ async def test_kernel_interrupt_pending(self, interface: CallableInterface):
98
+ interface.kernel.shell.user_ns["ready_event"] = ready = Event()
99
+ code = "ready_event.set()\nimport anyio\nawait anyio.sleep_forever()"
100
+ pen = interface.kernel.caller.call_soon(interface.kernel.do_execute, code, silent=False)
101
+ await ready
102
+ interface.kernel.do_interrupt()
103
+ result = await pen
104
+ assert result
105
+
106
+ async def test_kernel_interrupt_keyboard(self, interface: CallableInterface):
107
+ interface.kernel.shell.user_ns["ready_event"] = ready = Event()
108
+
109
+ async def run_code():
110
+ code = f"ready_event.set()\nimport time\ntime.sleep({utils.TIMEOUT * 2})"
111
+ pen = interface.kernel.caller.call_soon(interface.kernel.do_execute, code, silent=False)
112
+ await ready
113
+ interface.kernel.do_interrupt()
114
+ await pen.wait(result=False)
115
+
116
+ await interface.kernel.caller.to_thread(run_code)
@@ -38,14 +38,9 @@ async def test_execute_shell_timeout(client: AsyncKernelClient, kernel: Kernel,
38
38
  last_stop_time = kernel.shell._stop_on_error_info
39
39
  try:
40
40
  code = "\n".join(["import anyio", "await anyio.sleep_forever()"])
41
- msg_id, content = await utils.execute(client, code=code, metadata=metadata, clear_pub=False)
41
+ _, content = await utils.execute(client, code=code, metadata=metadata, clear_pub=False)
42
42
  assert last_stop_time == kernel.shell._stop_on_error_info, "Should not cause cancellation"
43
- assert content["status"] == "ok"
44
- await utils.check_pub_message(client, msg_id, execution_state="busy")
45
- await utils.check_pub_message(client, msg_id, msg_type="execute_input")
46
- expected = {"traceback": [], "ename": "TimeoutError", "evalue": "Cell execute timeout"}
47
- await utils.check_pub_message(client, msg_id, msg_type="error", **expected)
48
- await utils.check_pub_message(client, msg_id, execution_state="idle")
43
+ assert content["status"] == "error"
49
44
  finally:
50
45
  kernel.shell.timeout = 0.0
51
46
 
@@ -88,7 +83,7 @@ async def test_save_history(client: AsyncKernelClient, tmp_path):
88
83
  ("%%timeit\na\n\n", "complete"),
89
84
  ],
90
85
  )
91
- async def test_is_complete(client: AsyncKernelClient, code: str, status: str):
86
+ async def test_is_complete_2(client: AsyncKernelClient, code: str, status: str):
92
87
  # There are more test cases for this in core - here we just check
93
88
  # that the kernel exposes the interface correctly.
94
89
  client.is_complete(code)
@@ -141,7 +141,7 @@ async def test_execute_stop_on_error_task(client: AsyncKernelClient):
141
141
  assert content.get("status") == "error"
142
142
  assert "ValueError" in "".join(content["traceback"])
143
143
  content = await utils.get_shell_message(client, msg_id_1, "execute_reply")
144
- assert "await anyio.sleep_forever()" in "".join(content["traceback"])
144
+ assert "Stop on error cancellation" in "".join(content["traceback"])
145
145
 
146
146
 
147
147
  async def test_user_expressions(client: AsyncKernelClient):
@@ -1,12 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import pathlib
4
- import threading
5
4
  from typing import TYPE_CHECKING, Any, Literal
6
5
 
7
6
  import anyio
8
7
  import pytest
9
8
 
9
+ from async_kernel import Pending
10
10
  from async_kernel.interface.zmq import ZMQInterface
11
11
  from async_kernel.typing import MsgType
12
12
  from tests import utils
@@ -87,12 +87,12 @@ async def test_input(
87
87
 
88
88
 
89
89
  async def test_interrupt_request(client: AsyncKernelClient, kernel: Kernel):
90
- event = threading.Event()
91
- kernel.interrupts.add(event.set)
90
+ pen: Any = Pending()
91
+ kernel.active_execute_requests.add(pen)
92
92
  reply = await utils.send_control_message(client, MsgType.interrupt_request)
93
93
  assert reply["header"]["msg_type"] == "interrupt_reply"
94
94
  assert reply["content"] == {"status": "ok"}
95
- assert event
95
+ assert pen.cancelled()
96
96
 
97
97
 
98
98
  async def test_interrupt_request_async_request(subprocess_kernels_client: AsyncKernelClient):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes