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
a_sync/exceptions.py CHANGED
@@ -14,28 +14,53 @@ if TYPE_CHECKING:
14
14
  class ASyncFlagException(ValueError):
15
15
  """
16
16
  Base exception class for flag-related errors in the a_sync library.
17
+
18
+ A-Sync uses 'flags' to indicate whether objects or function calls will be sync or async.
19
+ You can use any of the provided flags, whichever makes most sense for your use case.
17
20
  """
18
- @property
19
- def viable_flags(self) -> Set[str]:
20
- """
21
- Returns the set of viable flags.
22
- """
23
- return VIABLE_FLAGS
21
+
22
+ viable_flags = VIABLE_FLAGS
23
+ """The set of viable flags."""
24
24
 
25
25
  def desc(self, target) -> str:
26
- if target == 'kwargs':
26
+ """
27
+ Returns a description of the target for the flag error message.
28
+
29
+ Args:
30
+ target: The target object or string to describe.
31
+
32
+ Examples:
33
+ >>> exception = ASyncFlagException()
34
+ >>> exception.desc("kwargs")
35
+ "flags present in 'kwargs'"
36
+
37
+ >>> exception.desc("some_target")
38
+ 'flag attributes defined on some_target'
39
+ """
40
+ if target == "kwargs":
27
41
  return "flags present in 'kwargs'"
28
42
  else:
29
- return f'flag attributes defined on {target}'
43
+ return f"flag attributes defined on {target}"
44
+
30
45
 
31
46
  class NoFlagsFound(ASyncFlagException):
32
47
  """
33
48
  Raised when no viable flags are found in the target.
49
+
50
+ Examples:
51
+ >>> try:
52
+ ... raise NoFlagsFound("some_target")
53
+ ... except NoFlagsFound as e:
54
+ ... print(e)
55
+ There are no viable a_sync flag attributes defined on some_target:
56
+ Viable flags: {'sync', 'asynchronous'}
57
+ This is likely an issue with a custom subclass definition.
34
58
  """
59
+
35
60
  def __init__(self, target, kwargs_keys=None):
36
61
  """
37
62
  Initializes the NoFlagsFound exception.
38
-
63
+
39
64
  Args:
40
65
  target: The target object where flags were expected.
41
66
  kwargs_keys: Optional; keys in the kwargs if applicable.
@@ -47,63 +72,117 @@ class NoFlagsFound(ASyncFlagException):
47
72
  err += "\nThis is likely an issue with a custom subclass definition."
48
73
  super().__init__(err)
49
74
 
75
+
50
76
  class TooManyFlags(ASyncFlagException):
51
77
  """
52
78
  Raised when multiple flags are found, but only one was expected.
79
+
80
+ Examples:
81
+ >>> try:
82
+ ... raise TooManyFlags("some_target", ["flag1", "flag2"])
83
+ ... except TooManyFlags as e:
84
+ ... print(e)
85
+ There are multiple a_sync flag attributes defined on some_target and there should only be one.
86
+ Present flags: ['flag1', 'flag2']
87
+ This is likely an issue with a custom subclass definition.
53
88
  """
89
+
54
90
  def __init__(self, target, present_flags):
55
91
  """
56
92
  Initializes the TooManyFlags exception.
57
-
93
+
58
94
  Args:
59
95
  target: The target object where flags were found.
60
96
  present_flags: The flags that were found.
97
+
98
+ See Also:
99
+ - :class:`ASyncFlagException`
61
100
  """
62
- err = f"There are multiple a_sync {self.__get_desc(target)} and there should only be one.\n"
101
+ err = f"There are multiple a_sync {self.desc(target)} and there should only be one.\n"
63
102
  err += f"Present flags: {present_flags}\n"
64
103
  err += "This is likely an issue with a custom subclass definition."
65
104
  super().__init__(err)
66
105
 
106
+
67
107
  class InvalidFlag(ASyncFlagException):
68
108
  """
69
109
  Raised when an invalid flag is encountered.
