async-kernel 0.22.0__tar.gz → 0.22.1__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 (119) hide show
  1. {async_kernel-0.22.0 → async_kernel-0.22.1}/CHANGELOG.md +17 -0
  2. {async_kernel-0.22.0 → async_kernel-0.22.1}/PKG-INFO +1 -1
  3. {async_kernel-0.22.0 → async_kernel-0.22.1}/_version.py +2 -2
  4. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/caller.py +1 -0
  5. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/event_loop/zmq_poll.py +40 -25
  6. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/interface/base.py +0 -9
  7. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/interface/callable.py +11 -10
  8. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/messaging/base.py +7 -3
  9. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/messaging/zmq.py +2 -2
  10. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_callable_interface.py +1 -3
  11. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_eventloop_zmq_poll.py +5 -5
  12. {async_kernel-0.22.0 → async_kernel-0.22.1}/.github/dependabot.yaml +0 -0
  13. {async_kernel-0.22.0 → async_kernel-0.22.1}/.github/release.yml +0 -0
  14. {async_kernel-0.22.0 → async_kernel-0.22.1}/.github/workflows/ci.yml +0 -0
  15. {async_kernel-0.22.0 → async_kernel-0.22.1}/.github/workflows/enforce-label.yml +0 -0
  16. {async_kernel-0.22.0 → async_kernel-0.22.1}/.github/workflows/new_release.yml +0 -0
  17. {async_kernel-0.22.0 → async_kernel-0.22.1}/.github/workflows/pre-commit.yml +0 -0
  18. {async_kernel-0.22.0 → async_kernel-0.22.1}/.github/workflows/publish-docs.yml +0 -0
  19. {async_kernel-0.22.0 → async_kernel-0.22.1}/.github/workflows/publish-to-pypi.yml +0 -0
  20. {async_kernel-0.22.0 → async_kernel-0.22.1}/.gitignore +0 -0
  21. {async_kernel-0.22.0 → async_kernel-0.22.1}/.pre-commit-config.yaml +0 -0
  22. {async_kernel-0.22.0 → async_kernel-0.22.1}/.vscode/launch.json +0 -0
  23. {async_kernel-0.22.0 → async_kernel-0.22.1}/.vscode/settings.json +0 -0
  24. {async_kernel-0.22.0 → async_kernel-0.22.1}/.vscode/spellright.dict +0 -0
  25. {async_kernel-0.22.0 → async_kernel-0.22.1}/CONTRIBUTING.md +0 -0
  26. {async_kernel-0.22.0 → async_kernel-0.22.1}/IPYTHON_LICENSE +0 -0
  27. {async_kernel-0.22.0 → async_kernel-0.22.1}/LICENSE +0 -0
  28. {async_kernel-0.22.0 → async_kernel-0.22.1}/README.md +0 -0
  29. {async_kernel-0.22.0 → async_kernel-0.22.1}/cliff.toml +0 -0
  30. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/about/changelog.md +0 -0
  31. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/about/contributing.md +0 -0
  32. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/about/index.md +0 -0
  33. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/about/license.md +0 -0
  34. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/index.md +0 -0
  35. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/javascripts/extra.js +0 -0
  36. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/notebooks/caller.ipynb +0 -0
  37. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/notebooks/concurrency.ipynb +0 -0
  38. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/notebooks/custom_kernel.ipynb +0 -0
  39. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/overrides/main.html +0 -0
  40. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/reference/caller.md +0 -0
  41. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/reference/comm.md +0 -0
  42. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/reference/command.md +0 -0
  43. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/reference/common.md +0 -0
  44. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/reference/debugger.md +0 -0
  45. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/reference/event_loop.md +0 -0
  46. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/reference/index.md +0 -0
  47. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/reference/interface.md +0 -0
  48. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/reference/ipshell.md +0 -0
  49. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/reference/kernel.md +0 -0
  50. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/reference/kernelspec.md +0 -0
  51. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/reference/messaging.md +0 -0
  52. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/reference/pending.md +0 -0
  53. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/reference/shell.md +0 -0
  54. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/reference/typing.md +0 -0
  55. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/reference/utils.md +0 -0
  56. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/stylesheets/extra.css +0 -0
  57. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/thread_safety.md +0 -0
  58. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/usage/commands.md +0 -0
  59. {async_kernel-0.22.0 → async_kernel-0.22.1}/docs/usage/index.md +0 -0
  60. {async_kernel-0.22.0 → async_kernel-0.22.1}/hatch_build.py +0 -0
  61. {async_kernel-0.22.0 → async_kernel-0.22.1}/mkdocs.yml +0 -0
  62. {async_kernel-0.22.0 → async_kernel-0.22.1}/pyproject.toml +0 -0
  63. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/__init__.py +0 -0
  64. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/__main__.py +0 -0
  65. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/comm.py +0 -0
  66. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/command.py +0 -0
  67. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/common.py +0 -0
  68. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/compat/attr_docs.py +0 -0
  69. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/compat/json.py +0 -0
  70. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/compiler.py +0 -0
  71. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/debugger.py +0 -0
  72. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/event_loop/__init__.py +0 -0
  73. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/event_loop/asyncio_guest.py +0 -0
  74. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/event_loop/qt_host.py +0 -0
  75. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/event_loop/run.py +0 -0
  76. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/event_loop/tk_host.py +0 -0
  77. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/interface/__init__.py +0 -0
  78. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/interface/ip_app.py +0 -0
  79. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/kernel.py +0 -0
  80. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/kernelspec.py +0 -0
  81. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/messaging/__init__.py +0 -0
  82. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/outstream.py +0 -0
  83. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/pending.py +0 -0
  84. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/py.typed +0 -0
  85. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/resources/logo-32x32.png +0 -0
  86. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/resources/logo-64x64.png +0 -0
  87. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/resources/logo-svg.svg +0 -0
  88. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/shell/__init__.py +0 -0
  89. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/shell/base.py +0 -0
  90. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/shell/ipshell.py +0 -0
  91. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/typing.py +0 -0
  92. {async_kernel-0.22.0 → async_kernel-0.22.1}/src/async_kernel/utils.py +0 -0
  93. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/__init__.py +0 -0
  94. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/conftest.py +0 -0
  95. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/references.py +0 -0
  96. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_caller.py +0 -0
  97. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_comm.py +0 -0
  98. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_command.py +0 -0
  99. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_common.py +0 -0
  100. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_compat.py +0 -0
  101. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_connection.py +0 -0
  102. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_debugger.py +0 -0
  103. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_debugger_static.py +0 -0
  104. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_enter_kernel.py +0 -0
  105. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_event_loop.py +0 -0
  106. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_interface.py +0 -0
  107. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_ipapp.py +0 -0
  108. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_kernel_ipshell.py +0 -0
  109. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_kernelspec.py +0 -0
  110. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_message_spec.py +0 -0
  111. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_outstream.py +0 -0
  112. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_pending.py +0 -0
  113. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_subclass.py +0 -0
  114. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_typing.py +0 -0
  115. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_utils.py +0 -0
  116. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_zmq_connection.py +0 -0
  117. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/test_zmq_subprocess.py +0 -0
  118. {async_kernel-0.22.0 → async_kernel-0.22.1}/tests/utils.py +0 -0
  119. {async_kernel-0.22.0 → async_kernel-0.22.1}/uv.lock +0 -0
