ez-a-sync 0.22.5__tar.gz → 0.22.7__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.

Potentially problematic release.


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

Files changed (116) hide show
  1. ez_a_sync-0.22.7/.sourcery.yaml +85 -0
  2. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/PKG-INFO +1 -1
  3. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/_typing.py +35 -5
  4. ez_a_sync-0.22.7/a_sync/a_sync/_descriptor.py +219 -0
  5. ez_a_sync-0.22.7/a_sync/a_sync/_flags.py +59 -0
  6. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/a_sync/_helpers.py +29 -0
  7. ez_a_sync-0.22.7/a_sync/a_sync/_kwargs.py +44 -0
  8. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/a_sync/abstract.py +2 -0
  9. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/a_sync/base.py +48 -8
  10. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/a_sync/config.py +16 -0
  11. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/a_sync/decorator.py +98 -85
  12. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/a_sync/function.py +392 -33
  13. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/a_sync/method.py +336 -1
  14. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/a_sync/property.py +69 -4
  15. ez_a_sync-0.22.7/a_sync/a_sync/singleton.py +36 -0
  16. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/asyncio/as_completed.py +3 -0
  17. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/asyncio/gather.py +29 -17
  18. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/exceptions.py +20 -10
  19. ez_a_sync-0.22.7/a_sync/iter.py +477 -0
  20. ez_a_sync-0.22.7/a_sync/primitives/_debug.py +82 -0
  21. ez_a_sync-0.22.7/a_sync/primitives/locks/counter.py +144 -0
  22. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/primitives/locks/semaphore.py +101 -5
  23. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/task.py +21 -2
  24. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/utils/__init__.py +35 -5
  25. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/utils/iterators.py +9 -12
  26. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/docs/conf.py +2 -1
  27. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/ez_a_sync.egg-info/PKG-INFO +1 -1
  28. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/ez_a_sync.egg-info/SOURCES.txt +2 -0
  29. ez_a_sync-0.22.7/tests/test_abstract.py +17 -0
  30. ez_a_sync-0.22.7/tests/test_base.py +235 -0
  31. ez_a_sync-0.22.7/tests/test_iter.py +294 -0
  32. ez_a_sync-0.22.5/a_sync/a_sync/_descriptor.py +0 -62
  33. ez_a_sync-0.22.5/a_sync/a_sync/_flags.py +0 -26
  34. ez_a_sync-0.22.5/a_sync/a_sync/_kwargs.py +0 -18
  35. ez_a_sync-0.22.5/a_sync/a_sync/singleton.py +0 -12
  36. ez_a_sync-0.22.5/a_sync/iter.py +0 -276
  37. ez_a_sync-0.22.5/a_sync/primitives/_debug.py +0 -30
  38. ez_a_sync-0.22.5/a_sync/primitives/locks/counter.py +0 -73
  39. ez_a_sync-0.22.5/tests/test_base.py +0 -99
  40. ez_a_sync-0.22.5/tests/test_iter.py +0 -106
  41. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/.coverage +0 -0
  42. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/.github/workflows/codeql.yaml +0 -0
  43. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/.github/workflows/docs.yaml +0 -0
  44. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/.github/workflows/mypy.yaml +0 -0
  45. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/.github/workflows/pytest.yaml +0 -0
  46. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/.github/workflows/release.yaml +0 -0
  47. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/.gitignore +0 -0
  48. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/LICENSE.txt +0 -0
  49. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/Makefile +0 -0
  50. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/README.md +0 -0
  51. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/TODO +0 -0
  52. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/ENVIRONMENT_VARIABLES.py +0 -0
  53. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/__init__.py +0 -0
  54. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/_smart.py +0 -0
  55. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/a_sync/__init__.py +0 -0
  56. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/a_sync/_meta.py +0 -0
  57. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/a_sync/modifiers/__init__.py +0 -0
  58. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/a_sync/modifiers/cache/__init__.py +0 -0
  59. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/a_sync/modifiers/cache/memory.py +0 -0
  60. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/a_sync/modifiers/limiter.py +0 -0
  61. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/a_sync/modifiers/manager.py +0 -0
  62. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/a_sync/modifiers/semaphores.py +0 -0
  63. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/aliases.py +0 -0
  64. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/asyncio/__init__.py +0 -0
  65. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/asyncio/create_task.py +0 -0
  66. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/asyncio/utils.py +0 -0
  67. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/executor.py +0 -0
  68. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/future.py +0 -0
  69. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/primitives/__init__.py +0 -0
  70. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/primitives/_loggable.py +0 -0
  71. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/primitives/locks/__init__.py +0 -0
  72. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/primitives/locks/event.py +0 -0
  73. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/primitives/locks/prio_semaphore.py +0 -0
  74. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/primitives/queue.py +0 -0
  75. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/py.typed +0 -0
  76. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/sphinx/__init__.py +0 -0
  77. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/a_sync/sphinx/ext.py +0 -0
  78. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/docs/Makefile +0 -0
  79. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/docs/_build/html/_static/alabaster.css +0 -0
  80. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/docs/_build/html/_static/basic.css +0 -0
  81. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/docs/_build/html/_static/custom.css +0 -0
  82. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/docs/_build/html/_static/doctools.js +0 -0
  83. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/docs/_build/html/_static/documentation_options.js +0 -0
  84. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/docs/_build/html/_static/file.png +0 -0
  85. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/docs/_build/html/_static/language_data.js +0 -0
  86. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/docs/_build/html/_static/minus.png +0 -0
  87. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/docs/_build/html/_static/plus.png +0 -0
  88. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/docs/_build/html/_static/pygments.css +0 -0
  89. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/docs/_build/html/_static/searchtools.js +0 -0
  90. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/docs/_build/html/_static/sphinx_highlight.js +0 -0
  91. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/docs/index.rst +0 -0
  92. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/docs/make.bat +0 -0
  93. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/ez_a_sync.egg-info/dependency_links.txt +0 -0
  94. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/ez_a_sync.egg-info/requires.txt +0 -0
  95. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/ez_a_sync.egg-info/top_level.txt +0 -0
  96. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/requirements-dev.txt +0 -0
  97. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/requirements.txt +0 -0
  98. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/setup.cfg +0 -0
  99. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/setup.py +0 -0
  100. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/tests/__init__.py +0 -0
  101. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/tests/conftest.py +0 -0
  102. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/tests/executor.py +0 -0
  103. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/tests/fixtures.py +0 -0
  104. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/tests/test_as_completed.py +0 -0
  105. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/tests/test_cache.py +0 -0
  106. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/tests/test_decorator.py +0 -0
  107. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/tests/test_executor.py +0 -0
  108. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/tests/test_future.py +0 -0
  109. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/tests/test_gather.py +0 -0
  110. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/tests/test_helpers.py +0 -0
  111. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/tests/test_limiter.py +0 -0
  112. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/tests/test_meta.py +0 -0
  113. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/tests/test_modified.py +0 -0
  114. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/tests/test_semaphore.py +0 -0
  115. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/tests/test_singleton.py +0 -0
  116. {ez_a_sync-0.22.5 → ez_a_sync-0.22.7}/tests/test_task.py +0 -0