110
+
111
+ Examples:
112
+ >>> try:
113
+ ... raise InvalidFlag("invalid_flag")
114
+ ... except InvalidFlag as e:
115
+ ... print(e)
116
+ 'flag' must be one of: {'sync', 'asynchronous'}. You passed invalid_flag.
117
+ This code should not be reached and likely indicates an issue with a custom subclass definition.
70
118
  """
119
+
71
120
  def __init__(self, flag: Optional[str]):
72
121
  """
73
122
  Initializes the InvalidFlag exception.
74
-
123
+
75
124
  Args:
76
125
  flag: The invalid flag.
126
+
127
+ See Also:
128
+ - :class:`ASyncFlagException`
77
129
  """
78
130
  err = f"'flag' must be one of: {self.viable_flags}. You passed {flag}."
79
131
  err += "\nThis code should not be reached and likely indicates an issue with a custom subclass definition."
80
132
  super().__init__(err)
81
133
 
134
+
82
135
  class InvalidFlagValue(ASyncFlagException):
83
136
  """
84
137
  Raised when a flag has an invalid value.
138
+
139
+ Examples:
140
+ >>> try:
141
+ ... raise InvalidFlagValue("some_flag", "not_a_boolean")
142
+ ... except InvalidFlagValue as e:
143
+ ... print(e)
144
+ 'some_flag' should be boolean. You passed not_a_boolean.
85
145
  """
146
+
86
147
  def __init__(self, flag: str, flag_value: Any):
87
148
  """
88
149
  Initializes the InvalidFlagValue exception.
89
-
150
+
90
151
  Args:
91
152
  flag: The flag with an invalid value.
92
153
  flag_value: The invalid value of the flag.
154
+
155
+ See Also:
156
+ - :class:`ASyncFlagException`
93
157
  """
94
158
  super().__init__(f"'{flag}' should be boolean. You passed {flag_value}.")
95
159
 
160
+
96
161
  class FlagNotDefined(ASyncFlagException):
97
162
  """
98
163
  Raised when a flag is not defined on an object.
164
+
165
+ Examples:
166
+ >>> class SomeClass:
167
+ ... pass
168
+ ...
169
+ >>> try:
170
+ ... raise FlagNotDefined(SomeClass, "some_flag")
171
+ ... except FlagNotDefined as e:
172
+ ... print(e)
173
+ <class '__main__.SomeClass'> flag some_flag is not defined.
99
174
  """
175
+
100
176
  def __init__(self, obj: Type, flag: str):
101
177
  """
102
178
  Initializes the FlagNotDefined exception.
103
-
179
+
104
180
  Args:
105
181
  obj: The object where the flag is not defined.
106
182
  flag: The undefined flag.
183
+
184
+ See Also:
185
+ - :class:`ASyncFlagException`
107
186
  """
108
187
  super().__init__(f"{obj} flag {flag} is not defined.")
109
188
 
@@ -111,51 +190,118 @@ class FlagNotDefined(ASyncFlagException):
111
190
  class ImproperFunctionType(ValueError):
112
191
  """
113
192
  Raised when a function that should be sync is async or vice-versa.
193
+
194
+ See Also:
195
+ - :class:`FunctionNotAsync`
196
+ - :class:`FunctionNotSync`
114
197
  """
115
198
 
199
+
116
200
  class FunctionNotAsync(ImproperFunctionType):
117
201
  """
118
202
  Raised when a function expected to be async is not.
203
+
204
+ Examples:
205
+ >>> def some_function():
206
+ ... pass
207
+ ...
208
+ >>> try:
209
+ ... raise FunctionNotAsync(some_function)
210
+ ... except FunctionNotAsync as e:
211
+ ... print(e)
212
+ `coro_fn` must be a coroutine function defined with `async def`. You passed <function some_function at 0x...>.
119
213
  """
214
+
120
215
  def __init__(self, fn):
121
216
  """
122
217
  Initializes the FunctionNotAsync exception.
123
-
218
+
124
219
  Args:
125
220
  fn: The function that is not async.
