ez-a-sync 0.22.14__py3-none-any.whl → 0.22.16__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.

Potentially problematic release.


This version of ez-a-sync might be problematic. Click here for more details.

Files changed (73) hide show
  1. a_sync/ENVIRONMENT_VARIABLES.py +37 -5
  2. a_sync/__init__.py +53 -12
  3. a_sync/_smart.py +231 -28
  4. a_sync/_typing.py +112 -15
  5. a_sync/a_sync/__init__.py +35 -10
  6. a_sync/a_sync/_descriptor.py +248 -38
  7. a_sync/a_sync/_flags.py +78 -9
  8. a_sync/a_sync/_helpers.py +46 -13
  9. a_sync/a_sync/_kwargs.py +33 -8
  10. a_sync/a_sync/_meta.py +149 -28
  11. a_sync/a_sync/abstract.py +150 -28
  12. a_sync/a_sync/base.py +34 -16
  13. a_sync/a_sync/config.py +85 -14
  14. a_sync/a_sync/decorator.py +441 -139
  15. a_sync/a_sync/function.py +709 -147
  16. a_sync/a_sync/method.py +437 -110
  17. a_sync/a_sync/modifiers/__init__.py +85 -5
  18. a_sync/a_sync/modifiers/cache/__init__.py +116 -17
  19. a_sync/a_sync/modifiers/cache/memory.py +130 -20
  20. a_sync/a_sync/modifiers/limiter.py +101 -22
  21. a_sync/a_sync/modifiers/manager.py +142 -16
  22. a_sync/a_sync/modifiers/semaphores.py +121 -15
  23. a_sync/a_sync/property.py +383 -82
  24. a_sync/a_sync/singleton.py +44 -19
  25. a_sync/aliases.py +0 -1
  26. a_sync/asyncio/__init__.py +140 -1
  27. a_sync/asyncio/as_completed.py +213 -79
  28. a_sync/asyncio/create_task.py +70 -20
  29. a_sync/asyncio/gather.py +125 -58
  30. a_sync/asyncio/utils.py +3 -3
  31. a_sync/exceptions.py +248 -26
  32. a_sync/executor.py +164 -69
  33. a_sync/future.py +1227 -168
  34. a_sync/iter.py +173 -56
  35. a_sync/primitives/__init__.py +14 -2
  36. a_sync/primitives/_debug.py +72 -18
  37. a_sync/primitives/_loggable.py +41 -10
  38. a_sync/primitives/locks/__init__.py +5 -2
  39. a_sync/primitives/locks/counter.py +107 -38
  40. a_sync/primitives/locks/event.py +21 -7
  41. a_sync/primitives/locks/prio_semaphore.py +262 -63
  42. a_sync/primitives/locks/semaphore.py +138 -89
  43. a_sync/primitives/queue.py +601 -60
  44. a_sync/sphinx/__init__.py +0 -1
  45. a_sync/sphinx/ext.py +160 -50
  46. a_sync/task.py +313 -112
  47. a_sync/utils/__init__.py +12 -6
  48. a_sync/utils/iterators.py +170 -50
  49. {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.16.dist-info}/METADATA +1 -1
  50. ez_a_sync-0.22.16.dist-info/RECORD +74 -0
  51. {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.16.dist-info}/WHEEL +1 -1
  52. tests/conftest.py +1 -2
  53. tests/executor.py +250 -9
  54. tests/fixtures.py +61 -32
  55. tests/test_abstract.py +22 -4
  56. tests/test_as_completed.py +54 -21
  57. tests/test_base.py +264 -19
  58. tests/test_cache.py +31 -15
  59. tests/test_decorator.py +54 -28
  60. tests/test_executor.py +31 -13
  61. tests/test_future.py +45 -8
  62. tests/test_gather.py +8 -2
  63. tests/test_helpers.py +2 -0
  64. tests/test_iter.py +55 -13
  65. tests/test_limiter.py +5 -3
  66. tests/test_meta.py +23 -9
  67. tests/test_modified.py +4 -1
  68. tests/test_semaphore.py +15 -8
  69. tests/test_singleton.py +28 -11
  70. tests/test_task.py +162 -36
  71. ez_a_sync-0.22.14.dist-info/RECORD +0 -74
  72. {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.16.dist-info}/LICENSE.txt +0 -0
  73. {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.16.dist-info}/top_level.txt +0 -0
