mode-streaming 0.5.2__tar.gz → 0.5.3__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 (164) hide show
  1. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/.github/workflows/tests.yml +1 -3
  2. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/PKG-INFO +2 -1
  3. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/loops.py +28 -2
  4. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode_streaming.egg-info/PKG-INFO +2 -1
  5. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode_streaming.egg-info/scm_version.json +2 -2
  6. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/pyproject.toml +1 -0
  7. mode_streaming-0.5.3/tests/unit/utils/test_loops.py +94 -0
  8. mode_streaming-0.5.2/tests/unit/utils/test_loops.py +0 -48
  9. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/.editorconfig +0 -0
  10. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/.github/ISSUE_TEMPLATE.md +0 -0
  11. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  12. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/.github/workflows/deploy-docs.yml +0 -0
  13. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/.github/workflows/publish.yml +0 -0
  14. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/.gitignore +0 -0
  15. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/.pre-commit-config.yaml +0 -0
  16. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/AUTHORS +0 -0
  17. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/CHANGELOG.md +0 -0
  18. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/CODE_OF_CONDUCT.md +0 -0
  19. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/CONTRIBUTING.md +0 -0
  20. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/LICENSE +0 -0
  21. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/README.md +0 -0
  22. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/changelog.md +0 -0
  23. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/contributing.md +0 -0
  24. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/creating-service.md +0 -0
  25. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/example-service.md +0 -0
  26. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/example-webapp.md +0 -0
  27. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/index.md +0 -0
  28. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.debug.md +0 -0
  29. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.exceptions.md +0 -0
  30. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.locals.md +0 -0
  31. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.loop.eventlet.md +0 -0
  32. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.loop.gevent.md +0 -0
  33. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.loop.md +0 -0
  34. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.loop.uvloop.md +0 -0
  35. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.proxy.md +0 -0
  36. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.services.md +0 -0
  37. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.signals.md +0 -0
  38. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.supervisors.md +0 -0
  39. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.threads.md +0 -0
  40. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.timers.md +0 -0
  41. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.types.md +0 -0
  42. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.types.services.md +0 -0
  43. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.types.signals.md +0 -0
  44. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.types.supervisors.md +0 -0
  45. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.aiter.md +0 -0
  46. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.collections.md +0 -0
  47. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.compat.md +0 -0
  48. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.contexts.md +0 -0
  49. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.cron.md +0 -0
  50. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.futures.md +0 -0
  51. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.graphs.md +0 -0
  52. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.imports.md +0 -0
  53. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.locals.md +0 -0
  54. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.locks.md +0 -0
  55. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.logging.md +0 -0
  56. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.loops.md +0 -0
  57. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.mocks.md +0 -0
  58. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.objects.md +0 -0
  59. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.queues.md +0 -0
  60. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.text.md +0 -0
  61. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.times.md +0 -0
  62. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.tracebacks.md +0 -0
  63. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.trees.md +0 -0
  64. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.types.graphs.md +0 -0
  65. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.utils.types.trees.md +0 -0
  66. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/docs/references/mode.worker.md +0 -0
  67. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/examples/service.py +0 -0
  68. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/examples/webapp.py +0 -0
  69. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mkdocs.yml +0 -0
  70. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/__init__.py +0 -0
  71. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/debug.py +0 -0
  72. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/exceptions.py +0 -0
  73. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/locals.py +0 -0
  74. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/loop/__init__.py +0 -0
  75. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/loop/_gevent_loop.py +0 -0
  76. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/loop/eventlet.py +0 -0
  77. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/loop/gevent.py +0 -0
  78. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/loop/uvloop.py +0 -0
  79. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/proxy.py +0 -0
  80. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/py.typed +0 -0
  81. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/services.py +0 -0
  82. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/signals.py +0 -0
  83. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/supervisors.py +0 -0
  84. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/threads.py +0 -0
  85. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/timers.py +0 -0
  86. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/types/__init__.py +0 -0
  87. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/types/services.py +0 -0
  88. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/types/signals.py +0 -0
  89. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/types/supervisors.py +0 -0
  90. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/__init__.py +0 -0
  91. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/aiter.py +0 -0
  92. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/collections.py +0 -0
  93. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/compat.py +0 -0
  94. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/contexts.py +0 -0
  95. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/cron.py +0 -0
  96. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/futures.py +0 -0
  97. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/graphs/__init__.py +0 -0
  98. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/graphs/formatter.py +0 -0
  99. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/graphs/graph.py +0 -0
  100. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/imports.py +0 -0
  101. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/locals.py +0 -0
  102. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/locks.py +0 -0
  103. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/logging.py +0 -0
  104. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/mocks.py +0 -0
  105. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/objects.py +0 -0
  106. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/queues.py +0 -0
  107. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/text.py +0 -0
  108. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/times.py +0 -0
  109. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/tracebacks.py +0 -0
  110. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/trees.py +0 -0
  111. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/types/__init__.py +0 -0
  112. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/types/graphs.py +0 -0
  113. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/utils/types/trees.py +0 -0
  114. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode/worker.py +0 -0
  115. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode_streaming.egg-info/SOURCES.txt +0 -0
  116. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode_streaming.egg-info/dependency_links.txt +0 -0
  117. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode_streaming.egg-info/requires.txt +0 -0
  118. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode_streaming.egg-info/scm_file_list.json +0 -0
  119. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/mode_streaming.egg-info/top_level.txt +0 -0
  120. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/requirements-docs.txt +0 -0
  121. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/requirements-tests.txt +0 -0
  122. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/requirements.txt +0 -0
  123. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/scripts/build-docs.sh +0 -0
  124. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/scripts/build.sh +0 -0
  125. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/scripts/bump.sh +0 -0
  126. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/scripts/clean.sh +0 -0
  127. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/scripts/format.sh +0 -0
  128. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/scripts/lint.sh +0 -0
  129. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/scripts/tests.sh +0 -0
  130. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/setup.cfg +0 -0
  131. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/__init__.py +0 -0
  132. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/conftest.py +0 -0
  133. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/__init__.py +0 -0
  134. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/test_mode.py +0 -0
  135. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/test_proxy.py +0 -0
  136. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/test_service.py +0 -0
  137. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/test_signals.py +0 -0
  138. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/test_supervisors.py +0 -0
  139. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/test_timers.py +0 -0
  140. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/utils/__init__.py +0 -0
  141. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/utils/test_aiter.py +0 -0
  142. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/utils/test_collections.py +0 -0
  143. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/utils/test_compat.py +0 -0
  144. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/utils/test_contexts.py +0 -0
  145. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/utils/test_futures.py +0 -0
  146. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/utils/test_locals.py +0 -0
  147. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/utils/test_queues.py +0 -0
  148. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/utils/test_text.py +0 -0
  149. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/utils/test_times.py +0 -0
  150. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/utils/test_tracebacks.py +0 -0
  151. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/functional/utils/test_trees.py +0 -0
  152. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/unit/__init__.py +0 -0
  153. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/unit/test_debug.py +0 -0
  154. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/unit/test_locals.py +0 -0
  155. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/unit/test_services.py +0 -0
  156. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/unit/test_supervisors.py +0 -0
  157. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/unit/test_threads.py +0 -0
  158. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/unit/test_worker.py +0 -0
  159. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/unit/utils/__init__.py +0 -0
  160. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/unit/utils/test_cron.py +0 -0
  161. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/unit/utils/test_imports.py +0 -0
  162. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/unit/utils/test_locks.py +0 -0
  163. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/unit/utils/test_logging.py +0 -0
  164. {mode_streaming-0.5.2 → mode_streaming-0.5.3}/tests/unit/utils/test_objects.py +0 -0