@@ -0,0 +1,85 @@
1
+ # 🪄 This is your project's Sourcery configuration file.
2
+
3
+ # You can use it to get Sourcery working in the way you want, such as
4
+ # ignoring specific refactorings, skipping directories in your project,
5
+ # or writing custom rules.
6
+
7
+ # 📚 For a complete reference to this file, see the documentation at
8
+ # https://docs.sourcery.ai/Configuration/Project-Settings/
9
+
10
+ # This file was auto-generated by Sourcery on 2024-09-06 at 01:04.
11
+
12
+ version: '1' # The schema version of this config file
13
+
14
+ ignore: # A list of paths or files which Sourcery will ignore.
15
+ - .git
16
+ - env
17
+ - .env
18
+ - .tox
19
+ - node_modules
20
+ - vendor
21
+ - venv
22
+ - .venv
23
+ - ~/.pyenv
24
+ - ~/.rye
25
+ - ~/.vscode
26
+ - .vscode
27
+ - ~/.cache
28
+ - ~/.config
29
+ - ~/.local
30
+
31
+ rule_settings:
32
+ enable:
33
+ - default
34
+ disable: [
35
+ no-conditionals-in-tests,
36
+ no-loop-in-tests,
37
+ dict-assign-update-to-union, # we want to be backward compatible with many pythons
38
+ ] # A list of rule IDs Sourcery will never suggest.
39
+ rule_types:
40
+ - refactoring
41
+ - suggestion
42
+ - comment
43
+ python_version: '3.9' # A string specifying the lowest Python version your project supports. Sourcery will not suggest refactorings requiring a higher Python version.
44
+
45
+ # rules: # A list of custom rules Sourcery will include in its analysis.
46
+ # - id: no-print-statements
47
+ # description: Do not use print statements in the test directory.
48
+ # pattern: print(...)
49
+ # language: python
50
+ # replacement:
51
+ # condition:
52
+ # explanation:
53
+ # paths:
54
+ # include:
55
+ # - test
56
+ # exclude:
57
+ # - conftest.py
58
+ # tests: []
59
+ # tags: []
60
+
61
+ # rule_tags: {} # Additional rule tags.
62
+
63
+ # metrics:
64
+ # quality_threshold: 25.0
65
+
66
+ # github:
67
+ # labels: []
68
+ # ignore_labels:
69
+ # - sourcery-ignore
70
+ # request_review: author
71
+ # sourcery_branch: sourcery/{base_branch}
72
+
73
+ # clone_detection:
74
+ # min_lines: 3
75
+ # min_duplicates: 2
76
+ # identical_clones_only: false
77
+
78
+ # proxy:
79
+ # url:
80
+ # ssl_certs_file:
81
+ # no_ssl_verify: false
82
+
83
+ # coding_assistant:
84
+ # project_description: ''
85
+ # enabled: true
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ez-a-sync
3
- Version: 0.22.5
3
+ Version: 0.22.7
4
4
  Summary: A library that makes it easy to define objects that can be used for both sync and async use cases.
