ez-a-sync 0.22.13__tar.gz → 0.22.15__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 (125) hide show
  1. ez_a_sync-0.22.15/.github/workflows/black.yaml +45 -0
  2. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/.github/workflows/mypy.yaml +1 -1
  3. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/PKG-INFO +1 -1
  4. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/README.md +3 -0
  5. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/ENVIRONMENT_VARIABLES.py +4 -3
  6. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/__init__.py +30 -12
  7. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/_smart.py +132 -28
  8. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/_typing.py +56 -12
  9. ez_a_sync-0.22.15/a_sync/a_sync/__init__.py +53 -0
  10. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/a_sync/_descriptor.py +74 -26
  11. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/a_sync/_flags.py +14 -6
  12. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/a_sync/_helpers.py +8 -7
  13. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/a_sync/_kwargs.py +3 -2
  14. ez_a_sync-0.22.15/a_sync/a_sync/_meta.py +191 -0
  15. ez_a_sync-0.22.15/a_sync/a_sync/abstract.py +154 -0
  16. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/a_sync/base.py +34 -16
  17. ez_a_sync-0.22.15/a_sync/a_sync/config.py +107 -0
  18. ez_a_sync-0.22.15/a_sync/a_sync/decorator.py +379 -0
  19. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/a_sync/function.py +416 -146
  20. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/a_sync/method.py +197 -59
  21. ez_a_sync-0.22.15/a_sync/a_sync/modifiers/__init__.py +61 -0
  22. ez_a_sync-0.22.15/a_sync/a_sync/modifiers/cache/__init__.py +89 -0
  23. ez_a_sync-0.22.15/a_sync/a_sync/modifiers/cache/memory.py +128 -0
  24. ez_a_sync-0.22.15/a_sync/a_sync/modifiers/limiter.py +85 -0
  25. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/a_sync/modifiers/manager.py +98 -16
  26. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/a_sync/modifiers/semaphores.py +48 -15
  27. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/a_sync/property.py +383 -82
  28. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/a_sync/singleton.py +1 -0
  29. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/aliases.py +0 -1
  30. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/asyncio/__init__.py +4 -1
  31. ez_a_sync-0.22.15/a_sync/asyncio/as_completed.py +273 -0
  32. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/asyncio/create_task.py +31 -17
  33. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/asyncio/gather.py +72 -52
  34. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/asyncio/utils.py +3 -3
  35. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/exceptions.py +78 -23
  36. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/executor.py +120 -71
  37. ez_a_sync-0.22.15/a_sync/future.py +967 -0
  38. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/iter.py +110 -50
  39. ez_a_sync-0.22.15/a_sync/primitives/__init__.py +30 -0
  40. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/primitives/_debug.py +13 -13
  41. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/primitives/_loggable.py +5 -4
  42. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/primitives/locks/__init__.py +5 -2
  43. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/primitives/locks/counter.py +38 -36
  44. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/primitives/locks/event.py +21 -7
  45. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/primitives/locks/prio_semaphore.py +182 -62
  46. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/primitives/locks/semaphore.py +78 -77
  47. ez_a_sync-0.22.15/a_sync/primitives/queue.py +880 -0
  48. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/sphinx/__init__.py +0 -1
  49. ez_a_sync-0.22.15/a_sync/sphinx/ext.py +299 -0
  50. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/task.py +262 -97
  51. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/utils/__init__.py +12 -6
  52. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/utils/iterators.py +127 -43
  53. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/docs/conf.py +59 -47
  54. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/ez_a_sync.egg-info/PKG-INFO +1 -1
  55. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/ez_a_sync.egg-info/SOURCES.txt +2 -0
  56. ez_a_sync-0.22.15/pyproject.yaml +2 -0
  57. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/setup.py +7 -7
  58. ez_a_sync-0.22.15/tests/conftest.py +3 -0
  59. ez_a_sync-0.22.15/tests/executor.py +114 -0
  60. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/tests/fixtures.py +61 -32
  61. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/tests/test_abstract.py +7 -4
  62. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/tests/test_as_completed.py +54 -21
  63. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/tests/test_base.py +66 -17
  64. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/tests/test_cache.py +31 -15
  65. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/tests/test_decorator.py +54 -28
  66. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/tests/test_executor.py +8 -13
  67. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/tests/test_future.py +45 -8
  68. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/tests/test_gather.py +8 -2
  69. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/tests/test_helpers.py +2 -0
  70. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/tests/test_iter.py +55 -13
  71. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/tests/test_limiter.py +5 -3
  72. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/tests/test_meta.py +23 -9
  73. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/tests/test_modified.py +4 -1
  74. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/tests/test_semaphore.py +15 -8
  75. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/tests/test_singleton.py +15 -10
  76. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/tests/test_task.py +126 -28
  77. ez_a_sync-0.22.13/a_sync/a_sync/__init__.py +0 -28
  78. ez_a_sync-0.22.13/a_sync/a_sync/_meta.py +0 -99
  79. ez_a_sync-0.22.13/a_sync/a_sync/abstract.py +0 -80
  80. ez_a_sync-0.22.13/a_sync/a_sync/config.py +0 -73
  81. ez_a_sync-0.22.13/a_sync/a_sync/decorator.py +0 -257
  82. ez_a_sync-0.22.13/a_sync/a_sync/modifiers/__init__.py +0 -19
  83. ez_a_sync-0.22.13/a_sync/a_sync/modifiers/cache/__init__.py +0 -60
  84. ez_a_sync-0.22.13/a_sync/a_sync/modifiers/cache/memory.py +0 -62
  85. ez_a_sync-0.22.13/a_sync/a_sync/modifiers/limiter.py +0 -55
  86. ez_a_sync-0.22.13/a_sync/asyncio/as_completed.py +0 -145
  87. ez_a_sync-0.22.13/a_sync/future.py +0 -550
  88. ez_a_sync-0.22.13/a_sync/primitives/__init__.py +0 -18
  89. ez_a_sync-0.22.13/a_sync/primitives/queue.py +0 -378
  90. ez_a_sync-0.22.13/a_sync/sphinx/ext.py +0 -189
  91. ez_a_sync-0.22.13/tests/conftest.py +0 -4
  92. ez_a_sync-0.22.13/tests/executor.py +0 -11
  93. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/.coverage +0 -0
  94. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/.github/workflows/codeql.yaml +0 -0
  95. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/.github/workflows/docs.yaml +0 -0
  96. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/.github/workflows/pytest.yaml +0 -0
  97. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/.github/workflows/release.yaml +0 -0
  98. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/.gitignore +0 -0
  99. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/.sourcery.yaml +0 -0
  100. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/LICENSE.txt +0 -0
  101. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/Makefile +0 -0
  102. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/TODO +0 -0
  103. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/a_sync/py.typed +0 -0
  104. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/docs/Makefile +0 -0
  105. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/docs/_build/html/_static/alabaster.css +0 -0
  106. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/docs/_build/html/_static/basic.css +0 -0
  107. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/docs/_build/html/_static/custom.css +0 -0
  108. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/docs/_build/html/_static/doctools.js +0 -0
  109. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/docs/_build/html/_static/documentation_options.js +0 -0
  110. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/docs/_build/html/_static/file.png +0 -0
  111. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/docs/_build/html/_static/language_data.js +0 -0
  112. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/docs/_build/html/_static/minus.png +0 -0
  113. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/docs/_build/html/_static/plus.png +0 -0
  114. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/docs/_build/html/_static/pygments.css +0 -0
  115. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/docs/_build/html/_static/searchtools.js +0 -0
  116. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/docs/_build/html/_static/sphinx_highlight.js +0 -0
  117. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/docs/index.rst +0 -0
  118. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/docs/make.bat +0 -0
  119. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/ez_a_sync.egg-info/dependency_links.txt +0 -0
  120. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/ez_a_sync.egg-info/requires.txt +0 -0
  121. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/ez_a_sync.egg-info/top_level.txt +0 -0
  122. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/requirements-dev.txt +0 -0
  123. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/requirements.txt +0 -0
  124. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/setup.cfg +0 -0
  125. {ez_a_sync-0.22.13 → ez_a_sync-0.22.15}/tests/__init__.py +0 -0