@@ -1,25 +1,55 @@
1
1
  """
2
- This module contains the ASyncDescriptor class, which is used to create sync/async methods
2
+ This module contains the :class:`ASyncDescriptor` class, which is used to create dual-function sync/async methods
3
3
  and properties.
4
4
 
5
- It also includes utility methods for mapping operations across multiple instances.
5
+ The :class:`ASyncDescriptor` class provides functionality for mapping operations across multiple instances
6
+ and includes utility methods for common operations such as checking if all or any results are truthy,
7
+ and finding the minimum, maximum, or sum of results of the method or property mapped across multiple instances.
8
+
9
+ See Also:
10
+ - :class:`~a_sync.a_sync.function.ASyncFunction`
11
+ - :class:`~a_sync.a_sync.method.ASyncMethodDescriptor`
6
12
  """
7
13
 
8
14
  import functools
9
15
 
10
16
  from a_sync._typing import *
11
17
  from a_sync.a_sync import decorator
12
- from a_sync.a_sync.function import ASyncFunction, ModifiedMixin, ModifierManager
18
+ from a_sync.a_sync.function import ASyncFunction, ModifierManager, _ModifiedMixin
13
19
 
14
20
  if TYPE_CHECKING:
15
21
  from a_sync import TaskMapping
16
22
 
17
- class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
23
+
24
+ class ASyncDescriptor(_ModifiedMixin, Generic[I, P, T]):
18
25
  """
19
- A descriptor base class for asynchronous methods and properties.
26
+ A descriptor base class for dual-function ASync methods and properties.
20
27
 
21
28
  This class provides functionality for mapping operations across multiple instances
22
- and includes utility methods for common operations.
29
+ and includes utility methods for common operations such as checking if all or any
30
+ results are truthy, and finding the minimum, maximum, or sum of results of the method
31
+ or property mapped across multiple instances.
32
+
33
+ Examples:
34
+ To create a dual-function method or property, subclass :class:`ASyncDescriptor` and implement
35
+ the desired functionality. You can then use the provided utility methods to perform operations
36
+ across multiple instances.
37
+
38
+ ```python
39
+ class MyDescriptor(ASyncDescriptor):
40
+ def __init__(self, func):
41
+ super().__init__(func)
42
+
43
+ class MyClass:
44
+ my_method = MyDescriptor(lambda x: x * 2)
45
+
46
+ instance = MyClass()
47
+ result = instance.my_method.map([1, 2, 3])
48
+ ```
49
+
50
+ See Also:
51
+ - :class:`~a_sync.a_sync.function.ASyncFunction`
52
+ - :class:`~a_sync.a_sync.method.ASyncMethodDescriptor`
23
53
  """
24
54
 
25
55
  __wrapped__: AnyFn[Concatenate[I, P], T]
@@ -28,13 +58,13 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
28
58
  __slots__ = "field_name", "_fget"
29
59
 
30
60
  def __init__(
31
- self,
32
- _fget: AnyFn[Concatenate[I, P], T],
33
- field_name: Optional[str] = None,
61
+ self,
62
+ _fget: AnyFn[Concatenate[I, P], T],
63
+ field_name: Optional[str] = None,
34
64
  **modifiers: ModifierKwargs,
35
65
  ) -> None:
36
66
  """
37
- Initialize the {cls}.
67
+ Initialize the :class:`ASyncDescriptor`.
38
68
 
39
69
  Args:
40
70
  _fget: The function to be wrapped.
@@ -45,19 +75,21 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
45
75
  ValueError: If _fget is not callable.
46
76
  """