5
5
  Home-page: https://github.com/BobTheBuidler/a-sync
6
6
  Author: BobTheBuidler
@@ -1,6 +1,7 @@
1
1
  """
2
2
  This module provides type definitions and type-related utilities for the a_sync library.
3
- It includes various type aliases, protocols, and TypedDicts used throughout the library.
3
+ It includes various type aliases, protocols, and TypedDicts used throughout the library
4
+ to enhance type checking and provide better IDE support.
4
5
  """
5
6
 
6
7
  import asyncio
@@ -24,15 +25,19 @@ T = TypeVar("T")
24
25
  K = TypeVar("K")
25
26
  V = TypeVar("V")
26
27
  I = TypeVar("I")
28
+ """A :class:`TypeVar` that is used to represent instances of a common class."""
29
+
27
30
  E = TypeVar('E', bound=Exception)
28
31
  TYPE = TypeVar("TYPE", bound=Type)
32
+
29
33
  P = ParamSpec("P")
34
+ """A :class:`ParamSpec` used everywhere in the lib."""
30
35
 
31
36
  Numeric = Union[int, float, Decimal]
32
- "Type alias for numeric values int, float, and Decimal."
37
+ """Type alias for numeric values of types int, float, or Decimal."""
33
38
 
34
39
  MaybeAwaitable = Union[Awaitable[T], T]
35
- "Type alias for values that may or may not be awaitable."
40
+ """Type alias for values that may or may not be awaitable. Useful for functions that can return either an awaitable or a direct value."""
36
41
 
37
42
  MaybeCoro = Union[Coroutine[Any, Any, T], T]
38
43
  "Type alias for values that may or may not be coroutine."
@@ -41,7 +46,7 @@ CoroFn = Callable[P, Awaitable[T]]
41
46
  "Type alias for any function that returns an awaitable."
42
47
 
43
48
  SyncFn = Callable[P, T]
44
- "Type alias for synchronous functions."
49
+ """Type alias for synchronous functions."""
45
50
 
46
51
  AnyFn = Union[CoroFn[P, T], SyncFn[P, T]]
47
52
  "Type alias for any function, whether synchronous or asynchronous."
@@ -49,6 +54,14 @@ AnyFn = Union[CoroFn[P, T], SyncFn[P, T]]
49
54
  class CoroBoundMethod(Protocol[I, P, T]):
50
55
  """
51
56
  Protocol for coroutine bound methods.
57
+
58
+ Example:
59
+ class MyClass:
60
+ async def my_method(self, x: int) -> str:
61
+ return str(x)
62
+
63
+ instance = MyClass()
64
+ bound_method: CoroBoundMethod[MyClass, [int], str] = instance.my_method
52
65
  """
53
66
  __self__: I
54
67
  __call__: Callable[P, Awaitable[T]]
@@ -56,6 +69,14 @@ class CoroBoundMethod(Protocol[I, P, T]):
56
69
  class SyncBoundMethod(Protocol[I, P, T]):