@@ -0,0 +1,45 @@
1
+ name: Black Formatter
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - master
7
+
8
+ jobs:
9
+ format:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Checkout code
14
+ uses: actions/checkout@v3
15
+ with:
16
+ ref: ${{ github.head_ref }} # Check out the PR branch
17
+
18
+ - name: Set up Python
19
+ uses: actions/setup-python@v4
20
+ with:
21
+ python-version: '3.12'
22
+
23
+ - name: Install Black
24
+ run: pip install black
25
+
26
+ - name: Run Black
27
+ run: black .
28
+
29
+ - name: Check for changes
30
+ id: changes
31
+ run: |
32
+ if [[ -n $(git status --porcelain) ]]; then
33
+ echo "changes_detected=true" >> $GITHUB_ENV
34
+ else
35
+ echo "changes_detected=false" >> $GITHUB_ENV
36
+ fi
37
+
38
+ - name: Commit changes
39
+ if: env.changes_detected == 'true'
40
+ run: |
41
+ git config --local user.name "github-actions[bot]"
42
+ git config --local user.email "github-actions[bot]@users.noreply.github.com"
43
+ git add .
44
+ git commit -m "chore: \`black .\`"
45
+ git push
@@ -30,4 +30,4 @@ jobs:
30
30
  python -m pip install --upgrade pip