221
+
222
+ See Also:
223
+ - :class:`ImproperFunctionType`
126
224
  """
127
- super().__init__(f"`coro_fn` must be a coroutine function defined with `async def`. You passed {fn}.")
225
+ super().__init__(
226
+ f"`coro_fn` must be a coroutine function defined with `async def`. You passed {fn}."
227
+ )
228
+
128
229
 
129
230
  class FunctionNotSync(ImproperFunctionType):
130
231
  """
131
- Raised when a function expected to be sync is not.
232
+ Raised when a function expected to be sync is actually async.
233
+
234
+ Examples:
235
+ >>> async def some_async_function():
236
+ ... pass
237
+ ...
238
+ >>> try:
239
+ ... raise FunctionNotSync(some_async_function)
240
+ ... except FunctionNotSync as e:
241
+ ... print(e)
242
+ `func` must be a coroutine function defined with `def`. You passed <function some_async_function at 0x...>.
132
243
  """
244
+
133
245
  def __init__(self, fn):
134
246
  """
135
247
  Initializes the FunctionNotSync exception.
136
-
248
+
137
249
  Args:
138
250
  fn: The function that is not sync.
251
+
252
+ See Also:
253
+ - :class:`ImproperFunctionType`
139
254
  """
140
- super().__init__(f"`func` must be a coroutine function defined with `def`. You passed {fn}.")
141
-
255
+ super().__init__(
256
+ f"`func` must be a coroutine function defined with `def`. You passed {fn}."
257
+ )
258
+
259
+
142
260
  class ASyncRuntimeError(RuntimeError):
261
+ """
262
+ Raised for runtime errors in asynchronous operations.
263
+
264
+ Examples:
265
+ >>> try:
266
+ ... raise ASyncRuntimeError(RuntimeError("Some runtime error"))
267
+ ... except ASyncRuntimeError as e:
268
+ ... print(e)
269
+ Some runtime error
270
+ """
271
+
143
272
  def __init__(self, e: RuntimeError):
144
273
  """
145
274
  Initializes the ASyncRuntimeError exception.
146
-
275
+
147
276
  Args:
148
277
  e: The original runtime error.
278
+
279
+ See Also:
280
+ - :class:`RuntimeError`
149
281
  """
150
282
  super().__init__(str(e))
151
283
 
284
+
152
285
  class SyncModeInAsyncContextError(ASyncRuntimeError):
153
286
  """
154
287
  Raised when synchronous code is used within an asynchronous context.
288
+
289
+ Examples:
290
+ >>> try:
291
+ ... raise SyncModeInAsyncContextError()
292
+ ... except SyncModeInAsyncContextError as e:
293
+ ... print(e)
294
+ The event loop is already running, which means you're trying to use an `ASyncFunction` synchronously from within an async context.
295
+ Check your traceback to determine which, then try calling asynchronously instead with one of the following kwargs:
296
+ {'sync', 'asynchronous'}
155
297
  """
156
- def __init__(self, err: str = ''):
298
+
299
+ def __init__(self, err: str = ""):
157
300
  """
158
301
  Initializes the SyncModeInAsyncContextError exception.
302
+
303
+ See Also:
304
+ - :class:`ASyncRuntimeError`
159
305
  """
160
306
  if not err:
161
307
  err = "The event loop is already running, which means you're trying to use an `ASyncFunction` synchronously from within an async context.\n"
@@ -163,45 +309,121 @@ class SyncModeInAsyncContextError(ASyncRuntimeError):
163
309
  err += f"{VIABLE_FLAGS}"
164
310
  super().__init__(err)
165
311
 
312
+
166
313
  class MappingError(Exception):
167
314
  """
168
315
  Base class for errors related to :class:`~TaskMapping`.