57
70
  """
58
71
  Protocol for synchronous bound methods.
72
+
73
+ Example:
74
+ class MyClass:
75
+ def my_method(self, x: int) -> str:
76
+ return str(x)
77
+
78
+ instance = MyClass()
79
+ bound_method: SyncBoundMethod[MyClass, [int], str] = instance.my_method
59
80
  """
60
81
  __self__: I
61
82
  __call__: Callable[P, T]
@@ -67,6 +88,9 @@ AnyBoundMethod = Union[CoroBoundMethod[Any, P, T], SyncBoundMethod[Any, P, T]]
67
88
  class AsyncUnboundMethod(Protocol[I, P, T]):
68
89
  """
69
90
  Protocol for unbound asynchronous methods.
91
+
92
+ An unbound method is a method that hasn't been bound to an instance of a class yet.
93
+ It's essentially the function object itself, before it's accessed through an instance.
70
94
  """
71
95
  __get__: Callable[[I, Type], CoroBoundMethod[I, P, T]]
72
96
 
@@ -74,6 +98,9 @@ class AsyncUnboundMethod(Protocol[I, P, T]):
74
98
  class SyncUnboundMethod(Protocol[I, P, T]):
75
99
  """
76
100
  Protocol for unbound synchronous methods.
101
+
102
+ An unbound method is a method that hasn't been bound to an instance of a class yet.
103
+ It's essentially the function object itself, before it's accessed through an instance.
77
104
  """
78
105
  __get__: Callable[[I, Type], SyncBoundMethod[I, P, T]]
79
106
 
@@ -122,4 +149,7 @@ AnyIterable = Union[AsyncIterable[K], Iterable[K]]
122
149
  "Type alias for any iterable, whether synchronous or asynchronous."
123
150
 
124
151
  AnyIterableOrAwaitableIterable = Union[AnyIterable[K], Awaitable[AnyIterable[K]]]