@@ -26,10 +26,8 @@ jobs:
26
26
  - "3.11"
27
27
  - "3.12"
28
28
  - "3.13"
29
+ - "3.14"
29
30
  experimental: [ false ]
30
- include:
31
- - python-version: "~3.14.0-0"
32
- experimental: true
33
31
 
34
32
  steps:
35
33
  - uses: actions/checkout@v3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mode-streaming
3
- Version: 0.5.2
3
+ Version: 0.5.3
4
4
  Summary: AsyncIO Service-based programming
5
5
  Author-email: Ask Solem Hoel <ask@robinhood.com>
6
6
  Maintainer-email: William Barnhart <williambbarnhart@gmail.com>
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
20
21
  Classifier: Programming Language :: Python :: Implementation :: CPython
21
22
  Classifier: Programming Language :: Python :: Implementation :: PyPy
22
23
  Classifier: Operating System :: POSIX
@@ -1,10 +1,19 @@
1
1
  """Event loop utilities."""
2
2
 
3
3
  import asyncio
4
+ import threading
4
5
  from typing import Any, Callable, Optional
5
6
 
6
7
  __all__ = ["call_asap", "clone_loop", "get_event_loop"]
7
8
 
9
+ #: Per-thread cache of the loop resolved by :func:`get_event_loop` for when
10
+ #: there is no *running* loop. ``threading.local`` gives every thread its
11
+ #: own attribute storage for free (no cross-thread leakage, no explicit
12
+ #: cleanup required when a thread exits) -- required for correctness, not
13
+ #: just performance, since Mode creates and runs a dedicated event loop per
14
+ #: :class:`~mode.threads.ServiceThread` worker thread.
15
+ _current_loop = threading.local()
16
+
8
17
 