316
+
317
+ Examples:
318
+ >>> from a_sync import TaskMapping
319
+ >>> try:
320
+ ... raise MappingError(TaskMapping(), "Some mapping error")
321
+ ... except MappingError as e:
322
+ ... print(e)
323
+ Some mapping error:
324
+ <TaskMapping object at 0x...>
325
+ {}
169
326
  """
327
+
170
328
  _msg: str
171
329
 
172
- def __init__(self, mapping: "TaskMapping", msg: str = ''):
330
+ def __init__(self, mapping: "TaskMapping", msg: str = ""):
173
331
  """
174
332
  Initializes the MappingError exception.
175
-
333
+
176
334
  Args:
177
335
  mapping: The TaskMapping where the error occurred.
178
336
  msg: An optional message describing the error.
337
+
338
+ See Also:
339
+ - :class:`TaskMapping`
179
340
  """
180
- msg = msg or self._msg + f":\n{mapping}"
341
+ msg = (msg or self._msg) + f":\n{mapping}"
181
342
  if mapping:
182
343
  msg += f"\n{dict(mapping)}"
183
344
  super().__init__(msg)
184
345
  self.mapping = mapping
185
346
 
347
+
186
348
  class MappingIsEmptyError(MappingError):
187
349
  """
188
350
  Raised when a TaskMapping is empty and an operation requires it to have items.
351
+
352
+ Examples:
353
+ >>> from a_sync import TaskMapping
354
+ >>> try:
355
+ ... raise MappingIsEmptyError(TaskMapping())
356
+ ... except MappingIsEmptyError as e:
357
+ ... print(e)
358
+ TaskMapping does not contain anything to yield:
359
+ <TaskMapping object at 0x...>
360
+ {}
189
361
  """
362
+
190
363
  _msg = "TaskMapping does not contain anything to yield"
191
364
 
365
+
192
366
  class MappingNotEmptyError(MappingError):
193
367
  """
194
368
  Raised when a TaskMapping is not empty and an operation requires it to be empty.
369
+
370
+ Examples:
371
+ >>> from a_sync import TaskMapping
372
+ >>> task_mapping = TaskMapping()
373
+ >>> task_mapping['key'] = 'value'
374
+ >>> try:
375
+ ... raise MappingNotEmptyError(task_mapping)
376
+ ... except MappingNotEmptyError as e:
377
+ ... print(e)
378
+ TaskMapping already contains some data. In order to use `map`, you need a fresh one:
379
+ <TaskMapping object at 0x...>
380
+ {'key': 'value'}
195
381
  """
382
+
196
383
  _msg = "TaskMapping already contains some data. In order to use `map`, you need a fresh one"
197
384
 
385
+
198
386
  class PersistedTaskException(Exception):
387
+ """
388
+ Raised when an exception persists in an asyncio Task.
389
+
390
+ Examples:
391
+ >>> import asyncio
392
+ >>> async def some_task():
393
+ ... raise ValueError("Some error")
394
+ ...
395
+ >>> task = asyncio.create_task(some_task())
396
+ >>> try:
397
+ ... raise PersistedTaskException(ValueError("Some error"), task)
398
+ ... except PersistedTaskException as e:
399
+ ... print(e)
400
+ ValueError: Some error
401
+ """
402
+
199
403
  def __init__(self, exc: E, task: asyncio.Task) -> None:
404
+ """
405
+ Initializes the PersistedTaskException exception.
406
+
407
+ Args:
408
+ exc: The exception that persisted.
409
+ task: The asyncio Task where the exception occurred.
410
+
411
+ See Also:
412
+ - :class:`asyncio.Task`
413
+ """
200
414
  super().__init__(f"{exc.__class__.__name__}: {exc}", task)
201
415
  self.exception = exc
202
416
  self.task = task
203
417
 
418
+
204
419
  class EmptySequenceError(ValueError):
205
420
  """
206
- Raised when an operation is attempted on an empty sequence but items are expected.
421
+ Raised when an operation is attempted on an empty sequence but items are required.
422
+
423
+ Examples:
424
+ >>> try:
425
+ ... raise EmptySequenceError("Sequence is empty")
426
+ ... except EmptySequenceError as e:
427
+ ... print(e)
428
+ Sequence is empty
207
429
  """