125
- "Type alias for any iterable, whether synchronous or asynchronous, or an awaitable that resolves to any iterable, whether synchronous or asynchronous."
152
+ """
153
+ Type alias for any iterable, whether synchronous or asynchronous,
154
+ or an awaitable that resolves to any iterable, whether synchronous or asynchronous.
155
+ """
@@ -0,0 +1,219 @@
1
+ """
2
+ This module contains the ASyncDescriptor class, which is used to create sync/async methods
3
+ and properties.
4
+
5
+ It also includes utility methods for mapping operations across multiple instances.
6
+ """
7
+
8
+ import functools
9
+
10
+ from a_sync._typing import *
11
+ from a_sync.a_sync import decorator
12
+ from a_sync.a_sync.function import ASyncFunction, ModifiedMixin, ModifierManager
13
+
14
+ if TYPE_CHECKING:
15
+ from a_sync import TaskMapping
16
+
17
+ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
18
+ """
19
+ A descriptor base class for asynchronous methods and properties.
20
+
21
+ This class provides functionality for mapping operations across multiple instances
22
+ and includes utility methods for common operations.
23
+ """
24
+
25
+ __wrapped__: AnyFn[Concatenate[I, P], T]
26
+ """The wrapped function or method."""
27
+
28
+ __slots__ = "field_name", "_fget"
29
+
30
+ def __init__(
31
+ self,
32
+ _fget: AnyFn[Concatenate[I, P], T],
33
+ field_name: Optional[str] = None,
34
+ **modifiers: ModifierKwargs,
35
+ ) -> None:
36
+ """
37
+ Initialize the {cls}.
38
+
39
+ Args:
40
+ _fget: The function to be wrapped.
41
+ field_name: Optional name for the field. If not provided, the function's name will be used.
42
+ **modifiers: Additional modifier arguments.
43
+
44
+ Raises:
45
+ ValueError: If _fget is not callable.
46
+ """
47
+ if not callable(_fget):
48
+ raise ValueError(f'Unable to decorate {_fget}')
49
+ self.modifiers = ModifierManager(modifiers)
50
+ if isinstance(_fget, ASyncFunction):
51
+ self.modifiers.update(_fget.modifiers)
52
+ self.__wrapped__ = _fget
53
+ elif asyncio.iscoroutinefunction(_fget):
54
+ self.__wrapped__: AsyncUnboundMethod[I, P, T] = self.modifiers.apply_async_modifiers(_fget)
55
+ else:
56
+ self.__wrapped__ = _fget
57
+
58
+ self.field_name = field_name or _fget.__name__
59
+ """The name of the field the {cls} is bound to."""
60
+
61
+ functools.update_wrapper(self, self.__wrapped__)
62
+
63
+ def __repr__(self) -> str:
64
+ return f"<{self.__class__.__name__} for {self.__wrapped__}>"
65
+
66
+ def __set_name__(self, owner, name):
67
+ """
68
+ Set the field name when the {cls} is assigned to a class.
69
+
70
+ Args:
71
+ owner: The class owning this descriptor.
72
+ name: The name assigned to this descriptor in the class.
73
+ """
74
+ self.field_name = name
75
+
76
+ def map(self, *instances: AnyIterable[I], **bound_method_kwargs: P.kwargs) -> "TaskMapping[I, T]":
77
+ """
78
+ Create a TaskMapping for the given instances.
79
+
80
+ Args:
81
+ *instances: Iterable of instances to map over.
82
+ **bound_method_kwargs: Additional keyword arguments for the bound method.
83
+
84
+ Returns:
85
+ A TaskMapping object.
86
+ """
87
+ from a_sync.task import TaskMapping
88
+ return TaskMapping(self, *instances, **bound_method_kwargs)
89
+
90
+ @functools.cached_property
91
+ def all(self) -> ASyncFunction[Concatenate[AnyIterable[I], P], bool]:
92
+ """
93
+ Create an :class:`~ASyncFunction` that checks if all results are truthy.
94
+
95
+ Returns:
96
+ An ASyncFunction object.
97
+ """
98
+ return decorator.a_sync(default=self.default)(self._all)
99
+
100
+ @functools.cached_property
101
+ def any(self) -> ASyncFunction[Concatenate[AnyIterable[I], P], bool]:
102
+ """
103
+ Create an :class:`~ASyncFunction` that checks if any result is truthy.
104
+
105
+ Returns:
106
+ An ASyncFunction object.
107
+ """
108
+ return decorator.a_sync(default=self.default)(self._any)
109
+
110
+ @functools.cached_property
111
+ def min(self) -> ASyncFunction[Concatenate[AnyIterable[I], P], T]:
112
+ """
113
+ Create an :class:`~ASyncFunction` that returns the minimum result.
114
+
115
+ Returns:
116
+ An ASyncFunction object.
117
+ """
118
+ return decorator.a_sync(default=self.default)(self._min)
119
+
120
+ @functools.cached_property
121
+ def max(self) -> ASyncFunction[Concatenate[AnyIterable[I], P], T]:
122
+ """
123
+ Create an :class:`~ASyncFunction` that returns the maximum result.
124
+
125
+ Returns:
126
+ An ASyncFunction object.
127
+ """
128
+ return decorator.a_sync(default=self.default)(self._max)
129
+
130
+ @functools.cached_property
131
+ def sum(self) -> ASyncFunction[Concatenate[AnyIterable[I], P], T]:
132
+ """
133
+ Create an :class:`~ASyncFunction` that returns the sum of results.
134
+
135
+ Returns:
136
+ An ASyncFunction object.
137
+ """
138
+ return decorator.a_sync(default=self.default)(self._sum)
139
+
140
+ async def _all(self, *instances: AnyIterable[I], concurrency: Optional[int] = None, name: str = "", **kwargs: P.kwargs) -> bool:
141
+ """
142
+ Check if all results are truthy.
143
+
144
+ Args:
145
+ *instances: Iterable of instances to check.
146
+ concurrency: Optional maximum number of concurrent tasks.
147
+ name: Optional name for the task.
148
+ **kwargs: Additional keyword arguments.
149
+
150
+ Returns:
151
+ 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
+
155
+ async def _any(self, *instances: AnyIterable[I], concurrency: Optional[int] = None, name: str = "", **kwargs: P.kwargs) -> bool:
156
+ """
157
+ Check if any result is truthy.
158
+
159
+ Args:
160
+ *instances: Iterable of instances to check.
161
+ concurrency: Optional maximum number of concurrent tasks.
162
+ name: Optional name for the task.
163
+ **kwargs: Additional keyword arguments.
164
+
165
+ Returns:
166
+ 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
+
170
+ async def _min(self, *instances: AnyIterable[I], concurrency: Optional[int] = None, name: str = "", **kwargs: P.kwargs) -> T:
171
+ """
172
+ Find the minimum result.
173
+
174
+ Args:
175
+ *instances: Iterable of instances to check.
176
+ concurrency: Optional maximum number of concurrent tasks.
177
+ name: Optional name for the task.
178
+ **kwargs: Additional keyword arguments.
179
+
180
+ Returns:
181
+ The minimum result.
182
+ """
183
+ return await self.map(*instances, concurrency=concurrency, name=name, **kwargs).min(pop=True, sync=False)
184
+
185
+ async def _max(self, *instances: AnyIterable[I], concurrency: Optional[int] = None, name: str = "", **kwargs: P.kwargs) -> T:
186
+ """
187
+ Find the maximum result.
188
+
189
+ Args:
190
+ *instances: Iterable of instances to check.
191
+ concurrency: Optional maximum number of concurrent tasks.
192
+ name: Optional name for the task.
193
+ **kwargs: Additional keyword arguments.
194
+
195
+ Returns:
196
+ The maximum result.
197
+ """
198
+ return await self.map(*instances, concurrency=concurrency, name=name, **kwargs).max(pop=True, sync=False)
199
+
200
+ async def _sum(self, *instances: AnyIterable[I], concurrency: Optional[int] = None, name: str = "", **kwargs: P.kwargs) -> T:
201
+ """
202
+ Calculate the sum of results.
203
+
204
+ Args:
205
+ *instances: Iterable of instances to sum.
206
+ concurrency: Optional maximum number of concurrent tasks.
207
+ name: Optional name for the task.
208
+ **kwargs: Additional keyword arguments.
209
+
210
+ Returns:
211
+ The sum of the results.
212
+ """
213
+ return await self.map(*instances, concurrency=concurrency, name=name, **kwargs).sum(pop=True, sync=False)
214
+
215
+ def __init_subclass__(cls) -> None:
216
+ for attr in cls.__dict__.values():
217
+ if attr.__doc__ and "{cls}" in attr.__doc__:
218
+ attr.__doc__ = attr.__doc__.replace("{cls}", f":class:`{cls.__name__}`")
219
+ return super().__init_subclass__()
@@ -0,0 +1,59 @@
1
+ """
2
+ This module provides functionality for handling synchronous and asynchronous flags
3
+ in the ez-a-sync library.
4
+
5
+ ez-a-sync uses 'flags' to indicate whether objects / function calls will be sync or async.
6
+
7
+ You can use any of the provided flags, whichever makes most sense for your use case.
8
+ """
9
+
10
+ from typing import Any
11
+
12
+ from a_sync import exceptions
13
+
14
+ AFFIRMATIVE_FLAGS = {'sync'}
15
+ """Set of flags indicating synchronous behavior."""
16
+
17
+ NEGATIVE_FLAGS = {'asynchronous'}
18
+ """Set of flags indicating asynchronous behavior."""
19
+
20
+ VIABLE_FLAGS = AFFIRMATIVE_FLAGS | NEGATIVE_FLAGS
21
+ """Set of all valid flags."""
22
+
23
+ def negate_if_necessary(flag: str, flag_value: bool) -> bool:
24
+ """Negate the flag value if necessary based on the flag type.
25
+
26
+ Args:
27
+ flag: The flag to check.
28
+ flag_value: The value of the flag.
29
+
30
+ Returns:
31
+ The potentially negated flag value.
32
+
33
+ Raises:
34
+ :class:`exceptions.InvalidFlag`: If the flag is not recognized.
35
+ """
36
+ validate_flag_value(flag, flag_value)
37
+ if flag in AFFIRMATIVE_FLAGS:
38
+ return bool(flag_value)
39
+ elif flag in NEGATIVE_FLAGS:
40
+ return bool(not flag_value)
41
+ raise exceptions.InvalidFlag(flag)
42
+
43
+ def validate_flag_value(flag: str, flag_value: Any) -> bool:
44
+ """
45
+ Validate that the flag value is a boolean.
46
+
47
+ Args:
48
+ flag: The flag being validated.
49
+ flag_value: The value to validate.
50
+
51
+ Returns:
52
+ The validated flag value.
53
+
54
+ Raises:
55
+ :class:`exceptions.InvalidFlagValue`: If the flag value is not a boolean.
56
+ """
57
+ if not isinstance(flag_value, bool):
58
+ raise exceptions.InvalidFlagValue(flag, flag_value)
59
+ return flag_value
@@ -1,3 +1,7 @@
1
+ """
2
+ This module provides utility functions for handling asynchronous operations
3
+ and converting synchronous functions to asynchronous ones.
4
+ """
1
5
 