47
77
  if not callable(_fget):
48
- raise ValueError(f'Unable to decorate {_fget}')
78
+ raise ValueError(f"Unable to decorate {_fget}")
49
79
  self.modifiers = ModifierManager(modifiers)
50
80
  if isinstance(_fget, ASyncFunction):
51
81
  self.modifiers.update(_fget.modifiers)
52
82
  self.__wrapped__ = _fget
53
83
  elif asyncio.iscoroutinefunction(_fget):
54
- self.__wrapped__: AsyncUnboundMethod[I, P, T] = self.modifiers.apply_async_modifiers(_fget)
84
+ self.__wrapped__: AsyncUnboundMethod[I, P, T] = (
85
+ self.modifiers.apply_async_modifiers(_fget)
86
+ )
55
87
  else:
56
88
  self.__wrapped__ = _fget
57
89
 
58
90
  self.field_name = field_name or _fget.__name__
59
- """The name of the field the {cls} is bound to."""
60
-
91
+ """The name of the field the :class:`ASyncDescriptor` is bound to."""
92
+
61
93
  functools.update_wrapper(self, self.__wrapped__)
62
94
 
63
95
  def __repr__(self) -> str:
@@ -65,7 +97,7 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
65
97
 
66
98
  def __set_name__(self, owner, name):
67
99
  """
68
- Set the field name when the {cls} is assigned to a class.
100
+ Set the field name when the :class:`ASyncDescriptor` is assigned to a class.
69
101
 
70
102
  Args:
71
103
  owner: The class owning this descriptor.
@@ -73,18 +105,32 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
73
105
  """
74
106
  self.field_name = name
75
107
 
76
- def map(self, *instances: AnyIterable[I], **bound_method_kwargs: P.kwargs) -> "TaskMapping[I, T]":
108
+ def map(
109
+ self, *instances: AnyIterable[I], **bound_method_kwargs: P.kwargs
110
+ ) -> "TaskMapping[I, T]":
77
111
  """
78
- Create a TaskMapping for the given instances.
112
+ Create a :class:`TaskMapping` for the given instances.
79
113
 
80
114
  Args:
81
115
  *instances: Iterable of instances to map over.
82
116
  **bound_method_kwargs: Additional keyword arguments for the bound method.
83
117
 
84
118
  Returns:
85
- A TaskMapping object.
119
+ A :class:`TaskMapping` object.
120
+
121
+ Examples:
122
+ class MyDescriptor(ASyncDescriptor):
123
+ def __init__(self, func):
124
+ super().__init__(func)
125
+
126
+ class MyClass:
127
+ my_method = MyDescriptor(lambda x: x * 2)
128
+
129
+ instance = MyClass()
130
+ result = instance.my_method.map([1, 2, 3])
86
131
  """
87
132
  from a_sync.task import TaskMapping
133
+
88
134
  return TaskMapping(self, *instances, **bound_method_kwargs)
89
135
 
90
136
  @functools.cached_property
@@ -93,7 +139,18 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
93
139
  Create an :class:`~ASyncFunction` that checks if all results are truthy.
94
140
 
95
141
  Returns:
96
- An ASyncFunction object.
142
+ An :class:`ASyncFunction` object.
143
+
144
+ Examples:
145
+ class MyDescriptor(ASyncDescriptor):
146
+ def __init__(self, func):
147
+ super().__init__(func)
148
+
149
+ class MyClass:
150
+ my_method = MyDescriptor(lambda x: x > 0)
151
+
152
+ instance = MyClass()
153
+ result = await instance.my_method.all([1, 2, 3])
97
154
  """
98
155
  return decorator.a_sync(default=self.default)(self._all)
99
156
 
@@ -103,7 +160,18 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
103
160
  Create an :class:`~ASyncFunction` that checks if any result is truthy.
104
161
 
105
162
  Returns:
106
- An ASyncFunction object.
163
+ An :class:`ASyncFunction` object.
164
+
165
+ Examples:
166
+ class MyDescriptor(ASyncDescriptor):
167
+ def __init__(self, func):
168
+ super().__init__(func)
169
+
170
+ class MyClass:
171
+ my_method = MyDescriptor(lambda x: x > 0)
172
+
173
+ instance = MyClass()
174
+ result = await instance.my_method.any([-1, 0, 1])
107
175
  """
