asgiref 3.8.1__py3-none-any.whl → 3.9.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 +1 -1
- asgiref/current_thread_executor.py +42 -34
- asgiref/local.py +18 -14
- asgiref/server.py +18 -2
- asgiref/sync.py +41 -70
- asgiref/testing.py +47 -13
- asgiref/typing.py +1 -0
- {asgiref-3.8.1.dist-info → asgiref-3.9.0.dist-info}/METADATA +11 -10
- asgiref-3.9.0.dist-info/RECORD +16 -0
- {asgiref-3.8.1.dist-info → asgiref-3.9.0.dist-info}/WHEEL +1 -1
- asgiref-3.8.1.dist-info/RECORD +0 -16
- {asgiref-3.8.1.dist-info → asgiref-3.9.0.dist-info/licenses}/LICENSE +0 -0
- {asgiref-3.8.1.dist-info → asgiref-3.9.0.dist-info}/top_level.txt +0 -0
asgiref/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "3.
|
|
1
|
+
__version__ = "3.9.0"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import queue
|
|
2
1
|
import sys
|
|
3
2
|
import threading
|
|
3
|
+
from collections import deque
|
|
4
4
|
from concurrent.futures import Executor, Future
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import Any, Callable, TypeVar
|
|
6
6
|
|
|
7
7
|
if sys.version_info >= (3, 10):
|
|
8
8
|
from typing import ParamSpec
|
|
@@ -53,10 +53,12 @@ class CurrentThreadExecutor(Executor):
|
|
|
53
53
|
the thread they came from.
|
|
54
54
|
"""
|
|
55
55
|
|
|
56
|
-
def __init__(self) -> None:
|
|
56
|
+
def __init__(self, old_executor: "CurrentThreadExecutor | None") -> None:
|
|
57
57
|
self._work_thread = threading.current_thread()
|
|
58
|
-
self.
|
|
59
|
-
self.
|
|
58
|
+
self._work_ready = threading.Condition(threading.Lock())
|
|
59
|
+
self._work_items = deque[_WorkItem]() # synchronized by _work_ready
|
|
60
|
+
self._broken = False # synchronized by _work_ready
|
|
61
|
+
self._old_executor = old_executor
|
|
60
62
|
|
|
61
63
|
def run_until_future(self, future: "Future[Any]") -> None:
|
|
62
64
|
"""
|
|
@@ -68,24 +70,30 @@ class CurrentThreadExecutor(Executor):
|
|
|
68
70
|
raise RuntimeError(
|
|
69
71
|
"You cannot run CurrentThreadExecutor from a different thread"
|
|
70
72
|
)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
|
|
74
|
+
def done(future: "Future[Any]") -> None:
|
|
75
|
+
with self._work_ready:
|
|
76
|
+
self._broken = True
|
|
77
|
+
self._work_ready.notify()
|
|
78
|
+
|
|
79
|
+
future.add_done_callback(done)
|
|
80
|
+
# Keep getting and running work items until the future we're waiting for
|
|
81
|
+
# is done and the queue is empty.
|
|
82
|
+
while True:
|
|
83
|
+
with self._work_ready:
|
|
84
|
+
while not self._work_items and not self._broken:
|
|
85
|
+
self._work_ready.wait()
|
|
86
|
+
if not self._work_items:
|
|
87
|
+
break
|
|
76
88
|
# Get a work item and run it
|
|
77
|
-
work_item = self.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
assert isinstance(work_item, _WorkItem)
|
|
81
|
-
work_item.run()
|
|
82
|
-
del work_item
|
|
83
|
-
finally:
|
|
84
|
-
self._broken = True
|
|
89
|
+
work_item = self._work_items.popleft()
|
|
90
|
+
work_item.run()
|
|
91
|
+
del work_item
|
|
85
92
|
|
|
86
|
-
def
|
|
93
|
+
def submit(
|
|
87
94
|
self,
|
|
88
95
|
fn: Callable[_P, _R],
|
|
96
|
+
/,
|
|
89
97
|
*args: _P.args,
|
|
90
98
|
**kwargs: _P.kwargs,
|
|
91
99
|
) -> "Future[_R]":
|
|
@@ -94,22 +102,22 @@ class CurrentThreadExecutor(Executor):
|
|
|
94
102
|
raise RuntimeError(
|
|
95
103
|
"You cannot submit onto CurrentThreadExecutor from its own thread"
|
|
96
104
|
)
|
|
97
|
-
# Check they're not too late or the executor errored
|
|
98
|
-
if self._broken:
|
|
99
|
-
raise RuntimeError("CurrentThreadExecutor already quit or is broken")
|
|
100
|
-
# Add to work queue
|
|
101
105
|
f: "Future[_R]" = Future()
|
|
102
106
|
work_item = _WorkItem(f, fn, *args, **kwargs)
|
|
103
|
-
self._work_queue.put(work_item)
|
|
104
|
-
# Return the future
|
|
105
|
-
return f
|
|
106
107
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
# Walk up the CurrentThreadExecutor stack to find the closest one still
|
|
109
|
+
# running
|
|
110
|
+
executor = self
|
|
111
|
+
while True:
|
|
112
|
+
with executor._work_ready:
|
|
113
|
+
if not executor._broken:
|
|
114
|
+
# Add to work queue
|
|
115
|
+
executor._work_items.append(work_item)
|
|
116
|
+
executor._work_ready.notify()
|
|
117
|
+
break
|
|
118
|
+
if executor._old_executor is None:
|
|
119
|
+
raise RuntimeError("CurrentThreadExecutor already quit or is broken")
|
|
120
|
+
executor = executor._old_executor
|
|
113
121
|
|
|
114
|
-
|
|
115
|
-
|
|
122
|
+
# Return the future
|
|
123
|
+
return f
|
asgiref/local.py
CHANGED
|
@@ -2,37 +2,38 @@ import asyncio
|
|
|
2
2
|
import contextlib
|
|
3
3
|
import contextvars
|
|
4
4
|
import threading
|
|
5
|
-
from typing import Any,
|
|
5
|
+
from typing import Any, Union
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class _CVar:
|
|
9
9
|
"""Storage utility for Local."""
|
|
10
10
|
|
|
11
11
|
def __init__(self) -> None:
|
|
12
|
-
self._data:
|
|
13
|
-
"asgiref.local"
|
|
14
|
-
)
|
|
12
|
+
self._data: dict[str, contextvars.ContextVar[Any]] = {}
|
|
15
13
|
|
|
16
|
-
def __getattr__(self, key):
|
|
17
|
-
storage_object = self._data.get({})
|
|
14
|
+
def __getattr__(self, key: str) -> Any:
|
|
18
15
|
try:
|
|
19
|
-
|
|
16
|
+
var = self._data[key]
|
|
20
17
|
except KeyError:
|
|
21
18
|
raise AttributeError(f"{self!r} object has no attribute {key!r}")
|
|
22
19
|
|
|
20
|
+
try:
|
|
21
|
+
return var.get()
|
|
22
|
+
except LookupError:
|
|
23
|
+
raise AttributeError(f"{self!r} object has no attribute {key!r}")
|
|
24
|
+
|
|
23
25
|
def __setattr__(self, key: str, value: Any) -> None:
|
|
24
26
|
if key == "_data":
|
|
25
27
|
return super().__setattr__(key, value)
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
var = self._data.get(key)
|
|
30
|
+
if var is None:
|
|
31
|
+
self._data[key] = var = contextvars.ContextVar(key)
|
|
32
|
+
var.set(value)
|
|
30
33
|
|
|
31
34
|
def __delattr__(self, key: str) -> None:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
del storage_object[key]
|
|
35
|
-
self._data.set(storage_object)
|
|
35
|
+
if key in self._data:
|
|
36
|
+
del self._data[key]
|
|
36
37
|
else:
|
|
37
38
|
raise AttributeError(f"{self!r} object has no attribute {key!r}")
|
|
38
39
|
|
|
@@ -82,12 +83,15 @@ class Local:
|
|
|
82
83
|
def _lock_storage(self):
|
|
83
84
|
# Thread safe access to storage
|
|
84
85
|
if self._thread_critical:
|
|
86
|
+
is_async = True
|
|
85
87
|
try:
|
|
86
88
|
# this is a test for are we in a async or sync
|
|
87
89
|
# thread - will raise RuntimeError if there is
|
|
88
90
|
# no current loop
|
|
89
91
|
asyncio.get_running_loop()
|
|
90
92
|
except RuntimeError:
|
|
93
|
+
is_async = False
|
|
94
|
+
if not is_async:
|
|
91
95
|
# We are in a sync thread, the storage is
|
|
92
96
|
# just the plain thread local (i.e, "global within
|
|
93
97
|
# this thread" - it doesn't matter where you are
|
asgiref/server.py
CHANGED
|
@@ -57,12 +57,28 @@ class StatelessServer:
|
|
|
57
57
|
Runs the asyncio event loop with our handler loop.
|
|
58
58
|
"""
|
|
59
59
|
event_loop = asyncio.get_event_loop()
|
|
60
|
-
asyncio.ensure_future(self.application_checker())
|
|
61
60
|
try:
|
|
62
|
-
event_loop.run_until_complete(self.
|
|
61
|
+
event_loop.run_until_complete(self.arun())
|
|
63
62
|
except KeyboardInterrupt:
|
|
64
63
|
logger.info("Exiting due to Ctrl-C/interrupt")
|
|
65
64
|
|
|
65
|
+
async def arun(self):
|
|
66
|
+
"""
|
|
67
|
+
Runs the asyncio event loop with our handler loop.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
class Done(Exception):
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
async def handle():
|
|
74
|
+
await self.handle()
|
|
75
|
+
raise Done
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
await asyncio.gather(self.application_checker(), handle())
|
|
79
|
+
except Done:
|
|
80
|
+
pass
|
|
81
|
+
|
|
66
82
|
async def handle(self):
|
|
67
83
|
raise NotImplementedError("You must implement handle()")
|
|
68
84
|
|
asgiref/sync.py
CHANGED
|
@@ -179,15 +179,14 @@ class AsyncToSync(Generic[_P, _R]):
|
|
|
179
179
|
|
|
180
180
|
# You can't call AsyncToSync from a thread with a running event loop
|
|
181
181
|
try:
|
|
182
|
-
|
|
182
|
+
asyncio.get_running_loop()
|
|
183
183
|
except RuntimeError:
|
|
184
184
|
pass
|
|
185
185
|
else:
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
)
|
|
186
|
+
raise RuntimeError(
|
|
187
|
+
"You cannot use AsyncToSync in the same thread as an async event loop - "
|
|
188
|
+
"just await the async function directly."
|
|
189
|
+
)
|
|
191
190
|
|
|
192
191
|
# Make a future for the return information
|
|
193
192
|
call_result: "Future[_R]" = Future()
|
|
@@ -196,7 +195,7 @@ class AsyncToSync(Generic[_P, _R]):
|
|
|
196
195
|
# need one for every sync frame, even if there's one above us in the
|
|
197
196
|
# same thread.
|
|
198
197
|
old_executor = getattr(self.executors, "current", None)
|
|
199
|
-
current_executor = CurrentThreadExecutor()
|
|
198
|
+
current_executor = CurrentThreadExecutor(old_executor)
|
|
200
199
|
self.executors.current = current_executor
|
|
201
200
|
|
|
202
201
|
# Wrapping context in list so it can be reassigned from within
|
|
@@ -207,7 +206,6 @@ class AsyncToSync(Generic[_P, _R]):
|
|
|
207
206
|
# an asyncio.CancelledError to.
|
|
208
207
|
task_context = getattr(SyncToAsync.threadlocal, "task_context", None)
|
|
209
208
|
|
|
210
|
-
loop = None
|
|
211
209
|
# Use call_soon_threadsafe to schedule a synchronous callback on the
|
|
212
210
|
# main event loop's thread if it's there, otherwise make a new loop
|
|
213
211
|
# in this thread.
|
|
@@ -217,35 +215,45 @@ class AsyncToSync(Generic[_P, _R]):
|
|
|
217
215
|
sys.exc_info(),
|
|
218
216
|
task_context,
|
|
219
217
|
context,
|
|
220
|
-
|
|
221
|
-
|
|
218
|
+
# prepare an awaitable which can be passed as is to self.main_wrap,
|
|
219
|
+
# so that `args` and `kwargs` don't need to be
|
|
220
|
+
# destructured when passed to self.main_wrap
|
|
221
|
+
# (which is required by `ParamSpec`)
|
|
222
|
+
# as that may cause overlapping arguments
|
|
223
|
+
self.awaitable(*args, **kwargs),
|
|
222
224
|
)
|
|
223
225
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
loop = asyncio.new_event_loop()
|
|
226
|
+
async def new_loop_wrap() -> None:
|
|
227
|
+
loop = asyncio.get_running_loop()
|
|
227
228
|
self.loop_thread_executors[loop] = current_executor
|
|
229
|
+
try:
|
|
230
|
+
await awaitable
|
|
231
|
+
finally:
|
|
232
|
+
del self.loop_thread_executors[loop]
|
|
233
|
+
|
|
234
|
+
if self.main_event_loop is not None:
|
|
235
|
+
try:
|
|
236
|
+
self.main_event_loop.call_soon_threadsafe(
|
|
237
|
+
self.main_event_loop.create_task, awaitable
|
|
238
|
+
)
|
|
239
|
+
except RuntimeError:
|
|
240
|
+
running_in_main_event_loop = False
|
|
241
|
+
else:
|
|
242
|
+
running_in_main_event_loop = True
|
|
243
|
+
# Run the CurrentThreadExecutor until the future is done.
|
|
244
|
+
current_executor.run_until_future(call_result)
|
|
245
|
+
else:
|
|
246
|
+
running_in_main_event_loop = False
|
|
247
|
+
|
|
248
|
+
if not running_in_main_event_loop:
|
|
249
|
+
# Make our own event loop - in a new thread - and run inside that.
|
|
228
250
|
loop_executor = ThreadPoolExecutor(max_workers=1)
|
|
229
|
-
loop_future = loop_executor.submit(
|
|
230
|
-
|
|
231
|
-
)
|
|
232
|
-
if current_executor:
|
|
233
|
-
# Run the CurrentThreadExecutor until the future is done
|
|
234
|
-
current_executor.run_until_future(loop_future)
|
|
251
|
+
loop_future = loop_executor.submit(asyncio.run, new_loop_wrap())
|
|
252
|
+
# Run the CurrentThreadExecutor until the future is done.
|
|
253
|
+
current_executor.run_until_future(loop_future)
|
|
235
254
|
# Wait for future and/or allow for exception propagation
|
|
236
255
|
loop_future.result()
|
|
237
|
-
else:
|
|
238
|
-
# Call it inside the existing loop
|
|
239
|
-
self.main_event_loop.call_soon_threadsafe(
|
|
240
|
-
self.main_event_loop.create_task, awaitable
|
|
241
|
-
)
|
|
242
|
-
if current_executor:
|
|
243
|
-
# Run the CurrentThreadExecutor until the future is done
|
|
244
|
-
current_executor.run_until_future(call_result)
|
|
245
256
|
finally:
|
|
246
|
-
# Clean up any executor we were running
|
|
247
|
-
if loop is not None:
|
|
248
|
-
del self.loop_thread_executors[loop]
|
|
249
257
|
_restore_context(context[0])
|
|
250
258
|
# Restore old current thread executor state
|
|
251
259
|
self.executors.current = old_executor
|
|
@@ -253,42 +261,6 @@ class AsyncToSync(Generic[_P, _R]):
|
|
|
253
261
|
# Wait for results from the future.
|
|
254
262
|
return call_result.result()
|
|
255
263
|
|
|
256
|
-
def _run_event_loop(self, loop, coro):
|
|
257
|
-
"""
|
|
258
|
-
Runs the given event loop (designed to be called in a thread).
|
|
259
|
-
"""
|
|
260
|
-
asyncio.set_event_loop(loop)
|
|
261
|
-
try:
|
|
262
|
-
loop.run_until_complete(coro)
|
|
263
|
-
finally:
|
|
264
|
-
try:
|
|
265
|
-
# mimic asyncio.run() behavior
|
|
266
|
-
# cancel unexhausted async generators
|
|
267
|
-
tasks = asyncio.all_tasks(loop)
|
|
268
|
-
for task in tasks:
|
|
269
|
-
task.cancel()
|
|
270
|
-
|
|
271
|
-
async def gather():
|
|
272
|
-
await asyncio.gather(*tasks, return_exceptions=True)
|
|
273
|
-
|
|
274
|
-
loop.run_until_complete(gather())
|
|
275
|
-
for task in tasks:
|
|
276
|
-
if task.cancelled():
|
|
277
|
-
continue
|
|
278
|
-
if task.exception() is not None:
|
|
279
|
-
loop.call_exception_handler(
|
|
280
|
-
{
|
|
281
|
-
"message": "unhandled exception during loop shutdown",
|
|
282
|
-
"exception": task.exception(),
|
|
283
|
-
"task": task,
|
|
284
|
-
}
|
|
285
|
-
)
|
|
286
|
-
if hasattr(loop, "shutdown_asyncgens"):
|
|
287
|
-
loop.run_until_complete(loop.shutdown_asyncgens())
|
|
288
|
-
finally:
|
|
289
|
-
loop.close()
|
|
290
|
-
asyncio.set_event_loop(self.main_event_loop)
|
|
291
|
-
|
|
292
264
|
def __get__(self, parent: Any, objtype: Any) -> Callable[_P, _R]:
|
|
293
265
|
"""
|
|
294
266
|
Include self for methods
|
|
@@ -302,8 +274,7 @@ class AsyncToSync(Generic[_P, _R]):
|
|
|
302
274
|
exc_info: "OptExcInfo",
|
|
303
275
|
task_context: "Optional[List[asyncio.Task[Any]]]",
|
|
304
276
|
context: List[contextvars.Context],
|
|
305
|
-
|
|
306
|
-
**kwargs: _P.kwargs,
|
|
277
|
+
awaitable: Union[Coroutine[Any, Any, _R], Awaitable[_R]],
|
|
307
278
|
) -> None:
|
|
308
279
|
"""
|
|
309
280
|
Wraps the awaitable with something that puts the result into the
|
|
@@ -326,9 +297,9 @@ class AsyncToSync(Generic[_P, _R]):
|
|
|
326
297
|
try:
|
|
327
298
|
raise exc_info[1]
|
|
328
299
|
except BaseException:
|
|
329
|
-
result = await
|
|
300
|
+
result = await awaitable
|
|
330
301
|
else:
|
|
331
|
-
result = await
|
|
302
|
+
result = await awaitable
|
|
332
303
|
except BaseException as e:
|
|
333
304
|
call_result.set_exception(e)
|
|
334
305
|
else:
|
asgiref/testing.py
CHANGED
|
@@ -13,18 +13,40 @@ class ApplicationCommunicator:
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
def __init__(self, application, scope):
|
|
16
|
+
self._future = None
|
|
16
17
|
self.application = guarantee_single_callable(application)
|
|
17
18
|
self.scope = scope
|
|
18
|
-
self.
|
|
19
|
-
self.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
self.
|
|
27
|
-
|
|
19
|
+
self._input_queue = None
|
|
20
|
+
self._output_queue = None
|
|
21
|
+
|
|
22
|
+
# For Python 3.9 we need to lazily bind the queues, on 3.10+ they bind the
|
|
23
|
+
# event loop lazily.
|
|
24
|
+
@property
|
|
25
|
+
def input_queue(self):
|
|
26
|
+
if self._input_queue is None:
|
|
27
|
+
self._input_queue = asyncio.Queue()
|
|
28
|
+
return self._input_queue
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def output_queue(self):
|
|
32
|
+
if self._output_queue is None:
|
|
33
|
+
self._output_queue = asyncio.Queue()
|
|
34
|
+
return self._output_queue
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def future(self):
|
|
38
|
+
if self._future is None:
|
|
39
|
+
# Clear context - this ensures that context vars set in the testing scope
|
|
40
|
+
# are not "leaked" into the application which would normally begin with
|
|
41
|
+
# an empty context. In Python >= 3.11 this could also be written as:
|
|
42
|
+
# asyncio.create_task(..., context=contextvars.Context())
|
|
43
|
+
self._future = contextvars.Context().run(
|
|
44
|
+
asyncio.create_task,
|
|
45
|
+
self.application(
|
|
46
|
+
self.scope, self.input_queue.get, self.output_queue.put
|
|
47
|
+
),
|
|
48
|
+
)
|
|
49
|
+
return self._future
|
|
28
50
|
|
|
29
51
|
async def wait(self, timeout=1):
|
|
30
52
|
"""
|
|
@@ -46,11 +68,15 @@ class ApplicationCommunicator:
|
|
|
46
68
|
pass
|
|
47
69
|
|
|
48
70
|
def stop(self, exceptions=True):
|
|
49
|
-
|
|
50
|
-
|
|
71
|
+
future = self._future
|
|
72
|
+
if future is None:
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
if not future.done():
|
|
76
|
+
future.cancel()
|
|
51
77
|
elif exceptions:
|
|
52
78
|
# Give a chance to raise any exceptions
|
|
53
|
-
|
|
79
|
+
future.result()
|
|
54
80
|
|
|
55
81
|
def __del__(self):
|
|
56
82
|
# Clean up on deletion
|
|
@@ -64,6 +90,10 @@ class ApplicationCommunicator:
|
|
|
64
90
|
"""
|
|
65
91
|
Sends a single message to the application
|
|
66
92
|
"""
|
|
93
|
+
# Make sure there's not an exception to raise from the task
|
|
94
|
+
if self.future.done():
|
|
95
|
+
self.future.result()
|
|
96
|
+
|
|
67
97
|
# Give it the message
|
|
68
98
|
await self.input_queue.put(message)
|
|
69
99
|
|
|
@@ -94,6 +124,10 @@ class ApplicationCommunicator:
|
|
|
94
124
|
"""
|
|
95
125
|
Checks that there is no message to receive in the given time.
|
|
96
126
|
"""
|
|
127
|
+
# Make sure there's not an exception to raise from the task
|
|
128
|
+
if self.future.done():
|
|
129
|
+
self.future.result()
|
|
130
|
+
|
|
97
131
|
# `interval` has precedence over `timeout`
|
|
98
132
|
start = time.monotonic()
|
|
99
133
|
while time.monotonic() - start < timeout:
|
asgiref/typing.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: asgiref
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.9.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
|
|
@@ -17,19 +17,20 @@ Classifier: Operating System :: OS Independent
|
|
|
17
17
|
Classifier: Programming Language :: Python
|
|
18
18
|
Classifier: Programming Language :: Python :: 3
|
|
19
19
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.9
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.10
|
|
23
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
24
23
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
25
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
26
|
-
Requires-Python: >=3.
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
27
|
License-File: LICENSE
|
|
28
|
-
Requires-Dist:
|
|
28
|
+
Requires-Dist: typing_extensions>=4; python_version < "3.11"
|
|
29
29
|
Provides-Extra: tests
|
|
30
|
-
Requires-Dist: pytest
|
|
31
|
-
Requires-Dist: pytest-asyncio
|
|
32
|
-
Requires-Dist: mypy
|
|
30
|
+
Requires-Dist: pytest; extra == "tests"
|
|
31
|
+
Requires-Dist: pytest-asyncio; extra == "tests"
|
|
32
|
+
Requires-Dist: mypy>=1.14.0; extra == "tests"
|
|
33
|
+
Dynamic: license-file
|
|
33
34
|
|
|
34
35
|
asgiref
|
|
35
36
|
=======
|
|
@@ -129,7 +130,7 @@ file handles for incoming POST bodies).
|
|
|
129
130
|
Dependencies
|
|
130
131
|
------------
|
|
131
132
|
|
|
132
|
-
``asgiref`` requires Python 3.
|
|
133
|
+
``asgiref`` requires Python 3.9 or higher.
|
|
133
134
|
|
|
134
135
|
|
|
135
136
|
Contributing
|
|
@@ -179,7 +180,7 @@ Then, build and push the packages::
|
|
|
179
180
|
|
|
180
181
|
python -m build
|
|
181
182
|
twine upload dist/*
|
|
182
|
-
rm -r
|
|
183
|
+
rm -r asgiref.egg-info dist
|
|
183
184
|
|
|
184
185
|
|
|
185
186
|
Implementation Details
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
asgiref/__init__.py,sha256=ya3Avm9qHzetIgZApSM6eHjyO1KYOqbRf5xjSmbIqdg,22
|
|
2
|
+
asgiref/compatibility.py,sha256=DhY1SOpOvOw0Y1lSEjCqg-znRUQKecG3LTaV48MZi68,1606
|
|
3
|
+
asgiref/current_thread_executor.py,sha256=42CU1VODLTk-_PYise-cP1XgyAvI5Djc8f97owFzdrs,4157
|
|
4
|
+
asgiref/local.py,sha256=XgqYyYlVcGgj8W95fIl5kKquvlajIq41_AqT0Isqu6Q,4874
|
|
5
|
+
asgiref/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
asgiref/server.py,sha256=3A68169Nuh2sTY_2O5JzRd_opKObWvvrEFcrXssq3kA,6311
|
|
7
|
+
asgiref/sync.py,sha256=5dlK0T61pMSNWf--49nUojn0mfduqq6ryuwyXxv2Y-A,20417
|
|
8
|
+
asgiref/testing.py,sha256=U5wcs_-ZYTO5SIGfl80EqRAGv_T8BHrAhvAKRuuztT4,4421
|
|
9
|
+
asgiref/timeout.py,sha256=LtGL-xQpG8JHprdsEUCMErJ0kNWj4qwWZhEHJ3iKu4s,3627
|
|
10
|
+
asgiref/typing.py,sha256=Zi72AZlOyF1C7N14LLZnpAdfUH4ljoBqFdQo_bBKMq0,6290
|
|
11
|
+
asgiref/wsgi.py,sha256=fxBLgUE_0PEVgcp13ticz6GHf3q-aKWcB5eFPhd6yxo,6753
|
|
12
|
+
asgiref-3.9.0.dist-info/licenses/LICENSE,sha256=uEZBXRtRTpwd_xSiLeuQbXlLxUbKYSn5UKGM0JHipmk,1552
|
|
13
|
+
asgiref-3.9.0.dist-info/METADATA,sha256=QF1mutxXPUG_-RWadNzX8hAgXrJVGGl4TD0Pklhku2Y,9286
|
|
14
|
+
asgiref-3.9.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
15
|
+
asgiref-3.9.0.dist-info/top_level.txt,sha256=bokQjCzwwERhdBiPdvYEZa4cHxT4NCeAffQNUqJ8ssg,8
|
|
16
|
+
asgiref-3.9.0.dist-info/RECORD,,
|
asgiref-3.8.1.dist-info/RECORD
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
asgiref/__init__.py,sha256=kZzGpxWKY4rWDQrrrlM7bN7YKRAjy17Wv4w__djvVYU,22
|
|
2
|
-
asgiref/compatibility.py,sha256=DhY1SOpOvOw0Y1lSEjCqg-znRUQKecG3LTaV48MZi68,1606
|
|
3
|
-
asgiref/current_thread_executor.py,sha256=EuowbT0oL_P4Fq8KTXNUyEgk3-k4Yh4E8F_anEVdeBI,3977
|
|
4
|
-
asgiref/local.py,sha256=bNeER_QIfw2-PAPYanqAZq6yAAEJ-aio7e9o8Up-mgI,4808
|
|
5
|
-
asgiref/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
asgiref/server.py,sha256=egTQhZo1k4G0F7SSBQNp_VOekpGcjBJZU2kkCoiGC_M,6005
|
|
7
|
-
asgiref/sync.py,sha256=Why0YQV84vSp7IBBr-JDbxYCua-InLgBjuiCMlj9WgI,21444
|
|
8
|
-
asgiref/testing.py,sha256=QgZgXKrwdq5xzhZqynr1msWOiTS3Kpastj7wHU2ePRY,3481
|
|
9
|
-
asgiref/timeout.py,sha256=LtGL-xQpG8JHprdsEUCMErJ0kNWj4qwWZhEHJ3iKu4s,3627
|
|
10
|
-
asgiref/typing.py,sha256=rLF3y_9OgvlQMaDm8yMw8QTgsO9Mv9YAc6Cj8xjvWo0,6264
|
|
11
|
-
asgiref/wsgi.py,sha256=fxBLgUE_0PEVgcp13ticz6GHf3q-aKWcB5eFPhd6yxo,6753
|
|
12
|
-
asgiref-3.8.1.dist-info/LICENSE,sha256=uEZBXRtRTpwd_xSiLeuQbXlLxUbKYSn5UKGM0JHipmk,1552
|
|
13
|
-
asgiref-3.8.1.dist-info/METADATA,sha256=Cbu67XPstSkMxAdA4puvY-FAzN9OrT_AasH7IuK6DaM,9259
|
|
14
|
-
asgiref-3.8.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
15
|
-
asgiref-3.8.1.dist-info/top_level.txt,sha256=bokQjCzwwERhdBiPdvYEZa4cHxT4NCeAffQNUqJ8ssg,8
|
|
16
|
-
asgiref-3.8.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|