31
31
  pip install mypy types-requests
32
32
  - name: Run MyPy
33
- run: mypy ./a_sync --pretty --ignore-missing-imports --show-error-codes --show-error-context --enable-incomplete-feature=Unpack
33
+ run: mypy ./a_sync --pretty --ignore-missing-imports --show-error-codes --show-error-context --enable-incomplete-feature=Unpack --install-types --non-interactive
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ez-a-sync
3
- Version: 0.22.13
3
+ Version: 0.22.15
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
@@ -19,6 +19,9 @@
19
19
 
20
20
  `ez-a-sync` is a Python library that enables developers to write both synchronous and asynchronous code without having to write redundant code. It provides a decorator `@a_sync()`, as well as a base class `ASyncGenericBase` which can be used to create classes that can be executed in both synchronous and asynchronous contexts.
21
21
 
22
+ It also contains implementations of various asyncio primitives with extra functionality, including queues and various types of locks.
23
+ \# TODO add links to various objects' docs
24
+
22
25
  ## Installation
23
26
 
24
27
  `ez-a-sync` can be installed via pip:
@@ -1,4 +1,3 @@
1
-
2
1
  from typed_envs import EnvVarFactory
3
2
 
4
3
  envs = EnvVarFactory("EZASYNC")
@@ -6,7 +5,9 @@ envs = EnvVarFactory("EZASYNC")
6
5
  # We have some envs here to help you debug your custom class implementations
7
6
 
8
7
  # If you're only interested in debugging a specific class, set this to the class name
9
- DEBUG_CLASS_NAME = envs.create_env("DEBUG_CLASS_NAME", str, default='', verbose=False)
8
+ DEBUG_CLASS_NAME = envs.create_env("DEBUG_CLASS_NAME", str, default="", verbose=False)
10
9
 
11
10
  # Set this to enable debug mode on all classes
12
- DEBUG_MODE = envs.create_env("DEBUG_MODE", bool, default=DEBUG_CLASS_NAME, verbose=False)
11
+ DEBUG_MODE = envs.create_env(
12
+ "DEBUG_MODE", bool, default=DEBUG_CLASS_NAME, verbose=False
13
+ )
@@ -1,8 +1,35 @@
1
+ """
2
+ This module initializes the a_sync library by importing and organizing various components, utilities, and classes.
3
+ It provides a convenient and unified interface for asynchronous programming with a focus on flexibility and efficiency.
4
+
5
+ The `a_sync` library offers decorators and base classes to facilitate writing both synchronous and asynchronous code.
6
+ It includes the `@a_sync()` decorator and the `ASyncGenericBase` class, which allow for creating functions and classes
7
+ that can operate in both synchronous and asynchronous contexts. Additionally, it provides enhanced asyncio primitives,
8
+ such as queues and locks, with extra functionality.
9
+
10
+ Modules and components included:
11
+ - `aliases`, `exceptions`, `iter`, `task`: Core modules of the library.
12
+ - `ASyncGenericBase`, `ASyncGenericSingleton`, `a_sync`: Base classes and decorators for dual-context execution.
13
+ - `apply_semaphore`: Function to apply semaphores to coroutines.
14
+ - `ASyncCachedPropertyDescriptor`, `ASyncPropertyDescriptor`, `cached_property`, `property`: Property descriptors for async properties.
15
+ - `as_completed`, `create_task`, `gather`: Enhanced asyncio functions.
16
+ - Executors: `AsyncThreadPoolExecutor`, `AsyncProcessPoolExecutor`, `PruningThreadPoolExecutor` for async execution.
17
+ - Iterators: `ASyncFilter`, `ASyncSorter`, `ASyncIterable`, `ASyncIterator` for async iteration.
18
+ - Utilities: `all`, `any`, `as_yielded` for async utilities.
19
+
20
+ Alias for backward compatibility:
21
+ - `ASyncBase` is an alias for `ASyncGenericBase`, which will be removed eventually, probably in version 0.1.0.
22
+ """
1
23
 
2
24
  from a_sync import aliases, exceptions, iter, task
3
25
  from a_sync.a_sync import ASyncGenericBase, ASyncGenericSingleton, a_sync
4
26
  from a_sync.a_sync.modifiers.semaphores import apply_semaphore