2
6
  import asyncio
3
7
  import functools
@@ -9,6 +13,18 @@ from a_sync._typing import *
9
13
 
10
14
 
11
15
  def _await(awaitable: Awaitable[T]) -> T:
16
+ """
17
+ Await an awaitable object in a synchronous context.
18
+
19
+ Args:
20
+ awaitable: The awaitable object to be awaited.
21
+
22
+ Returns:
23
+ The result of the awaitable.
24
+
25
+ Raises:
26
+ :class:`exceptions.SyncModeInAsyncContextError`: If the event loop is already running.
27
+ """
12
28
  try:
13
29
  return a_sync.asyncio.get_event_loop().run_until_complete(awaitable)
14
30
  except RuntimeError as e:
@@ -17,6 +33,19 @@ def _await(awaitable: Awaitable[T]) -> T:
17
33
  raise
18
34
 
19
35
  def _asyncify(func: SyncFn[P, T], executor: Executor) -> CoroFn[P, T]: # type: ignore [misc]
36
+ """
37
+ Convert a synchronous function to a coroutine function.
38
+
39
+ Args:
40
+ func: The synchronous function to be converted.
41
+ executor: The executor to run the synchronous function.
42
+
43
+ Returns:
44
+ A coroutine function wrapping the input function.
45
+
46
+ Raises:
47
+ :class:`exceptions.FunctionNotSync`: If the input function is already asynchronous.
48
+ """
20
49
  from a_sync.a_sync.function import ASyncFunction