9
18
  def get_event_loop() -> asyncio.AbstractEventLoop:
10
19
  """Return the current event loop, creating one if necessary.
@@ -20,17 +29,34 @@ def get_event_loop() -> asyncio.AbstractEventLoop:
20
29
  loop -- e.g. at import time, when agents/services are declared at module
21
30
  level -- so it needs the historical "get or create" semantics. This
22
31
  restores them in a way that works across Python 3.9-3.14.
32
+
33
+ Whether a loop is currently *running* can change on every call (that's
34
+ the whole point of an event loop), so :func:`asyncio.get_running_loop`
35
+ must always be re-checked and can't be cached. When nothing is running,
36
+ though, the resolved loop is stable for the lifetime of the thread that
37
+ resolved it, so it's cached in thread-local storage: once a thread has
38
+ created (or found) its loop, later calls on that thread return the
39
+ cached reference directly instead of re-running the
40
+ ``asyncio.get_event_loop()``-raises-then-create dance every time.
23
41
  """
24
42
  try:
25
43
  return asyncio.get_running_loop()
26
44
  except RuntimeError:
27
45
  pass
46
+
47
+ loop: Optional[asyncio.AbstractEventLoop] = getattr(
48
+ _current_loop, "loop", None
49
+ )
50
+ if loop is not None and not loop.is_closed():
51
+ return loop
52
+
28
53
  try:
29
- return asyncio.get_event_loop()
54
+ loop = asyncio.get_event_loop()
30
55
  except RuntimeError:
31
56
  loop = asyncio.new_event_loop()
32
57
  asyncio.set_event_loop(loop)
33
- return loop
58
+ _current_loop.loop = loop
59
+ return loop
34
60
 
35
61
 
36
62
  def _is_unix_loop(loop: asyncio.AbstractEventLoop) -> bool:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mode-streaming
3
- Version: 0.5.2
3
+ Version: 0.5.3
4
4
  Summary: AsyncIO Service-based programming
5
5
  Author-email: Ask Solem Hoel <ask@robinhood.com>
6
6
  Maintainer-email: William Barnhart <williambbarnhart@gmail.com>
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
20
21
  Classifier: Programming Language :: Python :: Implementation :: CPython
21
22
  Classifier: Programming Language :: Python :: Implementation :: PyPy
22
23
  Classifier: Operating System :: POSIX