108
176
  return decorator.a_sync(default=self.default)(self._any)
109
177
 
@@ -113,7 +181,20 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
113
181
  Create an :class:`~ASyncFunction` that returns the minimum result.
114
182
 
115
183
  Returns:
116
- An ASyncFunction object.
184
+ An :class:`ASyncFunction` object.
185
+
186
+ Examples:
187
+ ```python
188
+ class MyDescriptor(ASyncDescriptor):
189
+ def __init__(self, func):
190
+ super().__init__(func)
191
+
192
+ class MyClass:
193
+ my_method = MyDescriptor(lambda x: x)
194
+
195
+ instance = MyClass()
196
+ result = await instance.my_method.min([3, 1, 2])
197
+ ```
117
198
  """
118
199
  return decorator.a_sync(default=self.default)(self._min)
119
200
 
@@ -123,7 +204,18 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
123
204
  Create an :class:`~ASyncFunction` that returns the maximum result.
124
205
 
125
206
  Returns:
126
- An ASyncFunction object.
207
+ An :class:`ASyncFunction` object.
208
+
209
+ Examples:
210
+ class MyDescriptor(ASyncDescriptor):
211
+ def __init__(self, func):
212
+ super().__init__(func)
213
+
214
+ class MyClass:
215
+ my_method = MyDescriptor(lambda x: x)
216
+
217
+ instance = MyClass()
218
+ result = await instance.my_method.max([3, 1, 2])
127
219
  """
128
220
  return decorator.a_sync(default=self.default)(self._max)
129
221
 
@@ -133,11 +225,30 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
133
225
  Create an :class:`~ASyncFunction` that returns the sum of results.
134
226
 
135
227
  Returns:
136
- An ASyncFunction object.
228
+ An :class:`ASyncFunction` object.
229
+
230
+ Examples:
231
+ ```python
232
+ class MyDescriptor(ASyncDescriptor):
233
+ def __init__(self, func):
234
+ super().__init__(func)
235
+
236
+ class MyClass:
237
+ my_method = MyDescriptor(lambda x: x)
238
+
239
+ instance = MyClass()
240
+ result = await instance.my_method.sum([1, 2, 3])
241
+ ```
137
242
  """
138
243
  return decorator.a_sync(default=self.default)(self._sum)
139
244
 
140
- async def _all(self, *instances: AnyIterable[I], concurrency: Optional[int] = None, name: str = "", **kwargs: P.kwargs) -> bool:
245
+ async def _all(
246
+ self,
247
+ *instances: AnyIterable[I],
248
+ concurrency: Optional[int] = None,
249
+ name: str = "",
250
+ **kwargs: P.kwargs,
251
+ ) -> bool:
141
252
  """
142
253
  Check if all results are truthy.
143
254
 
@@ -149,10 +260,31 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
149
260
 
150
261
  Returns:
151
262
  A boolean indicating if all results are truthy.