21
50
  if asyncio.iscoroutinefunction(func) or isinstance(func, ASyncFunction):
22
51
  raise exceptions.FunctionNotSync(func)
@@ -0,0 +1,44 @@
1
+ """
2
+ This module provides utility functions for handling keyword arguments related to synchronous and asynchronous flags.
3
+ """
4
+
5
+ from typing import Optional
6
+
7
+ from a_sync import exceptions
8
+ from a_sync.a_sync import _flags
9
+
10
+
11
+ def get_flag_name(kwargs: dict) -> Optional[str]:
12
+ """
13
+ Get the name of the flag present in the kwargs.
14
+
15
+ Args:
16
+ kwargs: A dictionary of keyword arguments.
17
+
18
+ Returns:
19
+ The name of the flag if present, None otherwise.
20
+
21
+ Raises:
22
+ :class:`exceptions.TooManyFlags`: If more than one flag is present in the kwargs.
23
+ """
24
+ present_flags = [flag for flag in _flags.VIABLE_FLAGS if flag in kwargs]
25
+ if len(present_flags) == 0:
26
+ return None
27
+ if len(present_flags) != 1:
28
+ raise exceptions.TooManyFlags('kwargs', present_flags)
29
+ return present_flags[0]
30
+
31
+ def is_sync(flag: str, kwargs: dict, pop_flag: bool = False) -> bool:
32
+ """
33
+ Determine if the operation should be synchronous based on the flag value.
34
+
35
+ Args:
36
+ flag: The name of the flag to check.
37
+ kwargs: A dictionary of keyword arguments.
38
+ pop_flag: Whether to remove the flag from kwargs. Defaults to False.
39
+
40
+ Returns:
41
+ True if the operation should be synchronous, False otherwise.
42
+ """
43
+ flag_value = kwargs.pop(flag) if pop_flag else kwargs[flag]
44
+ return _flags.negate_if_necessary(flag, flag_value)
@@ -29,6 +29,8 @@ class ASyncABC(metaclass=ASyncMeta):
29
29
  @functools.cached_property
30
30
  def __a_sync_instance_should_await__(self) -> bool:
31
31
  """
32
+ A flag indicating whether the instance should default to asynchronous execution.
33
+
32
34
  You can override this if you want.
33
35
  If you want to be able to hotswap instance modes, you can redefine this as a non-cached property.
34
36
  """
@@ -1,4 +1,3 @@
1
-
2
1
  import functools
3
2
  import inspect
4
3
  import logging
@@ -14,10 +13,51 @@ logger = logging.getLogger(__name__)
14
13
 
15
14
  class ASyncGenericBase(ASyncABC):
