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.
Files changed (33) hide show
  1. {asgiref-3.10.0/asgiref.egg-info → asgiref-3.11.0}/PKG-INFO +1 -1
  2. asgiref-3.11.0/asgiref/__init__.py +1 -0
  3. {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/sync.py +10 -2
  4. {asgiref-3.10.0 → asgiref-3.11.0/asgiref.egg-info}/PKG-INFO +1 -1
  5. {asgiref-3.10.0 → asgiref-3.11.0}/tests/test_sync_contextvars.py +65 -1
  6. asgiref-3.10.0/asgiref/__init__.py +0 -1
  7. {asgiref-3.10.0 → asgiref-3.11.0}/LICENSE +0 -0
  8. {asgiref-3.10.0 → asgiref-3.11.0}/MANIFEST.in +0 -0
  9. {asgiref-3.10.0 → asgiref-3.11.0}/README.rst +0 -0
  10. {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/compatibility.py +0 -0
  11. {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/current_thread_executor.py +0 -0
  12. {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/local.py +0 -0
  13. {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/py.typed +0 -0
  14. {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/server.py +0 -0
  15. {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/testing.py +0 -0
  16. {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/timeout.py +0 -0
  17. {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/typing.py +0 -0
  18. {asgiref-3.10.0 → asgiref-3.11.0}/asgiref/wsgi.py +0 -0
  19. {asgiref-3.10.0 → asgiref-3.11.0}/asgiref.egg-info/SOURCES.txt +0 -0
  20. {asgiref-3.10.0 → asgiref-3.11.0}/asgiref.egg-info/dependency_links.txt +0 -0
  21. {asgiref-3.10.0 → asgiref-3.11.0}/asgiref.egg-info/not-zip-safe +0 -0
  22. {asgiref-3.10.0 → asgiref-3.11.0}/asgiref.egg-info/requires.txt +0 -0
  23. {asgiref-3.10.0 → asgiref-3.11.0}/asgiref.egg-info/top_level.txt +0 -0
  24. {asgiref-3.10.0 → asgiref-3.11.0}/setup.cfg +0 -0
  25. {asgiref-3.10.0 → asgiref-3.11.0}/setup.py +0 -0
  26. {asgiref-3.10.0 → asgiref-3.11.0}/tests/test_compatibility.py +0 -0
  27. {asgiref-3.10.0 → asgiref-3.11.0}/tests/test_garbage_collection.py +0 -0
  28. {asgiref-3.10.0 → asgiref-3.11.0}/tests/test_local.py +0 -0
  29. {asgiref-3.10.0 → asgiref-3.11.0}/tests/test_server.py +0 -0
  30. {asgiref-3.10.0 → asgiref-3.11.0}/tests/test_sync.py +0 -0
  31. {asgiref-3.10.0 → asgiref-3.11.0}/tests/test_testing.py +0 -0
  32. {asgiref-3.10.0 → asgiref-3.11.0}/tests/test_wsgi.py +0 -0
  33. {asgiref-3.10.0 → asgiref-3.11.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asgiref
3
- Version: 3.10.0
3
+ Version: 3.11.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
@@ -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
- _restore_context(context)
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asgiref
3
- Version: 3.10.0
3
+ Version: 3.11.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,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 sync function
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