152
- """
153
- return await self.map(*instances, concurrency=concurrency, name=name, **kwargs).all(pop=True, sync=False)
154
263
 
155
- async def _any(self, *instances: AnyIterable[I], concurrency: Optional[int] = None, name: str = "", **kwargs: P.kwargs) -> bool:
264
+ Examples:
265
+ ```python
266
+ class MyDescriptor(ASyncDescriptor):
267
+ def __init__(self, func):
268
+ super().__init__(func)
269
+
270
+ class MyClass:
271
+ my_method = MyDescriptor(lambda x: x > 0)
272
+
273
+ instance = MyClass()
274
+ result = await instance.my_method._all([1, 2, 3])
275
+ ```
276
+ """
277
+ return await self.map(
278
+ *instances, concurrency=concurrency, name=name, **kwargs
279
+ ).all(pop=True, sync=False)
280
+
281
+ async def _any(
282
+ self,
283
+ *instances: AnyIterable[I],
284
+ concurrency: Optional[int] = None,
285
+ name: str = "",
286
+ **kwargs: P.kwargs,
287
+ ) -> bool:
156
288
  """
157
289
  Check if any result is truthy.
158
290
 
@@ -164,10 +296,31 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
164
296
 
165
297
  Returns:
166
298
  A boolean indicating if any result is truthy.
167
- """
168
- return await self.map(*instances, concurrency=concurrency, name=name, **kwargs).any(pop=True, sync=False)
169
299
 
170
- async def _min(self, *instances: AnyIterable[I], concurrency: Optional[int] = None, name: str = "", **kwargs: P.kwargs) -> T:
300
+ Examples:
301
+ ```python
302
+ class MyDescriptor(ASyncDescriptor):
303
+ def __init__(self, func):
304
+ super().__init__(func)
305
+
306
+ class MyClass:
307
+ my_method = MyDescriptor(lambda x: x > 0)
308
+
309
+ instance = MyClass()
310
+ result = await instance.my_method._any([-1, 0, 1])
311
+ ```
312
+ """
313
+ return await self.map(
314
+ *instances, concurrency=concurrency, name=name, **kwargs
315
+ ).any(pop=True, sync=False)
316
+
317
+ async def _min(
318
+ self,
319
+ *instances: AnyIterable[I],
320
+ concurrency: Optional[int] = None,
321
+ name: str = "",
322
+ **kwargs: P.kwargs,
323
+ ) -> T:
171
324
  """
172
325
  Find the minimum result.
173
326
 
@@ -179,10 +332,31 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
179
332
 
180
333
  Returns:
181
334
  The minimum result.
182
- """
183
- return await self.map(*instances, concurrency=concurrency, name=name, **kwargs).min(pop=True, sync=False)
184
335
 
185
- async def _max(self, *instances: AnyIterable[I], concurrency: Optional[int] = None, name: str = "", **kwargs: P.kwargs) -> T:
336
+ Examples:
337
+ ```python
338
+ class MyDescriptor(ASyncDescriptor):
339
+ def __init__(self, func):
340
+ super().__init__(func)
341
+
342
+ class MyClass:
343
+ my_method = MyDescriptor(lambda x: x)
344
+
345
+ instance = MyClass()
346
+ result = await instance.my_method._min([3, 1, 2])
347
+ ```
348
+ """
349
+ return await self.map(
350
+ *instances, concurrency=concurrency, name=name, **kwargs
351
+ ).min(pop=True, sync=False)
352
+
353
+ async def _max(
354
+ self,
355
+ *instances: AnyIterable[I],
356
+ concurrency: Optional[int] = None,
357
+ name: str = "",
358
+ **kwargs: P.kwargs,
359
+ ) -> T:
186
360
  """
187
361
  Find the maximum result.
188
362
 
@@ -194,10 +368,31 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
194
368
 
195
369
  Returns:
196
370
  The maximum result.
197
- """
198
- return await self.map(*instances, concurrency=concurrency, name=name, **kwargs).max(pop=True, sync=False)
199
371
 