@@ -5,6 +5,20 @@ 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.22.1] - 2026-08-18
9
+
10
+ ### <!-- 0 --> 🏗️ Breaking changes
11
+
12
+ - Update zmq_poll docstrings. [#567](https://github.com/fleming79/async-kernel/pull/567)
13
+
14
+ ### <!-- 5 --> 📝 Documentation
15
+
16
+ - ZMQPoll typing and doc fixes. [#568](https://github.com/fleming79/async-kernel/pull/568)
17
+
18
+ ### <!-- 6 --> 🌀 Miscellaneous
19
+
20
+ - Refactor create_interface_messge_callback_handler to only use one connection [#569](https://github.com/fleming79/async-kernel/pull/569)
21
+
8
22
  ## [0.22.0] - 2026-08-17
9
23
 
10
24
  ### <!-- 0 --> 🏗️ Breaking changes
@@ -37,6 +51,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
37
51
 
38
52
  ### <!-- 6 --> 🌀 Miscellaneous
39
53
 
54
+ - Prepare for release v0.22.0 [#566](https://github.com/fleming79/async-kernel/pull/566)
55
+
40
56
  - Bump uv from 0.11.32 to 0.12.4 [#562](https://github.com/fleming79/async-kernel/pull/562)
41
57
 
42
58
  - Bump astral-sh/setup-uv from 9.0.0 to 10.0.1 in the actions group [#559](https://github.com/fleming79/async-kernel/pull/559)
@@ -1529,6 +1545,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1529
1545
 
1530
1546
  - Bump the actions group across 1 directory with 2 updates [#3](https://github.com/fleming79/async-kernel/pull/3)
1531
1547
 
1548
+ [0.22.1]: https://github.com/fleming79/async-kernel/compare/v0.22.0..v0.22.1
1532
1549
  [0.22.0]: https://github.com/fleming79/async-kernel/compare/v0.21.0..v0.22.0
1533
1550
  [0.21.0]: https://github.com/fleming79/async-kernel/compare/v0.20.2..v0.21.0
1534
1551
  [0.20.2]: https://github.com/fleming79/async-kernel/compare/v0.20.1..v0.20.2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: async-kernel
3
- Version: 0.22.0
3
+ Version: 0.22.1
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.22.0'
22
- __version_tuple__ = version_tuple = (0, 22, 0)
21
+ __version__ = version = '0.22.1'
22
+ __version_tuple__ = version_tuple = (0, 22, 1)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -949,6 +949,7 @@ class Caller:
949
949
  pass
950
950
  except IndexError:
951
951
  caller = self.get()
952
+ caller._name = f"async-kernel worker of {self.id}"
952
953
  pen = caller.call_soon(func, *args, **kwargs)
953
954
  pen.add_done_callback(_to_thread_on_done)
954
955
  return pen
@@ -14,7 +14,7 @@ from typing import TYPE_CHECKING, Any, Literal, Self
14
14
  import zmq
15
15
  from aiologic import BinarySemaphore, BusyResourceError
16
16
  from typing_extensions import override
17
- from zmq.backend import zmq_poll
17
+ from zmq.backend import zmq_poll as _zmq_poll
18
18
 
19
19
  from async_kernel import Caller, utils
20
20
  from async_kernel.common import Fixed
@@ -144,19 +144,30 @@ class ZMQPollSocket(zmq.sugar.Socket[bytes]):
144
144
  self.zmq_poll.execute(super().unsubscribe, topic)
145
145
 
146
146
 
147
- T_key = tuple[Any | ZMQPollSocket, int]
147
+ T_key = tuple[ZMQPollSocket, int]
148
148
 
149
149
 
150
150
  class ZMQPoll:
151
151
  """A [zmq_poll](https://libzmq.readthedocs.io/en/latest/zmq_poll.html) based event loop.
152
152
 
153
- This event loop is synchronous and is intended for quick message dispatch to separate threads
154
- for handling. It provides thread-safe execution and manages its own custom sockets which use
155
- the same executor for setting attributes and calling methods.
153
+ When a `(socket, flags) -> handler` mapping context manager is used [ZMQPoll.event_handler][]
154
+ `handler` is called with the socket and event when the event occurs on the socket. The
155
+ callback occurs in the zmq poll thread. [async_kernel.caller.Caller][] is recommended to
156
+ to scheduling code execution in different threads.
157
+
158
+ Only [ZMQPollSocket][] sockets created using the [ZMQPoll.socket][] factory function are
159
+ allowed.
160
+
161
+ The methods [ZMQPoll.execute][] and the async version [ZMQPoll.aexecute][] are provided
162
+ to executed code in the zmq_poll thread, which is useful when creating and configuring
163
+ sockets to reduce thread switching.
156
164
  """
157
165
 
158
166
  stopped: Fixed[Self, ProtectedPending] = Fixed(ProtectedPending)
167
+ """Set when the poll thread event has stopped."""
168
+
159
169
  sockets: Fixed[Self, set[ZMQPollSocket]] = Fixed(set)
170
+ """The sockets currently registered with this instance."""
160
171
 
161
172
  def __init__(self) -> None:
162
173
 
@@ -235,7 +246,7 @@ class ZMQPoll:
235
246
  sock.recv()
236
247
 
237
248
  def do_execute() -> None:
238
- """Execute pending items added by the `execute` and `execute_async` methods."""
249
+ """Execute pending items added by the `execute` and `aexecute` methods."""
239
250
  while execute:
240
251
  md = (pen := execute.popleft()).metadata
241
252
  try:
@@ -244,15 +255,16 @@ class ZMQPoll:
244
255
  pen.set_exception(e)
245
256
  del pen
246
257
 
247
- send = context.socket(zmq.SocketType.PAIR)
248
- wake = context.socket(zmq.SocketType.PAIR)
258
+ send: ZMQPollSocket = context.socket(zmq.SocketType.PAIR) # pyright: ignore[reportAssignmentType]
259
+ wake: ZMQPollSocket = context.socket(zmq.SocketType.PAIR) # pyright: ignore[reportAssignmentType]
249
260
  addr = "inproc://async_kernel_zmq_poller_wake"
250
261
  sockets = None
251
262
  handlers[(wake, zmq.POLLIN)] = on_wake
252
263
 
253
264
  with context, wake, send, wake.bind(addr), send.connect(addr):
265
+ k: T_key
254
266
  c: tuple[int, Callable] | None
255
- started.set_result(send) # pyright: ignore[reportArgumentType]
267
+ started.set_result(send)
256
268
  # The main loop polls the handler keys for events in a loop.
257
269
  # It will block until an event occurs.
258
270
  try:
@@ -263,9 +275,9 @@ class ZMQPoll:
263
275
  do_execute()
264
276
  continue
265
277
  try:
266
- for k in zmq_poll(sockets, timeout=-1):
278
+ for k in _zmq_poll(sockets, timeout=-1): # pyright: ignore[reportAssignmentType]
267
279
  try:
268
- handlers[k](*k) # pyright: ignore[reportArgumentType]
280
+ handlers[k](*k)
269
281
  except KeyError:
270
282
  sockets = None
271
283
  except SystemExit:
@@ -317,6 +329,7 @@ class ZMQPoll:
317
329
  self.log.debug("ZMQPoll event loop started")
318
330
 
319
331
  def validate_socket(self, sock: ZMQPollSocket | Any) -> ZMQPollSocket:
332
+ """Check `sock` is correctly registered."""
320
333
  if sock not in self.sockets:
321
334
  msg = f"Invalid socket detected! {sock=}"
322
335
  raise ValueError(msg)
@@ -334,7 +347,7 @@ class ZMQPoll:
334
347
  return self.validate_socket(self.execute(self._zmq_context.socket, socket_type))
335
348
 
336
349
  def execute(self, func: Callable[P, T], /, *args: P.args, **kwargs: P.kwargs) -> T:
337
- """Execute `func` in the thread waiting for the result synchronously."""
350
+ """Execute `func` in the 'zmq_poll' thread waiting for the result synchronously."""
338
351
  if hasattr(self, "thread"):
339
352
  if threading.current_thread() is self.thread:
340
353
  return func(*args, **kwargs)
@@ -346,17 +359,17 @@ class ZMQPoll:
346
359
  finally:
347
360
  pen.metadata.clear()
348
361
  del pen
349
- msg = f"Unable to execute {func=} in {self}. Execution is only support while in context."
362
+ msg = f"Unable to execute {func=} in {self}. Execution is only supported while in context."
350
363
  raise RuntimeError(msg)
351
364
 
352
- async def execute_async(self, func: Callable[P, T], /, *args: P.args, **kwargs: P.kwargs) -> T:
353
- """Execute `func` in the thread waiting for the result asynchronously."""
365
+ async def aexecute(self, func: Callable[P, T], /, *args: P.args, **kwargs: P.kwargs) -> T:
366
+ """Execute `func` in the 'zmq_poll' thread waiting for the result asynchronously."""
354
367
  if hasattr(self, "thread"):
355
368
  self._execute.append(pen := Pending[T](func=func, args=args, kwargs=kwargs))
356
369
  if not self.stopped.done():
357
370
  self._wake()
358
371
  return await pen
359
- msg = f"Unable to execute {func=} in {self}. Execution is only support while in context."
372
+ msg = f"Unable to execute {func=} in {self}. Execution is only supported while in context."
360
373
  raise RuntimeError(msg)
361
374
 
362
375
  @contextmanager
@@ -370,14 +383,12 @@ class ZMQPoll:
370
383
  count: tuple[int, Callable[[], Any]] | None = None,
371
384
  canceller: Callable[[], Any] | NoValue | None = NoValue,
372
385
  ) -> Generator[None, Any, None]:
373
- """A context manager where `handler` is called with the event number when it occurs for `sock`.
386
+ """A context manager where `handler` is called in the 'zmq_poll' thread with the event number when it occurs for `sock`.
374
387
 
375
- Only one `handler` is allowed per `(socket, flags)` combination. If this context manager is entered
376
- inside a caller managed task (call_soon, etc) and the ZMQPoll is stopped, the associated pending
377
- will be cancelled.
388
+ Only one `handler` is allowed per `(socket, flags)` combination.
378
389
 
379
390
  Args:
380
- sock: A zmq socket or a IO style object with a `fileno`.
391
+ sock: A registered [ZMQPollSocket][].
381
392
  handler: A handler to handle the event. The handler is called inside the
382
393
  zmq_poll thread. Thread-safe primitives must be used by the handler such
383
394
  as [async_kernel.caller.Caller.call_soon][],[async_kernel.caller.Caller.queue_call][], etc.
@@ -388,12 +399,16 @@ class ZMQPoll:
388
399
  The callback could be an `event.set` to release the context.
389
400
  canceller: A callback to use on the event the poll is stopped. The default cancellation
390
401
  behavior is to cancel the pending returned by [async_kernel.caller.Caller.current_pending][].
391
- Set to None to disable cancellation support. This is fine when this context manager is inside
392
- the context of this object.
402
+ Set to `None` to disable cancellation support. This is safe when this context manager is
403
+ inside the context of this instance.
404
+
405
+ Raises:
406
+ BusyResourceError: If the `(sock, flags)` combination is already in use.
407
+ RuntimeError: If the default canceller can not be created.
393
408
 
394
409
  Tip:
395
- The handler is called inside a dedicated thread which may have been marked using
396
- [async_kernel.utils.mark_thread_pydev_do_not_trace][] which disables debug breakpoints.
410
+ The zmq_poll thread normally disables debugging in the zmq_poll thread so inserting breakpoints
411
+ in the event handler may interfere with debugging.
397
412
  """
398
413
  assert not self.stopped.done()
399
414
  if canceller is NoValue:
@@ -184,9 +184,6 @@ class Interface(StartStopTask, Application, Generic[T_shell_co]):
184
184
  _connections: tuple[Connection[Self], ...] = ()
185
185
  """The connections to the interface for messaging."""
186
186
 
187
- iopub_send_first_connection_only = traitlets.Bool(False).tag(config=True)
188
- """Send iopub messages on the first connection only."""
189
-
190
187
  _connections_lock = Fixed(BinarySemaphore)
191
188
 
192
189
  shell: Fixed[Self, T_shell_co] = Fixed(lambda c: c["owner"].kernel.main_shell)
@@ -226,10 +223,6 @@ class Interface(StartStopTask, Application, Generic[T_shell_co]):
226
223
 
227
224
  return IPShell
228
225
 
229
- @traitlets.default("iopub_send_first_connection_only")
230
- def _default_iopub_send_first_connection_only(self) -> bool:
231
- return sys.platform == "emscripten"
232
-
233
226
  @classmethod
234
227
  @override
235
228
  def initialized(cls) -> bool:
@@ -490,8 +483,6 @@ class Interface(StartStopTask, Application, Generic[T_shell_co]):
490
483
  for c in self._connections:
491
484
  try:
492
485
  c.iopub_send(msg_type, content, metadata=metadata, parent=parent, ident=ident, buffers=buffers)
493
- if self.iopub_send_first_connection_only:
494
- break
495
486
  except Exception as e:
496
487
  self.log.exception("iopub_send failed for connection %r", c, exc_info=e)
497
488
 
@@ -79,38 +79,39 @@ def create_interface_messge_callback_handler(
79
79
  pack_unpack: tuple[Callable[[Message], T], Callable[[T], Message]] = (pack_json_str, unpack_json),
80
80
  ) -> Callable[[T, BuffersType], None]:
81
81
  ""
82
- cache: dict[str, Connection] = {}
82
+ conn: Connection | None = None
83
83
  lock = BinarySemaphore()
84
84
  session_calls = set()
85
85
  pack, unpack = pack_unpack
86
86
 
87
87
  def handle_msg(packed_msg: T, buffers: BuffersType | None = None) -> None:
88
88
  """Handle a packed message."""
89
+ nonlocal conn
89
90
  msg: Message = unpack(packed_msg)
90
91
  msg["buffers"] = [] if buffers is None else buffers
91
92
  session: str = msg["header"]["session"]
92
93
  session_calls.add(session)
93
- conn: Connection | None
94
94
 
95
- if (conn := cache.get(session)) is None or conn.stopping.done():
95
+ if conn is None:
96
96
  with lock:
97
- if (conn := cache.get(session)) is None or conn.stopping.done():
97
+ if conn is None:
98
98
  conn = Connection(interface.caller, session_id=session)
99
99
 
100
- def transmit_msg(msg: Message, ident: list[bytes]) -> None:
100
+ def transmit_msg(msg: Message, ident: list[bytes], conn=conn) -> None:
101
101
  """Pack and send a message."""
102
- # `ident` is not sent.
102
+ # Jupyterlite
103
+ if parent := msg["parent_header"]:
104
+ msg["header"]["session"] = parent["session"]
105
+
103
106
  buffers: BuffersType = msg.pop("buffers") # pyright: ignore[reportAssignmentType]
104
107
  reply = send(pack(msg), buffers, blocking_reply := msg["channel"] == Channel.stdin)
105
108
  if blocking_reply:
106
- conn.handle_incoming_msg(unpack(reply), [conn.bsession])
109
+ conn.handle_incoming_msg(unpack(reply), [])
107
110
 
108
111
  conn.transmit_msg = transmit_msg
109
112
  conn.stopped.add_done_callback(lambda _: delattr(conn, "transmit_msg"))
110
113
  conn.start()
111
- conn.stopping.add_done_callback(lambda _: cache.pop(session))
112
114
 
113
- cache[session] = conn
114
- conn.handle_incoming_msg(msg, [conn.bsession])
115
+ conn.handle_incoming_msg(msg, [])
115
116
 
116
117
  return handle_msg
@@ -74,9 +74,6 @@ class BaseMessage(StartStopTask, LoggingConfigurable, MessageProtocol):
74
74
  session_id: Fixed[Self, str] = Fixed(lambda c: c["owner"]._session_id)
75
75
  """Used to identify this object as the `session` in a message header."""
76
76
 
77
- bsession: Fixed[Self, bytes] = Fixed[Self, bytes](lambda c: c["owner"].session_id.encode())
78
- """Used to identfiy this object as the origin of a message."""
79
-
80
77
  _pending_messages: Fixed[Self, dict[str, PendingMessage[Any]]] = Fixed(dict)
81
78
  """A mapping of the `msg_id` of message requests to the pending that is resolved with a reply."""
82
79
 
@@ -201,6 +198,13 @@ class Connection(HasInterface[T_interface_co], BaseMessage, Generic[T_interface_
201
198
 
202
199
  @override
203
200
  def handle_incoming_msg(self, msg: Message, ident: list[bytes]) -> None:
201
+ """The handler for messages received on this connection.
202
+
203
+ Args:
204
+ msg: A new message.
205
+ ident: A list of bytes to route a reply message back to the origin. This can
206
+ be an empty list when there is only one connection, such as [LocalClient][].
207
+ """
204
208
  if msg["header"]["msg_type"].endswith("_reply"):
205
209
  self.handle_reply(msg)
206
210
  else:
@@ -211,7 +211,7 @@ class ZMQConnection(ZMQMessage, Connection[T_interface_co], Generic[T_interface_
211
211
  self._sockets[channel] = socket
212
212
  socket.bind(addr)
213
213
 
214
- await self.zmq_poll.execute_async(bind_sockets)
214
+ await self.zmq_poll.aexecute(bind_sockets)
215
215
 
216
216
  @override
217
217
  def connection_info(self) -> str:
@@ -277,7 +277,7 @@ class ZMQClient(BaseClient[T_interface_co], ZMQMessage, Generic[T_interface_co])
277
277
  self._sockets[channel] = socket
278
278
  return socket
279
279
 
280
- return await self.zmq_poll.execute_async(open_socket)
280
+ return await self.zmq_poll.aexecute(open_socket)
281
281
 
282
282
  async def _establish_connection(self, timeout: float | None) -> None:
283
283
  # Wait for welcome
@@ -35,9 +35,7 @@ async def test_start_kernel_callable_interface(anyio_backend: Backend):
35
35
  messages.append(msg)
36
36
  return None
37
37
 
38
- callable_interface = await start_kernel_callable_interface(
39
- send=from_interface, stopped=stopped.set, settings={"Interface.iopub_send_first_connection_only": True}
40
- )
38
+ callable_interface = await start_kernel_callable_interface(send=from_interface, stopped=stopped.set)
41
39
  interface = Interface.instance()
42
40
  async with LocalClient().start() as client:
43
41
  await client.kernel_info()
@@ -158,15 +158,15 @@ class Test_zmq_Poll:
158
158
 
159
159
  async def test_poll_execute_states(self, caller: Caller):
160
160
  zmq_poll = ZMQPoll()
161
- match = "Execution is only support while in context"
161
+ match = "Unable to execute"
162
162
  # Pre-running
163
163
  with pytest.raises(RuntimeError, match=match):
164
164
  zmq_poll.execute(lambda: 1 + 1)
165
165
  with pytest.raises(RuntimeError, match=match):
166
- await zmq_poll.execute_async(lambda: 1 + 1)
166
+ await zmq_poll.aexecute(lambda: 1 + 1)
167
167
  # Running
168
168
  with zmq_poll:
169
- assert (await zmq_poll.execute_async(lambda: 1 + 1)) == 2
169
+ assert (await zmq_poll.aexecute(lambda: 1 + 1)) == 2
170
170
  assert (zmq_poll.execute(lambda: 1 + 1)) == 2
171
171
  with pytest.raises(RuntimeError, match="stopped"), zmq_poll:
172
172
  None # noqa: B018 # pyright: ignore[reportUnusedExpression]
@@ -174,11 +174,11 @@ class Test_zmq_Poll:
174
174
  with pytest.raises(RuntimeError, match=match):
175
175
  assert zmq_poll.execute(threading.current_thread) is zmq_poll.thread
176
176
  with pytest.raises(RuntimeError, match=match):
177
- assert await zmq_poll.execute_async(threading.current_thread) is zmq_poll.thread
177
+ assert await zmq_poll.aexecute(threading.current_thread) is zmq_poll.thread
178
178
 
179
179
  async def test_poll_socket_states(self, caller: Caller):
180
180
  zmq_poll = ZMQPoll()
181
- match = "Execution is only support while in context"
181
+ match = "Unable to execute"
182
182
  # Pre-running
183
183
  with pytest.raises(RuntimeError, match=match):
184
184
  zmq_poll.socket(zmq.SocketType.DEALER)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes