asynkit 0.19.1__tar.gz → 0.19.2__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 (41) hide show
  1. {asynkit-0.19.1/src/asynkit.egg-info → asynkit-0.19.2}/PKG-INFO +12 -1
  2. {asynkit-0.19.1 → asynkit-0.19.2}/README.md +11 -0
  3. {asynkit-0.19.1 → asynkit-0.19.2}/pyproject.toml +2 -4
  4. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit/coroutine.py +57 -0
  5. {asynkit-0.19.1 → asynkit-0.19.2/src/asynkit.egg-info}/PKG-INFO +12 -1
  6. {asynkit-0.19.1 → asynkit-0.19.2}/tests/test_coro.py +23 -0
  7. {asynkit-0.19.1 → asynkit-0.19.2}/tests/test_namespace.py +2 -0
  8. {asynkit-0.19.1 → asynkit-0.19.2}/LICENSE +0 -0
  9. {asynkit-0.19.1 → asynkit-0.19.2}/setup.cfg +0 -0
  10. {asynkit-0.19.1 → asynkit-0.19.2}/setup.py +0 -0
  11. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit/__init__.py +0 -0
  12. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit/_cext/corostart.c +0 -0
  13. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit/compat.py +0 -0
  14. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit/experimental/__init__.py +0 -0
  15. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit/experimental/anyio.py +0 -0
  16. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit/experimental/interrupt.py +0 -0
  17. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit/experimental/priority.py +0 -0
  18. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit/loop/__init__.py +0 -0
  19. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit/loop/default.py +0 -0
  20. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit/loop/eventloop.py +0 -0
  21. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit/loop/extensions.py +0 -0
  22. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit/loop/schedulingloop.py +0 -0
  23. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit/loop/types.py +0 -0
  24. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit/monitor.py +0 -0
  25. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit/py.typed +0 -0
  26. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit/scheduling.py +0 -0
  27. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit/tools.py +0 -0
  28. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit.egg-info/SOURCES.txt +0 -0
  29. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit.egg-info/dependency_links.txt +0 -0
  30. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit.egg-info/not-zip-safe +0 -0
  31. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit.egg-info/requires.txt +0 -0
  32. {asynkit-0.19.1 → asynkit-0.19.2}/src/asynkit.egg-info/top_level.txt +0 -0
  33. {asynkit-0.19.1 → asynkit-0.19.2}/tests/test_anyio.py +0 -0
  34. {asynkit-0.19.1 → asynkit-0.19.2}/tests/test_compat_eager.py +0 -0
  35. {asynkit-0.19.1 → asynkit-0.19.2}/tests/test_df.py +0 -0
  36. {asynkit-0.19.1 → asynkit-0.19.2}/tests/test_eager_task_factory.py +0 -0
  37. {asynkit-0.19.1 → asynkit-0.19.2}/tests/test_eager_task_factory_context.py +0 -0
  38. {asynkit-0.19.1 → asynkit-0.19.2}/tests/test_implementation_params.py +0 -0
  39. {asynkit-0.19.1 → asynkit-0.19.2}/tests/test_loop.py +0 -0
  40. {asynkit-0.19.1 → asynkit-0.19.2}/tests/test_monitor.py +0 -0
  41. {asynkit-0.19.1 → asynkit-0.19.2}/tests/test_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asynkit
3
- Version: 0.19.1
3
+ Version: 0.19.2
4
4
  Summary: A toolkit for Python coroutines
5
5
  Author-email: Kristján Valur Jónsson <sweskman@gmail.com>
6
6
  License: MIT
@@ -460,6 +460,17 @@ so that it is executed using `await_sync()`:
460
460
  >>>
461
461
  ```
462
462
 
463
+ For methods, use `syncmethod()` when creating a class-body alias. It behaves
464
+ like `syncfunction()` at runtime, but exposes descriptor typing so type checkers
465
+ can distinguish bound and unbound method access:
466
+
467
+ ```python
468
+ class Runner:
469
+ async def arun(self, *, yield_every: int | None = None) -> None: ...
470
+
471
+ run = asynkit.syncmethod(arun)
472
+ ```
473
+
463
474
  the `asyncfunction()` utility can be used when passing synchronous callbacks to async
464
475
  code, to make them async. This, along with `syncfunction()` and `await_sync()`,
465
476
  can be used to integrate synchronous code with async middleware:
@@ -432,6 +432,17 @@ so that it is executed using `await_sync()`:
432
432
  >>>
433
433
  ```
