diff --git a/.gitignore b/.gitignore index c4d966ad..f7b184f9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,6 @@ dist/ .eggs/ *.egg-info cache/**/*.json -cache/**/*.pkl \ No newline at end of file +cache/**/*.pkl +mypy_plugin_tests/mypy +mypy_plugin_tests/venv \ No newline at end of file diff --git a/a_sync/_smart.py b/a_sync/_smart.py index 67d44058..060ec316 100644 --- a/a_sync/_smart.py +++ b/a_sync/_smart.py @@ -29,6 +29,26 @@ class _SmartFutureMixin(Generic[T]): Mixin class that provides common functionality for smart futures and tasks. This mixin provides methods for managing waiters and integrating with a smart processing queue. + It uses weak references to manage resources efficiently. + + Example: + Creating a SmartFuture and awaiting it: + + ```python + future = SmartFuture() + result = await future + ``` + + Creating a SmartTask and awaiting it: + + ```python + task = SmartTask(coro=my_coroutine()) + result = await task + ``` + + See Also: + - :class:`SmartFuture` + - :class:`SmartTask` """ _queue: Optional["SmartProcessingQueue[Any, Any, T]"] = None @@ -73,18 +93,22 @@ def __await__(self: Union["SmartFuture", "SmartTask"]) -> Generator[Any, None, T @property def num_waiters(self: Union["SmartFuture", "SmartTask"]) -> int: - # NOTE: we check .done() because the callback may not have ran yet and its very lightweight """ Get the number of waiters currently awaiting the future or task. + This property checks if the future or task is done to ensure accurate counting + of waiters, as the callback may not have run yet. + Example: ```python future = SmartFuture() print(future.num_waiters) ``` + + See Also: + - :meth:`_waiter_done_cleanup_callback` """ if self.done(): - # if there are any waiters left, there won't be once the event loop runs once return 0 return sum(getattr(waiter, "num_waiters", 1) or 1 for waiter in self._waiters) @@ -94,10 +118,13 @@ def _waiter_done_cleanup_callback( """ Callback to clean up waiters when a waiter task is done. - Removes the waiter from _waiters, and _queue._futs if applicable + Removes the waiter from _waiters, and _queue._futs if applicable. Args: waiter: The waiter task to clean up. + + Example: + Automatically called when a waiter task completes. """ if not self.done(): self._waiters.remove(waiter) @@ -105,6 +132,8 @@ def _waiter_done_cleanup_callback( def _self_done_cleanup_callback(self: Union["SmartFuture", "SmartTask"]) -> None: """ Callback to clean up waiters and remove the future from the queue when done. + + This method clears all waiters and removes the future from the associated queue. """ self._waiters.clear() if queue := self._queue: @@ -125,6 +154,10 @@ class SmartFuture(_SmartFutureMixin[T], asyncio.Future): future = SmartFuture() await future ``` + + See Also: + - :class:`_SmartFutureMixin` + - :class:`asyncio.Future` """ _queue = None @@ -149,6 +182,9 @@ def __init__( ```python future = SmartFuture(queue=my_queue, key=my_key) ``` + + See Also: + - :class:`SmartProcessingQueue` """ super().__init__(loop=loop) if queue: @@ -175,6 +211,9 @@ def __lt__(self, other: "SmartFuture[T]") -> bool: future2 = SmartFuture() print(future1 < future2) ``` + + See Also: + - :meth:`num_waiters` """ return self.num_waiters > other.num_waiters @@ -202,6 +241,9 @@ def create_future( ```python future = create_future(queue=my_queue, key=my_key) ``` + + See Also: + - :class:`SmartFuture` """ return SmartFuture(queue=queue, key=key, loop=loop or asyncio.get_event_loop()) @@ -220,6 +262,10 @@ class SmartTask(_SmartFutureMixin[T], asyncio.Task): task = SmartTask(coro=my_coroutine()) await task ``` + + See Also: + - :class:`_SmartFutureMixin` + - :class:`asyncio.Task` """ def __init__( @@ -241,6 +287,9 @@ def __init__( ```python task = SmartTask(coro=my_coroutine(), name="my_task") ``` + + See Also: + - :func:`asyncio.create_task` """ super().__init__(coro, loop=loop, name=name) self._waiters: Set["asyncio.Task[T]"] = set() @@ -272,6 +321,7 @@ def smart_task_factory( See Also: - :func:`set_smart_task_factory` + - :class:`SmartTask` """ return SmartTask(coro, loop=loop) diff --git a/a_sync/a_sync/_descriptor.py b/a_sync/a_sync/_descriptor.py index eff09281..3ffaf186 100644 --- a/a_sync/a_sync/_descriptor.py +++ b/a_sync/a_sync/_descriptor.py @@ -2,13 +2,15 @@ This module contains the :class:`ASyncDescriptor` class, which is used to create dual-function sync/async methods and properties. -The :class:`ASyncDescriptor` class provides functionality for mapping operations across multiple instances -and includes utility methods for common operations such as checking if all or any results are truthy, -and finding the minimum, maximum, or sum of results of the method or property mapped across multiple instances. +The :class:`ASyncDescriptor` class provides a base for creating descriptors that can handle both synchronous and asynchronous +operations. It includes utility methods for mapping operations across multiple instances and provides access to common +operations such as checking if all or any results are truthy, and finding the minimum, maximum, or sum of results of the +method or property mapped across multiple instances through the use of :class:`~a_sync.a_sync.function.ASyncFunction`. See Also: - :class:`~a_sync.a_sync.function.ASyncFunction` - :class:`~a_sync.a_sync.method.ASyncMethodDescriptor` + - :class:`~a_sync.a_sync.property.ASyncPropertyDescriptor` """ import functools @@ -28,7 +30,7 @@ class ASyncDescriptor(_ModifiedMixin, Generic[I, P, T]): This class provides functionality for mapping operations across multiple instances and includes utility methods for common operations such as checking if all or any results are truthy, and finding the minimum, maximum, or sum of results of the method - or property mapped across multiple instances. + or property mapped across multiple instances through the use of :class:`~a_sync.a_sync.function.ASyncFunction`. Examples: To create a dual-function method or property, subclass :class:`ASyncDescriptor` and implement @@ -36,12 +38,10 @@ class ASyncDescriptor(_ModifiedMixin, Generic[I, P, T]): across multiple instances. ```python - class MyDescriptor(ASyncDescriptor): - def __init__(self, func): - super().__init__(func) - class MyClass: - my_method = MyDescriptor(lambda x: x * 2) + @ASyncDescriptor + def my_method(self, x): + return x * 2 instance = MyClass() result = instance.my_method.map([1, 2, 3]) @@ -119,12 +119,10 @@ def map( A :class:`TaskMapping` object. Examples: - class MyDescriptor(ASyncDescriptor): - def __init__(self, func): - super().__init__(func) - class MyClass: - my_method = MyDescriptor(lambda x: x * 2) + @ASyncDescriptor + def my_method(self, x): + return x * 2 instance = MyClass() result = instance.my_method.map([1, 2, 3]) @@ -142,12 +140,10 @@ def all(self) -> ASyncFunction[Concatenate[AnyIterable[I], P], bool]: An :class:`ASyncFunction` object. Examples: - class MyDescriptor(ASyncDescriptor): - def __init__(self, func): - super().__init__(func) - class MyClass: - my_method = MyDescriptor(lambda x: x > 0) + @ASyncDescriptor + def my_method(self, x): + return x > 0 instance = MyClass() result = await instance.my_method.all([1, 2, 3]) @@ -163,12 +159,10 @@ def any(self) -> ASyncFunction[Concatenate[AnyIterable[I], P], bool]: An :class:`ASyncFunction` object. Examples: - class MyDescriptor(ASyncDescriptor): - def __init__(self, func): - super().__init__(func) - class MyClass: - my_method = MyDescriptor(lambda x: x > 0) + @ASyncDescriptor + def my_method(self, x): + return x > 0 instance = MyClass() result = await instance.my_method.any([-1, 0, 1]) @@ -185,12 +179,10 @@ def min(self) -> ASyncFunction[Concatenate[AnyIterable[I], P], T]: Examples: ```python - class MyDescriptor(ASyncDescriptor): - def __init__(self, func): - super().__init__(func) - class MyClass: - my_method = MyDescriptor(lambda x: x) + @ASyncDescriptor + def my_method(self, x): + return x instance = MyClass() result = await instance.my_method.min([3, 1, 2]) @@ -207,12 +199,10 @@ def max(self) -> ASyncFunction[Concatenate[AnyIterable[I], P], T]: An :class:`ASyncFunction` object. Examples: - class MyDescriptor(ASyncDescriptor): - def __init__(self, func): - super().__init__(func) - class MyClass: - my_method = MyDescriptor(lambda x: x) + @ASyncDescriptor + def my_method(self, x): + return x instance = MyClass() result = await instance.my_method.max([3, 1, 2]) @@ -229,12 +219,10 @@ def sum(self) -> ASyncFunction[Concatenate[AnyIterable[I], P], T]: Examples: ```python - class MyDescriptor(ASyncDescriptor): - def __init__(self, func): - super().__init__(func) - class MyClass: - my_method = MyDescriptor(lambda x: x) + @ASyncDescriptor + def my_method(self, x): + return x instance = MyClass() result = await instance.my_method.sum([1, 2, 3]) @@ -258,17 +246,12 @@ async def _all( name: Optional name for the task. **kwargs: Additional keyword arguments. - Returns: - A boolean indicating if all results are truthy. - Examples: ```python - class MyDescriptor(ASyncDescriptor): - def __init__(self, func): - super().__init__(func) - class MyClass: - my_method = MyDescriptor(lambda x: x > 0) + @ASyncDescriptor + def my_method(self, x): + return x > 0 instance = MyClass() result = await instance.my_method._all([1, 2, 3]) @@ -294,17 +277,12 @@ async def _any( name: Optional name for the task. **kwargs: Additional keyword arguments. - Returns: - A boolean indicating if any result is truthy. - Examples: ```python - class MyDescriptor(ASyncDescriptor): - def __init__(self, func): - super().__init__(func) - class MyClass: - my_method = MyDescriptor(lambda x: x > 0) + @ASyncDescriptor + def my_method(self, x): + return x > 0 instance = MyClass() result = await instance.my_method._any([-1, 0, 1]) @@ -330,17 +308,12 @@ async def _min( name: Optional name for the task. **kwargs: Additional keyword arguments. - Returns: - The minimum result. - Examples: ```python - class MyDescriptor(ASyncDescriptor): - def __init__(self, func): - super().__init__(func) - class MyClass: - my_method = MyDescriptor(lambda x: x) + @ASyncDescriptor + def my_method(self, x): + return x instance = MyClass() result = await instance.my_method._min([3, 1, 2]) @@ -366,17 +339,12 @@ async def _max( name: Optional name for the task. **kwargs: Additional keyword arguments. - Returns: - The maximum result. - Examples: ```python - class MyDescriptor(ASyncDescriptor): - def __init__(self, func): - super().__init__(func) - class MyClass: - my_method = MyDescriptor(lambda x: x) + @ASyncDescriptor + def my_method(self, x): + return x instance = MyClass() result = await instance.my_method._max([3, 1, 2]) @@ -402,17 +370,12 @@ async def _sum( name: Optional name for the task. **kwargs: Additional keyword arguments. - Returns: - The sum of the results. - Examples: ```python - class MyDescriptor(ASyncDescriptor): - def __init__(self, func): - super().__init__(func) - class MyClass: - my_method = MyDescriptor(lambda x: x) + @ASyncDescriptor + def my_method(self, x): + return x instance = MyClass() result = await instance.my_method._sum([1, 2, 3]) diff --git a/a_sync/a_sync/_flags.py b/a_sync/a_sync/_flags.py index b6e70fd7..67b5ae35 100644 --- a/a_sync/a_sync/_flags.py +++ b/a_sync/a_sync/_flags.py @@ -6,15 +6,11 @@ You can use any of the provided flags, whichever makes the most sense for your use case. -AFFIRMATIVE_FLAGS: Set of flags indicating synchronous behavior. Currently includes "sync". +:obj:`AFFIRMATIVE_FLAGS`: Set of flags indicating synchronous behavior. Currently includes "sync". -NEGATIVE_FLAGS: Set of flags indicating asynchronous behavior. Currently includes "asynchronous". +:obj:`NEGATIVE_FLAGS`: Set of flags indicating asynchronous behavior. Currently includes "asynchronous". -VIABLE_FLAGS: Set of all valid flags, combining both synchronous and asynchronous indicators. - -Functions: - - negate_if_necessary: Negates the flag value if necessary based on the flag type. - - validate_flag_value: Validates that the flag value is a boolean. +:obj:`VIABLE_FLAGS`: Set of all valid flags, combining both synchronous and asynchronous indicators. """ from typing import Any @@ -24,30 +20,45 @@ AFFIRMATIVE_FLAGS = {"sync"} """Set of flags indicating synchronous behavior. +This set currently contains only the flag "sync", which is used to denote +synchronous operations within the ez-a-sync library. + Examples: >>> 'sync' in AFFIRMATIVE_FLAGS True >>> 'async' in AFFIRMATIVE_FLAGS False + +See Also: + :data:`NEGATIVE_FLAGS`: Flags indicating asynchronous behavior. + :data:`VIABLE_FLAGS`: All valid flags, combining both sync and async indicators. """ NEGATIVE_FLAGS = {"asynchronous"} """Set of flags indicating asynchronous behavior. +This set currently contains only the flag "asynchronous", which is used to denote +asynchronous operations within the ez-a-sync library. + Examples: >>> 'asynchronous' in NEGATIVE_FLAGS True >>> 'sync' in NEGATIVE_FLAGS False + +See Also: + :data:`AFFIRMATIVE_FLAGS`: Flags indicating synchronous behavior. + :data:`VIABLE_FLAGS`: All valid flags, combining both sync and async indicators. """ VIABLE_FLAGS = AFFIRMATIVE_FLAGS | NEGATIVE_FLAGS """Set of all valid flags, combining both synchronous and asynchronous indicators. -A-Sync uses 'flags' to indicate whether objects or function calls will be sync or async. -You can use any of the provided flags, whichever makes most sense for your use case. +The ez-a-sync library uses these flags to indicate whether objects or function +calls will be synchronous or asynchronous. You can use any of the provided flags, +whichever makes the most sense for your use case. Examples: >>> 'sync' in VIABLE_FLAGS @@ -58,6 +69,10 @@ >>> 'invalid' in VIABLE_FLAGS False + +See Also: + :data:`AFFIRMATIVE_FLAGS`: Flags indicating synchronous behavior. + :data:`NEGATIVE_FLAGS`: Flags indicating asynchronous behavior. """ diff --git a/a_sync/a_sync/base.py b/a_sync/a_sync/base.py index bf635a94..5949c577 100644 --- a/a_sync/a_sync/base.py +++ b/a_sync/a_sync/base.py @@ -16,7 +16,7 @@ class ASyncGenericBase(ASyncABC): """ Base class for creating dual-function sync/async-capable classes without writing all your code twice. - This class provides the foundation for creating hybrid sync/async classes. It allows methods + This class, via its inherited metaclass :class:`~ASyncMeta', provides the foundation for creating hybrid sync/async classes. It allows methods and properties to be defined once and used in both synchronous and asynchronous contexts. The class uses the :func:`a_sync` decorator internally to create dual-mode methods and properties. diff --git a/a_sync/a_sync/modifiers/manager.py b/a_sync/a_sync/modifiers/manager.py index 8461cb30..dd37cb61 100644 --- a/a_sync/a_sync/modifiers/manager.py +++ b/a_sync/a_sync/modifiers/manager.py @@ -89,32 +89,55 @@ def __getattribute__(self, modifier_key: str) -> Any: Returns: The value of the modifier, or the default value if not set. + + Examples: + >>> manager = ModifierManager(ModifierKwargs(cache_type='memory')) + >>> manager.cache_type + 'memory' """ if modifier_key not in valid_modifiers: return super().__getattribute__(modifier_key) return ( - self[modifier_key] if modifier_key in self else user_defaults[modifier_key] + self[modifier_key] if modifier_key in self else USER_DEFAULTS[modifier_key] ) @property def use_limiter(self) -> bool: - """Determines if a rate limiter should be used.""" - return self.runs_per_minute != nulls.runs_per_minute + """Determines if a rate limiter should be used. + + Examples: + >>> manager = ModifierManager(ModifierKwargs(runs_per_minute=60)) + >>> manager.use_limiter + True + """ + return self.runs_per_minute != NULLS.runs_per_minute @property def use_semaphore(self) -> bool: - """Determines if a semaphore should be used.""" - return self.semaphore != nulls.semaphore + """Determines if a semaphore should be used. + + Examples: + >>> manager = ModifierManager(ModifierKwargs(semaphore=SemaphoreSpec())) + >>> manager.use_semaphore + True + """ + return self.semaphore != NULLS.semaphore @property def use_cache(self) -> bool: - """Determines if caching should be used.""" + """Determines if caching should be used. + + Examples: + >>> manager = ModifierManager(ModifierKwargs(cache_type='memory')) + >>> manager.use_cache + True + """ return any( [ - self.cache_type != nulls.cache_type, - self.ram_cache_maxsize != nulls.ram_cache_maxsize, - self.ram_cache_ttl != nulls.ram_cache_ttl, - self.cache_typed != nulls.cache_typed, + self.cache_type != NULLS.cache_type, + self.ram_cache_maxsize != NULLS.ram_cache_maxsize, + self.ram_cache_ttl != NULLS.ram_cache_ttl, + self.cache_typed != NULLS.cache_typed, ] ) @@ -175,27 +198,66 @@ def sync_modifier_wrap(*args: P.args, **kwargs: P.kwargs) -> T: # Dictionary api def keys(self) -> KeysView[str]: # type: ignore [override] - """Returns the keys of the modifiers.""" + """Returns the keys of the modifiers. + + Examples: + >>> manager = ModifierManager(ModifierKwargs(cache_type='memory')) + >>> list(manager.keys()) + ['cache_type'] + """ return self._modifiers.keys() def values(self) -> ValuesView[Any]: # type: ignore [override] - """Returns the values of the modifiers.""" + """Returns the values of the modifiers. + + Examples: + >>> manager = ModifierManager(ModifierKwargs(cache_type='memory')) + >>> list(manager.values()) + ['memory'] + """ return self._modifiers.values() def items(self) -> ItemsView[str, Any]: # type: ignore [override] - """Returns the items of the modifiers.""" + """Returns the items of the modifiers. + + Examples: + >>> manager = ModifierManager(ModifierKwargs(cache_type='memory')) + >>> list(manager.items()) + [('cache_type', 'memory')] + """ return self._modifiers.items() def __contains__(self, key: str) -> bool: # type: ignore [override] - """Checks if a key is in the modifiers.""" + """Checks if a key is in the modifiers. + + Args: + key: The key to check. + + Examples: + >>> manager = ModifierManager(ModifierKwargs(cache_type='memory')) + >>> 'cache_type' in manager + True + """ return key in self._modifiers def __iter__(self) -> Iterator[str]: - """Returns an iterator over the modifier keys.""" + """Returns an iterator over the modifier keys. + + Examples: + >>> manager = ModifierManager(ModifierKwargs(cache_type='memory')) + >>> list(iter(manager)) + ['cache_type'] + """ return self._modifiers.__iter__() def __len__(self) -> int: - """Returns the number of modifiers.""" + """Returns the number of modifiers. + + Examples: + >>> manager = ModifierManager(ModifierKwargs(cache_type='memory')) + >>> len(manager) + 1 + """ return len(self._modifiers) def __getitem__(self, modifier_key: str): @@ -206,10 +268,15 @@ def __getitem__(self, modifier_key: str): Returns: The value of the modifier. + + Examples: + >>> manager = ModifierManager(ModifierKwargs(cache_type='memory')) + >>> manager['cache_type'] + 'memory' """ return self._modifiers[modifier_key] # type: ignore [literal-required] # TODO give us docstrings -nulls = ModifierManager(null_modifiers) -user_defaults = ModifierManager(user_set_default_modifiers) +NULLS = ModifierManager(null_modifiers) +USER_DEFAULTS = ModifierManager(user_set_default_modifiers) diff --git a/a_sync/a_sync/modifiers/semaphores.py b/a_sync/a_sync/modifiers/semaphores.py index daef90e3..59c31d64 100644 --- a/a_sync/a_sync/modifiers/semaphores.py +++ b/a_sync/a_sync/modifiers/semaphores.py @@ -105,7 +105,7 @@ def apply_semaphore( The semaphore to apply, which can be an integer, an `asyncio.Semaphore`, or a `primitives.Semaphore`. Raises: - ValueError: If both `coro_fn` and `semaphore` are provided and the first argument is an integer or `asyncio.Semaphore`. + ValueError: If `coro_fn` is an integer or `asyncio.Semaphore` and `semaphore` is not None. exceptions.FunctionNotAsync: If the provided function is not a coroutine. TypeError: If the semaphore is not an integer, an `asyncio.Semaphore`, or a `primitives.Semaphore`. diff --git a/a_sync/a_sync/property.py b/a_sync/a_sync/property.py index 3601241f..d030b980 100644 --- a/a_sync/a_sync/property.py +++ b/a_sync/a_sync/property.py @@ -197,7 +197,9 @@ class ASyncPropertyDescriptorSyncDefault(property[I, T]): synchronously but can also be used asynchronously if needed. """ + # TODO give all of these docstrings default = "sync" + # TODO and give these ones examples any: ASyncFunctionSyncDefault[AnyIterable[I], bool] all: ASyncFunctionSyncDefault[AnyIterable[I], bool] min: ASyncFunctionSyncDefault[AnyIterable[I], T] @@ -230,7 +232,9 @@ class ASyncPropertyDescriptorAsyncDefault(property[I, T]): asynchronously but can also be used synchronously if needed. """ + # TODO give all of these docstrings default = "async" + # TODO and give these ones examples any: ASyncFunctionAsyncDefault[AnyIterable[I], bool] all: ASyncFunctionAsyncDefault[AnyIterable[I], bool] min: ASyncFunctionAsyncDefault[AnyIterable[I], T] @@ -238,6 +242,7 @@ class ASyncPropertyDescriptorAsyncDefault(property[I, T]): sum: ASyncFunctionAsyncDefault[AnyIterable[I], T] +# Give all of these docstrings ASyncPropertyDecorator = Callable[[AnyGetterFunction[I, T]], property[I, T]] ASyncPropertyDecoratorSyncDefault = Callable[ [AnyGetterFunction[I, T]], ASyncPropertyDescriptorSyncDefault[I, T] diff --git a/a_sync/a_sync/singleton.py b/a_sync/a_sync/singleton.py index fb6682f6..c3211897 100644 --- a/a_sync/a_sync/singleton.py +++ b/a_sync/a_sync/singleton.py @@ -18,7 +18,8 @@ class ASyncGenericSingleton(ASyncGenericBase, metaclass=ASyncSingletonMeta): while maintaining the singleton pattern within each context. Note: - This class is abstract and cannot be instantiated directly. Subclasses should define + This class can be instantiated directly, but it is intended to be subclassed + to define specific asynchronous behavior. Subclasses should define the necessary properties and methods to specify the asynchronous behavior, as outlined in :class:`ASyncABC`. diff --git a/a_sync/asyncio/as_completed.py b/a_sync/asyncio/as_completed.py index e0d970fc..fd001dd0 100644 --- a/a_sync/asyncio/as_completed.py +++ b/a_sync/asyncio/as_completed.py @@ -75,17 +75,17 @@ def as_completed( Differences from :func:`asyncio.as_completed`: - Uses type hints for use with static type checkers. - Supports either individual awaitables or a k:v mapping of awaitables. - - Can be used as an async iterator which yields the result values. + - Can be used as an async iterator which yields the result values using :class:`ASyncIterator`. - Provides progress reporting using :mod:`tqdm` if 'tqdm' is set to True. Note: - The `return_exceptions` parameter is partially implemented. While exceptions can be wrapped and returned instead of being raised, this behavior may not be fully consistent across all scenarios. Users should test their specific use cases to ensure the desired behavior. + The `return_exceptions` parameter is not directly used in this function but is relevant for wrapped awaitables in mappings. Users should test their specific use cases to ensure the desired behavior. Args: fs: The awaitables to await concurrently. It can be a list of individual awaitables or a mapping of awaitables. timeout: The maximum time, in seconds, to wait for the completion of awaitables. Defaults to None (no timeout). return_exceptions: If True, exceptions are wrapped and returned as results instead of raising them. Defaults to False. - aiter: If True, returns an async iterator of results. Defaults to False. + aiter: If True, returns an async iterator of results using :class:`ASyncIterator`. Defaults to False. tqdm: If True, enables progress reporting using :mod:`tqdm`. Defaults to False. **tqdm_kwargs: Additional keyword arguments for :mod:`tqdm` if progress reporting is enabled. @@ -176,7 +176,7 @@ def as_completed_mapping( mapping: A dictionary-like object where keys are of type K and values are awaitable objects of type V. timeout: The maximum time, in seconds, to wait for the completion of awaitables. Defaults to None (no timeout). return_exceptions: If True, exceptions are wrapped and returned as results instead of raising them. Defaults to False. - aiter: If True, returns an async iterator of results. Defaults to False. + aiter: If True, returns an async iterator of results using :class:`ASyncIterator`. Defaults to False. tqdm: If True, enables progress reporting using :mod:`tqdm`. Defaults to False. **tqdm_kwargs: Additional keyword arguments for :mod:`tqdm` if progress reporting is enabled. diff --git a/a_sync/exceptions.py b/a_sync/exceptions.py index 684483a1..92d07e62 100644 --- a/a_sync/exceptions.py +++ b/a_sync/exceptions.py @@ -16,11 +16,21 @@ class ASyncFlagException(ValueError): Base exception class for flag-related errors in the a_sync library. A-Sync uses 'flags' to indicate whether objects or function calls will be sync or async. - You can use any of the provided flags, whichever makes most sense for your use case. + You can use any of the provided flags, which include 'sync' and 'asynchronous', whichever makes most sense for your use case. + + Examples: + >>> try: + ... raise ASyncFlagException("An error occurred with flags.") + ... except ASyncFlagException as e: + ... print(e) + An error occurred with flags. + + See Also: + - :const:`VIABLE_FLAGS` """ viable_flags = VIABLE_FLAGS - """The set of viable flags.""" + """The set of viable flags: {'sync', 'asynchronous'}.""" def desc(self, target) -> str: """ diff --git a/a_sync/future.py b/a_sync/future.py index 0c6f9759..a9ac5f7b 100644 --- a/a_sync/future.py +++ b/a_sync/future.py @@ -156,13 +156,31 @@ class ASyncFuture(concurrent.futures.Future, Awaitable[T]): 42 Note: - Some arithmetic operations are currently broken or incomplete. Use with caution. + Arithmetic operations are implemented, allowing for mathematical operations on future results. + You no longer have to choose between optimized async code and clean, readable code. - TODO include a simple mathematics example a one complex example with numerous variables and operations - TODO include attribute access examples - TODO describe a bit more about both of the above 2 TODOs somewhere in this class-level docstring - TODO describe why a user would want to use these (to write cleaner code that doesn't require as many ugly gathers) - TODO include comparisons between the 'new way' with this future class and the 'old way' with gathers + Example: + >>> future1 = ASyncFuture(asyncio.sleep(1, result=10)) + >>> future2 = ASyncFuture(asyncio.sleep(1, result=5)) + >>> future3 = ASyncFuture(asyncio.sleep(1, result=10)) + >>> future4 = ASyncFuture(asyncio.sleep(1, result=2)) + >>> result = (future1 + future2) / future3 ** future4 + >>> await result + 0.15 + + Attribute Access: + The `ASyncFuture` allows attribute access on the materialized result. + + Example: + >>> class Example: + ... def __init__(self, value): + ... self.value = value + >>> future = ASyncFuture(asyncio.sleep(1, result=Example(42))) + >>> future.value + 42 + + See Also: + :func:`future` for creating `ASyncFuture` instances. """ __slots__ = "__awaitable__", "__dependencies", "__dependants", "__task" diff --git a/a_sync/iter.py b/a_sync/iter.py index 2d87c2ae..ce6190f7 100644 --- a/a_sync/iter.py +++ b/a_sync/iter.py @@ -121,11 +121,23 @@ def __init_subclass__(cls, **kwargs) -> None: class ASyncIterable(_AwaitableAsyncIterableMixin[T], Iterable[T]): """ - A hybrid Iterable/AsyncIterable implementation designed to offer dual compatibility with both synchronous and asynchronous iteration protocols. - - This class allows objects to be iterated over using either a standard `for` loop or an `async for` loop, making it versatile in scenarios where the mode of iteration (synchronous or asynchronous) needs to be flexible or is determined at runtime. - - The class achieves this by implementing both `__iter__` and `__aiter__` methods, enabling it to return appropriate iterator objects that can handle synchronous and asynchronous iteration, respectively. However, note that synchronous iteration relies on the :class:`ASyncIterator` class, which uses `asyncio.get_event_loop().run_until_complete` to fetch items. This can raise a `RuntimeError` if the event loop is already running, resulting in a :class:`SyncModeInAsyncContextError`. + A hybrid Iterable/AsyncIterable implementation designed to offer + dual compatibility with both synchronous and asynchronous + iteration protocols. + + This class allows objects to be iterated over using either a + standard `for` loop or an `async for` loop, making it versatile + in scenarios where the mode of iteration (synchronous or asynchronous) + needs to be flexible or is determined at runtime. + + The class achieves this by implementing both `__iter__` and `__aiter__` + methods, enabling it to return appropriate iterator objects that can + handle synchronous and asynchronous iteration, respectively. However, + note that synchronous iteration relies on the :class:`ASyncIterator` + class, which uses `asyncio.get_event_loop().run_until_complete` to + fetch items. This can raise a `RuntimeError` if the event loop is + already running, and in such cases, a :class:`SyncModeInAsyncContextError` + is raised from the `RuntimeError`. Example: >>> async_iterable = ASyncIterable(some_async_iterable) @@ -191,7 +203,7 @@ class ASyncIterator(_AwaitableAsyncIterableMixin[T], Iterator[T]): By implementing both `__next__` and `__anext__` methods, ASyncIterator enables objects to be iterated using standard iteration protocols while internally managing the complexities of asynchronous iteration. This design simplifies the use of asynchronous iterables in environments or frameworks that are not inherently asynchronous, such as standard synchronous functions or older codebases being gradually migrated to asynchronous IO. Note: - Synchronous iteration with `ASyncIterator` uses `asyncio.get_event_loop().run_until_complete`, which can raise a `RuntimeError` if the event loop is already running. In such cases, a :class:`SyncModeInAsyncContextError` is raised, indicating that synchronous iteration is not possible in an already running event loop. + Synchronous iteration with `ASyncIterator` uses `asyncio.get_event_loop().run_until_complete`, which can raise a `RuntimeError` if the event loop is already running. In such cases, a :class:`SyncModeInAsyncContextError` is raised from the `RuntimeError`, indicating that synchronous iteration is not possible in an already running event loop. Example: >>> async_iterator = ASyncIterator(some_async_iterator) diff --git a/a_sync/primitives/__init__.py b/a_sync/primitives/__init__.py index 0108e86a..5f8cfb11 100644 --- a/a_sync/primitives/__init__.py +++ b/a_sync/primitives/__init__.py @@ -2,17 +2,40 @@ This module includes both new primitives and modified versions of standard asyncio primitives. The primitives provided in this module are: +- :class:`~a_sync.primitives.locks.Semaphore` +- :class:`~a_sync.primitives.locks.PrioritySemaphore` +- :class:`~a_sync.primitives.ThreadsafeSemaphore` +- :class:`~a_sync.primitives.locks.CounterLock` +- :class:`~a_sync.primitives.locks.Event` +- :class:`~a_sync.primitives.queue.Queue` +- :class:`~a_sync.primitives.queue.ProcessingQueue` +- :class:`~a_sync.primitives.queue.SmartProcessingQueue` + + These primitives extend or modify the functionality of standard asyncio primitives to provide additional features or improved performance for specific use cases. -- Semaphore -- ThreadsafeSemaphore -- PrioritySemaphore -- CounterLock -- Event -- Queue -- ProcessingQueue -- SmartProcessingQueue +Examples: + Using a Semaphore to limit concurrent access: + + >>> from a_sync.primitives.locks import Semaphore + >>> semaphore = Semaphore(2) + >>> async with semaphore: + ... # perform some operation + ... pass + + Using a Queue to manage tasks: + + >>> from a_sync.primitives.queue import Queue + >>> queue = Queue() + >>> await queue.put('task1') + >>> task = await queue.get() + >>> print(task) + task1 + +See Also: + - :mod:`asyncio` for standard asyncio primitives. + - :mod:`a_sync.primitives.locks` for lock-related primitives. + - :mod:`a_sync.primitives.queue` for queue-related primitives. -These primitives extend or modify the functionality of standard asyncio primitives to provide additional features or improved performance for specific use cases. """ from a_sync.primitives.locks import * diff --git a/a_sync/primitives/_debug.py b/a_sync/primitives/_debug.py index c23f3d7b..b31bece2 100644 --- a/a_sync/primitives/_debug.py +++ b/a_sync/primitives/_debug.py @@ -80,7 +80,7 @@ def _ensure_debug_daemon(self, *args, **kwargs) -> "asyncio.Future[None]": """ Ensures that the debug daemon task is running. - This method checks if the debug daemon is already running and starts it if necessary. It will only start the daemon if it is not already running. + This method checks if the debug daemon is already running and starts it if necessary. If debug logging is not enabled, it sets the daemon to a dummy future. Args: *args: Positional arguments for the debug daemon. diff --git a/a_sync/primitives/_loggable.py b/a_sync/primitives/_loggable.py index 14ee8268..d038c188 100644 --- a/a_sync/primitives/_loggable.py +++ b/a_sync/primitives/_loggable.py @@ -31,7 +31,7 @@ def logger(self) -> Logger: >>> instance = MyClass() >>> logger = instance.logger >>> logger.name - '__main__.MyClass.example' + 'your_module_name.MyClass.example' >>> class AnotherClass(_LoggerMixin): ... pass @@ -39,7 +39,10 @@ def logger(self) -> Logger: >>> another_instance = AnotherClass() >>> another_logger = another_instance.logger >>> another_logger.name - '__main__.AnotherClass' + 'your_module_name.AnotherClass' + + Note: + Replace `your_module_name` with the actual module name where the class is defined. See Also: - :func:`logging.getLogger` diff --git a/a_sync/primitives/locks/counter.py b/a_sync/primitives/locks/counter.py index 68a104bc..d9f688f9 100644 --- a/a_sync/primitives/locks/counter.py +++ b/a_sync/primitives/locks/counter.py @@ -1,7 +1,7 @@ """ This module provides two specialized async flow management classes, :class:`CounterLock` and :class:`CounterLockCluster`. -These primitives manage :class:`asyncio.Task` objects that must wait for an internal counter to reach a specific value. +These primitives manage synchronization of tasks that must wait for an internal counter to reach a specific value. """ import asyncio @@ -48,10 +48,10 @@ def __init__(self, start_value: int = 0, name: Optional[str] = None): """The current value of the counter.""" self._events: DefaultDict[int, Event] = defaultdict(Event) - """A defaultdict that maps each awaited value to an :class:`asyncio.Event` that manages the waiters for that value.""" + """A defaultdict that maps each awaited value to an :class:`Event` that manages the waiters for that value.""" self.is_ready = lambda v: self._value >= v - """A lambda function that indicates whether a given value has already been surpassed.""" + """A lambda function that indicates whether the current counter value is greater than or equal to a given value.""" async def wait_for(self, value: int) -> bool: """ diff --git a/a_sync/primitives/locks/prio_semaphore.py b/a_sync/primitives/locks/prio_semaphore.py index 836b5750..8a46ca9c 100644 --- a/a_sync/primitives/locks/prio_semaphore.py +++ b/a_sync/primitives/locks/prio_semaphore.py @@ -125,6 +125,8 @@ async def __aexit__(self, *_) -> None: async def acquire(self) -> Literal[True]: """Acquires the semaphore with the top priority. + This method overrides :meth:`Semaphore.acquire` to handle priority-based logic. + Examples: >>> semaphore = _AbstractPrioritySemaphore(5) >>> await semaphore.acquire() @@ -347,6 +349,8 @@ async def acquire(self) -> Literal[True]: called release() to make it larger than 0, and then return True. + This method overrides :meth:`Semaphore.acquire` to handle priority-based logic. + Examples: >>> context_manager = _AbstractPrioritySemaphoreContextManager(parent, priority=1) >>> await context_manager.acquire() @@ -371,6 +375,8 @@ async def acquire(self) -> Literal[True]: def release(self) -> None: """Releases the semaphore for this context manager. + This method overrides :meth:`Semaphore.release` to handle priority-based logic. + Examples: >>> context_manager = _AbstractPrioritySemaphoreContextManager(parent, priority=1) >>> context_manager.release() diff --git a/a_sync/primitives/locks/semaphore.py b/a_sync/primitives/locks/semaphore.py index e99904c9..506e8cdb 100644 --- a/a_sync/primitives/locks/semaphore.py +++ b/a_sync/primitives/locks/semaphore.py @@ -140,7 +140,10 @@ async def monitor(): while self._waiters: await asyncio.sleep(60) self.logger.debug( - f"{self} has {len(self)} waiters for any of: {self._decorated}" + "%s has %s waiters for any of: %s", + self, + len(self), + self._decorated, ) diff --git a/tests/executor.py b/tests/executor.py index 2209f1fc..1c4e0443 100644 --- a/tests/executor.py +++ b/tests/executor.py @@ -8,15 +8,14 @@ @pytest.mark.asyncio async def test_process_pool_executor_run(): - """Tests the :class:`ProcessPoolExecutor` by running and submitting the work function asynchronously. + """Test the :class:`ProcessPoolExecutor` by running and submitting the work function asynchronously. - This test ensures that the `run` method of the `ProcessPoolExecutor` returns a coroutine - when executed with a synchronous function. + This test ensures that the :meth:`~ProcessPoolExecutor.run` method of the + :class:`~ProcessPoolExecutor` returns a coroutine when executed with a synchronous function. See Also: - - :meth:`ProcessPoolExecutor.run` + - :meth:`~ProcessPoolExecutor.run` - :func:`asyncio.iscoroutine` - """ executor = ProcessPoolExecutor(1) coro = executor.run(time.sleep, 0.1) @@ -26,15 +25,14 @@ async def test_process_pool_executor_run(): @pytest.mark.asyncio async def test_thread_pool_executor_run(): - """Tests the :class:`ThreadPoolExecutor` by running and submitting the work function asynchronously. + """Test the :class:`ThreadPoolExecutor` by running and submitting the work function asynchronously. - This test ensures that the `run` method of the `ThreadPoolExecutor` returns a coroutine - when executed with a synchronous function. + This test ensures that the :meth:`~ThreadPoolExecutor.run` method of the + :class:`~ThreadPoolExecutor` returns a coroutine when executed with a synchronous function. See Also: - - :meth:`ThreadPoolExecutor.run` + - :meth:`~ThreadPoolExecutor.run` - :func:`asyncio.iscoroutine` - """ executor = ThreadPoolExecutor(1) coro = executor.run(time.sleep, 0.1) @@ -44,15 +42,14 @@ async def test_thread_pool_executor_run(): @pytest.mark.asyncio async def test_pruning_thread_pool_executor_run(): - """Tests the :class:`PruningThreadPoolExecutor` by running and submitting the work function asynchronously. + """Test the :class:`PruningThreadPoolExecutor` by running and submitting the work function asynchronously. - This test ensures that the `run` method of the `PruningThreadPoolExecutor` returns a coroutine - when executed with a synchronous function. + This test ensures that the :meth:`~PruningThreadPoolExecutor.run` method of the + :class:`~PruningThreadPoolExecutor` returns a coroutine when executed with a synchronous function. See Also: - - :meth:`PruningThreadPoolExecutor.run` + - :meth:`~PruningThreadPoolExecutor.run` - :func:`asyncio.iscoroutine` - """ executor = PruningThreadPoolExecutor(1) coro = executor.run(time.sleep, 0.1) @@ -62,20 +59,19 @@ async def test_pruning_thread_pool_executor_run(): @pytest.mark.asyncio async def test_process_pool_executor_submit(): - """Tests the :class:`ProcessPoolExecutor` by submitting the work function. + """Test the :class:`ProcessPoolExecutor` by submitting the work function. - This test ensures that the `submit` method of the `ProcessPoolExecutor` returns an - :class:`asyncio.Future` when executed with a synchronous function. + This test ensures that the :meth:`~ProcessPoolExecutor.submit` method of the + :class:`~ProcessPoolExecutor` returns an :class:`asyncio.Future` when executed with a synchronous function. Note: - The `submit` method in this context returns an `asyncio.Future`, not a `concurrent.futures.Future`. - This is specific to the implementation of the executors in the `a_sync` library, which adapts the - behavior to integrate with the asyncio event loop. + The :meth:`~ProcessPoolExecutor.submit` method in this context returns an :class:`asyncio.Future`, + not a :class:`concurrent.futures.Future`. This is specific to the implementation of the executors + in the `a_sync` library, which adapts the behavior to integrate with the asyncio event loop. See Also: - - :meth:`ProcessPoolExecutor.submit` + - :meth:`~ProcessPoolExecutor.submit` - :class:`asyncio.Future` - """ executor = ProcessPoolExecutor(1) fut = executor.submit(time.sleep, 0.1) @@ -85,20 +81,19 @@ async def test_process_pool_executor_submit(): @pytest.mark.asyncio async def test_thread_pool_executor_submit(): - """Tests the :class:`ThreadPoolExecutor` by submitting the work function. + """Test the :class:`ThreadPoolExecutor` by submitting the work function. - This test ensures that the `submit` method of the `ThreadPoolExecutor` returns an - :class:`asyncio.Future` when executed with a synchronous function. + This test ensures that the :meth:`~ThreadPoolExecutor.submit` method of the + :class:`~ThreadPoolExecutor` returns an :class:`asyncio.Future` when executed with a synchronous function. Note: - The `submit` method in this context returns an `asyncio.Future`, not a `concurrent.futures.Future`. - This is specific to the implementation of the executors in the `a_sync` library, which adapts the - behavior to integrate with the asyncio event loop. + The :meth:`~ThreadPoolExecutor.submit` method in this context returns an :class:`asyncio.Future`, + not a :class:`concurrent.futures.Future`. This is specific to the implementation of the executors + in the `a_sync` library, which adapts the behavior to integrate with the asyncio event loop. See Also: - - :meth:`ThreadPoolExecutor.submit` + - :meth:`~ThreadPoolExecutor.submit` - :class:`asyncio.Future` - """ executor = ThreadPoolExecutor(1) fut = executor.submit(time.sleep, 0.1) @@ -108,20 +103,19 @@ async def test_thread_pool_executor_submit(): @pytest.mark.asyncio async def test_pruning_thread_pool_executor_submit(): - """Tests the :class:`PruningThreadPoolExecutor` by submitting the work function. + """Test the :class:`PruningThreadPoolExecutor` by submitting the work function. - This test ensures that the `submit` method of the `PruningThreadPoolExecutor` returns an - :class:`asyncio.Future` when executed with a synchronous function. + This test ensures that the :meth:`~PruningThreadPoolExecutor.submit` method of the + :class:`~PruningThreadPoolExecutor` returns an :class:`asyncio.Future` when executed with a synchronous function. Note: - The `submit` method in this context returns an `asyncio.Future`, not a `concurrent.futures.Future`. - This is specific to the implementation of the executors in the `a_sync` library, which adapts the - behavior to integrate with the asyncio event loop. + The :meth:`~PruningThreadPoolExecutor.submit` method in this context returns an :class:`asyncio.Future`, + not a :class:`concurrent.futures.Future`. This is specific to the implementation of the executors + in the `a_sync` library, which adapts the behavior to integrate with the asyncio event loop. See Also: - - :meth:`PruningThreadPoolExecutor.submit` + - :meth:`~PruningThreadPoolExecutor.submit` - :class:`asyncio.Future` - """ executor = PruningThreadPoolExecutor(1) fut = executor.submit(time.sleep, 0.1) @@ -131,15 +125,14 @@ async def test_pruning_thread_pool_executor_submit(): @pytest.mark.asyncio async def test_process_pool_executor_sync_run(): - """Tests the :class:`ProcessPoolExecutor` by running and submitting the work function synchronously. + """Test the :class:`ProcessPoolExecutor` by running and submitting the work function synchronously. - This test ensures that the `run` method of the `ProcessPoolExecutor` returns a coroutine - when executed with a synchronous function. + This test ensures that the :meth:`~ProcessPoolExecutor.run` method of the + :class:`~ProcessPoolExecutor` returns a coroutine when executed with a synchronous function. See Also: - - :meth:`ProcessPoolExecutor.run` + - :meth:`~ProcessPoolExecutor.run` - :func:`asyncio.iscoroutine` - """ executor = ProcessPoolExecutor(0) coro = executor.run(time.sleep, 0.1) @@ -149,15 +142,14 @@ async def test_process_pool_executor_sync_run(): @pytest.mark.asyncio async def test_thread_pool_executor_sync_run(): - """Tests the :class:`ThreadPoolExecutor` by running and submitting the work function synchronously. + """Test the :class:`ThreadPoolExecutor` by running and submitting the work function synchronously. - This test ensures that the `run` method of the `ThreadPoolExecutor` returns a coroutine - when executed with a synchronous function. + This test ensures that the :meth:`~ThreadPoolExecutor.run` method of the + :class:`~ThreadPoolExecutor` returns a coroutine when executed with a synchronous function. See Also: - - :meth:`ThreadPoolExecutor.run` + - :meth:`~ThreadPoolExecutor.run` - :func:`asyncio.iscoroutine` - """ executor = ThreadPoolExecutor(0) coro = executor.run(time.sleep, 0.1) @@ -167,15 +159,14 @@ async def test_thread_pool_executor_sync_run(): @pytest.mark.asyncio async def test_pruning_thread_pool_executor_sync_run(): - """Tests the :class:`PruningThreadPoolExecutor` by running and submitting the work function synchronously. + """Test the :class:`PruningThreadPoolExecutor` by running and submitting the work function synchronously. - This test ensures that the `run` method of the `PruningThreadPoolExecutor` returns a coroutine - when executed with a synchronous function. + This test ensures that the :meth:`~PruningThreadPoolExecutor.run` method of the + :class:`~PruningThreadPoolExecutor` returns a coroutine when executed with a synchronous function. See Also: - - :meth:`PruningThreadPoolExecutor.run` + - :meth:`~PruningThreadPoolExecutor.run` - :func:`asyncio.iscoroutine` - """ executor = PruningThreadPoolExecutor(0) coro = executor.run(time.sleep, 0.1) @@ -185,20 +176,19 @@ async def test_pruning_thread_pool_executor_sync_run(): @pytest.mark.asyncio async def test_process_pool_executor_sync_submit(): - """Tests the :class:`ProcessPoolExecutor` by submitting the work function synchronously. + """Test the :class:`ProcessPoolExecutor` by submitting the work function synchronously. - This test ensures that the `submit` method of the `ProcessPoolExecutor` returns an - :class:`asyncio.Future` when executed with a synchronous function. + This test ensures that the :meth:`~ProcessPoolExecutor.submit` method of the + :class:`~ProcessPoolExecutor` returns an :class:`asyncio.Future` when executed with a synchronous function. Note: - The `submit` method in this context returns an `asyncio.Future`, not a `concurrent.futures.Future`. - This is specific to the implementation of the executors in the `a_sync` library, which adapts the - behavior to integrate with the asyncio event loop. + The :meth:`~ProcessPoolExecutor.submit` method in this context returns an :class:`asyncio.Future`, + not a :class:`concurrent.futures.Future`. This is specific to the implementation of the executors + in the `a_sync` library, which adapts the behavior to integrate with the asyncio event loop. See Also: - - :meth:`ProcessPoolExecutor.submit` + - :meth:`~ProcessPoolExecutor.submit` - :class:`asyncio.Future` - """ executor = ProcessPoolExecutor(0) fut = executor.submit(time.sleep, 0.1) @@ -208,20 +198,19 @@ async def test_process_pool_executor_sync_submit(): @pytest.mark.asyncio async def test_thread_pool_executor_sync_submit(): - """Tests the :class:`ThreadPoolExecutor` by submitting the work function synchronously. + """Test the :class:`ThreadPoolExecutor` by submitting the work function synchronously. - This test ensures that the `submit` method of the `ThreadPoolExecutor` returns an - :class:`asyncio.Future` when executed with a synchronous function. + This test ensures that the :meth:`~ThreadPoolExecutor.submit` method of the + :class:`~ThreadPoolExecutor` returns an :class:`asyncio.Future` when executed with a synchronous function. Note: - The `submit` method in this context returns an `asyncio.Future`, not a `concurrent.futures.Future`. - This is specific to the implementation of the executors in the `a_sync` library, which adapts the - behavior to integrate with the asyncio event loop. + The :meth:`~ThreadPoolExecutor.submit` method in this context returns an :class:`asyncio.Future`, + not a :class:`concurrent.futures.Future`. This is specific to the implementation of the executors + in the `a_sync` library, which adapts the behavior to integrate with the asyncio event loop. See Also: - - :meth:`ThreadPoolExecutor.submit` + - :meth:`~ThreadPoolExecutor.submit` - :class:`asyncio.Future` - """ executor = ThreadPoolExecutor(0) fut = executor.submit(time.sleep, 0.1) @@ -231,20 +220,19 @@ async def test_thread_pool_executor_sync_submit(): @pytest.mark.asyncio async def test_pruning_thread_pool_executor_sync_submit(): - """Tests the :class:`PruningThreadPoolExecutor` by submitting the work function synchronously. + """Test the :class:`PruningThreadPoolExecutor` by submitting the work function synchronously. - This test ensures that the `submit` method of the `PruningThreadPoolExecutor` returns an - :class:`asyncio.Future` when executed with a synchronous function. + This test ensures that the :meth:`~PruningThreadPoolExecutor.submit` method of the + :class:`~PruningThreadPoolExecutor` returns an :class:`asyncio.Future` when executed with a synchronous function. Note: - The `submit` method in this context returns an `asyncio.Future`, not a `concurrent.futures.Future`. - This is specific to the implementation of the executors in the `a_sync` library, which adapts the - behavior to integrate with the asyncio event loop. + The :meth:`~PruningThreadPoolExecutor.submit` method in this context returns an :class:`asyncio.Future`, + not a :class:`concurrent.futures.Future`. This is specific to the implementation of the executors + in the `a_sync` library, which adapts the behavior to integrate with the asyncio event loop. See Also: - - :meth:`PruningThreadPoolExecutor.submit` + - :meth:`~PruningThreadPoolExecutor.submit` - :class:`asyncio.Future` - """ executor = PruningThreadPoolExecutor(0) fut = executor.submit(time.sleep, 0.1) diff --git a/tests/test_executor.py b/tests/test_executor.py index 71fef0d6..3a49ccc2 100644 --- a/tests/test_executor.py +++ b/tests/test_executor.py @@ -22,19 +22,19 @@ def do_work(i, kwarg=None): def test_executor(): - """Tests the functionality of the ProcessPoolExecutor. + """Tests the functionality of the :class:`~a_sync.executor.ProcessPoolExecutor`. - This test verifies that the ProcessPoolExecutor behaves as expected, + This test verifies that the :class:`~a_sync.executor.ProcessPoolExecutor` behaves as expected, including running tasks, handling futures, and managing exceptions. Note: - `ProcessPoolExecutor` is an alias for `AsyncProcessPoolExecutor`, + :class:`~a_sync.executor.ProcessPoolExecutor` is an alias for :class:`~a_sync.executor.AsyncProcessPoolExecutor`, which is why the assertion `assert isinstance(executor, AsyncProcessPoolExecutor)` is always true. See Also: - - :class:`a_sync.executor.AsyncProcessPoolExecutor` - - :meth:`a_sync.executor._AsyncExecutorMixin.run` - - :meth:`a_sync.executor._AsyncExecutorMixin.submit` + - :class:`~a_sync.executor.AsyncProcessPoolExecutor` + - :meth:`~a_sync.executor._AsyncExecutorMixin.run` + - :meth:`~a_sync.executor._AsyncExecutorMixin.submit` """ executor = ProcessPoolExecutor(1) assert isinstance(executor, AsyncProcessPoolExecutor)