asgiref 3.9.2__py3-none-any.whl → 3.10.0__py3-none-any.whl

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.
asgiref/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "3.9.2"
1
+ __version__ = "3.10.0"
asgiref/sync.py CHANGED
@@ -69,6 +69,45 @@ else:
69
69
  return func
70
70
 
71
71
 
72
+ class AsyncSingleThreadContext:
73
+ """Context manager to run async code inside the same thread.
74
+
75
+ Normally, AsyncToSync functions run either inside a separate ThreadPoolExecutor or
76
+ the main event loop if it exists. This context manager ensures that all AsyncToSync
77
+ functions execute within the same thread.
78
+
79
+ This context manager is re-entrant, so only the outer-most call to
80
+ AsyncSingleThreadContext will set the context.
81
+
82
+ Usage:
83
+
84
+ >>> import asyncio
85
+ >>> with AsyncSingleThreadContext():
86
+ ... async_to_sync(asyncio.sleep(1))()
87
+ """
88
+
89
+ def __init__(self):
90
+ self.token = None
91
+
92
+ def __enter__(self):
93
+ try:
94
+ AsyncToSync.async_single_thread_context.get()
95
+ except LookupError:
96
+ self.token = AsyncToSync.async_single_thread_context.set(self)
97
+
98
+ return self
99
+
100
+ def __exit__(self, exc, value, tb):
101
+ if not self.token:
102
+ return
103
+
104
+ executor = AsyncToSync.context_to_thread_executor.pop(self, None)
105
+ if executor:
106
+ executor.shutdown()
107
+
108
+ AsyncToSync.async_single_thread_context.reset(self.token)
109
+
110
+
72
111
  class ThreadSensitiveContext:
73
112
  """Async context manager to manage context for thread sensitive mode
74
113
 
@@ -131,6 +170,14 @@ class AsyncToSync(Generic[_P, _R]):
131
170
  # inside create_task, we'll look it up here from the running event loop.
132
171
  loop_thread_executors: "Dict[asyncio.AbstractEventLoop, CurrentThreadExecutor]" = {}
133
172
 
173
+ async_single_thread_context: "contextvars.ContextVar[AsyncSingleThreadContext]" = (
174
+ contextvars.ContextVar("async_single_thread_context")
175
+ )
176
+
177
+ context_to_thread_executor: "weakref.WeakKeyDictionary[AsyncSingleThreadContext, ThreadPoolExecutor]" = (
178
+ weakref.WeakKeyDictionary()
179
+ )
180
+
134
181
  def __init__(
135
182
  self,
136
183
  awaitable: Union[
@@ -246,8 +293,24 @@ class AsyncToSync(Generic[_P, _R]):
246
293
  running_in_main_event_loop = False
247
294
 
248
295
  if not running_in_main_event_loop:
249
- # Make our own event loop - in a new thread - and run inside that.
250
- loop_executor = ThreadPoolExecutor(max_workers=1)
296
+ loop_executor = None
297
+
298
+ if self.async_single_thread_context.get(None):
299
+ single_thread_context = self.async_single_thread_context.get()
300
+
301
+ if single_thread_context in self.context_to_thread_executor:
302
+ loop_executor = self.context_to_thread_executor[
303
+ single_thread_context
304
+ ]
305
+ else:
306
+ loop_executor = ThreadPoolExecutor(max_workers=1)
307
+ self.context_to_thread_executor[
308
+ single_thread_context
309
+ ] = loop_executor
310
+ else:
311
+ # Make our own event loop - in a new thread - and run inside that.
312
+ loop_executor = ThreadPoolExecutor(max_workers=1)
313
+
251
314
  loop_future = loop_executor.submit(asyncio.run, new_loop_wrap())
252
315
  # Run the CurrentThreadExecutor until the future is done.
253
316
  current_executor.run_until_future(loop_future)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asgiref
3
- Version: 3.9.2
3
+ Version: 3.10.0
4
4
  Summary: ASGI specs, helper code, and adapters
5
5
  Home-page: https://github.com/django/asgiref/
6
6
  Author: Django Software Foundation
@@ -1,16 +1,16 @@
1
- asgiref/__init__.py,sha256=C8rdAWx8KsGqJyg7wT8baKp3bTQuk-lnx5ayNr4NpKM,22
1
+ asgiref/__init__.py,sha256=iKJAvc5i0UTDDSSefTGL0Tq-kWQ4S3OJJgvyaQfQNF8,23
2
2
  asgiref/compatibility.py,sha256=DhY1SOpOvOw0Y1lSEjCqg-znRUQKecG3LTaV48MZi68,1606
3
3
  asgiref/current_thread_executor.py,sha256=42CU1VODLTk-_PYise-cP1XgyAvI5Djc8f97owFzdrs,4157
4
4
  asgiref/local.py,sha256=ZZeWWIXptVU4GbNApMMWQ-skuglvodcQA5WpzJDMxh4,4912
5
5
  asgiref/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  asgiref/server.py,sha256=3A68169Nuh2sTY_2O5JzRd_opKObWvvrEFcrXssq3kA,6311
7
- asgiref/sync.py,sha256=5dlK0T61pMSNWf--49nUojn0mfduqq6ryuwyXxv2Y-A,20417
7
+ asgiref/sync.py,sha256=CEKxFyePiksUoA7MronOKaF6mmNQxUYZjXlfJZXEQCM,22551
8
8
  asgiref/testing.py,sha256=U5wcs_-ZYTO5SIGfl80EqRAGv_T8BHrAhvAKRuuztT4,4421
9
9
  asgiref/timeout.py,sha256=LtGL-xQpG8JHprdsEUCMErJ0kNWj4qwWZhEHJ3iKu4s,3627
10
10
  asgiref/typing.py,sha256=Zi72AZlOyF1C7N14LLZnpAdfUH4ljoBqFdQo_bBKMq0,6290
11
11
  asgiref/wsgi.py,sha256=J8OAgirfsYHZmxxqIGfFiZ43uq1qKKv2xGMkRISNIo4,6742
12
- asgiref-3.9.2.dist-info/licenses/LICENSE,sha256=uEZBXRtRTpwd_xSiLeuQbXlLxUbKYSn5UKGM0JHipmk,1552
13
- asgiref-3.9.2.dist-info/METADATA,sha256=VOjvFQ67_G0u-NeiTgzrtRKo6W1K_V5fRcH_YnetyJ8,9286
14
- asgiref-3.9.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
- asgiref-3.9.2.dist-info/top_level.txt,sha256=bokQjCzwwERhdBiPdvYEZa4cHxT4NCeAffQNUqJ8ssg,8
16
- asgiref-3.9.2.dist-info/RECORD,,
12
+ asgiref-3.10.0.dist-info/licenses/LICENSE,sha256=uEZBXRtRTpwd_xSiLeuQbXlLxUbKYSn5UKGM0JHipmk,1552
13
+ asgiref-3.10.0.dist-info/METADATA,sha256=TlcKOCn3FwSCGD62jZkbckPRh-RjAhkCLLDnfmDZTyA,9287
14
+ asgiref-3.10.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ asgiref-3.10.0.dist-info/top_level.txt,sha256=bokQjCzwwERhdBiPdvYEZa4cHxT4NCeAffQNUqJ8ssg,8
16
+ asgiref-3.10.0.dist-info/RECORD,,