434
434
 
435
+ For methods, use `syncmethod()` when creating a class-body alias. It behaves
436
+ like `syncfunction()` at runtime, but exposes descriptor typing so type checkers
437
+ can distinguish bound and unbound method access:
438
+
439
+ ```python
440
+ class Runner:
441
+ async def arun(self, *, yield_every: int | None = None) -> None: ...
442
+
443
+ run = asynkit.syncmethod(arun)
444
+ ```
445
+
435
446
  the `asyncfunction()` utility can be used when passing synchronous callbacks to async
436
447
  code, to make them async. This, along with `syncfunction()` and `await_sync()`,
437
448
  can be used to integrate synchronous code with async middleware:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "asynkit"
3
- version = "0.19.1"
3
+ version = "0.19.2"
4
4
  description = "A toolkit for Python coroutines"
5
5
  authors = [
6
6
  {name = "Kristján Valur Jónsson", email = "sweskman@gmail.com"}
@@ -141,10 +141,8 @@ ignore = [
141
141
  python-version = "3.10"
142
142
 
143
143
  [tool.ty.src]
144
- include = ["src/asynkit"]
144
+ include = ["src/asynkit", "tests/typing"]
145
145
  exclude = [
146
- "tests",
147
- "examples",
148
146
  "build",
149
147
  "src/asynkit.egg-info",
150
148
  ]
@@ -22,6 +22,7 @@ from types import FrameType
22
22
  from typing import (
23
23
  TYPE_CHECKING,
24
24
  Any,
25
+ Concatenate,
25
26
  Generic,
26
27
  TypeVar,
27
28
  cast,
@@ -133,6 +134,8 @@ __all__ = [
133
134
  "SynchronousAbort",
134
135
  "asyncfunction",
135
136
  "syncfunction",
137
+ "syncmethod",
138
+ "SyncMethod",
136
139
  ]
137
140
 
138
141
 
@@ -150,6 +153,7 @@ def get_current_context() -> Context:
150
153
 
151
154
  T = TypeVar("T")
152
155
  S = TypeVar("S")
156
+ SelfT = TypeVar("SelfT")
153
157
  P = ParamSpec("P")
154
158
  T_co = TypeVar("T_co", covariant=True)
155
159
  Suspendable = (
@@ -1168,6 +1172,59 @@ def syncfunction(func: Callable[P, Coroutine[Any, Any, T]]) -> Callable[P, T]:
1168
1172
  return helper
1169
1173
 
1170
1174
 
1175
+ class SyncMethod(Generic[SelfT, P, T]):
1176
+ """Descriptor which makes an async method synchronous.
1177
+
1178
+ Unlike `syncfunction()`, this preserves method binding semantics when used
1179
+ for class-body aliases such as `run = syncmethod(arun)`.
1180
+ """
1181
+
1182
+ def __init__(
1183
+ self,
1184
+ func: Callable[Concatenate[SelfT, P], Coroutine[Any, Any, T]],
1185
+ ) -> None:
1186
+ self._func = func
1187
+ functools.update_wrapper(self, func)
1188
+
1189
+ @overload
1190
+ def __get__(
1191
+ self,
1192
+ instance: None,
1193
+ owner: type[SelfT],
1194
+ ) -> Callable[Concatenate[SelfT, P], T]: ...
1195
+
1196
+ @overload
1197
+ def __get__(
1198
+ self,
1199
+ instance: SelfT,
1200
+ owner: type[SelfT] | None = None,
1201
+ ) -> Callable[P, T]: ...
1202
+
1203
+ def __get__(
1204
+ self,
1205
+ instance: SelfT | None,
1206
+ owner: type[SelfT] | None = None,
1207
+ ) -> Callable[..., T]:
1208
+ if instance is None:
1209
+ return self
1210
+
1211
+ @functools.wraps(self._func)
1212
+ def helper(*args: P.args, **kwargs: P.kwargs) -> T:
1213
+ return self(instance, *args, **kwargs)
1214
+
1215
+ return helper
1216
+
1217
+ def __call__(self, instance: SelfT, *args: P.args, **kwargs: P.kwargs) -> T:
1218
+ return await_sync(self._func(instance, *args, **kwargs))
1219
+
1220
+
1221
+ def syncmethod(
1222
+ func: Callable[Concatenate[SelfT, P], Coroutine[Any, Any, T]],
1223
+ ) -> SyncMethod[SelfT, P, T]:
1224
+ """Make an async method synchronous while preserving descriptor typing."""
1225
+ return SyncMethod(func)
1226
+
1227
+
1171
1228
  def asyncfunction(func: Callable[P, T]) -> Callable[P, Coroutine[Any, Any, T]]:
1172
1229
  """Make a regular function async, so that its result needs to be awaited.
1173
1230
  Useful when providing synchronous callbacks to async code.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asynkit
3
- Version: 0.19.1
3
+ Version: 0.19.2
4
4
  Summary: A toolkit for Python coroutines
5
5
  Author-email: Kristján Valur Jónsson <sweskman@gmail.com>
6
6
  License: MIT
@@ -460,6 +460,17 @@ so that it is executed using `await_sync()`:
460
460
  >>>
461
461
  ```
462
462
 
463
+ For methods, use `syncmethod()` when creating a class-body alias. It behaves
464
+ like `syncfunction()` at runtime, but exposes descriptor typing so type checkers
465
+ can distinguish bound and unbound method access:
466
+
467
+ ```python
468
+ class Runner:
469
+ async def arun(self, *, yield_every: int | None = None) -> None: ...
470
+
471
+ run = asynkit.syncmethod(arun)
472
+ ```
473
+
463
474
  the `asyncfunction()` utility can be used when passing synchronous callbacks to async
464
475
  code, to make them async. This, along with `syncfunction()` and `await_sync()`,
465
476
  can be used to integrate synchronous code with async middleware:
@@ -826,6 +826,9 @@ class TestCoroRun:
826
826
  await self.sleep(0)
827
827
  return "nullsleep"
828
828
 
829
+ async def blocks(self):
830
+ await drive_yield("blocked")
831
+
829
832
  @asynkit.syncfunction
830
833
  async def sync_simple(self):
831
834
  return await self.simple()
@@ -838,6 +841,9 @@ class TestCoroRun:
838
841
  async def sync_genexit(self):
839
842
  return await self.genexit()
840
843
 
844
+ method_simple = asynkit.syncmethod(simple)
845
+ method_blocks = asynkit.syncmethod(blocks)
846
+
841
847
  def test_simple(self):
842
848
  assert asynkit.await_sync(self.simple()) == "simple"
843
849
 
@@ -852,6 +858,23 @@ class TestCoroRun:
852
858
  def test_sync_simple(self):
853
859
  assert self.sync_simple() == "simple"
854
860
 
861
+ def test_syncmethod_bound(self):
862
+ assert self.method_simple() == "simple"
863
+
864
+ def test_syncmethod_unbound(self):
865
+ assert type(self).method_simple(self) == "simple"
866
+
867
+ def test_syncmethod_descriptor(self):
868
+ descriptor = type(self).__dict__["method_simple"]
869
+ assert isinstance(descriptor, asynkit.SyncMethod)
870
+ assert type(self).method_simple.__name__ == "simple"
871
+ assert self.method_simple.__name__ == "simple"
872
+
873
+ def test_syncmethod_blocks(self):
874
+ with pytest.raises(asynkit.SynchronousError) as err:
875
+ self.method_blocks()
876
+ assert err.match("failed to complete synchronously")
877
+
855
878
  def test_cleanup(self):
856
879
  assert self.sync_cleanup() is None
857
880
 
@@ -20,6 +20,8 @@ def test_package_star_exports_public_api_only():
20
20
  assert exports == set(asynkit.__all__)
21
21
  assert "coro_start" in exports
22
22
  assert "coro_drive" in exports
23
+ assert "syncmethod" in exports
24
+ assert "SyncMethod" in exports
23
25
  assert "cancelling" in exports
24
26
  assert "coroutine" not in exports
25
27
  assert "loop" not in exports
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