16
15
  """
17
- Inherit from this class to a-syncify all of your bound methods.
18
- Allows for the use of a variety of flags out-of-box.
19
- You can choose which flag(s) work best for your subclass implementation.
16
+ Base class for creating dual-function sync/async-capable classes without writing all your code twice.
17
+
18
+ This class provides the foundation for creating hybrid sync/async classes. It allows methods
19
+ and properties to be defined once and used in both synchronous and asynchronous contexts.
20
+
21
+ The class uses the :func:`a_sync` decorator internally to create dual-mode methods and properties.
22
+ Subclasses should define their methods as coroutines (using `async def`) where possible, and
23
+ use the `@a_sync.property` or `@a_sync.cached_property` decorators for properties that need to support both modes.
24
+
25
+ Example:
26
+ ```python
27
+ class MyClass(ASyncGenericBase):
28
+ def __init__(self, sync: bool):
29
+ self.sync = sync
30
+
31
+ @a_sync.property
32
+ async def my_property(self):
33
+ return await some_async_operation()
34
+
35
+ @a_sync
36
+ async def my_method(self):
37
+ return await another_async_operation()
38
+
39
+ # Synchronous usage
40
+ obj = MyClass(sync=True)
41
+ sync_result = obj.my_property
42
+ sync_method_result = obj.my_method()
43
+
44
+ # Asynchronous usage
45
+ obj = MyClass(sync=False)
46
+ async_result = await obj.my_property
47
+ async_method_result = await obj.my_method()
48
+ ```
49
+
50
+ Note:
51
+ When subclassing, be aware that all async methods and properties will be
52
+ automatically wrapped to support both sync and async calls. This allows for
53
+ seamless usage in different contexts without changing the underlying implementation.
20
54
  """
55
+
56
+ def __init__(self):
57
+ if type(self) is ASyncGenericBase:
58
+ cls_name = type(self).__name__
59
+ raise NotImplementedError(f"You should not create instances of `{cls_name}` directly, you should subclass `ASyncGenericBase` instead.")
60
+
21
61
  @functools.cached_property
22
62
  def __a_sync_flag_name__(self) -> str:
23
63
  logger.debug("checking a_sync flag for %s", self)
@@ -29,10 +69,10 @@ class ASyncGenericBase(ASyncABC):
29
69
  # Let's check the instance's atributes
30
70
  logger.debug("unable to find flag name using `%s.__init__` signature, checking for flag attributes defined on %s", self.__class__.__name__, self)
31
71
  present_flags = [flag for flag in _flags.VIABLE_FLAGS if hasattr(self, flag)]
32
- if len(present_flags) == 0:
33
- raise exceptions.NoFlagsFound(self)
72
+ if not present_flags:
73
+ raise exceptions.NoFlagsFound(self) from None
34
74
  if len(present_flags) > 1:
35
- raise exceptions.TooManyFlags(self, present_flags)
75
+ raise exceptions.TooManyFlags(self, present_flags) from None
36
76
  flag = present_flags[0]
37
77
  if not isinstance(flag, str):
38
78
  raise exceptions.InvalidFlag(flag)
@@ -106,7 +146,7 @@ class ASyncGenericBase(ASyncABC):
106
146
  @classmethod
107
147
  def __parse_flag_name_from_list(cls, items: Dict[str, Any]) -> str:
108
148
  present_flags = [flag for flag in _flags.VIABLE_FLAGS if flag in items]
109
- if len(present_flags) == 0:
149
+ if not present_flags:
110
150
  logger.debug("There are too many flags defined on %s", cls)
111
151
  raise exceptions.NoFlagsFound(cls, items.keys())
112
152
  if len(present_flags) > 1:
@@ -1,3 +1,10 @@
1
+ """
2
+ Configuration module for a_sync library.
3
+
4
+ This module provides configuration options and default settings for the a_sync library.
5
+ It includes functionality for setting up executors, defining default modifiers,
6
+ and handling environment variable configurations.
7
+ """
1
8
 
2
9
  import functools
3
10
  import os
@@ -11,6 +18,15 @@ EXECUTOR_VALUE = int(os.environ.get("A_SYNC_EXECUTOR_VALUE", 8))
11
18
 
12
19
  @functools.lru_cache(maxsize=1)
13
20
  def get_default_executor() -> Executor:
21
+ """
22
+ Get the default executor based on the :obj:`EXECUTOR_TYPE` environment variable.
23
+
24
+ Returns:
25
+ Executor: An instance of either ProcessPoolExecutor or ThreadPoolExecutor.
26
+
27
+ Raises:
28
+ :class:`ValueError`: If an invalid EXECUTOR_TYPE is specified.
29
+ """
14
30
  if EXECUTOR_TYPE.lower().startswith('p'): # p, P, proc, Processes, etc
15
31
  return ProcessPoolExecutor(EXECUTOR_VALUE)
16
32
  elif EXECUTOR_TYPE.lower().startswith('t'): # t, T, thread, THREADS, etc