200
- async def _sum(self, *instances: AnyIterable[I], concurrency: Optional[int] = None, name: str = "", **kwargs: P.kwargs) -> T:
372
+ Examples:
373
+ ```python
374
+ class MyDescriptor(ASyncDescriptor):
375
+ def __init__(self, func):
376
+ super().__init__(func)
377
+
378
+ class MyClass:
379
+ my_method = MyDescriptor(lambda x: x)
380
+
381
+ instance = MyClass()
382
+ result = await instance.my_method._max([3, 1, 2])
383
+ ```
384
+ """
385
+ return await self.map(
386
+ *instances, concurrency=concurrency, name=name, **kwargs
387
+ ).max(pop=True, sync=False)
388
+
389
+ async def _sum(
390
+ self,
391
+ *instances: AnyIterable[I],
392
+ concurrency: Optional[int] = None,
393
+ name: str = "",
394
+ **kwargs: P.kwargs,
395
+ ) -> T:
201
396
  """
202
397
  Calculate the sum of results.
203
398
 
@@ -209,11 +404,26 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
209
404
 
210
405
  Returns:
211
406
  The sum of the results.
407
+
408
+ Examples:
409
+ ```python
410
+ class MyDescriptor(ASyncDescriptor):
411
+ def __init__(self, func):
412
+ super().__init__(func)
413
+
414
+ class MyClass:
415
+ my_method = MyDescriptor(lambda x: x)
416
+
417
+ instance = MyClass()
418
+ result = await instance.my_method._sum([1, 2, 3])
419
+ ```
212
420
  """
213
- return await self.map(*instances, concurrency=concurrency, name=name, **kwargs).sum(pop=True, sync=False)
421
+ return await self.map(
422
+ *instances, concurrency=concurrency, name=name, **kwargs
423
+ ).sum(pop=True, sync=False)
214
424
 
215
425
  def __init_subclass__(cls) -> None:
216
426
  for attr in cls.__dict__.values():
217
427
  if attr.__doc__ and "{cls}" in attr.__doc__:
218
428
  attr.__doc__ = attr.__doc__.replace("{cls}", f":class:`{cls.__name__}`")
219
- return super().__init_subclass__()
429
+ return super().__init_subclass__()
a_sync/a_sync/_flags.py CHANGED
@@ -2,27 +2,71 @@
2
2
  This module provides functionality for handling synchronous and asynchronous flags
3
3
  in the ez-a-sync library.
4
4
 
5
- ez-a-sync uses 'flags' to indicate whether objects / function calls will be sync or async.
5
+ ez-a-sync uses 'flags' to indicate whether objects or function calls will be synchronous or asynchronous.
6
6
 
7
- You can use any of the provided flags, whichever makes most sense for your use case.
7
+ You can use any of the provided flags, whichever makes the most sense for your use case.
8
+
9
+ AFFIRMATIVE_FLAGS: Set of flags indicating synchronous behavior. Currently includes "sync".
10
+
11
+ NEGATIVE_FLAGS: Set of flags indicating asynchronous behavior. Currently includes "asynchronous".
12
+
13
+ VIABLE_FLAGS: Set of all valid flags, combining both synchronous and asynchronous indicators.
14
+
15
+ Functions:
16
+ - negate_if_necessary: Negates the flag value if necessary based on the flag type.
17
+ - validate_flag_value: Validates that the flag value is a boolean.
8
18
  """
9
19
 
10
20
  from typing import Any
11
21
 
12
22
  from a_sync import exceptions
13
23
 
14
- AFFIRMATIVE_FLAGS = {'sync'}
15
- """Set of flags indicating synchronous behavior."""
24
+ AFFIRMATIVE_FLAGS = {"sync"}
25
+ """Set of flags indicating synchronous behavior.
26
+
27
+ Examples:
28
+ >>> 'sync' in AFFIRMATIVE_FLAGS
29
+ True
30
+
31
+ >>> 'async' in AFFIRMATIVE_FLAGS
32
+ False
33
+ """
34
+
35
+ NEGATIVE_FLAGS = {"asynchronous"}
36
+ """Set of flags indicating asynchronous behavior.
16
37
 
17
- NEGATIVE_FLAGS = {'asynchronous'}
18
- """Set of flags indicating asynchronous behavior."""
38
+ Examples:
39
+ >>> 'asynchronous' in NEGATIVE_FLAGS
40
+ True
41
+
42
+ >>> 'sync' in NEGATIVE_FLAGS
43
+ False
44
+ """
19
45
 