5
- from a_sync.a_sync.property import ASyncCachedPropertyDescriptor, ASyncPropertyDescriptor, cached_property, property
27
+ from a_sync.a_sync.property import (
28
+ ASyncCachedPropertyDescriptor,
29
+ ASyncPropertyDescriptor,
30
+ cached_property,
31
+ property,
32
+ )
6
33
  from a_sync.asyncio import as_completed, create_task, gather
7
34
  from a_sync.executor import *
8
35
  from a_sync.executor import AsyncThreadPoolExecutor as ThreadPoolExecutor
@@ -29,16 +56,13 @@ __all__ = [
29
56
  "exceptions",
30
57
  "iter",
31
58
  "task",
32
-
33
59
  # builtins
34
60
  "sorted",
35
61
  "filter",
36
-
37
62
  # asyncio
38
63
  "create_task",
39
- "gather",
64
+ "gather",
40
65
  "as_completed",
41
-
42
66
  # functions
43
67
  "a_sync",
44
68
  "all",
@@ -47,33 +71,27 @@ __all__ = [
47
71
  "exhaust_iterator",
48
72
  "exhaust_iterators",
49
73
  "map",
50
-
51
74
  # classes
52
75
  "ASyncIterable",
53
76
  "ASyncIterator",
54
77
  "ASyncGenericSingleton",
55
- "TaskMapping",
56
-
78
+ "TaskMapping",
57
79
  # property
58
80
  "cached_property",
59
81
  "property",
60
82
  "ASyncPropertyDescriptor",
61
83
  "ASyncCachedPropertyDescriptor",
62
-
63
84
  # semaphores
64
85
  "Semaphore",
65
86
  "PrioritySemaphore",
66
87
  "ThreadsafeSemaphore",
67
-
68
88
  # queues
69
89
  "Queue",
70
90
  "ProcessingQueue",
71
91
  "SmartProcessingQueue",
72
-
73
92
  # locks
74
93
  "CounterLock",
75
94
  "Event",
76
-
77
95
  # executors
78
96
  "AsyncThreadPoolExecutor",
79
97
  "PruningThreadPoolExecutor",
@@ -1,3 +1,8 @@
1
+ """
2
+ This module defines smart future and task utilities for the a_sync library.
3
+ These utilities provide enhanced functionality for managing asynchronous tasks and futures,
4
+ including task shielding and a custom task factory for creating SmartTask instances.
5
+ """
1
6
 
2
7
  import asyncio
3
8
  import logging
@@ -17,11 +22,22 @@ _Key = Tuple[_Args, _Kwargs]
17
22
 
18
23
  logger = logging.getLogger(__name__)
19
24
 
25
+
20
26
  class _SmartFutureMixin(Generic[T]):
27
+ """
28
+ Mixin class that provides common functionality for smart futures and tasks.
29
+
30
+ This mixin provides methods for managing waiters and integrating with a smart processing queue.
31
+ """
32
+
21
33
  _queue: Optional["SmartProcessingQueue[Any, Any, T]"] = None
22
34
  _key: _Key
23
35
  _waiters: "weakref.WeakSet[SmartTask[T]]"
36
+
24
37
  def __await__(self: Union["SmartFuture", "SmartTask"]) -> Generator[Any, None, T]:
38
+ """
39
+ Await the smart future or task, handling waiters and logging.
40
+ """
25
41
  if self.done():
26
42
  return self.result() # May raise too.
27
43
  self._asyncio_future_blocking = True
@@ -32,33 +48,64 @@ class _SmartFutureMixin(Generic[T]):
32
48
  if not self.done():
33
49
  raise RuntimeError("await wasn't used with future")
34
50
  return self.result() # May raise too.
51
+
35
52
  @property
36
53
  def num_waiters(self: Union["SmartFuture", "SmartTask"]) -> int:
37
54
  # NOTE: we check .done() because the callback may not have ran yet and its very lightweight
55
+ """
56
+ Get the number of waiters currently awaiting the future or task.
57
+ """
38
58
  if self.done():
39
59
  # if there are any waiters left, there won't be once the event loop runs once
40
60
  return 0
41
- return sum(getattr(waiter, 'num_waiters', 1) or 1 for waiter in self._waiters)
42
- def _waiter_done_cleanup_callback(self: Union["SmartFuture", "SmartTask"], waiter: "SmartTask") -> None:
43
- "Removes the waiter from _waiters, and _queue._futs if applicable"
61
+ return sum(getattr(waiter, "num_waiters", 1) or 1 for waiter in self._waiters)
62
+
63
+ def _waiter_done_cleanup_callback(
64
+ self: Union["SmartFuture", "SmartTask"], waiter: "SmartTask"
65
+ ) -> None:
66
+ """
67
+ Callback to clean up waiters when a waiter task is done.
68
+
69
+ Removes the waiter from _waiters, and _queue._futs if applicable
70
+ """
44
71
  if not self.done():
45
72
  self._waiters.remove(waiter)
73
+
46
74
  def _self_done_cleanup_callback(self: Union["SmartFuture", "SmartTask"]) -> None:
75
+ """
76
+ Callback to clean up waiters and remove the future from the queue when done.
77
+ """
47
78
  self._waiters.clear()
48
79
  if queue := self._queue:
49
80
  queue._futs.pop(self._key)
50
81
 
51
82
 
52
83
  class SmartFuture(_SmartFutureMixin[T], asyncio.Future):
84
+ """
85
+ A smart future that tracks waiters and integrates with a smart processing queue.
86
+
87
+ Inherits from both _SmartFutureMixin and asyncio.Future, providing additional functionality
88
+ for tracking waiters and integrating with a smart processing queue.
89
+ """
90
+
53
91
  _queue = None
54
92
  _key = None
93
+
55
94
  def __init__(
56
- self,
57
- *,
58
- queue: Optional["SmartProcessingQueue[Any, Any, T]"],
59
- key: Optional[_Key] = None,
95
+ self,
96
+ *,
97
+ queue: Optional["SmartProcessingQueue[Any, Any, T]"],
98
+ key: Optional[_Key] = None,
60
99
  loop: Optional[asyncio.AbstractEventLoop] = None,
61
100
  ) -> None:
101
+ """
102
+ Initialize the SmartFuture with an optional queue and key.
103
+
104
+ Args:
105
+ queue: Optional; a smart processing queue.
106
+ key: Optional; a key identifying the future.
107
+ loop: Optional; the event loop.
108
+ """
62
109
  super().__init__(loop=loop)
63
110
  if queue:
64
111
  self._queue = weakref.proxy(queue)
@@ -66,55 +113,94 @@ class SmartFuture(_SmartFutureMixin[T], asyncio.Future):
66
113
  self._key = key
67
114
  self._waiters = weakref.WeakSet()
68
115
  self.add_done_callback(SmartFuture._self_done_cleanup_callback)
116
+
69
117
  def __repr__(self):
70
118
  return f"<{type(self).__name__} key={self._key} waiters={self.num_waiters} {self._state}>"
119
+
71
120
  def __lt__(self, other: "SmartFuture[T]") -> bool:
72
- """heap considers lower values as higher priority so a future with more waiters will be 'less than' a future with less waiters."""
73
- #other = other_ref()
74
- #if other is None:
75
- # # garbage collected refs should always process first so they can be popped from the queue
76
- # return False
121
+ """
122
+ Compare the number of waiters to determine priority in a heap.
123
+ Lower values indicate higher priority, so more waiters means 'less than'.
124
+
125
+ Args:
126
+ other: Another SmartFuture to compare with.
127
+
128
+ Returns:
129
+ True if self has more waiters than other.
130
+ """
77
131
  return self.num_waiters > other.num_waiters
78
132
 
133
+
79
134
  def create_future(
80
135
  *,
81
- queue: Optional["SmartProcessingQueue"] = None,
82
- key: Optional[_Key] = None,
136
+ queue: Optional["SmartProcessingQueue"] = None,
137
+ key: Optional[_Key] = None,
83
138
  loop: Optional[asyncio.AbstractEventLoop] = None,
84
139
  ) -> SmartFuture[V]:
140
+ """
141
+ Create a SmartFuture instance.
142
+
143
+ Args:
144
+ queue: Optional; a smart processing queue.
145
+ key: Optional; a key identifying the future.
146
+ loop: Optional; the event loop.
147
+
148
+ Returns:
149
+ A SmartFuture instance.
150
+ """
85
151
  return SmartFuture(queue=queue, key=key, loop=loop or asyncio.get_event_loop())
86
152
 
153
+
87
154
  class SmartTask(_SmartFutureMixin[T], asyncio.Task):
155
+ """
156
+ A smart task that tracks waiters and integrates with a smart processing queue.
157
+
158
+ Inherits from both _SmartFutureMixin and asyncio.Task, providing additional functionality
159
+ for tracking waiters and integrating with a smart processing queue.
160
+ """
161
+
88
162
  def __init__(
89
- self,
90
- coro: Awaitable[T],
91
- *,
92
- loop: Optional[asyncio.AbstractEventLoop] = None,
163
+ self,
164
+ coro: Awaitable[T],
165
+ *,
166
+ loop: Optional[asyncio.AbstractEventLoop] = None,
93
167
  name: Optional[str] = None,
94
168
  ) -> None:
169
+ """
170
+ Initialize the SmartTask with a coroutine and optional event loop.
171
+
172
+ Args:
173
+ coro: The coroutine to run in the task.
174
+ loop: Optional; the event loop.
175
+ name: Optional; the name of the task.
176
+ """
95
177
  super().__init__(coro, loop=loop, name=name)
96
178
  self._waiters: Set["asyncio.Task[T]"] = set()
97
179
  self.add_done_callback(SmartTask._self_done_cleanup_callback)
98
180
 
99
- def smart_task_factory(loop: asyncio.AbstractEventLoop, coro: Awaitable[T]) -> SmartTask[T]:
181
+
182
+ def smart_task_factory(
183
+ loop: asyncio.AbstractEventLoop, coro: Awaitable[T]
184
+ ) -> SmartTask[T]:
100
185
  """
101
186
  Task factory function that an event loop calls to create new tasks.
102
-
187
+
103
188
  This factory function utilizes ez-a-sync's custom :class:`~SmartTask` implementation.
104
-
189
+
105
190
  Args:
106
191
  loop: The event loop.
107
192
  coro: The coroutine to run in the task.
108
-
193
+
109
194
  Returns:
110
195
  A SmartTask instance running the provided coroutine.
111
196
  """
112
197
  return SmartTask(coro, loop=loop)
113
198
 
199
+
114
200
  def set_smart_task_factory(loop: asyncio.AbstractEventLoop = None) -> None:
115
201
  """
116
202
  Set the event loop's task factory to :func:`~smart_task_factory` so all tasks will be SmartTask instances.
117
-
203
+
118
204
  Args:
119
205
  loop: Optional; the event loop. If None, the current event loop is used.
120
206
  """
@@ -122,7 +208,10 @@ def set_smart_task_factory(loop: asyncio.AbstractEventLoop = None) -> None:
122
208
  loop = a_sync.asyncio.get_event_loop()
123
209
  loop.set_task_factory(smart_task_factory)
124
210
 
125
- def shield(arg: Awaitable[T], *, loop: Optional[asyncio.AbstractEventLoop] = None) -> SmartFuture[T]:
211
+
212
+ def shield(
213
+ arg: Awaitable[T], *, loop: Optional[asyncio.AbstractEventLoop] = None
214
+ ) -> SmartFuture[T]:
126
215
  """
127
216
  Wait for a future, shielding it from cancellation.
128
217
 
@@ -148,11 +237,18 @@ def shield(arg: Awaitable[T], *, loop: Optional[asyncio.AbstractEventLoop] = Non
148
237
  res = await shield(something())
149
238
  except CancelledError:
150
239
  res = None
240
+
241
+ Args:
242
+ arg: The awaitable to shield from cancellation.
243
+ loop: Optional; the event loop. Deprecated since Python 3.8.
151
244
  """
152
245
  if loop is not None:
153
- warnings.warn("The loop argument is deprecated since Python 3.8, "
154
- "and scheduled for removal in Python 3.10.",
155
- DeprecationWarning, stacklevel=2)
246
+ warnings.warn(
247
+ "The loop argument is deprecated since Python 3.8, "
248
+ "and scheduled for removal in Python 3.10.",
249
+ DeprecationWarning,
250
+ stacklevel=2,
251
+ )
156
252
  inner = asyncio.ensure_future(arg, loop=loop)
157
253
  if inner.done():
158
254
  # Shortcut.
@@ -162,6 +258,7 @@ def shield(arg: Awaitable[T], *, loop: Optional[asyncio.AbstractEventLoop] = Non
162
258
  # special handling to connect SmartFutures to SmartTasks if enabled
163
259
  if (waiters := getattr(inner, "_waiters", None)) is not None:
164
260
  waiters.add(outer)
261
+
165
262
  def _inner_done_callback(inner):
166
263
  if outer.cancelled():
167
264
  if not inner.cancelled():
@@ -187,4 +284,11 @@ def shield(arg: Awaitable[T], *, loop: Optional[asyncio.AbstractEventLoop] = Non
187
284
  return outer
188
285
 
189
286
 
190
- __all__ = ["create_future", "shield", "SmartFuture", "SmartTask", "smart_task_factory", "set_smart_task_factory"]
287
+ __all__ = [
288
+ "create_future",
289
+ "shield",
290
+ "SmartFuture",
291
+ "SmartTask",
292
+ "smart_task_factory",
293
+ "set_smart_task_factory",
294
+ ]
@@ -7,16 +7,47 @@ to enhance type checking and provide better IDE support.
7
7
  import asyncio
8
8
  from concurrent.futures._base import Executor
9
9
  from decimal import Decimal
10
- from typing import (TYPE_CHECKING, Any, AsyncGenerator, AsyncIterable, AsyncIterator,
11
- Awaitable, Callable, Coroutine, DefaultDict, Deque, Dict, Generator,
12
- Generic, ItemsView, Iterable, Iterator, KeysView, List, Literal,
13
- Mapping, NoReturn, Optional, Protocol, Set, Tuple, Type, TypedDict,
14
- TypeVar, Union, ValuesView, final, overload, runtime_checkable)
10
+ from typing import (
11
+ TYPE_CHECKING,
12
+ Any,
13
+ AsyncGenerator,
14
+ AsyncIterable,
15
+ AsyncIterator,
16
+ Awaitable,
17
+ Callable,
18
+ Coroutine,
19
+ DefaultDict,
20
+ Deque,
21
+ Dict,
22
+ Generator,
23
+ Generic,
24
+ ItemsView,
25
+ Iterable,
26
+ Iterator,
27
+ KeysView,
28
+ List,
29
+ Literal,
30
+ Mapping,
31
+ NoReturn,
32
+ Optional,
33
+ Protocol,
34
+ Set,
35
+ Tuple,
36
+ Type,
37
+ TypedDict,
38
+ TypeVar,
39
+ Union,
40
+ ValuesView,
41
+ final,
42
+ overload,
43
+ runtime_checkable,
44
+ )
15
45
 
16
46
  from typing_extensions import Concatenate, ParamSpec, Self, Unpack
17
47
 
18
48
  if TYPE_CHECKING:
19
49
  from a_sync import ASyncGenericBase
50
+
20
51
  B = TypeVar("B", bound=ASyncGenericBase)
21
52
  else:
22
53
  B = TypeVar("B")
@@ -27,7 +58,7 @@ V = TypeVar("V")
27
58
  I = TypeVar("I")
28
59
  """A :class:`TypeVar` that is used to represent instances of a common class."""
29
60
 
30
- E = TypeVar('E', bound=Exception)
61
+ E = TypeVar("E", bound=Exception)
31
62
  TYPE = TypeVar("TYPE", bound=Type)
32
63
 
33
64
  P = ParamSpec("P")
@@ -51,6 +82,7 @@ SyncFn = Callable[P, T]
51
82
  AnyFn = Union[CoroFn[P, T], SyncFn[P, T]]
52
83
  "Type alias for any function, whether synchronous or asynchronous."
53
84
 
85
+
54
86
  class CoroBoundMethod(Protocol[I, P, T]):
55
87
  """
56
88
  Protocol for coroutine bound methods.
@@ -59,13 +91,15 @@ class CoroBoundMethod(Protocol[I, P, T]):
59
91
  class MyClass:
60
92
  async def my_method(self, x: int) -> str:
61
93
  return str(x)
62
-
94
+
63
95
  instance = MyClass()
64
96
  bound_method: CoroBoundMethod[MyClass, [int], str] = instance.my_method
65
97
  """
98
+
66
99
  __self__: I
67
100
  __call__: Callable[P, Awaitable[T]]
68
101
 
102
+
69
103
  class SyncBoundMethod(Protocol[I, P, T]):
70
104
  """
71
105
  Protocol for synchronous bound methods.
@@ -74,36 +108,43 @@ class SyncBoundMethod(Protocol[I, P, T]):
74
108
  class MyClass:
75
109
  def my_method(self, x: int) -> str:
76
110
  return str(x)
77
-
111
+
78
112
  instance = MyClass()
79
113
  bound_method: SyncBoundMethod[MyClass, [int], str] = instance.my_method
80
114
  """
115
+
81
116
  __self__: I
82
117
  __call__: Callable[P, T]
83
118
 
119
+
84
120
  AnyBoundMethod = Union[CoroBoundMethod[Any, P, T], SyncBoundMethod[Any, P, T]]
85
121
  "Type alias for any bound method, whether synchronous or asynchronous."
86
122
 
123
+
87
124
  @runtime_checkable
88
125
  class AsyncUnboundMethod(Protocol[I, P, T]):
89
126
  """
90
127
  Protocol for unbound asynchronous methods.
91
-
128
+
92
129
  An unbound method is a method that hasn't been bound to an instance of a class yet.
93
130
  It's essentially the function object itself, before it's accessed through an instance.
94
131
  """
132
+
95
133
  __get__: Callable[[I, Type], CoroBoundMethod[I, P, T]]
96
134
 
135
+
97
136
  @runtime_checkable
98
137
  class SyncUnboundMethod(Protocol[I, P, T]):
99
138
  """
100
139
  Protocol for unbound synchronous methods.
101
-
140
+
102
141
  An unbound method is a method that hasn't been bound to an instance of a class yet.
103
142
  It's essentially the function object itself, before it's accessed through an instance.
104
143
  """
144
+
105
145
  __get__: Callable[[I, Type], SyncBoundMethod[I, P, T]]
106
146
 
147
+
107
148
  AnyUnboundMethod = Union[AsyncUnboundMethod[I, P, T], SyncUnboundMethod[I, P, T]]
108
149
  "Type alias for any unbound method, whether synchronous or asynchronous."
109
150
 
@@ -122,19 +163,21 @@ AsyncDecorator = Callable[[CoroFn[P, T]], CoroFn[P, T]]
122
163
  AsyncDecoratorOrCoroFn = Union[AsyncDecorator[P, T], CoroFn[P, T]]
123
164
  "Type alias for either an asynchronous decorator or a coroutine function."
124
165
 
125
- DefaultMode = Literal['sync', 'async', None]
166
+ DefaultMode = Literal["sync", "async", None]
126
167
  "Type alias for default modes of operation."
127
168
 
128
- CacheType = Literal['memory', None]
169
+ CacheType = Literal["memory", None]
129
170
  "Type alias for cache types."
130
171
 
131
172
  SemaphoreSpec = Optional[Union[asyncio.Semaphore, int]]
132
173
  "Type alias for semaphore specifications."
133
174
 
175
+
134
176
  class ModifierKwargs(TypedDict, total=False):
135
177
  """
136
178
  TypedDict for keyword arguments that modify the behavior of asynchronous operations.
137
179
  """
180
+
138
181
  default: DefaultMode
139
182
  cache_type: CacheType
140
183
  cache_typed: bool
@@ -145,6 +188,7 @@ class ModifierKwargs(TypedDict, total=False):
145
188
  # sync modifiers
146
189
  executor: Executor
147
190
 
191
+
148
192
  AnyIterable = Union[AsyncIterable[K], Iterable[K]]
149
193
  "Type alias for any iterable, whether synchronous or asynchronous."
150
194
 
@@ -0,0 +1,53 @@
1
+ """
2
+ This module enables developers to write both synchronous and asynchronous code without having to write redundant code.
3
+
4
+ The two main objects you should use are
5
+ - a decorator `@a_sync()`
6
+ - a base class `ASyncGenericBase` which can be used to create classes that can be utilized in both synchronous and asynchronous contexts.
7
+
8
+ The rest of the objects are exposed for type checking only, you should not make use of them otherwise.
9
+ """
10
+
11
+ # TODO: double check on these before adding them to docs
12
+ # - two decorators @:class:`property` and @:class:`cached_property` for the creation of dual-function properties and cached properties, respectively.
13
+
14
+ from a_sync.a_sync.base import ASyncGenericBase
15
+ from a_sync.a_sync.decorator import a_sync
16
+ from a_sync.a_sync.function import (
17
+ ASyncFunction,
18
+ ASyncFunctionAsyncDefault,
19
+ ASyncFunctionSyncDefault,
20
+ )
21
+ from a_sync.a_sync.modifiers.semaphores import apply_semaphore
22
+
23
+ # NOTE: Some of these we purposely import without including in __all__. Do not remove.
24
+ from a_sync.a_sync.property import (
25
+ ASyncCachedPropertyDescriptor,
26
+ ASyncCachedPropertyDescriptorAsyncDefault,
27
+ ASyncCachedPropertyDescriptorSyncDefault,
28
+ ASyncPropertyDescriptor,
29
+ ASyncPropertyDescriptorAsyncDefault,
30
+ ASyncPropertyDescriptorSyncDefault,
31
+ HiddenMethod,
32
+ HiddenMethodDescriptor,
33
+ cached_property,
34
+ property,
35
+ )
36
+ from a_sync.a_sync.singleton import ASyncGenericSingleton
37
+
38
+
39
+ __all__ = [
40
+ # entrypoints
41
+ "a_sync",
42
+ "ASyncGenericBase",
43
+ # maybe entrypoints (?)
44
+ # TODO: double check how I intended for these to be used
45
+ "property",
46
+ "cached_property",
47
+ # classes exposed for type hinting only
48
+ "ASyncFunction",
49
+ "ASyncPropertyDescriptor",
50
+ "ASyncCachedPropertyDescriptor",
51
+ "HiddenMethod",
52
+ "HiddenMethodDescriptor",
53
+ ]