@@ -1,7 +1,7 @@
1
1
  {
2
- "tag": "0.5.2",
2
+ "tag": "0.5.3",
3
3
  "distance": 0,
4
- "node": "g95118563cf82e6445e255bcf2fa7beeb1b23b5d3",
4
+ "node": "g9a3d6aeae585ec2ef1b322756c53c6f1af9b0bf2",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-07-19"
@@ -40,6 +40,7 @@ classifiers = [
40
40
  "Programming Language :: Python :: 3.11",
41
41
  "Programming Language :: Python :: 3.12",
42
42
  "Programming Language :: Python :: 3.13",
43
+ "Programming Language :: Python :: 3.14",
43
44
  "Programming Language :: Python :: Implementation :: CPython",
44
45
  "Programming Language :: Python :: Implementation :: PyPy",
45
46
  "Operating System :: POSIX",
@@ -0,0 +1,94 @@
1
+ import asyncio
2
+ import threading
3
+ from unittest.mock import patch
4
+
5
+ from mode.utils.loops import get_event_loop
6
+
7
+
8
+ def test_get_event_loop__returns_running_loop_when_running():
9
+ async def main():
10
+ return get_event_loop()
11
+
12
+ running = asyncio.new_event_loop()
13
+ try:
14
+ got = running.run_until_complete(main())
15
+ assert got is running
16
+ finally:
17
+ running.close()
18
+
19
+
20
+ def test_get_event_loop__reuses_current_loop_when_set():
21
+ loop = asyncio.new_event_loop()
22
+ asyncio.set_event_loop(loop)
23
+ try:
24
+ assert get_event_loop() is loop
25
+ finally:
26
+ asyncio.set_event_loop(None)
27
+ loop.close()
28
+
29
+
30
+ def test_get_event_loop__creates_loop_when_none_set():
31
+ # Emulates Python 3.14, where asyncio.get_event_loop() no longer creates
32
+ # a loop implicitly and raises RuntimeError instead.
33
+ asyncio.set_event_loop(None)
34
+ loop = get_event_loop()
35
+ try:
36
+ assert isinstance(loop, asyncio.AbstractEventLoop)
37
+ assert asyncio.get_event_loop() is loop
38
+ finally:
39
+ asyncio.set_event_loop(None)
40
+ loop.close()
41
+
42
+
43
+ def test_get_event_loop__is_idempotent_without_running_loop():
44
+ asyncio.set_event_loop(None)
45
+ first = get_event_loop()
46
+ try:
47
+ assert get_event_loop() is first
48
+ finally:
49
+ asyncio.set_event_loop(None)
50
+ first.close()
51
+
52
+
53
+ def test_get_event_loop__caches_loop_without_repeated_lookup():
54
+ # The whole point of the thread-local cache: once a loop has been
55
+ # resolved for a thread, later calls on that thread must not re-invoke
56
+ # (and potentially re-raise/re-catch RuntimeError from)
57
+ # asyncio.get_event_loop() again.
58
+ asyncio.set_event_loop(None)
59
+ first = get_event_loop()
60
+ try:
61
+ with patch(
62
+ "mode.utils.loops.asyncio.get_event_loop"
63
+ ) as get_event_loop_mock:
64
+ second = get_event_loop()
65
+ assert second is first
66
+ get_event_loop_mock.assert_not_called()
67
+ finally:
68
+ asyncio.set_event_loop(None)
69
+ first.close()
70
+
71
+
72
+ def test_get_event_loop__caches_per_thread_not_globally():
73
+ # Mode runs a dedicated event loop per ServiceThread worker thread
74
+ # (see mode.threads.ServiceThread), so the cache must be thread-local:
75
+ # one thread's cached loop must never leak into another thread's call.
76
+ asyncio.set_event_loop(None)
77
+ main_loop = get_event_loop()
78
+ other_loop_holder: dict = {}
79
+
80
+ def other_thread() -> None:
81
+ other_loop_holder["loop"] = get_event_loop()
82
+
83
+ try:
84
+ thread = threading.Thread(target=other_thread)
85
+ thread.start()
86
+ thread.join()
87
+
88
+ other_loop = other_loop_holder["loop"]
89
+ assert other_loop is not main_loop
90
+ assert get_event_loop() is main_loop
91
+ finally:
92
+ asyncio.set_event_loop(None)
93
+ main_loop.close()
94
+ other_loop_holder["loop"].close()
@@ -1,48 +0,0 @@
1
- import asyncio
2
-
3
- from mode.utils.loops import get_event_loop
4
-
5
-
6
- def test_get_event_loop__returns_running_loop_when_running():
7
- async def main():
8
- return get_event_loop()
9
-
10
- running = asyncio.new_event_loop()
11
- try:
12
- got = running.run_until_complete(main())
13
- assert got is running
14
- finally:
15
- running.close()
16
-
17
-
18
- def test_get_event_loop__reuses_current_loop_when_set():
19
- loop = asyncio.new_event_loop()
20
- asyncio.set_event_loop(loop)
21
- try:
22
- assert get_event_loop() is loop
23
- finally:
24
- asyncio.set_event_loop(None)
25
- loop.close()
26
-
27
-
28
- def test_get_event_loop__creates_loop_when_none_set():
29
- # Emulates Python 3.14, where asyncio.get_event_loop() no longer creates
30
- # a loop implicitly and raises RuntimeError instead.
31
- asyncio.set_event_loop(None)
32
- loop = get_event_loop()
33
- try:
34
- assert isinstance(loop, asyncio.AbstractEventLoop)
35
- assert asyncio.get_event_loop() is loop
36
- finally:
37
- asyncio.set_event_loop(None)
38
- loop.close()
39
-
40
-
41
- def test_get_event_loop__is_idempotent_without_running_loop():
42
- asyncio.set_event_loop(None)
43
- first = get_event_loop()
44
- try:
45
- assert get_event_loop() is first
46
- finally:
47
- asyncio.set_event_loop(None)
48
- first.close()
File without changes
File without changes
File without changes
File without changes