asgiref 3.10.0__tar.gz → 3.11.0__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.
- {asgiref-3.10.0/asgiref.egg-info → asgiref-3.11.0}/PKG-INFO +1 -1
- asgiref-3.11.0/asgiref/__init__.py +1 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/sync.py +10 -2
- {asgiref-3.10.0 → asgiref-3.11.0/asgiref.egg-info}/PKG-INFO +1 -1
- {asgiref-3.10.0 → asgiref-3.11.0}/tests/test_sync_contextvars.py +65 -1
- asgiref-3.10.0/asgiref/__init__.py +0 -1
- {asgiref-3.10.0 → asgiref-3.11.0}/LICENSE +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/MANIFEST.in +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/README.rst +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/compatibility.py +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/current_thread_executor.py +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/local.py +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/py.typed +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/server.py +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/testing.py +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/timeout.py +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/typing.py +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/wsgi.py +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/asgiref.egg-info/SOURCES.txt +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/asgiref.egg-info/dependency_links.txt +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/asgiref.egg-info/not-zip-safe +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/asgiref.egg-info/requires.txt +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/asgiref.egg-info/top_level.txt +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/setup.cfg +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/setup.py +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/tests/test_compatibility.py +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/tests/test_garbage_collection.py +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/tests/test_local.py +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/tests/test_server.py +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/tests/test_sync.py +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/tests/test_testing.py +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/tests/test_wsgi.py +0 -0
- {asgiref-3.10.0 → asgiref-3.11.0}/tox.ini +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "3.11.0"
|
|
@@ -424,6 +424,7 @@ class SyncToAsync(Generic[_P, _R]):
|
|
|
424
424
|
func: Callable[_P, _R],
|
|
425
425
|
thread_sensitive: bool = True,
|
|
426
426
|
executor: Optional["ThreadPoolExecutor"] = None,
|
|
427
|
+
context: Optional[contextvars.Context] = None,
|
|
427
428
|
) -> None:
|
|
428
429
|
if (
|
|
429
430
|
not callable(func)
|
|
@@ -432,6 +433,7 @@ class SyncToAsync(Generic[_P, _R]):
|
|
|
432
433
|
):
|
|
433
434
|
raise TypeError("sync_to_async can only be applied to sync functions.")
|
|
434
435
|
self.func = func
|
|
436
|
+
self.context = context
|
|
435
437
|
functools.update_wrapper(self, func)
|
|
436
438
|
self._thread_sensitive = thread_sensitive
|
|
437
439
|
markcoroutinefunction(self)
|
|
@@ -480,7 +482,7 @@ class SyncToAsync(Generic[_P, _R]):
|
|
|
480
482
|
# Use the passed in executor, or the loop's default if it is None
|
|
481
483
|
executor = self._executor
|
|
482
484
|
|
|
483
|
-
context = contextvars.copy_context()
|
|
485
|
+
context = contextvars.copy_context() if self.context is None else self.context
|
|
484
486
|
child = functools.partial(self.func, *args, **kwargs)
|
|
485
487
|
func = context.run
|
|
486
488
|
task_context: List[asyncio.Task[Any]] = []
|
|
@@ -518,7 +520,8 @@ class SyncToAsync(Generic[_P, _R]):
|
|
|
518
520
|
exec_coro.cancel()
|
|
519
521
|
ret = await exec_coro
|
|
520
522
|
finally:
|
|
521
|
-
|
|
523
|
+
if self.context is None:
|
|
524
|
+
_restore_context(context)
|
|
522
525
|
self.deadlock_context.set(False)
|
|
523
526
|
|
|
524
527
|
return ret
|
|
@@ -611,6 +614,7 @@ def sync_to_async(
|
|
|
611
614
|
*,
|
|
612
615
|
thread_sensitive: bool = True,
|
|
613
616
|
executor: Optional["ThreadPoolExecutor"] = None,
|
|
617
|
+
context: Optional[contextvars.Context] = None,
|
|
614
618
|
) -> Callable[[Callable[_P, _R]], Callable[_P, Coroutine[Any, Any, _R]]]:
|
|
615
619
|
...
|
|
616
620
|
|
|
@@ -621,6 +625,7 @@ def sync_to_async(
|
|
|
621
625
|
*,
|
|
622
626
|
thread_sensitive: bool = True,
|
|
623
627
|
executor: Optional["ThreadPoolExecutor"] = None,
|
|
628
|
+
context: Optional[contextvars.Context] = None,
|
|
624
629
|
) -> Callable[_P, Coroutine[Any, Any, _R]]:
|
|
625
630
|
...
|
|
626
631
|
|
|
@@ -630,6 +635,7 @@ def sync_to_async(
|
|
|
630
635
|
*,
|
|
631
636
|
thread_sensitive: bool = True,
|
|
632
637
|
executor: Optional["ThreadPoolExecutor"] = None,
|
|
638
|
+
context: Optional[contextvars.Context] = None,
|
|
633
639
|
) -> Union[
|
|
634
640
|
Callable[[Callable[_P, _R]], Callable[_P, Coroutine[Any, Any, _R]]],
|
|
635
641
|
Callable[_P, Coroutine[Any, Any, _R]],
|
|
@@ -639,9 +645,11 @@ def sync_to_async(
|
|
|
639
645
|
f,
|
|
640
646
|
thread_sensitive=thread_sensitive,
|
|
641
647
|
executor=executor,
|
|
648
|
+
context=context,
|
|
642
649
|
)
|
|
643
650
|
return SyncToAsync(
|
|
644
651
|
func,
|
|
645
652
|
thread_sensitive=thread_sensitive,
|
|
646
653
|
executor=executor,
|
|
654
|
+
context=context,
|
|
647
655
|
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import contextvars
|
|
3
|
+
import sys
|
|
3
4
|
import threading
|
|
4
5
|
import time
|
|
5
6
|
|
|
@@ -55,13 +56,76 @@ async def test_sync_to_async_contextvars():
|
|
|
55
56
|
assert foo.get() == "baz"
|
|
56
57
|
|
|
57
58
|
|
|
59
|
+
@pytest.mark.asyncio
|
|
60
|
+
async def test_sync_to_async_contextvars_with_custom_context():
|
|
61
|
+
"""
|
|
62
|
+
Passing a custom context to `sync_to_async` ensures that changes to context
|
|
63
|
+
variables within the synchronous function are isolated to the provided
|
|
64
|
+
context and do not affect the caller's context. Specifically, verifies that
|
|
65
|
+
modifications to a context variable inside the sync function are reflected
|
|
66
|
+
only in the custom context and not in the outer context.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def sync_function():
|
|
70
|
+
time.sleep(1)
|
|
71
|
+
assert foo.get() == "bar"
|
|
72
|
+
foo.set("baz")
|
|
73
|
+
return 42
|
|
74
|
+
|
|
75
|
+
foo.set("bar")
|
|
76
|
+
context = contextvars.copy_context()
|
|
77
|
+
|
|
78
|
+
async_function = sync_to_async(sync_function, context=context)
|
|
79
|
+
assert await async_function() == 42
|
|
80
|
+
|
|
81
|
+
# Current context remains unchanged.
|
|
82
|
+
assert foo.get() == "bar"
|
|
83
|
+
|
|
84
|
+
# Custom context reflects the changes made within the sync function.
|
|
85
|
+
assert context.get(foo) == "baz"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@pytest.mark.asyncio
|
|
89
|
+
@pytest.mark.skipif(sys.version_info < (3, 11), reason="requires python3.11")
|
|
90
|
+
async def test_sync_to_async_contextvars_with_custom_context_and_parallel_tasks():
|
|
91
|
+
"""
|
|
92
|
+
Using a custom context with `sync_to_async` and asyncio tasks isolates
|
|
93
|
+
contextvars changes, leaving the original context unchanged and reflecting
|
|
94
|
+
all modifications in the custom context.
|
|
95
|
+
"""
|
|
96
|
+
foo.set("")
|
|
97
|
+
|
|
98
|
+
def sync_function():
|
|
99
|
+
foo.set(foo.get() + "1")
|
|
100
|
+
return 1
|
|
101
|
+
|
|
102
|
+
async def async_function():
|
|
103
|
+
foo.set(foo.get() + "1")
|
|
104
|
+
return 1
|
|
105
|
+
|
|
106
|
+
context = contextvars.copy_context()
|
|
107
|
+
|
|
108
|
+
await asyncio.gather(
|
|
109
|
+
sync_to_async(sync_function, context=context)(),
|
|
110
|
+
sync_to_async(sync_function, context=context)(),
|
|
111
|
+
asyncio.create_task(async_function(), context=context),
|
|
112
|
+
asyncio.create_task(async_function(), context=context),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Current context remains unchanged
|
|
116
|
+
assert foo.get() == ""
|
|
117
|
+
|
|
118
|
+
# Custom context reflects the changes made within all the gathered tasks.
|
|
119
|
+
assert context.get(foo) == "1111"
|
|
120
|
+
|
|
121
|
+
|
|
58
122
|
def test_async_to_sync_contextvars():
|
|
59
123
|
"""
|
|
60
124
|
Tests to make sure that contextvars from the calling context are
|
|
61
125
|
present in the called context, and that any changes in the called context
|
|
62
126
|
are then propagated back to the calling context.
|
|
63
127
|
"""
|
|
64
|
-
# Define
|
|
128
|
+
# Define async function
|
|
65
129
|
async def async_function():
|
|
66
130
|
await asyncio.sleep(1)
|
|
67
131
|
assert foo.get() == "bar"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "3.10.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|