20
46
  VIABLE_FLAGS = AFFIRMATIVE_FLAGS | NEGATIVE_FLAGS
21
- """Set of all valid flags."""
47
+ """Set of all valid flags, combining both synchronous and asynchronous indicators.
48
+
49
+ A-Sync uses 'flags' to indicate whether objects or function calls will be sync or async.
50
+ You can use any of the provided flags, whichever makes most sense for your use case.
51
+
52
+ Examples:
53
+ >>> 'sync' in VIABLE_FLAGS
54
+ True
55
+
56
+ >>> 'asynchronous' in VIABLE_FLAGS
57
+ True
58
+
59
+ >>> 'invalid' in VIABLE_FLAGS
60
+ False
61
+ """
62
+
22
63
 
23
64
  def negate_if_necessary(flag: str, flag_value: bool) -> bool:
24
65
  """Negate the flag value if necessary based on the flag type.
25
66
 
67
+ This function checks if the provided flag is in the set of affirmative or negative flags
68
+ and negates the flag value accordingly. If the flag is not recognized, it raises an exception.
69
+
26
70
  Args:
27
71
  flag: The flag to check.
28
72
  flag_value: The value of the flag.
@@ -31,7 +75,17 @@ def negate_if_necessary(flag: str, flag_value: bool) -> bool:
31
75
  The potentially negated flag value.
32
76
 
33
77
  Raises:
34
- :class:`exceptions.InvalidFlag`: If the flag is not recognized.
78
+ exceptions.InvalidFlag: If the flag is not recognized.
79
+
80
+ Examples:
81
+ >>> negate_if_necessary('sync', True)
82
+ True
83
+
84
+ >>> negate_if_necessary('asynchronous', True)
85
+ False
86
+
87
+ See Also:
88
+ - :func:`validate_flag_value`: Validates that the flag value is a boolean.
35
89
  """
36
90
  validate_flag_value(flag, flag_value)
37
91
  if flag in AFFIRMATIVE_FLAGS:
@@ -40,10 +94,13 @@ def negate_if_necessary(flag: str, flag_value: bool) -> bool:
40
94
  return bool(not flag_value)
41
95
  raise exceptions.InvalidFlag(flag)
42
96
 
97
+
43
98
  def validate_flag_value(flag: str, flag_value: Any) -> bool:
44
99
  """
45
100
  Validate that the flag value is a boolean.
46
101
 
102
+ This function ensures that the provided flag value is of type boolean. If not, it raises an exception.
103
+
47
104
  Args:
48
105
  flag: The flag being validated.
49
106
  flag_value: The value to validate.
@@ -52,7 +109,19 @@ def validate_flag_value(flag: str, flag_value: Any) -> bool:
52
109
  The validated flag value.
53
110
 
54
111
  Raises:
55
- :class:`exceptions.InvalidFlagValue`: If the flag value is not a boolean.
112
+ exceptions.InvalidFlagValue: If the flag value is not a boolean.
113
+
114
+ Examples:
115
+ >>> validate_flag_value('sync', True)
116
+ True
117
+
118
+ >>> validate_flag_value('asynchronous', 'yes')
119
+ Traceback (most recent call last):
120
+ ...
121
+ exceptions.InvalidFlagValue: Invalid flag value for 'asynchronous': 'yes'
122
+
123
+ See Also:
124
+ - :func:`negate_if_necessary`: Negates the flag value if necessary based on the flag type.
56
125
  """
57
126
  if not isinstance(flag_value, bool):
58
127
  raise exceptions.InvalidFlagValue(flag, flag_value)
a_sync/a_sync/_helpers.py CHANGED
@@ -17,13 +17,21 @@ def _await(awaitable: Awaitable[T]) -> T:
17
17
  Await an awaitable object in a synchronous context.
18
18
 
19
19
  Args:
20
- awaitable: The awaitable object to be awaited.
21
-
22
- Returns:
23
- The result of the awaitable.
20
+ awaitable (Awaitable[T]): The awaitable object to be awaited.
24
21
 
25
22
  Raises:
26
- :class:`exceptions.SyncModeInAsyncContextError`: If the event loop is already running.
23
+ exceptions.SyncModeInAsyncContextError: If the event loop is already running.
24
+
25
+ Examples:
26
+ >>> async def example_coroutine():
27
+ ... return 42
28
+ ...
29
+ >>> result = _await(example_coroutine())
30
+ >>> print(result)
31
+ 42
32
+
33
+ See Also:
34
+ - :func:`asyncio.run`: For running the main entry point of an asyncio program.
27
35
  """
28
36
  try:
29
37
  return a_sync.asyncio.get_event_loop().run_until_complete(awaitable)
@@ -32,27 +40,52 @@ def _await(awaitable: Awaitable[T]) -> T:
32
40
  raise exceptions.SyncModeInAsyncContextError from None
33
41
  raise
34
42
 
43
+
35
44
  def _asyncify(func: SyncFn[P, T], executor: Executor) -> CoroFn[P, T]: # type: ignore [misc]
36
45
  """
37
- Convert a synchronous function to a coroutine function.
46
+ Convert a synchronous function to a coroutine function using an executor.
38
47
 
39
- Args:
40
- func: The synchronous function to be converted.
41
- executor: The executor to run the synchronous function.
48
+ This function submits the synchronous function to the provided executor and wraps
49
+ the resulting future in a coroutine function. This allows the synchronous function
50
+ to be executed asynchronously.
51
+
52
+ Note:
53
+ The function `_asyncify` uses `asyncio.futures.wrap_future` to wrap the future
54
+ returned by the executor, specifying the event loop with `a_sync.asyncio.get_event_loop()`.
55
+ Ensure that your environment supports this usage or adjust the import if necessary.
42
56
 
43
- Returns:
44
- A coroutine function wrapping the input function.
57
+ Args:
58
+ func (SyncFn[P, T]): The synchronous function to be converted.
59
+ executor (Executor): The executor used to run the synchronous function.
45
60
 
46
61
  Raises:
47
- :class:`exceptions.FunctionNotSync`: If the input function is already asynchronous.
62
+ exceptions.FunctionNotSync: If the input function is a coroutine function or an instance of :class:`~a_sync.a_sync.function.ASyncFunction`.
63
+
64
+ Examples:
65
+ >>> from concurrent.futures import ThreadPoolExecutor
66
+ >>> def sync_function(x):
67
+ ... return x * 2
68
+ ...
69
+ >>> executor = ThreadPoolExecutor()
70
+ >>> async_function = _asyncify(sync_function, executor)
71
+ >>> result = await async_function(3)
72
+ >>> print(result)
73
+ 6
74
+
75
+ See Also:
76
+ - :class:`concurrent.futures.Executor`: For managing pools of threads or processes.
77
+ - :func:`asyncio.to_thread`: For running blocking code in a separate thread.
48
78
  """
49
79
  from a_sync.a_sync.function import ASyncFunction
80
+
50
81
  if asyncio.iscoroutinefunction(func) or isinstance(func, ASyncFunction):
51
82
  raise exceptions.FunctionNotSync(func)
83
+
52
84
  @functools.wraps(func)
53
85
  async def _asyncify_wrap(*args: P.args, **kwargs: P.kwargs) -> T:
54
86
  return await asyncio.futures.wrap_future(
55
- executor.submit(func, *args, **kwargs),
87
+ executor.submit(func, *args, **kwargs),
56
88
  loop=a_sync.asyncio.get_event_loop(),
57
89
  )
90
+
58
91
  return _asyncify_wrap